##// END OF EJS Templates
auth decorators are not used anymore on __before__...
marcink -
r3749:b950b884 beta
parent child Browse files
Show More
@@ -1,57 +1,57 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.bookmarks
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Bookmarks controller for rhodecode
7 7
8 8 :created_on: Dec 1, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import logging
26 26
27 27 from pylons import tmpl_context as c
28 28
29 29 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
30 30 from rhodecode.lib.base import BaseRepoController, render
31 31 from rhodecode.lib.compat import OrderedDict
32 32 from webob.exc import HTTPNotFound
33 33
34 34 log = logging.getLogger(__name__)
35 35
36 36
37 37 class BookmarksController(BaseRepoController):
38 38
39 def __before__(self):
40 super(BookmarksController, self).__before__()
41
39 42 @LoginRequired()
40 43 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
41 44 'repository.admin')
42 def __before__(self):
43 super(BookmarksController, self).__before__()
44
45 45 def index(self):
46 46 if c.rhodecode_repo.alias != 'hg':
47 47 raise HTTPNotFound()
48 48
49 49 c.repo_bookmarks = OrderedDict()
50 50
51 51 bookmarks = [(name, c.rhodecode_repo.get_changeset(hash_)) for \
52 52 name, hash_ in c.rhodecode_repo._repo._bookmarks.items()]
53 53 ordered_tags = sorted(bookmarks, key=lambda x: x[1].date, reverse=True)
54 54 for name, cs_book in ordered_tags:
55 55 c.repo_bookmarks[name] = cs_book
56 56
57 57 return render('bookmarks/bookmarks.html')
@@ -1,76 +1,75 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.branches
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 branches controller for rhodecode
7 7
8 8 :created_on: Apr 21, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import binascii
28 28
29 29 from pylons import tmpl_context as c
30 30
31 31 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
32 32 from rhodecode.lib.base import BaseRepoController, render
33 33 from rhodecode.lib.compat import OrderedDict
34 34 from rhodecode.lib.utils2 import safe_unicode
35 35
36 36 log = logging.getLogger(__name__)
37 37
38 38
39 39 class BranchesController(BaseRepoController):
40 40
41 def __before__(self):
42 super(BranchesController, self).__before__()
43
41 44 @LoginRequired()
42 45 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
43 46 'repository.admin')
44 def __before__(self):
45 super(BranchesController, self).__before__()
46
47 47 def index(self):
48 48
49 49 def _branchtags(localrepo):
50 50 bt_closed = {}
51 51 for bn, heads in localrepo.branchmap().iteritems():
52 52 tip = heads[-1]
53 53 if 'close' in localrepo.changelog.read(tip)[5]:
54 54 bt_closed[bn] = tip
55 55 return bt_closed
56 56
57 57 cs_g = c.rhodecode_repo.get_changeset
58 58
59 59 c.repo_closed_branches = {}
60 60 if c.rhodecode_db_repo.repo_type == 'hg':
61 61 bt_closed = _branchtags(c.rhodecode_repo._repo)
62 62 _closed_branches = [(safe_unicode(n), cs_g(binascii.hexlify(h)),)
63 63 for n, h in bt_closed.items()]
64 64
65 65 c.repo_closed_branches = OrderedDict(sorted(_closed_branches,
66 66 key=lambda ctx: ctx[0],
67 67 reverse=False))
68 68
69 69 _branches = [(safe_unicode(n), cs_g(h))
70 70 for n, h in c.rhodecode_repo.branches.items()]
71 71 c.repo_branches = OrderedDict(sorted(_branches,
72 72 key=lambda ctx: ctx[0],
73 73 reverse=False))
74 74
75
76 75 return render('branches/branches.html')
@@ -1,119 +1,122 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.changelog
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 changelog controller for rhodecode
7 7
8 8 :created_on: Apr 21, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28
29 29 from pylons import request, url, session, tmpl_context as c
30 30 from pylons.controllers.util import redirect
31 31 from pylons.i18n.translation import _
32 32
33 33 import rhodecode.lib.helpers as h
34 34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
35 35 from rhodecode.lib.base import BaseRepoController, render
36 36 from rhodecode.lib.helpers import RepoPage
37 37 from rhodecode.lib.compat import json
38 38 from rhodecode.lib.graphmod import _colored, _dagwalker
39 39 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError
40 40 from rhodecode.lib.utils2 import safe_int
41 41
42 42 log = logging.getLogger(__name__)
43 43
44 44
45 45 class ChangelogController(BaseRepoController):
46 46
47 @LoginRequired()
48 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
49 'repository.admin')
50 47 def __before__(self):
51 48 super(ChangelogController, self).__before__()
52 49 c.affected_files_cut_off = 60
53 50
51 def _graph(self, repo, revs_int, repo_size, size, p):
52 """
53 Generates a DAG graph for repo
54
55 :param repo:
56 :param revs_int:
57 :param repo_size:
58 :param size:
59 :param p:
60 """
61 if not revs_int:
62 c.jsdata = json.dumps([])
63 return
64
65 data = []
66 revs = revs_int
67
68 dag = _dagwalker(repo, revs, repo.alias)
69 dag = _colored(dag)
70 for (id, type, ctx, vtx, edges) in dag:
71 data.append(['', vtx, edges])
72
73 c.jsdata = json.dumps(data)
74
75 @LoginRequired()
76 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
77 'repository.admin')
54 78 def index(self):
55 79 limit = 100
56 80 default = 20
57 81 if request.GET.get('size'):
58 82 c.size = max(min(safe_int(request.GET.get('size')), limit), 1)
59 83 session['changelog_size'] = c.size
60 84 session.save()
61 85 else:
62 86 c.size = int(session.get('changelog_size', default))
63 87 # min size must be 1
64 88 c.size = max(c.size, 1)
65 89 p = safe_int(request.GET.get('page', 1), 1)
66 90 branch_name = request.GET.get('branch', None)
67 91 try:
68 92 collection = c.rhodecode_repo.get_changesets(start=0,
69 93 branch_name=branch_name)
70 94 c.total_cs = len(collection)
71 95
72 96 c.pagination = RepoPage(collection, page=p, item_count=c.total_cs,
73 97 items_per_page=c.size, branch=branch_name)
74 98 collection = list(c.pagination)
75 99 page_revisions = [x.raw_id for x in c.pagination]
76 100 c.comments = c.rhodecode_db_repo.get_comments(page_revisions)
77 101 c.statuses = c.rhodecode_db_repo.statuses(page_revisions)
78 102 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
79 103 log.error(traceback.format_exc())
80 104 h.flash(str(e), category='error')
81 105 return redirect(url('changelog_home', repo_name=c.repo_name))
82 106
83 107 c.branch_name = branch_name
84 108 c.branch_filters = [('', _('All Branches'))] + \
85 109 [(k, k) for k in c.rhodecode_repo.branches.keys()]
86 110
87 111 self._graph(c.rhodecode_repo, [x.revision for x in c.pagination],
88 112 c.total_cs, c.size, p)
89 113
90 114 return render('changelog/changelog.html')
91 115
116 @LoginRequired()
117 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
118 'repository.admin')
92 119 def changelog_details(self, cs):
93 120 if request.environ.get('HTTP_X_PARTIAL_XHR'):
94 121 c.cs = c.rhodecode_repo.get_changeset(cs)
95 122 return render('changelog/changelog_details.html')
96
97 def _graph(self, repo, revs_int, repo_size, size, p):
98 """
99 Generates a DAG graph for repo
100
101 :param repo:
102 :param revs_int:
103 :param repo_size:
104 :param size:
105 :param p:
106 """
107 if not revs_int:
108 c.jsdata = json.dumps([])
109 return
110
111 data = []
112 revs = revs_int
113
114 dag = _dagwalker(repo, revs, repo.alias)
115 dag = _colored(dag)
116 for (id, type, ctx, vtx, edges) in dag:
117 data.append(['', vtx, edges])
118
119 c.jsdata = json.dumps(data)
@@ -1,416 +1,437 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.changeset
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 changeset controller for pylons showoing changes beetween
7 7 revisions
8 8
9 9 :created_on: Apr 25, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26 import logging
27 27 import traceback
28 28 from collections import defaultdict
29 29 from webob.exc import HTTPForbidden, HTTPBadRequest, HTTPNotFound
30 30
31 31 from pylons import tmpl_context as c, url, request, response
32 32 from pylons.i18n.translation import _
33 33 from pylons.controllers.util import redirect
34 34 from rhodecode.lib.utils import jsonify
35 35
36 36 from rhodecode.lib.vcs.exceptions import RepositoryError, \
37 37 ChangesetDoesNotExistError
38 38
39 39 import rhodecode.lib.helpers as h
40 40 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
41 41 NotAnonymous
42 42 from rhodecode.lib.base import BaseRepoController, render
43 43 from rhodecode.lib.utils import action_logger
44 44 from rhodecode.lib.compat import OrderedDict
45 45 from rhodecode.lib import diffs
46 46 from rhodecode.model.db import ChangesetComment, ChangesetStatus
47 47 from rhodecode.model.comment import ChangesetCommentsModel
48 48 from rhodecode.model.changeset_status import ChangesetStatusModel
49 49 from rhodecode.model.meta import Session
50 50 from rhodecode.model.repo import RepoModel
51 51 from rhodecode.lib.diffs import LimitedDiffContainer
52 52 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
53 53 from rhodecode.lib.vcs.backends.base import EmptyChangeset
54 54 from rhodecode.lib.utils2 import safe_unicode
55 55
56 56 log = logging.getLogger(__name__)
57 57
58 58
59 59 def _update_with_GET(params, GET):
60 60 for k in ['diff1', 'diff2', 'diff']:
61 61 params[k] += GET.getall(k)
62 62
63 63
64 64 def anchor_url(revision, path, GET):
65 65 fid = h.FID(revision, path)
66 66 return h.url.current(anchor=fid, **dict(GET))
67 67
68 68
69 69 def get_ignore_ws(fid, GET):
70 70 ig_ws_global = GET.get('ignorews')
71 71 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
72 72 if ig_ws:
73 73 try:
74 74 return int(ig_ws[0].split(':')[-1])
75 75 except Exception:
76 76 pass
77 77 return ig_ws_global
78 78
79 79
80 80 def _ignorews_url(GET, fileid=None):
81 81 fileid = str(fileid) if fileid else None
82 82 params = defaultdict(list)
83 83 _update_with_GET(params, GET)
84 84 lbl = _('Show white space')
85 85 ig_ws = get_ignore_ws(fileid, GET)
86 86 ln_ctx = get_line_ctx(fileid, GET)
87 87 # global option
88 88 if fileid is None:
89 89 if ig_ws is None:
90 90 params['ignorews'] += [1]
91 91 lbl = _('Ignore white space')
92 92 ctx_key = 'context'
93 93 ctx_val = ln_ctx
94 94 # per file options
95 95 else:
96 96 if ig_ws is None:
97 97 params[fileid] += ['WS:1']
98 98 lbl = _('Ignore white space')
99 99
100 100 ctx_key = fileid
101 101 ctx_val = 'C:%s' % ln_ctx
102 102 # if we have passed in ln_ctx pass it along to our params
103 103 if ln_ctx:
104 104 params[ctx_key] += [ctx_val]
105 105
106 106 params['anchor'] = fileid
107 107 img = h.image(h.url('/images/icons/text_strikethrough.png'), lbl, class_='icon')
108 108 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
109 109
110 110
111 111 def get_line_ctx(fid, GET):
112 112 ln_ctx_global = GET.get('context')
113 113 if fid:
114 114 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
115 115 else:
116 116 _ln_ctx = filter(lambda k: k.startswith('C'), GET)
117 117 ln_ctx = GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global
118 118 if ln_ctx:
119 119 ln_ctx = [ln_ctx]
120 120
121 121 if ln_ctx:
122 122 retval = ln_ctx[0].split(':')[-1]
123 123 else:
124 124 retval = ln_ctx_global
125 125
126 126 try:
127 127 return int(retval)
128 128 except Exception:
129 129 return 3
130 130
131 131
132 132 def _context_url(GET, fileid=None):
133 133 """
134 134 Generates url for context lines
135 135
136 136 :param fileid:
137 137 """
138 138
139 139 fileid = str(fileid) if fileid else None
140 140 ig_ws = get_ignore_ws(fileid, GET)
141 141 ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
142 142
143 143 params = defaultdict(list)
144 144 _update_with_GET(params, GET)
145 145
146 146 # global option
147 147 if fileid is None:
148 148 if ln_ctx > 0:
149 149 params['context'] += [ln_ctx]
150 150
151 151 if ig_ws:
152 152 ig_ws_key = 'ignorews'
153 153 ig_ws_val = 1
154 154
155 155 # per file option
156 156 else:
157 157 params[fileid] += ['C:%s' % ln_ctx]
158 158 ig_ws_key = fileid
159 159 ig_ws_val = 'WS:%s' % 1
160 160
161 161 if ig_ws:
162 162 params[ig_ws_key] += [ig_ws_val]
163 163
164 164 lbl = _('%s line context') % ln_ctx
165 165
166 166 params['anchor'] = fileid
167 167 img = h.image(h.url('/images/icons/table_add.png'), lbl, class_='icon')
168 168 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
169 169
170 170
171 171 class ChangesetController(BaseRepoController):
172 172
173 @LoginRequired()
174 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
175 'repository.admin')
176 173 def __before__(self):
177 174 super(ChangesetController, self).__before__()
178 175 c.affected_files_cut_off = 60
179 176 repo_model = RepoModel()
180 177 c.users_array = repo_model.get_users_js()
181 178 c.users_groups_array = repo_model.get_users_groups_js()
182 179
180 @LoginRequired()
181 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
182 'repository.admin')
183 183 def index(self, revision, method='show'):
184 184 c.anchor_url = anchor_url
185 185 c.ignorews_url = _ignorews_url
186 186 c.context_url = _context_url
187 187 c.fulldiff = fulldiff = request.GET.get('fulldiff')
188 188 #get ranges of revisions if preset
189 189 rev_range = revision.split('...')[:2]
190 190 enable_comments = True
191 191 try:
192 192 if len(rev_range) == 2:
193 193 enable_comments = False
194 194 rev_start = rev_range[0]
195 195 rev_end = rev_range[1]
196 196 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
197 197 end=rev_end)
198 198 else:
199 199 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
200 200
201 201 c.cs_ranges = list(rev_ranges)
202 202 if not c.cs_ranges:
203 203 raise RepositoryError('Changeset range returned empty result')
204 204
205 205 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
206 206 log.error(traceback.format_exc())
207 207 h.flash(str(e), category='error')
208 208 raise HTTPNotFound()
209 209
210 210 c.changes = OrderedDict()
211 211
212 212 c.lines_added = 0 # count of lines added
213 213 c.lines_deleted = 0 # count of lines removes
214 214
215 215 c.changeset_statuses = ChangesetStatus.STATUSES
216 216 c.comments = []
217 217 c.statuses = []
218 218 c.inline_comments = []
219 219 c.inline_cnt = 0
220 220
221 221 # Iterate over ranges (default changeset view is always one changeset)
222 222 for changeset in c.cs_ranges:
223 223 inlines = []
224 224 if method == 'show':
225 225 c.statuses.extend([ChangesetStatusModel().get_status(
226 226 c.rhodecode_db_repo.repo_id, changeset.raw_id)])
227 227
228 228 c.comments.extend(ChangesetCommentsModel()\
229 229 .get_comments(c.rhodecode_db_repo.repo_id,
230 230 revision=changeset.raw_id))
231 231
232 232 #comments from PR
233 233 st = ChangesetStatusModel().get_statuses(
234 234 c.rhodecode_db_repo.repo_id, changeset.raw_id,
235 235 with_revisions=True)
236 236 # from associated statuses, check the pull requests, and
237 237 # show comments from them
238 238
239 239 prs = set([x.pull_request for x in
240 240 filter(lambda x: x.pull_request != None, st)])
241 241
242 242 for pr in prs:
243 243 c.comments.extend(pr.comments)
244 244 inlines = ChangesetCommentsModel()\
245 245 .get_inline_comments(c.rhodecode_db_repo.repo_id,
246 246 revision=changeset.raw_id)
247 247 c.inline_comments.extend(inlines)
248 248
249 249 c.changes[changeset.raw_id] = []
250 250
251 251 cs2 = changeset.raw_id
252 252 cs1 = changeset.parents[0].raw_id if changeset.parents else EmptyChangeset()
253 253 context_lcl = get_line_ctx('', request.GET)
254 254 ign_whitespace_lcl = ign_whitespace_lcl = get_ignore_ws('', request.GET)
255 255
256 256 _diff = c.rhodecode_repo.get_diff(cs1, cs2,
257 257 ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
258 258 diff_limit = self.cut_off_limit if not fulldiff else None
259 259 diff_processor = diffs.DiffProcessor(_diff,
260 260 vcs=c.rhodecode_repo.alias,
261 261 format='gitdiff',
262 262 diff_limit=diff_limit)
263 263 cs_changes = OrderedDict()
264 264 if method == 'show':
265 265 _parsed = diff_processor.prepare()
266 266 c.limited_diff = False
267 267 if isinstance(_parsed, LimitedDiffContainer):
268 268 c.limited_diff = True
269 269 for f in _parsed:
270 270 st = f['stats']
271 271 if st[0] != 'b':
272 272 c.lines_added += st[0]
273 273 c.lines_deleted += st[1]
274 274 fid = h.FID(changeset.raw_id, f['filename'])
275 275 diff = diff_processor.as_html(enable_comments=enable_comments,
276 276 parsed_lines=[f])
277 277 cs_changes[fid] = [cs1, cs2, f['operation'], f['filename'],
278 278 diff, st]
279 279 else:
280 280 # downloads/raw we only need RAW diff nothing else
281 281 diff = diff_processor.as_raw()
282 282 cs_changes[''] = [None, None, None, None, diff, None]
283 283 c.changes[changeset.raw_id] = cs_changes
284 284
285 285 #sort comments by how they were generated
286 286 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
287 287
288 288 # count inline comments
289 289 for __, lines in c.inline_comments:
290 290 for comments in lines.values():
291 291 c.inline_cnt += len(comments)
292 292
293 293 if len(c.cs_ranges) == 1:
294 294 c.changeset = c.cs_ranges[0]
295 295 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id
296 296 for x in c.changeset.parents])
297 297 if method == 'download':
298 298 response.content_type = 'text/plain'
299 299 response.content_disposition = 'attachment; filename=%s.diff' \
300 300 % revision[:12]
301 301 return diff
302 302 elif method == 'patch':
303 303 response.content_type = 'text/plain'
304 304 c.diff = safe_unicode(diff)
305 305 return render('changeset/patch_changeset.html')
306 306 elif method == 'raw':
307 307 response.content_type = 'text/plain'
308 308 return diff
309 309 elif method == 'show':
310 310 if len(c.cs_ranges) == 1:
311 311 return render('changeset/changeset.html')
312 312 else:
313 313 return render('changeset/changeset_range.html')
314 314
315 @LoginRequired()
316 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
317 'repository.admin')
315 318 def changeset_raw(self, revision):
316 319 return self.index(revision, method='raw')
317 320
321 @LoginRequired()
322 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
323 'repository.admin')
318 324 def changeset_patch(self, revision):
319 325 return self.index(revision, method='patch')
320 326
327 @LoginRequired()
328 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
329 'repository.admin')
321 330 def changeset_download(self, revision):
322 331 return self.index(revision, method='download')
323 332
333 @LoginRequired()
324 334 @NotAnonymous()
335 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
336 'repository.admin')
325 337 @jsonify
326 338 def comment(self, repo_name, revision):
327 339 status = request.POST.get('changeset_status')
328 340 change_status = request.POST.get('change_changeset_status')
329 341 text = request.POST.get('text')
330 342 if status and change_status:
331 343 text = text or (_('Status change -> %s')
332 344 % ChangesetStatus.get_status_lbl(status))
333 345
334 346 c.co = comm = ChangesetCommentsModel().create(
335 347 text=text,
336 348 repo=c.rhodecode_db_repo.repo_id,
337 349 user=c.rhodecode_user.user_id,
338 350 revision=revision,
339 351 f_path=request.POST.get('f_path'),
340 352 line_no=request.POST.get('line'),
341 353 status_change=(ChangesetStatus.get_status_lbl(status)
342 354 if status and change_status else None)
343 355 )
344 356
345 357 # get status if set !
346 358 if status and change_status:
347 359 # if latest status was from pull request and it's closed
348 360 # disallow changing status !
349 361 # dont_allow_on_closed_pull_request = True !
350 362
351 363 try:
352 364 ChangesetStatusModel().set_status(
353 365 c.rhodecode_db_repo.repo_id,
354 366 status,
355 367 c.rhodecode_user.user_id,
356 368 comm,
357 369 revision=revision,
358 370 dont_allow_on_closed_pull_request=True
359 371 )
360 372 except StatusChangeOnClosedPullRequestError:
361 373 log.error(traceback.format_exc())
362 374 msg = _('Changing status on a changeset associated with '
363 375 'a closed pull request is not allowed')
364 376 h.flash(msg, category='warning')
365 377 return redirect(h.url('changeset_home', repo_name=repo_name,
366 378 revision=revision))
367 379 action_logger(self.rhodecode_user,
368 380 'user_commented_revision:%s' % revision,
369 381 c.rhodecode_db_repo, self.ip_addr, self.sa)
370 382
371 383 Session().commit()
372 384
373 385 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
374 386 return redirect(h.url('changeset_home', repo_name=repo_name,
375 387 revision=revision))
376 388 #only ajax below
377 389 data = {
378 390 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
379 391 }
380 392 if comm:
381 393 data.update(comm.get_dict())
382 394 data.update({'rendered_text':
383 395 render('changeset/changeset_comment_block.html')})
384 396
385 397 return data
386 398
399 @LoginRequired()
387 400 @NotAnonymous()
401 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
402 'repository.admin')
388 403 def preview_comment(self):
389 404 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
390 405 raise HTTPBadRequest()
391 406 text = request.POST.get('text')
392 407 if text:
393 408 return h.rst_w_mentions(text)
394 409 return ''
395 410
411 @LoginRequired()
396 412 @NotAnonymous()
413 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
414 'repository.admin')
397 415 @jsonify
398 416 def delete_comment(self, repo_name, comment_id):
399 417 co = ChangesetComment.get(comment_id)
400 418 owner = co.author.user_id == c.rhodecode_user.user_id
401 419 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
402 420 ChangesetCommentsModel().delete(comment=co)
403 421 Session().commit()
404 422 return True
405 423 else:
406 424 raise HTTPForbidden()
407 425
426 @LoginRequired()
427 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
428 'repository.admin')
408 429 @jsonify
409 430 def changeset_info(self, repo_name, revision):
410 431 if request.is_xhr:
411 432 try:
412 433 return c.rhodecode_repo.get_changeset(revision)
413 434 except ChangesetDoesNotExistError, e:
414 435 return EmptyChangeset(message=str(e))
415 436 else:
416 437 raise HTTPBadRequest()
@@ -1,269 +1,269 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.compare
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 compare controller for pylons showing differences between two
7 7 repos, branches, bookmarks or tips
8 8
9 9 :created_on: May 6, 2012
10 10 :author: marcink
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 27 import logging
28 28 import traceback
29 29 import re
30 30
31 31 from webob.exc import HTTPNotFound
32 32 from pylons import request, response, session, tmpl_context as c, url
33 33 from pylons.controllers.util import abort, redirect
34 34 from pylons.i18n.translation import _
35 35
36 36 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError, RepositoryError
37 37 from rhodecode.lib.vcs.utils import safe_str
38 38 from rhodecode.lib.vcs.utils.hgcompat import scmutil
39 39 from rhodecode.lib import helpers as h
40 40 from rhodecode.lib.base import BaseRepoController, render
41 41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
42 42 from rhodecode.lib import diffs, unionrepo
43 43
44 44 from rhodecode.model.db import Repository
45 45 from webob.exc import HTTPBadRequest
46 46 from rhodecode.lib.diffs import LimitedDiffContainer
47 47
48 48
49 49 log = logging.getLogger(__name__)
50 50
51 51
52 52 class CompareController(BaseRepoController):
53 53
54 @LoginRequired()
55 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
56 'repository.admin')
57 54 def __before__(self):
58 55 super(CompareController, self).__before__()
59 56
60 57 def __get_cs_or_redirect(self, rev, repo, redirect_after=True,
61 58 partial=False):
62 59 """
63 60 Safe way to get changeset if error occur it redirects to changeset with
64 61 proper message. If partial is set then don't do redirect raise Exception
65 62 instead
66 63
67 64 :param rev: revision to fetch
68 65 :param repo: repo instance
69 66 """
70 67
71 68 try:
72 69 type_, rev = rev
73 70 return repo.scm_instance.get_changeset(rev)
74 71 except EmptyRepositoryError, e:
75 72 if not redirect_after:
76 73 return None
77 74 h.flash(h.literal(_('There are no changesets yet')),
78 75 category='warning')
79 76 redirect(url('summary_home', repo_name=repo.repo_name))
80 77
81 78 except RepositoryError, e:
82 79 log.error(traceback.format_exc())
83 80 h.flash(str(e), category='warning')
84 81 if not partial:
85 82 redirect(h.url('summary_home', repo_name=repo.repo_name))
86 83 raise HTTPBadRequest()
87 84
85 def _get_changesets(self, alias, org_repo, org_ref, other_repo, other_ref, merge):
86 """
87 Returns a list of changesets that can be merged from org_repo@org_ref
88 to other_repo@other_ref ... and the ancestor that would be used for merge
89
90 :param org_repo:
91 :param org_ref:
92 :param other_repo:
93 :param other_ref:
94 :param tmp:
95 """
96
97 ancestor = None
98
99 if alias == 'hg':
100 # lookup up the exact node id
101 _revset_predicates = {
102 'branch': 'branch',
103 'book': 'bookmark',
104 'tag': 'tag',
105 'rev': 'id',
106 }
107
108 org_rev_spec = "max(%s('%s'))" % (_revset_predicates[org_ref[0]],
109 safe_str(org_ref[1]))
110 org_revs = scmutil.revrange(org_repo._repo, [org_rev_spec])
111 org_rev = org_repo._repo[org_revs[-1] if org_revs else -1].hex()
112
113 other_rev_spec = "max(%s('%s'))" % (_revset_predicates[other_ref[0]],
114 safe_str(other_ref[1]))
115 other_revs = scmutil.revrange(other_repo._repo, [other_rev_spec])
116 other_rev = other_repo._repo[other_revs[-1] if other_revs else -1].hex()
117
118 #case two independent repos
119 if org_repo != other_repo:
120 hgrepo = unionrepo.unionrepository(other_repo.baseui,
121 other_repo.path,
122 org_repo.path)
123 # all the changesets we are looking for will be in other_repo,
124 # so rev numbers from hgrepo can be used in other_repo
125
126 #no remote compare do it on the same repository
127 else:
128 hgrepo = other_repo._repo
129
130 if merge:
131 revs = ["ancestors(id('%s')) and not ancestors(id('%s')) and not id('%s')" %
132 (other_rev, org_rev, org_rev)]
133
134 ancestors = scmutil.revrange(hgrepo,
135 ["ancestor(id('%s'), id('%s'))" % (org_rev, other_rev)])
136 if ancestors:
137 # pick arbitrary ancestor - but there is usually only one
138 ancestor = hgrepo[ancestors[0]].hex()
139 else:
140 # TODO: have both + and - changesets
141 revs = ["id('%s') :: id('%s') - id('%s')" %
142 (org_rev, other_rev, org_rev)]
143
144 changesets = [other_repo.get_changeset(cs)
145 for cs in scmutil.revrange(hgrepo, revs)]
146
147 elif alias == 'git':
148 assert org_repo == other_repo, (org_repo, other_repo) # no git support for different repos
149 so, se = org_repo.run_git_command(
150 'log --reverse --pretty="format: %%H" -s -p %s..%s' % (org_ref[1],
151 other_ref[1])
152 )
153 changesets = [org_repo.get_changeset(cs)
154 for cs in re.findall(r'[0-9a-fA-F]{40}', so)]
155
156 return changesets, ancestor
157
158 @LoginRequired()
159 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
160 'repository.admin')
88 161 def index(self, org_ref_type, org_ref, other_ref_type, other_ref):
89 162 # org_ref will be evaluated in org_repo
90 163 org_repo = c.rhodecode_db_repo.repo_name
91 164 org_ref = (org_ref_type, org_ref)
92 165 # other_ref will be evaluated in other_repo
93 166 other_ref = (other_ref_type, other_ref)
94 167 other_repo = request.GET.get('other_repo', org_repo)
95 168 # If merge is True:
96 169 # Show what org would get if merged with other:
97 170 # List changesets that are ancestors of other but not of org.
98 171 # New changesets in org is thus ignored.
99 172 # Diff will be from common ancestor, and merges of org to other will thus be ignored.
100 173 # If merge is False:
101 174 # Make a raw diff from org to other, no matter if related or not.
102 175 # Changesets in one and not in the other will be ignored
103 176 merge = bool(request.GET.get('merge'))
104 177 # fulldiff disables cut_off_limit
105 178 c.fulldiff = request.GET.get('fulldiff')
106 179 # partial uses compare_cs.html template directly
107 180 partial = request.environ.get('HTTP_X_PARTIAL_XHR')
108 181 # as_form puts hidden input field with changeset revisions
109 182 c.as_form = partial and request.GET.get('as_form')
110 183 # swap url for compare_diff page - never partial and never as_form
111 184 c.swap_url = h.url('compare_url',
112 185 repo_name=other_repo,
113 186 org_ref_type=other_ref[0], org_ref=other_ref[1],
114 187 other_repo=org_repo,
115 188 other_ref_type=org_ref[0], other_ref=org_ref[1],
116 189 merge=merge or '')
117 190
118 191 org_repo = Repository.get_by_repo_name(org_repo)
119 192 other_repo = Repository.get_by_repo_name(other_repo)
120 193
121 194 if org_repo is None:
122 195 log.error('Could not find org repo %s' % org_repo)
123 196 raise HTTPNotFound
124 197 if other_repo is None:
125 198 log.error('Could not find other repo %s' % other_repo)
126 199 raise HTTPNotFound
127 200
128 201 if org_repo != other_repo and h.is_git(org_repo):
129 202 log.error('compare of two remote repos not available for GIT REPOS')
130 203 raise HTTPNotFound
131 204
132 205 if org_repo.scm_instance.alias != other_repo.scm_instance.alias:
133 206 log.error('compare of two different kind of remote repos not available')
134 207 raise HTTPNotFound
135 208
136 209 self.__get_cs_or_redirect(rev=org_ref, repo=org_repo, partial=partial)
137 210 self.__get_cs_or_redirect(rev=other_ref, repo=other_repo, partial=partial)
138 211
139 212 c.org_repo = org_repo
140 213 c.other_repo = other_repo
141 214 c.org_ref = org_ref[1]
142 215 c.other_ref = other_ref[1]
143 216 c.org_ref_type = org_ref[0]
144 217 c.other_ref_type = other_ref[0]
145 218
146 219 c.cs_ranges, c.ancestor = self._get_changesets(org_repo.scm_instance.alias,
147 220 org_repo.scm_instance, org_ref,
148 221 other_repo.scm_instance, other_ref,
149 222 merge)
150 223
151 224 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
152 225 c.cs_ranges])
153 226 if partial:
154 227 assert c.ancestor
155 228 return render('compare/compare_cs.html')
156 229
157 230 if c.ancestor:
158 231 assert merge
159 232 # case we want a simple diff without incoming changesets,
160 233 # previewing what will be merged.
161 234 # Make the diff on the other repo (which is known to have other_ref)
162 235 log.debug('Using ancestor %s as org_ref instead of %s'
163 236 % (c.ancestor, org_ref))
164 237 org_ref = ('rev', c.ancestor)
165 238 org_repo = other_repo
166 239
167 240 diff_limit = self.cut_off_limit if not c.fulldiff else None
168 241
169 242 log.debug('running diff between %s@%s and %s@%s'
170 243 % (org_repo.scm_instance.path, org_ref,
171 244 other_repo.scm_instance.path, other_ref))
172 245 _diff = org_repo.scm_instance.get_diff(rev1=safe_str(org_ref[1]), rev2=safe_str(other_ref[1]))
173 246
174 247 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
175 248 diff_limit=diff_limit)
176 249 _parsed = diff_processor.prepare()
177 250
178 251 c.limited_diff = False
179 252 if isinstance(_parsed, LimitedDiffContainer):
180 253 c.limited_diff = True
181 254
182 255 c.files = []
183 256 c.changes = {}
184 257 c.lines_added = 0
185 258 c.lines_deleted = 0
186 259 for f in _parsed:
187 260 st = f['stats']
188 261 if st[0] != 'b':
189 262 c.lines_added += st[0]
190 263 c.lines_deleted += st[1]
191 264 fid = h.FID('', f['filename'])
192 265 c.files.append([fid, f['operation'], f['filename'], f['stats']])
193 266 diff = diff_processor.as_html(enable_comments=False, parsed_lines=[f])
194 267 c.changes[fid] = [f['operation'], f['filename'], diff]
195 268
196 269 return render('compare/compare_diff.html')
197
198 def _get_changesets(self, alias, org_repo, org_ref, other_repo, other_ref, merge):
199 """
200 Returns a list of changesets that can be merged from org_repo@org_ref
201 to other_repo@other_ref ... and the ancestor that would be used for merge
202
203 :param org_repo:
204 :param org_ref:
205 :param other_repo:
206 :param other_ref:
207 :param tmp:
208 """
209
210 ancestor = None
211
212 if alias == 'hg':
213 # lookup up the exact node id
214 _revset_predicates = {
215 'branch': 'branch',
216 'book': 'bookmark',
217 'tag': 'tag',
218 'rev': 'id',
219 }
220
221 org_rev_spec = "max(%s('%s'))" % (_revset_predicates[org_ref[0]],
222 safe_str(org_ref[1]))
223 org_revs = scmutil.revrange(org_repo._repo, [org_rev_spec])
224 org_rev = org_repo._repo[org_revs[-1] if org_revs else -1].hex()
225
226 other_rev_spec = "max(%s('%s'))" % (_revset_predicates[other_ref[0]],
227 safe_str(other_ref[1]))
228 other_revs = scmutil.revrange(other_repo._repo, [other_rev_spec])
229 other_rev = other_repo._repo[other_revs[-1] if other_revs else -1].hex()
230
231 #case two independent repos
232 if org_repo != other_repo:
233 hgrepo = unionrepo.unionrepository(other_repo.baseui,
234 other_repo.path,
235 org_repo.path)
236 # all the changesets we are looking for will be in other_repo,
237 # so rev numbers from hgrepo can be used in other_repo
238
239 #no remote compare do it on the same repository
240 else:
241 hgrepo = other_repo._repo
242
243 if merge:
244 revs = ["ancestors(id('%s')) and not ancestors(id('%s')) and not id('%s')" %
245 (other_rev, org_rev, org_rev)]
246
247 ancestors = scmutil.revrange(hgrepo,
248 ["ancestor(id('%s'), id('%s'))" % (org_rev, other_rev)])
249 if ancestors:
250 # pick arbitrary ancestor - but there is usually only one
251 ancestor = hgrepo[ancestors[0]].hex()
252 else:
253 # TODO: have both + and - changesets
254 revs = ["id('%s') :: id('%s') - id('%s')" %
255 (org_rev, other_rev, org_rev)]
256
257 changesets = [other_repo.get_changeset(cs)
258 for cs in scmutil.revrange(hgrepo, revs)]
259
260 elif alias == 'git':
261 assert org_repo == other_repo, (org_repo, other_repo) # no git support for different repos
262 so, se = org_repo.run_git_command(
263 'log --reverse --pretty="format: %%H" -s -p %s..%s' % (org_ref[1],
264 other_ref[1])
265 )
266 changesets = [org_repo.get_changeset(cs)
267 for cs in re.findall(r'[0-9a-fA-F]{40}', so)]
268
269 return changesets, ancestor
@@ -1,647 +1,650 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.files
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Files controller for RhodeCode
7 7
8 8 :created_on: Apr 21, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 from __future__ import with_statement
26 26 import os
27 27 import logging
28 28 import traceback
29 29 import tempfile
30 30 import shutil
31 31
32 32 from pylons import request, response, tmpl_context as c, url
33 33 from pylons.i18n.translation import _
34 34 from pylons.controllers.util import redirect
35 35 from rhodecode.lib.utils import jsonify
36 36
37 37 from rhodecode.lib import diffs
38 38 from rhodecode.lib import helpers as h
39 39
40 40 from rhodecode.lib.compat import OrderedDict
41 41 from rhodecode.lib.utils2 import convert_line_endings, detect_mode, safe_str,\
42 42 str2bool
43 43 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
44 44 from rhodecode.lib.base import BaseRepoController, render
45 45 from rhodecode.lib.vcs.backends.base import EmptyChangeset
46 46 from rhodecode.lib.vcs.conf import settings
47 47 from rhodecode.lib.vcs.exceptions import RepositoryError, \
48 48 ChangesetDoesNotExistError, EmptyRepositoryError, \
49 49 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,\
50 50 NodeDoesNotExistError, ChangesetError, NodeError
51 51 from rhodecode.lib.vcs.nodes import FileNode
52 52
53 53 from rhodecode.model.repo import RepoModel
54 54 from rhodecode.model.scm import ScmModel
55 55 from rhodecode.model.db import Repository
56 56
57 57 from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
58 58 _context_url, get_line_ctx, get_ignore_ws
59 59 from webob.exc import HTTPNotFound
60 60
61 61
62 62 log = logging.getLogger(__name__)
63 63
64 64
65 65 class FilesController(BaseRepoController):
66 66
67 67 def __before__(self):
68 68 super(FilesController, self).__before__()
69 69 c.cut_off_limit = self.cut_off_limit
70 70
71 71 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
72 72 """
73 73 Safe way to get changeset if error occur it redirects to tip with
74 74 proper message
75 75
76 76 :param rev: revision to fetch
77 77 :param repo_name: repo name to redirect after
78 78 """
79 79
80 80 try:
81 81 return c.rhodecode_repo.get_changeset(rev)
82 82 except EmptyRepositoryError, e:
83 83 if not redirect_after:
84 84 return None
85 85 url_ = url('files_add_home',
86 86 repo_name=c.repo_name,
87 87 revision=0, f_path='')
88 88 add_new = h.link_to(_('Click here to add new file'), url_)
89 89 h.flash(h.literal(_('There are no files yet %s') % add_new),
90 90 category='warning')
91 91 redirect(h.url('summary_home', repo_name=repo_name))
92 92
93 93 except RepositoryError, e: # including ChangesetDoesNotExistError
94 94 h.flash(str(e), category='error')
95 95 raise HTTPNotFound()
96 96
97 97 def __get_filenode_or_redirect(self, repo_name, cs, path):
98 98 """
99 99 Returns file_node, if error occurs or given path is directory,
100 100 it'll redirect to top level path
101 101
102 102 :param repo_name: repo_name
103 103 :param cs: given changeset
104 104 :param path: path to lookup
105 105 """
106 106
107 107 try:
108 108 file_node = cs.get_node(path)
109 109 if file_node.is_dir():
110 110 raise RepositoryError('given path is a directory')
111 111 except RepositoryError, e:
112 112 h.flash(str(e), category='error')
113 113 raise HTTPNotFound()
114 114
115 115 return file_node
116 116
117 117 @LoginRequired()
118 118 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
119 119 'repository.admin')
120 120 def index(self, repo_name, revision, f_path, annotate=False):
121 121 # redirect to given revision from form if given
122 122 post_revision = request.POST.get('at_rev', None)
123 123 if post_revision:
124 124 cs = self.__get_cs_or_redirect(post_revision, repo_name)
125 125
126 126 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
127 127 c.branch = request.GET.get('branch', None)
128 128 c.f_path = f_path
129 129 c.annotate = annotate
130 130 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
131 131 cur_rev = c.changeset.revision
132 132
133 133 # prev link
134 134 try:
135 135 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
136 136 c.url_prev = url('files_home', repo_name=c.repo_name,
137 137 revision=prev_rev.raw_id, f_path=f_path)
138 138 if c.branch:
139 139 c.url_prev += '?branch=%s' % c.branch
140 140 except (ChangesetDoesNotExistError, VCSError):
141 141 c.url_prev = '#'
142 142
143 143 # next link
144 144 try:
145 145 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
146 146 c.url_next = url('files_home', repo_name=c.repo_name,
147 147 revision=next_rev.raw_id, f_path=f_path)
148 148 if c.branch:
149 149 c.url_next += '?branch=%s' % c.branch
150 150 except (ChangesetDoesNotExistError, VCSError):
151 151 c.url_next = '#'
152 152
153 153 # files or dirs
154 154 try:
155 155 c.file = c.changeset.get_node(f_path)
156 156
157 157 if c.file.is_file():
158 158 c.load_full_history = False
159 159 file_last_cs = c.file.last_changeset
160 160 c.file_changeset = (c.changeset
161 161 if c.changeset.revision < file_last_cs.revision
162 162 else file_last_cs)
163 163 #determine if we're on branch head
164 164 _branches = c.rhodecode_repo.branches
165 165 c.on_branch_head = revision in _branches.keys() + _branches.values()
166 166 _hist = []
167 167 c.file_history = []
168 168 if c.load_full_history:
169 169 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
170 170
171 171 c.authors = []
172 172 for a in set([x.author for x in _hist]):
173 173 c.authors.append((h.email(a), h.person(a)))
174 174 else:
175 175 c.authors = c.file_history = []
176 176 except RepositoryError, e:
177 177 h.flash(str(e), category='error')
178 178 raise HTTPNotFound()
179 179
180 180 if request.environ.get('HTTP_X_PARTIAL_XHR'):
181 181 return render('files/files_ypjax.html')
182 182
183 183 return render('files/files.html')
184 184
185 @LoginRequired()
186 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
187 'repository.admin')
185 188 def history(self, repo_name, revision, f_path, annotate=False):
186 189 if request.environ.get('HTTP_X_PARTIAL_XHR'):
187 190 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
188 191 c.f_path = f_path
189 192 c.annotate = annotate
190 193 c.file = c.changeset.get_node(f_path)
191 194 if c.file.is_file():
192 195 file_last_cs = c.file.last_changeset
193 196 c.file_changeset = (c.changeset
194 197 if c.changeset.revision < file_last_cs.revision
195 198 else file_last_cs)
196 199 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
197 200 c.authors = []
198 201 for a in set([x.author for x in _hist]):
199 202 c.authors.append((h.email(a), h.person(a)))
200 203 return render('files/files_history_box.html')
201 204
202 205 @LoginRequired()
203 206 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
204 207 'repository.admin')
205 208 def rawfile(self, repo_name, revision, f_path):
206 209 cs = self.__get_cs_or_redirect(revision, repo_name)
207 210 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
208 211
209 212 response.content_disposition = 'attachment; filename=%s' % \
210 213 safe_str(f_path.split(Repository.url_sep())[-1])
211 214
212 215 response.content_type = file_node.mimetype
213 216 return file_node.content
214 217
215 218 @LoginRequired()
216 219 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
217 220 'repository.admin')
218 221 def raw(self, repo_name, revision, f_path):
219 222 cs = self.__get_cs_or_redirect(revision, repo_name)
220 223 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
221 224
222 225 raw_mimetype_mapping = {
223 226 # map original mimetype to a mimetype used for "show as raw"
224 227 # you can also provide a content-disposition to override the
225 228 # default "attachment" disposition.
226 229 # orig_type: (new_type, new_dispo)
227 230
228 231 # show images inline:
229 232 'image/x-icon': ('image/x-icon', 'inline'),
230 233 'image/png': ('image/png', 'inline'),
231 234 'image/gif': ('image/gif', 'inline'),
232 235 'image/jpeg': ('image/jpeg', 'inline'),
233 236 'image/svg+xml': ('image/svg+xml', 'inline'),
234 237 }
235 238
236 239 mimetype = file_node.mimetype
237 240 try:
238 241 mimetype, dispo = raw_mimetype_mapping[mimetype]
239 242 except KeyError:
240 243 # we don't know anything special about this, handle it safely
241 244 if file_node.is_binary:
242 245 # do same as download raw for binary files
243 246 mimetype, dispo = 'application/octet-stream', 'attachment'
244 247 else:
245 248 # do not just use the original mimetype, but force text/plain,
246 249 # otherwise it would serve text/html and that might be unsafe.
247 250 # Note: underlying vcs library fakes text/plain mimetype if the
248 251 # mimetype can not be determined and it thinks it is not
249 252 # binary.This might lead to erroneous text display in some
250 253 # cases, but helps in other cases, like with text files
251 254 # without extension.
252 255 mimetype, dispo = 'text/plain', 'inline'
253 256
254 257 if dispo == 'attachment':
255 258 dispo = 'attachment; filename=%s' % \
256 259 safe_str(f_path.split(os.sep)[-1])
257 260
258 261 response.content_disposition = dispo
259 262 response.content_type = mimetype
260 263 return file_node.content
261 264
262 265 @LoginRequired()
263 266 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
264 267 def edit(self, repo_name, revision, f_path):
265 268 repo = c.rhodecode_db_repo
266 269 if repo.enable_locking and repo.locked[0]:
267 270 h.flash(_('This repository is has been locked by %s on %s')
268 271 % (h.person_by_id(repo.locked[0]),
269 272 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
270 273 'warning')
271 274 return redirect(h.url('files_home',
272 275 repo_name=repo_name, revision='tip'))
273 276
274 277 # check if revision is a branch identifier- basically we cannot
275 278 # create multiple heads via file editing
276 279 _branches = repo.scm_instance.branches
277 280 # check if revision is a branch name or branch hash
278 281 if revision not in _branches.keys() + _branches.values():
279 282 h.flash(_('You can only edit files with revision '
280 283 'being a valid branch '), category='warning')
281 284 return redirect(h.url('files_home',
282 285 repo_name=repo_name, revision='tip',
283 286 f_path=f_path))
284 287
285 288 r_post = request.POST
286 289
287 290 c.cs = self.__get_cs_or_redirect(revision, repo_name)
288 291 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
289 292
290 293 if c.file.is_binary:
291 294 return redirect(url('files_home', repo_name=c.repo_name,
292 295 revision=c.cs.raw_id, f_path=f_path))
293 296 c.default_message = _('Edited file %s via RhodeCode') % (f_path)
294 297 c.f_path = f_path
295 298
296 299 if r_post:
297 300
298 301 old_content = c.file.content
299 302 sl = old_content.splitlines(1)
300 303 first_line = sl[0] if sl else ''
301 304 # modes: 0 - Unix, 1 - Mac, 2 - DOS
302 305 mode = detect_mode(first_line, 0)
303 306 content = convert_line_endings(r_post.get('content'), mode)
304 307
305 308 message = r_post.get('message') or c.default_message
306 309 author = self.rhodecode_user.full_contact
307 310
308 311 if content == old_content:
309 312 h.flash(_('No changes'), category='warning')
310 313 return redirect(url('changeset_home', repo_name=c.repo_name,
311 314 revision='tip'))
312 315 try:
313 316 self.scm_model.commit_change(repo=c.rhodecode_repo,
314 317 repo_name=repo_name, cs=c.cs,
315 318 user=self.rhodecode_user.user_id,
316 319 author=author, message=message,
317 320 content=content, f_path=f_path)
318 321 h.flash(_('Successfully committed to %s') % f_path,
319 322 category='success')
320 323
321 324 except Exception:
322 325 log.error(traceback.format_exc())
323 326 h.flash(_('Error occurred during commit'), category='error')
324 327 return redirect(url('changeset_home',
325 328 repo_name=c.repo_name, revision='tip'))
326 329
327 330 return render('files/files_edit.html')
328 331
329 332 @LoginRequired()
330 333 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
331 334 def add(self, repo_name, revision, f_path):
332 335
333 336 repo = Repository.get_by_repo_name(repo_name)
334 337 if repo.enable_locking and repo.locked[0]:
335 338 h.flash(_('This repository is has been locked by %s on %s')
336 339 % (h.person_by_id(repo.locked[0]),
337 340 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
338 341 'warning')
339 342 return redirect(h.url('files_home',
340 343 repo_name=repo_name, revision='tip'))
341 344
342 345 r_post = request.POST
343 346 c.cs = self.__get_cs_or_redirect(revision, repo_name,
344 347 redirect_after=False)
345 348 if c.cs is None:
346 349 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
347 350 c.default_message = (_('Added file via RhodeCode'))
348 351 c.f_path = f_path
349 352
350 353 if r_post:
351 354 unix_mode = 0
352 355 content = convert_line_endings(r_post.get('content'), unix_mode)
353 356
354 357 message = r_post.get('message') or c.default_message
355 358 filename = r_post.get('filename')
356 359 location = r_post.get('location')
357 360 file_obj = r_post.get('upload_file', None)
358 361
359 362 if file_obj is not None and hasattr(file_obj, 'filename'):
360 363 filename = file_obj.filename
361 364 content = file_obj.file
362 365
363 366 if not content:
364 367 h.flash(_('No content'), category='warning')
365 368 return redirect(url('changeset_home', repo_name=c.repo_name,
366 369 revision='tip'))
367 370 if not filename:
368 371 h.flash(_('No filename'), category='warning')
369 372 return redirect(url('changeset_home', repo_name=c.repo_name,
370 373 revision='tip'))
371 374 if location.startswith('/') or location.startswith('.') or '../' in location:
372 375 h.flash(_('Location must be relative path and must not '
373 376 'contain .. in path'), category='warning')
374 377 return redirect(url('changeset_home', repo_name=c.repo_name,
375 378 revision='tip'))
376 379 if location:
377 380 location = os.path.normpath(location)
378 381 filename = os.path.basename(filename)
379 382 node_path = os.path.join(location, filename)
380 383 author = self.rhodecode_user.full_contact
381 384
382 385 try:
383 386 self.scm_model.create_node(repo=c.rhodecode_repo,
384 387 repo_name=repo_name, cs=c.cs,
385 388 user=self.rhodecode_user.user_id,
386 389 author=author, message=message,
387 390 content=content, f_path=node_path)
388 391 h.flash(_('Successfully committed to %s') % node_path,
389 392 category='success')
390 393 except (NodeError, NodeAlreadyExistsError), e:
391 394 h.flash(_(e), category='error')
392 395 except Exception:
393 396 log.error(traceback.format_exc())
394 397 h.flash(_('Error occurred during commit'), category='error')
395 398 return redirect(url('changeset_home',
396 399 repo_name=c.repo_name, revision='tip'))
397 400
398 401 return render('files/files_add.html')
399 402
400 403 @LoginRequired()
401 404 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
402 405 'repository.admin')
403 406 def archivefile(self, repo_name, fname):
404 407
405 408 fileformat = None
406 409 revision = None
407 410 ext = None
408 411 subrepos = request.GET.get('subrepos') == 'true'
409 412
410 413 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
411 414 archive_spec = fname.split(ext_data[1])
412 415 if len(archive_spec) == 2 and archive_spec[1] == '':
413 416 fileformat = a_type or ext_data[1]
414 417 revision = archive_spec[0]
415 418 ext = ext_data[1]
416 419
417 420 try:
418 421 dbrepo = RepoModel().get_by_repo_name(repo_name)
419 422 if not dbrepo.enable_downloads:
420 423 return _('Downloads disabled')
421 424
422 425 if c.rhodecode_repo.alias == 'hg':
423 426 # patch and reset hooks section of UI config to not run any
424 427 # hooks on fetching archives with subrepos
425 428 for k, v in c.rhodecode_repo._repo.ui.configitems('hooks'):
426 429 c.rhodecode_repo._repo.ui.setconfig('hooks', k, None)
427 430
428 431 cs = c.rhodecode_repo.get_changeset(revision)
429 432 content_type = settings.ARCHIVE_SPECS[fileformat][0]
430 433 except ChangesetDoesNotExistError:
431 434 return _('Unknown revision %s') % revision
432 435 except EmptyRepositoryError:
433 436 return _('Empty repository')
434 437 except (ImproperArchiveTypeError, KeyError):
435 438 return _('Unknown archive type')
436 439 # archive cache
437 440 from rhodecode import CONFIG
438 441 rev_name = cs.raw_id[:12]
439 442 archive_name = '%s-%s%s' % (safe_str(repo_name.replace('/', '_')),
440 443 safe_str(rev_name), ext)
441 444
442 445 use_cached_archive = False # defines if we use cached version of archive
443 446 archive_cache_enabled = CONFIG.get('archive_cache_dir')
444 447 if not subrepos and archive_cache_enabled:
445 448 #check if we it's ok to write
446 449 if not os.path.isdir(CONFIG['archive_cache_dir']):
447 450 os.makedirs(CONFIG['archive_cache_dir'])
448 451 cached_archive_path = os.path.join(CONFIG['archive_cache_dir'], archive_name)
449 452 if os.path.isfile(cached_archive_path):
450 453 log.debug('Found cached archive in %s' % cached_archive_path)
451 454 fd, archive = None, cached_archive_path
452 455 use_cached_archive = True
453 456 else:
454 457 log.debug('Archive %s is not yet cached' % (archive_name))
455 458
456 459 if not use_cached_archive:
457 460 #generate new archive
458 461 try:
459 462 fd, archive = tempfile.mkstemp()
460 463 t = open(archive, 'wb')
461 464 log.debug('Creating new temp archive in %s' % archive)
462 465 cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
463 466 if archive_cache_enabled:
464 467 #if we generated the archive and use cache rename that
465 468 log.debug('Storing new archive in %s' % cached_archive_path)
466 469 shutil.move(archive, cached_archive_path)
467 470 archive = cached_archive_path
468 471 finally:
469 472 t.close()
470 473
471 474 def get_chunked_archive(archive):
472 475 stream = open(archive, 'rb')
473 476 while True:
474 477 data = stream.read(16 * 1024)
475 478 if not data:
476 479 stream.close()
477 480 if fd: # fd means we used temporary file
478 481 os.close(fd)
479 482 if not archive_cache_enabled:
480 483 log.debug('Destroing temp archive %s' % archive)
481 484 os.remove(archive)
482 485 break
483 486 yield data
484 487
485 488 response.content_disposition = str('attachment; filename=%s' % (archive_name))
486 489 response.content_type = str(content_type)
487 490 return get_chunked_archive(archive)
488 491
489 492 @LoginRequired()
490 493 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
491 494 'repository.admin')
492 495 def diff(self, repo_name, f_path):
493 496 ignore_whitespace = request.GET.get('ignorews') == '1'
494 497 line_context = request.GET.get('context', 3)
495 498 diff1 = request.GET.get('diff1', '')
496 499 diff2 = request.GET.get('diff2', '')
497 500 c.action = request.GET.get('diff')
498 501 c.no_changes = diff1 == diff2
499 502 c.f_path = f_path
500 503 c.big_diff = False
501 504 c.anchor_url = anchor_url
502 505 c.ignorews_url = _ignorews_url
503 506 c.context_url = _context_url
504 507 c.changes = OrderedDict()
505 508 c.changes[diff2] = []
506 509
507 510 #special case if we want a show rev only, it's impl here
508 511 #to reduce JS and callbacks
509 512
510 513 if request.GET.get('show_rev'):
511 514 if str2bool(request.GET.get('annotate', 'False')):
512 515 _url = url('files_annotate_home', repo_name=c.repo_name,
513 516 revision=diff1, f_path=c.f_path)
514 517 else:
515 518 _url = url('files_home', repo_name=c.repo_name,
516 519 revision=diff1, f_path=c.f_path)
517 520
518 521 return redirect(_url)
519 522 try:
520 523 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
521 524 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
522 525 try:
523 526 node1 = c.changeset_1.get_node(f_path)
524 527 if node1.is_dir():
525 528 raise NodeError('%s path is a %s not a file'
526 529 % (node1, type(node1)))
527 530 except NodeDoesNotExistError:
528 531 c.changeset_1 = EmptyChangeset(cs=diff1,
529 532 revision=c.changeset_1.revision,
530 533 repo=c.rhodecode_repo)
531 534 node1 = FileNode(f_path, '', changeset=c.changeset_1)
532 535 else:
533 536 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
534 537 node1 = FileNode(f_path, '', changeset=c.changeset_1)
535 538
536 539 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
537 540 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
538 541 try:
539 542 node2 = c.changeset_2.get_node(f_path)
540 543 if node2.is_dir():
541 544 raise NodeError('%s path is a %s not a file'
542 545 % (node2, type(node2)))
543 546 except NodeDoesNotExistError:
544 547 c.changeset_2 = EmptyChangeset(cs=diff2,
545 548 revision=c.changeset_2.revision,
546 549 repo=c.rhodecode_repo)
547 550 node2 = FileNode(f_path, '', changeset=c.changeset_2)
548 551 else:
549 552 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
550 553 node2 = FileNode(f_path, '', changeset=c.changeset_2)
551 554 except (RepositoryError, NodeError):
552 555 log.error(traceback.format_exc())
553 556 return redirect(url('files_home', repo_name=c.repo_name,
554 557 f_path=f_path))
555 558
556 559 if c.action == 'download':
557 560 _diff = diffs.get_gitdiff(node1, node2,
558 561 ignore_whitespace=ignore_whitespace,
559 562 context=line_context)
560 563 diff = diffs.DiffProcessor(_diff, format='gitdiff')
561 564
562 565 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
563 566 response.content_type = 'text/plain'
564 567 response.content_disposition = (
565 568 'attachment; filename=%s' % diff_name
566 569 )
567 570 return diff.as_raw()
568 571
569 572 elif c.action == 'raw':
570 573 _diff = diffs.get_gitdiff(node1, node2,
571 574 ignore_whitespace=ignore_whitespace,
572 575 context=line_context)
573 576 diff = diffs.DiffProcessor(_diff, format='gitdiff')
574 577 response.content_type = 'text/plain'
575 578 return diff.as_raw()
576 579
577 580 else:
578 581 fid = h.FID(diff2, node2.path)
579 582 line_context_lcl = get_line_ctx(fid, request.GET)
580 583 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
581 584
582 585 lim = request.GET.get('fulldiff') or self.cut_off_limit
583 586 _, cs1, cs2, diff, st = diffs.wrapped_diff(filenode_old=node1,
584 587 filenode_new=node2,
585 588 cut_off_limit=lim,
586 589 ignore_whitespace=ign_whitespace_lcl,
587 590 line_context=line_context_lcl,
588 591 enable_comments=False)
589 592 op = ''
590 593 filename = node1.path
591 594 cs_changes = {
592 595 'fid': [cs1, cs2, op, filename, diff, st]
593 596 }
594 597 c.changes = cs_changes
595 598
596 599 return render('files/file_diff.html')
597 600
598 601 def _get_node_history(self, cs, f_path, changesets=None):
599 602 """
600 603 get changesets history for given node
601 604
602 605 :param cs: changeset to calculate history
603 606 :param f_path: path for node to calculate history for
604 607 :param changesets: if passed don't calculate history and take
605 608 changesets defined in this list
606 609 """
607 610 # calculate history based on tip
608 611 tip_cs = c.rhodecode_repo.get_changeset()
609 612 if changesets is None:
610 613 try:
611 614 changesets = tip_cs.get_file_history(f_path)
612 615 except (NodeDoesNotExistError, ChangesetError):
613 616 #this node is not present at tip !
614 617 changesets = cs.get_file_history(f_path)
615 618 hist_l = []
616 619
617 620 changesets_group = ([], _("Changesets"))
618 621 branches_group = ([], _("Branches"))
619 622 tags_group = ([], _("Tags"))
620 623 _hg = cs.repository.alias == 'hg'
621 624 for chs in changesets:
622 625 #_branch = '(%s)' % chs.branch if _hg else ''
623 626 _branch = chs.branch
624 627 n_desc = 'r%s:%s (%s)' % (chs.revision, chs.short_id, _branch)
625 628 changesets_group[0].append((chs.raw_id, n_desc,))
626 629 hist_l.append(changesets_group)
627 630
628 631 for name, chs in c.rhodecode_repo.branches.items():
629 632 branches_group[0].append((chs, name),)
630 633 hist_l.append(branches_group)
631 634
632 635 for name, chs in c.rhodecode_repo.tags.items():
633 636 tags_group[0].append((chs, name),)
634 637 hist_l.append(tags_group)
635 638
636 639 return hist_l, changesets
637 640
638 641 @LoginRequired()
639 642 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
640 643 'repository.admin')
641 644 @jsonify
642 645 def nodelist(self, repo_name, revision, f_path):
643 646 if request.environ.get('HTTP_X_PARTIAL_XHR'):
644 647 cs = self.__get_cs_or_redirect(revision, repo_name)
645 648 _d, _f = ScmModel().get_nodes(repo_name, cs.raw_id, f_path,
646 649 flat=False)
647 650 return {'nodes': _d + _f}
@@ -1,58 +1,58 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.followers
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Followers controller for rhodecode
7 7
8 8 :created_on: Apr 23, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import logging
26 26
27 27 from pylons import tmpl_context as c, request
28 28
29 29 from rhodecode.lib.helpers import Page
30 30 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
31 31 from rhodecode.lib.base import BaseRepoController, render
32 32 from rhodecode.model.db import Repository, User, UserFollowing
33 33 from rhodecode.lib.utils2 import safe_int
34 34
35 35 log = logging.getLogger(__name__)
36 36
37 37
38 38 class FollowersController(BaseRepoController):
39 39
40 def __before__(self):
41 super(FollowersController, self).__before__()
42
40 43 @LoginRequired()
41 44 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
42 45 'repository.admin')
43 def __before__(self):
44 super(FollowersController, self).__before__()
45
46 46 def followers(self, repo_name):
47 47 p = safe_int(request.GET.get('page', 1), 1)
48 48 repo_id = c.rhodecode_db_repo.repo_id
49 49 d = UserFollowing.get_repo_followers(repo_id)\
50 50 .order_by(UserFollowing.follows_from)
51 51 c.followers_pager = Page(d, page=p, items_per_page=20)
52 52
53 53 c.followers_data = render('/followers/followers_data.html')
54 54
55 55 if request.environ.get('HTTP_X_PARTIAL_XHR'):
56 56 return c.followers_data
57 57
58 58 return render('/followers/followers.html')
@@ -1,191 +1,193 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.forks
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 forks controller for rhodecode
7 7
8 8 :created_on: Apr 23, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import logging
26 26 import formencode
27 27 import traceback
28 28 from formencode import htmlfill
29 29
30 30 from pylons import tmpl_context as c, request, url
31 31 from pylons.controllers.util import redirect
32 32 from pylons.i18n.translation import _
33 33
34 34 import rhodecode.lib.helpers as h
35 35
36 36 from rhodecode.lib.helpers import Page
37 37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \
38 38 NotAnonymous, HasRepoPermissionAny, HasPermissionAllDecorator,\
39 39 HasPermissionAnyDecorator
40 40 from rhodecode.lib.base import BaseRepoController, render
41 41 from rhodecode.model.db import Repository, RepoGroup, UserFollowing, User,\
42 42 RhodeCodeUi
43 43 from rhodecode.model.repo import RepoModel
44 44 from rhodecode.model.forms import RepoForkForm
45 45 from rhodecode.model.scm import ScmModel, RepoGroupList
46 46 from rhodecode.lib.utils2 import safe_int
47 47
48 48 log = logging.getLogger(__name__)
49 49
50 50
51 51 class ForksController(BaseRepoController):
52 52
53 @LoginRequired()
54 53 def __before__(self):
55 54 super(ForksController, self).__before__()
56 55
57 56 def __load_defaults(self):
58 57 acl_groups = RepoGroupList(RepoGroup.query().all(),
59 58 perm_set=['group.write', 'group.admin'])
60 59 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
61 60 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
62 61 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
63 62 c.landing_revs_choices = choices
64 63 c.can_update = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_UPDATE).ui_active
65 64
66 65 def __load_data(self, repo_name=None):
67 66 """
68 67 Load defaults settings for edit, and update
69 68
70 69 :param repo_name:
71 70 """
72 71 self.__load_defaults()
73 72
74 73 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
75 74 repo = db_repo.scm_instance
76 75
77 76 if c.repo_info is None:
78 77 h.not_mapped_error(repo_name)
79 78 return redirect(url('repos'))
80 79
81 80 c.default_user_id = User.get_default_user().user_id
82 81 c.in_public_journal = UserFollowing.query()\
83 82 .filter(UserFollowing.user_id == c.default_user_id)\
84 83 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
85 84
86 85 if c.repo_info.stats:
87 86 last_rev = c.repo_info.stats.stat_on_revision+1
88 87 else:
89 88 last_rev = 0
90 89 c.stats_revision = last_rev
91 90
92 91 c.repo_last_rev = repo.count() if repo.revisions else 0
93 92
94 93 if last_rev == 0 or c.repo_last_rev == 0:
95 94 c.stats_percentage = 0
96 95 else:
97 96 c.stats_percentage = '%.2f' % ((float((last_rev)) /
98 97 c.repo_last_rev) * 100)
99 98
100 99 defaults = RepoModel()._get_defaults(repo_name)
101 100 # alter the description to indicate a fork
102 101 defaults['description'] = ('fork of repository: %s \n%s'
103 102 % (defaults['repo_name'],
104 103 defaults['description']))
105 104 # add suffix to fork
106 105 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
107 106
108 107 return defaults
109 108
109 @LoginRequired()
110 110 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
111 111 'repository.admin')
112 112 def forks(self, repo_name):
113 113 p = safe_int(request.GET.get('page', 1), 1)
114 114 repo_id = c.rhodecode_db_repo.repo_id
115 115 d = []
116 116 for r in Repository.get_repo_forks(repo_id):
117 117 if not HasRepoPermissionAny(
118 118 'repository.read', 'repository.write', 'repository.admin'
119 119 )(r.repo_name, 'get forks check'):
120 120 continue
121 121 d.append(r)
122 122 c.forks_pager = Page(d, page=p, items_per_page=20)
123 123
124 124 c.forks_data = render('/forks/forks_data.html')
125 125
126 126 if request.environ.get('HTTP_X_PARTIAL_XHR'):
127 127 return c.forks_data
128 128
129 129 return render('/forks/forks.html')
130 130
131 @LoginRequired()
131 132 @NotAnonymous()
132 133 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
133 134 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
134 135 'repository.admin')
135 136 def fork(self, repo_name):
136 137 c.repo_info = Repository.get_by_repo_name(repo_name)
137 138 if not c.repo_info:
138 139 h.not_mapped_error(repo_name)
139 140 return redirect(url('home'))
140 141
141 142 defaults = self.__load_data(repo_name)
142 143
143 144 return htmlfill.render(
144 145 render('forks/fork.html'),
145 146 defaults=defaults,
146 147 encoding="UTF-8",
147 148 force_defaults=False
148 149 )
149 150
151 @LoginRequired()
150 152 @NotAnonymous()
151 153 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
152 154 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
153 155 'repository.admin')
154 156 def fork_create(self, repo_name):
155 157 self.__load_defaults()
156 158 c.repo_info = Repository.get_by_repo_name(repo_name)
157 159 _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type},
158 160 repo_groups=c.repo_groups_choices,
159 161 landing_revs=c.landing_revs_choices)()
160 162 form_result = {}
161 163 try:
162 164 form_result = _form.to_python(dict(request.POST))
163 165
164 166 # an approximation that is better than nothing
165 167 if not RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_UPDATE).ui_active:
166 168 form_result['update_after_clone'] = False
167 169
168 170 # create fork is done sometimes async on celery, db transaction
169 171 # management is handled there.
170 172 RepoModel().create_fork(form_result, self.rhodecode_user.user_id)
171 173 fork_url = h.link_to(form_result['repo_name_full'],
172 174 h.url('summary_home', repo_name=form_result['repo_name_full']))
173 175
174 176 h.flash(h.literal(_('Forked repository %s as %s') \
175 177 % (repo_name, fork_url)),
176 178 category='success')
177 179 except formencode.Invalid, errors:
178 180 c.new_repo = errors.value['repo_name']
179 181
180 182 return htmlfill.render(
181 183 render('forks/fork.html'),
182 184 defaults=errors.value,
183 185 errors=errors.error_dict or {},
184 186 prefix_error=False,
185 187 encoding="UTF-8")
186 188 except Exception:
187 189 log.error(traceback.format_exc())
188 190 h.flash(_('An error occurred during repository forking %s') %
189 191 repo_name, category='error')
190 192
191 193 return redirect(h.url('summary_home', repo_name=repo_name))
@@ -1,87 +1,89 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.home
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Home controller for Rhodecode
7 7
8 8 :created_on: Feb 18, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27
28 28 from pylons import tmpl_context as c, request
29 29 from pylons.i18n.translation import _
30 30 from webob.exc import HTTPBadRequest
31 31 from sqlalchemy.sql.expression import func
32 32
33 33 import rhodecode
34 34 from rhodecode.lib import helpers as h
35 35 from rhodecode.lib.compat import json
36 36 from rhodecode.lib.auth import LoginRequired
37 37 from rhodecode.lib.base import BaseController, render
38 38 from rhodecode.model.db import Repository
39 39 from rhodecode.model.repo import RepoModel
40 40
41 41
42 42 log = logging.getLogger(__name__)
43 43
44 44
45 45 class HomeController(BaseController):
46 46
47 @LoginRequired()
48 47 def __before__(self):
49 48 super(HomeController, self).__before__()
50 49
50 @LoginRequired()
51 51 def index(self):
52 52 c.groups = self.scm_model.get_repos_groups()
53 53 c.group = None
54 54
55 55 if not c.visual.lightweight_dashboard:
56 56 c.repos_list = self.scm_model.get_repos()
57 57 ## lightweight version of dashboard
58 58 else:
59 59 c.repos_list = Repository.query()\
60 60 .filter(Repository.group_id == None)\
61 61 .order_by(func.lower(Repository.repo_name))\
62 62 .all()
63 63
64 64 repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
65 65 admin=False)
66 66 #json used to render the grid
67 67 c.data = json.dumps(repos_data)
68 68
69 69 return render('/index.html')
70 70
71 @LoginRequired()
71 72 def repo_switcher(self):
72 73 if request.is_xhr:
73 74 all_repos = Repository.query().order_by(Repository.repo_name).all()
74 75 c.repos_list = self.scm_model.get_repos(all_repos,
75 76 sort_key='name_sort',
76 77 simple=True)
77 78 return render('/repo_switcher_list.html')
78 79 else:
79 80 raise HTTPBadRequest()
80 81
82 @LoginRequired()
81 83 def branch_tag_switcher(self, repo_name):
82 84 if request.is_xhr:
83 85 c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
84 86 if c.rhodecode_db_repo:
85 87 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
86 88 return render('/switch_to_list.html')
87 89 raise HTTPBadRequest()
@@ -1,379 +1,379 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.journal
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Journal controller for pylons
7 7
8 8 :created_on: Nov 21, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import logging
26 26 from itertools import groupby
27 27
28 28 from sqlalchemy import or_
29 29 from sqlalchemy.orm import joinedload
30 30 from sqlalchemy.sql.expression import func
31 31
32 32 from webhelpers.paginate import Page
33 33 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
34 34
35 35 from webob.exc import HTTPBadRequest
36 36 from pylons import request, tmpl_context as c, response, url
37 37 from pylons.i18n.translation import _
38 38
39 39 import rhodecode.lib.helpers as h
40 40 from rhodecode.lib.auth import LoginRequired, NotAnonymous
41 41 from rhodecode.lib.base import BaseController, render
42 42 from rhodecode.model.db import UserLog, UserFollowing, Repository, User
43 43 from rhodecode.model.meta import Session
44 44 from rhodecode.lib.utils2 import safe_int, AttributeDict
45 45 from rhodecode.controllers.admin.admin import _journal_filter
46 46 from rhodecode.model.repo import RepoModel
47 47 from rhodecode.lib.compat import json
48 48
49 49 log = logging.getLogger(__name__)
50 50
51 51
52 52 class JournalController(BaseController):
53 53
54 54 def __before__(self):
55 55 super(JournalController, self).__before__()
56 56 self.language = 'en-us'
57 57 self.ttl = "5"
58 58 self.feed_nr = 20
59 59 c.search_term = request.GET.get('filter')
60 60
61 def _get_daily_aggregate(self, journal):
62 groups = []
63 for k, g in groupby(journal, lambda x: x.action_as_day):
64 user_group = []
65 #groupby username if it's a present value, else fallback to journal username
66 for _, g2 in groupby(list(g), lambda x: x.user.username if x.user else x.username):
67 l = list(g2)
68 user_group.append((l[0].user, l))
69
70 groups.append((k, user_group,))
71
72 return groups
73
74 def _get_journal_data(self, following_repos):
75 repo_ids = [x.follows_repository.repo_id for x in following_repos
76 if x.follows_repository is not None]
77 user_ids = [x.follows_user.user_id for x in following_repos
78 if x.follows_user is not None]
79
80 filtering_criterion = None
81
82 if repo_ids and user_ids:
83 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
84 UserLog.user_id.in_(user_ids))
85 if repo_ids and not user_ids:
86 filtering_criterion = UserLog.repository_id.in_(repo_ids)
87 if not repo_ids and user_ids:
88 filtering_criterion = UserLog.user_id.in_(user_ids)
89 if filtering_criterion is not None:
90 journal = self.sa.query(UserLog)\
91 .options(joinedload(UserLog.user))\
92 .options(joinedload(UserLog.repository))
93 #filter
94 try:
95 journal = _journal_filter(journal, c.search_term)
96 except Exception:
97 # we want this to crash for now
98 raise
99 journal = journal.filter(filtering_criterion)\
100 .order_by(UserLog.action_date.desc())
101 else:
102 journal = []
103
104 return journal
105
106 def _atom_feed(self, repos, public=True):
107 journal = self._get_journal_data(repos)
108 if public:
109 _link = url('public_journal_atom', qualified=True)
110 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
111 'atom feed')
112 else:
113 _link = url('journal_atom', qualified=True)
114 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'atom feed')
115
116 feed = Atom1Feed(title=_desc,
117 link=_link,
118 description=_desc,
119 language=self.language,
120 ttl=self.ttl)
121
122 for entry in journal[:self.feed_nr]:
123 user = entry.user
124 if user is None:
125 #fix deleted users
126 user = AttributeDict({'short_contact': entry.username,
127 'email': '',
128 'full_contact': ''})
129 action, action_extra, ico = h.action_parser(entry, feed=True)
130 title = "%s - %s %s" % (user.short_contact, action(),
131 entry.repository.repo_name)
132 desc = action_extra()
133 _url = None
134 if entry.repository is not None:
135 _url = url('changelog_home',
136 repo_name=entry.repository.repo_name,
137 qualified=True)
138
139 feed.add_item(title=title,
140 pubdate=entry.action_date,
141 link=_url or url('', qualified=True),
142 author_email=user.email,
143 author_name=user.full_contact,
144 description=desc)
145
146 response.content_type = feed.mime_type
147 return feed.writeString('utf-8')
148
149 def _rss_feed(self, repos, public=True):
150 journal = self._get_journal_data(repos)
151 if public:
152 _link = url('public_journal_atom', qualified=True)
153 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
154 'rss feed')
155 else:
156 _link = url('journal_atom', qualified=True)
157 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'rss feed')
158
159 feed = Rss201rev2Feed(title=_desc,
160 link=_link,
161 description=_desc,
162 language=self.language,
163 ttl=self.ttl)
164
165 for entry in journal[:self.feed_nr]:
166 user = entry.user
167 if user is None:
168 #fix deleted users
169 user = AttributeDict({'short_contact': entry.username,
170 'email': '',
171 'full_contact': ''})
172 action, action_extra, ico = h.action_parser(entry, feed=True)
173 title = "%s - %s %s" % (user.short_contact, action(),
174 entry.repository.repo_name)
175 desc = action_extra()
176 _url = None
177 if entry.repository is not None:
178 _url = url('changelog_home',
179 repo_name=entry.repository.repo_name,
180 qualified=True)
181
182 feed.add_item(title=title,
183 pubdate=entry.action_date,
184 link=_url or url('', qualified=True),
185 author_email=user.email,
186 author_name=user.full_contact,
187 description=desc)
188
189 response.content_type = feed.mime_type
190 return feed.writeString('utf-8')
191
61 192 @LoginRequired()
62 193 @NotAnonymous()
63 194 def index(self):
64 195 # Return a rendered template
65 196 p = safe_int(request.GET.get('page', 1), 1)
66 197 c.user = User.get(self.rhodecode_user.user_id)
67 198 c.following = self.sa.query(UserFollowing)\
68 199 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
69 200 .options(joinedload(UserFollowing.follows_repository))\
70 201 .all()
71 202
72 203 journal = self._get_journal_data(c.following)
73 204
74 205 def url_generator(**kw):
75 206 return url.current(filter=c.search_term, **kw)
76 207
77 208 c.journal_pager = Page(journal, page=p, items_per_page=20, url=url_generator)
78 209 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
79 210
80 211 c.journal_data = render('journal/journal_data.html')
81 212 if request.environ.get('HTTP_X_PARTIAL_XHR'):
82 213 return c.journal_data
83 214
84 215 repos_list = Session().query(Repository)\
85 216 .filter(Repository.user_id ==
86 217 self.rhodecode_user.user_id)\
87 218 .order_by(func.lower(Repository.repo_name)).all()
88 219
89 220 repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list,
90 221 admin=True)
91 222 #json used to render the grid
92 223 c.data = json.dumps(repos_data)
93 224
94 225 watched_repos_data = []
95 226
96 227 ## watched repos
97 228 _render = RepoModel._render_datatable
98 229
99 230 def quick_menu(repo_name):
100 231 return _render('quick_menu', repo_name)
101 232
102 233 def repo_lnk(name, rtype, private, fork_of):
103 234 return _render('repo_name', name, rtype, private, fork_of,
104 235 short_name=False, admin=False)
105 236
106 237 def last_rev(repo_name, cs_cache):
107 238 return _render('revision', repo_name, cs_cache.get('revision'),
108 239 cs_cache.get('raw_id'), cs_cache.get('author'),
109 240 cs_cache.get('message'))
110 241
111 242 def desc(desc):
112 243 from pylons import tmpl_context as c
113 244 if c.visual.stylify_metatags:
114 245 return h.urlify_text(h.desc_stylize(h.truncate(desc, 60)))
115 246 else:
116 247 return h.urlify_text(h.truncate(desc, 60))
117 248
118 249 def repo_actions(repo_name):
119 250 return _render('repo_actions', repo_name)
120 251
121 252 def owner_actions(user_id, username):
122 253 return _render('user_name', user_id, username)
123 254
124 255 def toogle_follow(repo_id):
125 256 return _render('toggle_follow', repo_id)
126 257
127 258 for entry in c.following:
128 259 repo = entry.follows_repository
129 260 cs_cache = repo.changeset_cache
130 261 row = {
131 262 "menu": quick_menu(repo.repo_name),
132 263 "raw_name": repo.repo_name.lower(),
133 264 "name": repo_lnk(repo.repo_name, repo.repo_type,
134 265 repo.private, repo.fork),
135 266 "last_changeset": last_rev(repo.repo_name, cs_cache),
136 267 "raw_tip": cs_cache.get('revision'),
137 268 "action": toogle_follow(repo.repo_id)
138 269 }
139 270
140 271 watched_repos_data.append(row)
141 272
142 273 c.watched_data = json.dumps({
143 274 "totalRecords": len(c.following),
144 275 "startIndex": 0,
145 276 "sort": "name",
146 277 "dir": "asc",
147 278 "records": watched_repos_data
148 279 })
149 280 return render('journal/journal.html')
150 281
151 282 @LoginRequired(api_access=True)
152 283 @NotAnonymous()
153 284 def journal_atom(self):
154 285 """
155 286 Produce an atom-1.0 feed via feedgenerator module
156 287 """
157 288 following = self.sa.query(UserFollowing)\
158 289 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
159 290 .options(joinedload(UserFollowing.follows_repository))\
160 291 .all()
161 292 return self._atom_feed(following, public=False)
162 293
163 294 @LoginRequired(api_access=True)
164 295 @NotAnonymous()
165 296 def journal_rss(self):
166 297 """
167 298 Produce an rss feed via feedgenerator module
168 299 """
169 300 following = self.sa.query(UserFollowing)\
170 301 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
171 302 .options(joinedload(UserFollowing.follows_repository))\
172 303 .all()
173 304 return self._rss_feed(following, public=False)
174 305
175 def _get_daily_aggregate(self, journal):
176 groups = []
177 for k, g in groupby(journal, lambda x: x.action_as_day):
178 user_group = []
179 #groupby username if it's a present value, else fallback to journal username
180 for _, g2 in groupby(list(g), lambda x: x.user.username if x.user else x.username):
181 l = list(g2)
182 user_group.append((l[0].user, l))
183
184 groups.append((k, user_group,))
185
186 return groups
187
188 def _get_journal_data(self, following_repos):
189 repo_ids = [x.follows_repository.repo_id for x in following_repos
190 if x.follows_repository is not None]
191 user_ids = [x.follows_user.user_id for x in following_repos
192 if x.follows_user is not None]
193
194 filtering_criterion = None
195
196 if repo_ids and user_ids:
197 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
198 UserLog.user_id.in_(user_ids))
199 if repo_ids and not user_ids:
200 filtering_criterion = UserLog.repository_id.in_(repo_ids)
201 if not repo_ids and user_ids:
202 filtering_criterion = UserLog.user_id.in_(user_ids)
203 if filtering_criterion is not None:
204 journal = self.sa.query(UserLog)\
205 .options(joinedload(UserLog.user))\
206 .options(joinedload(UserLog.repository))
207 #filter
208 try:
209 journal = _journal_filter(journal, c.search_term)
210 except Exception:
211 # we want this to crash for now
212 raise
213 journal = journal.filter(filtering_criterion)\
214 .order_by(UserLog.action_date.desc())
215 else:
216 journal = []
217
218 return journal
219
220 306 @LoginRequired()
221 307 @NotAnonymous()
222 308 def toggle_following(self):
223 309 cur_token = request.POST.get('auth_token')
224 310 token = h.get_token()
225 311 if cur_token == token:
226 312
227 313 user_id = request.POST.get('follows_user_id')
228 314 if user_id:
229 315 try:
230 316 self.scm_model.toggle_following_user(user_id,
231 317 self.rhodecode_user.user_id)
232 318 Session.commit()
233 319 return 'ok'
234 320 except Exception:
235 321 raise HTTPBadRequest()
236 322
237 323 repo_id = request.POST.get('follows_repo_id')
238 324 if repo_id:
239 325 try:
240 326 self.scm_model.toggle_following_repo(repo_id,
241 327 self.rhodecode_user.user_id)
242 328 Session.commit()
243 329 return 'ok'
244 330 except Exception:
245 331 raise HTTPBadRequest()
246 332
247 333 log.debug('token mismatch %s vs %s' % (cur_token, token))
248 334 raise HTTPBadRequest()
249 335
250 336 @LoginRequired()
251 337 def public_journal(self):
252 338 # Return a rendered template
253 339 p = safe_int(request.GET.get('page', 1), 1)
254 340
255 341 c.following = self.sa.query(UserFollowing)\
256 342 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
257 343 .options(joinedload(UserFollowing.follows_repository))\
258 344 .all()
259 345
260 346 journal = self._get_journal_data(c.following)
261 347
262 348 c.journal_pager = Page(journal, page=p, items_per_page=20)
263 349
264 350 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
265 351
266 352 c.journal_data = render('journal/journal_data.html')
267 353 if request.environ.get('HTTP_X_PARTIAL_XHR'):
268 354 return c.journal_data
269 355 return render('journal/public_journal.html')
270 356
271 def _atom_feed(self, repos, public=True):
272 journal = self._get_journal_data(repos)
273 if public:
274 _link = url('public_journal_atom', qualified=True)
275 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
276 'atom feed')
277 else:
278 _link = url('journal_atom', qualified=True)
279 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'atom feed')
280
281 feed = Atom1Feed(title=_desc,
282 link=_link,
283 description=_desc,
284 language=self.language,
285 ttl=self.ttl)
286
287 for entry in journal[:self.feed_nr]:
288 user = entry.user
289 if user is None:
290 #fix deleted users
291 user = AttributeDict({'short_contact': entry.username,
292 'email': '',
293 'full_contact': ''})
294 action, action_extra, ico = h.action_parser(entry, feed=True)
295 title = "%s - %s %s" % (user.short_contact, action(),
296 entry.repository.repo_name)
297 desc = action_extra()
298 _url = None
299 if entry.repository is not None:
300 _url = url('changelog_home',
301 repo_name=entry.repository.repo_name,
302 qualified=True)
303
304 feed.add_item(title=title,
305 pubdate=entry.action_date,
306 link=_url or url('', qualified=True),
307 author_email=user.email,
308 author_name=user.full_contact,
309 description=desc)
310
311 response.content_type = feed.mime_type
312 return feed.writeString('utf-8')
313
314 def _rss_feed(self, repos, public=True):
315 journal = self._get_journal_data(repos)
316 if public:
317 _link = url('public_journal_atom', qualified=True)
318 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
319 'rss feed')
320 else:
321 _link = url('journal_atom', qualified=True)
322 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'rss feed')
323
324 feed = Rss201rev2Feed(title=_desc,
325 link=_link,
326 description=_desc,
327 language=self.language,
328 ttl=self.ttl)
329
330 for entry in journal[:self.feed_nr]:
331 user = entry.user
332 if user is None:
333 #fix deleted users
334 user = AttributeDict({'short_contact': entry.username,
335 'email': '',
336 'full_contact': ''})
337 action, action_extra, ico = h.action_parser(entry, feed=True)
338 title = "%s - %s %s" % (user.short_contact, action(),
339 entry.repository.repo_name)
340 desc = action_extra()
341 _url = None
342 if entry.repository is not None:
343 _url = url('changelog_home',
344 repo_name=entry.repository.repo_name,
345 qualified=True)
346
347 feed.add_item(title=title,
348 pubdate=entry.action_date,
349 link=_url or url('', qualified=True),
350 author_email=user.email,
351 author_name=user.full_contact,
352 description=desc)
353
354 response.content_type = feed.mime_type
355 return feed.writeString('utf-8')
356
357 357 @LoginRequired(api_access=True)
358 358 def public_journal_atom(self):
359 359 """
360 360 Produce an atom-1.0 feed via feedgenerator module
361 361 """
362 362 c.following = self.sa.query(UserFollowing)\
363 363 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
364 364 .options(joinedload(UserFollowing.follows_repository))\
365 365 .all()
366 366
367 367 return self._atom_feed(c.following)
368 368
369 369 @LoginRequired(api_access=True)
370 370 def public_journal_rss(self):
371 371 """
372 372 Produce an rss2 feed via feedgenerator module
373 373 """
374 374 c.following = self.sa.query(UserFollowing)\
375 375 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
376 376 .options(joinedload(UserFollowing.follows_repository))\
377 377 .all()
378 378
379 379 return self._rss_feed(c.following)
@@ -1,521 +1,542 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.pullrequests
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 pull requests controller for rhodecode for initializing pull requests
7 7
8 8 :created_on: May 7, 2012
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import logging
26 26 import traceback
27 27 import formencode
28 28
29 29 from webob.exc import HTTPNotFound, HTTPForbidden
30 30 from collections import defaultdict
31 31 from itertools import groupby
32 32
33 33 from pylons import request, response, session, tmpl_context as c, url
34 34 from pylons.controllers.util import abort, redirect
35 35 from pylons.i18n.translation import _
36 36
37 37 from rhodecode.lib.compat import json
38 38 from rhodecode.lib.base import BaseRepoController, render
39 39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
40 40 NotAnonymous
41 41 from rhodecode.lib.helpers import Page
42 42 from rhodecode.lib import helpers as h
43 43 from rhodecode.lib import diffs
44 44 from rhodecode.lib.utils import action_logger, jsonify
45 45 from rhodecode.lib.vcs.utils import safe_str
46 46 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
47 47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
48 48 from rhodecode.lib.diffs import LimitedDiffContainer
49 49 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
50 50 ChangesetComment
51 51 from rhodecode.model.pull_request import PullRequestModel
52 52 from rhodecode.model.meta import Session
53 53 from rhodecode.model.repo import RepoModel
54 54 from rhodecode.model.comment import ChangesetCommentsModel
55 55 from rhodecode.model.changeset_status import ChangesetStatusModel
56 56 from rhodecode.model.forms import PullRequestForm
57 57 from mercurial import scmutil
58 58 from rhodecode.lib.utils2 import safe_int
59 59
60 60 log = logging.getLogger(__name__)
61 61
62 62
63 63 class PullrequestsController(BaseRepoController):
64 64
65 @LoginRequired()
66 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
67 'repository.admin')
68 65 def __before__(self):
69 66 super(PullrequestsController, self).__before__()
70 67 repo_model = RepoModel()
71 68 c.users_array = repo_model.get_users_js()
72 69 c.users_groups_array = repo_model.get_users_groups_js()
73 70
74 71 def _get_repo_refs(self, repo, rev=None, branch_rev=None):
75 72 """return a structure with repo's interesting changesets, suitable for
76 73 the selectors in pullrequest.html"""
77 74 # list named branches that has been merged to this named branch - it should probably merge back
78 75 peers = []
79 76
80 77 if rev:
81 78 rev = safe_str(rev)
82 79
83 80 if branch_rev:
84 81 branch_rev = safe_str(branch_rev)
85 82 # not restricting to merge() would also get branch point and be better
86 83 # (especially because it would get the branch point) ... but is currently too expensive
87 84 revs = ["sort(parents(branch(id('%s')) and merge()) - branch(id('%s')))" %
88 85 (branch_rev, branch_rev)]
89 86 otherbranches = {}
90 87 for i in scmutil.revrange(repo._repo, revs):
91 88 cs = repo.get_changeset(i)
92 89 otherbranches[cs.branch] = cs.raw_id
93 90 for branch, node in otherbranches.iteritems():
94 91 selected = 'branch:%s:%s' % (branch, node)
95 92 peers.append((selected, branch))
96 93
97 94 selected = None
98 95 branches = []
99 96 for branch, branchrev in repo.branches.iteritems():
100 97 n = 'branch:%s:%s' % (branch, branchrev)
101 98 branches.append((n, branch))
102 99 if rev == branchrev:
103 100 selected = n
104 101 bookmarks = []
105 102 for bookmark, bookmarkrev in repo.bookmarks.iteritems():
106 103 n = 'book:%s:%s' % (bookmark, bookmarkrev)
107 104 bookmarks.append((n, bookmark))
108 105 if rev == bookmarkrev:
109 106 selected = n
110 107 tags = []
111 108 for tag, tagrev in repo.tags.iteritems():
112 109 n = 'tag:%s:%s' % (tag, tagrev)
113 110 tags.append((n, tag))
114 111 if rev == tagrev and tag != 'tip': # tip is not a real tag - and its branch is better
115 112 selected = n
116 113
117 114 # prio 1: rev was selected as existing entry above
118 115
119 116 # prio 2: create special entry for rev; rev _must_ be used
120 117 specials = []
121 118 if rev and selected is None:
122 119 selected = 'rev:%s:%s' % (rev, rev)
123 120 specials = [(selected, '%s: %s' % (_("Changeset"), rev[:12]))]
124 121
125 122 # prio 3: most recent peer branch
126 123 if peers and not selected:
127 124 selected = peers[0][0][0]
128 125
129 126 # prio 4: tip revision
130 127 if not selected:
131 128 selected = 'tag:tip:%s' % repo.tags['tip']
132 129
133 130 groups = [(specials, _("Special")),
134 131 (peers, _("Peer branches")),
135 132 (bookmarks, _("Bookmarks")),
136 133 (branches, _("Branches")),
137 134 (tags, _("Tags")),
138 135 ]
139 136 return [g for g in groups if g[0]], selected
140 137
141 138 def _get_is_allowed_change_status(self, pull_request):
142 139 owner = self.rhodecode_user.user_id == pull_request.user_id
143 140 reviewer = self.rhodecode_user.user_id in [x.user_id for x in
144 141 pull_request.reviewers]
145 142 return (self.rhodecode_user.admin or owner or reviewer)
146 143
147 def show_all(self, repo_name):
148 c.pull_requests = PullRequestModel().get_all(repo_name)
149 c.repo_name = repo_name
150 p = safe_int(request.GET.get('page', 1), 1)
151
152 c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=10)
153
154 c.pullrequest_data = render('/pullrequests/pullrequest_data.html')
155
156 if request.environ.get('HTTP_X_PARTIAL_XHR'):
157 return c.pullrequest_data
158
159 return render('/pullrequests/pullrequest_show_all.html')
160
161 @NotAnonymous()
162 def index(self):
163 org_repo = c.rhodecode_db_repo
164
165 if org_repo.scm_instance.alias != 'hg':
166 log.error('Review not available for GIT REPOS')
167 raise HTTPNotFound
168
169 try:
170 org_repo.scm_instance.get_changeset()
171 except EmptyRepositoryError, e:
172 h.flash(h.literal(_('There are no changesets yet')),
173 category='warning')
174 redirect(url('summary_home', repo_name=org_repo.repo_name))
175
176 org_rev = request.GET.get('rev_end')
177 # rev_start is not directly useful - its parent could however be used
178 # as default for other and thus give a simple compare view
179 #other_rev = request.POST.get('rev_start')
180
181 c.org_repos = []
182 c.org_repos.append((org_repo.repo_name, org_repo.repo_name))
183 c.default_org_repo = org_repo.repo_name
184 c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance, org_rev)
185
186 c.other_repos = []
187 other_repos_info = {}
188
189 def add_other_repo(repo, branch_rev=None):
190 if repo.repo_name in other_repos_info: # shouldn't happen
191 return
192 c.other_repos.append((repo.repo_name, repo.repo_name))
193 other_refs, selected_other_ref = self._get_repo_refs(repo.scm_instance, branch_rev=branch_rev)
194 other_repos_info[repo.repo_name] = {
195 'user': dict(user_id=repo.user.user_id,
196 username=repo.user.username,
197 firstname=repo.user.firstname,
198 lastname=repo.user.lastname,
199 gravatar_link=h.gravatar_url(repo.user.email, 14)),
200 'description': repo.description.split('\n', 1)[0],
201 'revs': h.select('other_ref', selected_other_ref, other_refs, class_='refs')
202 }
203
204 # add org repo to other so we can open pull request against peer branches on itself
205 add_other_repo(org_repo, branch_rev=org_rev)
206 c.default_other_repo = org_repo.repo_name
207
208 # gather forks and add to this list ... even though it is rare to
209 # request forks to pull from their parent
210 for fork in org_repo.forks:
211 add_other_repo(fork)
212
213 # add parents of this fork also, but only if it's not empty
214 if org_repo.parent and org_repo.parent.scm_instance.revisions:
215 add_other_repo(org_repo.parent)
216 c.default_other_repo = org_repo.parent.repo_name
217
218 c.default_other_repo_info = other_repos_info[c.default_other_repo]
219 c.other_repos_info = json.dumps(other_repos_info)
220
221 return render('/pullrequests/pullrequest.html')
222
223 @NotAnonymous()
224 def create(self, repo_name):
225 repo = RepoModel()._get_repo(repo_name)
226 try:
227 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
228 except formencode.Invalid, errors:
229 log.error(traceback.format_exc())
230 if errors.error_dict.get('revisions'):
231 msg = 'Revisions: %s' % errors.error_dict['revisions']
232 elif errors.error_dict.get('pullrequest_title'):
233 msg = _('Pull request requires a title with min. 3 chars')
234 else:
235 msg = _('Error creating pull request')
236
237 h.flash(msg, 'error')
238 return redirect(url('pullrequest_home', repo_name=repo_name))
239
240 org_repo = _form['org_repo']
241 org_ref = 'rev:merge:%s' % _form['merge_rev']
242 other_repo = _form['other_repo']
243 other_ref = 'rev:ancestor:%s' % _form['ancestor_rev']
244 revisions = reversed(_form['revisions'])
245 reviewers = _form['review_members']
246
247 title = _form['pullrequest_title']
248 description = _form['pullrequest_desc']
249
250 try:
251 pull_request = PullRequestModel().create(
252 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
253 other_ref, revisions, reviewers, title, description
254 )
255 Session().commit()
256 h.flash(_('Successfully opened new pull request'),
257 category='success')
258 except Exception:
259 h.flash(_('Error occurred during sending pull request'),
260 category='error')
261 log.error(traceback.format_exc())
262 return redirect(url('pullrequest_home', repo_name=repo_name))
263
264 return redirect(url('pullrequest_show', repo_name=other_repo,
265 pull_request_id=pull_request.pull_request_id))
266
267 @NotAnonymous()
268 @jsonify
269 def update(self, repo_name, pull_request_id):
270 pull_request = PullRequest.get_or_404(pull_request_id)
271 if pull_request.is_closed():
272 raise HTTPForbidden()
273 #only owner or admin can update it
274 owner = pull_request.author.user_id == c.rhodecode_user.user_id
275 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
276 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
277 request.POST.get('reviewers_ids', '').split(',')))
278
279 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
280 Session().commit()
281 return True
282 raise HTTPForbidden()
283
284 @NotAnonymous()
285 @jsonify
286 def delete(self, repo_name, pull_request_id):
287 pull_request = PullRequest.get_or_404(pull_request_id)
288 #only owner can delete it !
289 if pull_request.author.user_id == c.rhodecode_user.user_id:
290 PullRequestModel().delete(pull_request)
291 Session().commit()
292 h.flash(_('Successfully deleted pull request'),
293 category='success')
294 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
295 raise HTTPForbidden()
296
297 144 def _load_compare_data(self, pull_request, enable_comments=True):
298 145 """
299 146 Load context data needed for generating compare diff
300 147
301 148 :param pull_request:
302 149 :type pull_request:
303 150 """
304 151 org_repo = pull_request.org_repo
305 152 (org_ref_type,
306 153 org_ref_name,
307 154 org_ref_rev) = pull_request.org_ref.split(':')
308 155
309 156 other_repo = org_repo
310 157 (other_ref_type,
311 158 other_ref_name,
312 159 other_ref_rev) = pull_request.other_ref.split(':')
313 160
314 161 # despite opening revisions for bookmarks/branches/tags, we always
315 162 # convert this to rev to prevent changes after bookmark or branch change
316 163 org_ref = ('rev', org_ref_rev)
317 164 other_ref = ('rev', other_ref_rev)
318 165
319 166 c.org_repo = org_repo
320 167 c.other_repo = other_repo
321 168
322 169 c.fulldiff = fulldiff = request.GET.get('fulldiff')
323 170
324 171 c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
325 172
326 173 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
327 174
328 175 c.org_ref = org_ref[1]
329 176 c.org_ref_type = org_ref[0]
330 177 c.other_ref = other_ref[1]
331 178 c.other_ref_type = other_ref[0]
332 179
333 180 diff_limit = self.cut_off_limit if not fulldiff else None
334 181
335 182 # we swap org/other ref since we run a simple diff on one repo
336 183 log.debug('running diff between %s@%s and %s@%s'
337 184 % (org_repo.scm_instance.path, org_ref,
338 185 other_repo.scm_instance.path, other_ref))
339 186 _diff = org_repo.scm_instance.get_diff(rev1=safe_str(other_ref[1]), rev2=safe_str(org_ref[1]))
340 187
341 188 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
342 189 diff_limit=diff_limit)
343 190 _parsed = diff_processor.prepare()
344 191
345 192 c.limited_diff = False
346 193 if isinstance(_parsed, LimitedDiffContainer):
347 194 c.limited_diff = True
348 195
349 196 c.files = []
350 197 c.changes = {}
351 198 c.lines_added = 0
352 199 c.lines_deleted = 0
353 200 for f in _parsed:
354 201 st = f['stats']
355 202 if st[0] != 'b':
356 203 c.lines_added += st[0]
357 204 c.lines_deleted += st[1]
358 205 fid = h.FID('', f['filename'])
359 206 c.files.append([fid, f['operation'], f['filename'], f['stats']])
360 207 diff = diff_processor.as_html(enable_comments=enable_comments,
361 208 parsed_lines=[f])
362 209 c.changes[fid] = [f['operation'], f['filename'], diff]
363 210
211 @LoginRequired()
212 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
213 'repository.admin')
214 def show_all(self, repo_name):
215 c.pull_requests = PullRequestModel().get_all(repo_name)
216 c.repo_name = repo_name
217 p = safe_int(request.GET.get('page', 1), 1)
218
219 c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=10)
220
221 c.pullrequest_data = render('/pullrequests/pullrequest_data.html')
222
223 if request.environ.get('HTTP_X_PARTIAL_XHR'):
224 return c.pullrequest_data
225
226 return render('/pullrequests/pullrequest_show_all.html')
227
228 @LoginRequired()
229 @NotAnonymous()
230 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
231 'repository.admin')
232 def index(self):
233 org_repo = c.rhodecode_db_repo
234
235 if org_repo.scm_instance.alias != 'hg':
236 log.error('Review not available for GIT REPOS')
237 raise HTTPNotFound
238
239 try:
240 org_repo.scm_instance.get_changeset()
241 except EmptyRepositoryError, e:
242 h.flash(h.literal(_('There are no changesets yet')),
243 category='warning')
244 redirect(url('summary_home', repo_name=org_repo.repo_name))
245
246 org_rev = request.GET.get('rev_end')
247 # rev_start is not directly useful - its parent could however be used
248 # as default for other and thus give a simple compare view
249 #other_rev = request.POST.get('rev_start')
250
251 c.org_repos = []
252 c.org_repos.append((org_repo.repo_name, org_repo.repo_name))
253 c.default_org_repo = org_repo.repo_name
254 c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance, org_rev)
255
256 c.other_repos = []
257 other_repos_info = {}
258
259 def add_other_repo(repo, branch_rev=None):
260 if repo.repo_name in other_repos_info: # shouldn't happen
261 return
262 c.other_repos.append((repo.repo_name, repo.repo_name))
263 other_refs, selected_other_ref = self._get_repo_refs(repo.scm_instance, branch_rev=branch_rev)
264 other_repos_info[repo.repo_name] = {
265 'user': dict(user_id=repo.user.user_id,
266 username=repo.user.username,
267 firstname=repo.user.firstname,
268 lastname=repo.user.lastname,
269 gravatar_link=h.gravatar_url(repo.user.email, 14)),
270 'description': repo.description.split('\n', 1)[0],
271 'revs': h.select('other_ref', selected_other_ref, other_refs, class_='refs')
272 }
273
274 # add org repo to other so we can open pull request against peer branches on itself
275 add_other_repo(org_repo, branch_rev=org_rev)
276 c.default_other_repo = org_repo.repo_name
277
278 # gather forks and add to this list ... even though it is rare to
279 # request forks to pull from their parent
280 for fork in org_repo.forks:
281 add_other_repo(fork)
282
283 # add parents of this fork also, but only if it's not empty
284 if org_repo.parent and org_repo.parent.scm_instance.revisions:
285 add_other_repo(org_repo.parent)
286 c.default_other_repo = org_repo.parent.repo_name
287
288 c.default_other_repo_info = other_repos_info[c.default_other_repo]
289 c.other_repos_info = json.dumps(other_repos_info)
290
291 return render('/pullrequests/pullrequest.html')
292
293 @LoginRequired()
294 @NotAnonymous()
295 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
296 'repository.admin')
297 def create(self, repo_name):
298 repo = RepoModel()._get_repo(repo_name)
299 try:
300 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
301 except formencode.Invalid, errors:
302 log.error(traceback.format_exc())
303 if errors.error_dict.get('revisions'):
304 msg = 'Revisions: %s' % errors.error_dict['revisions']
305 elif errors.error_dict.get('pullrequest_title'):
306 msg = _('Pull request requires a title with min. 3 chars')
307 else:
308 msg = _('Error creating pull request')
309
310 h.flash(msg, 'error')
311 return redirect(url('pullrequest_home', repo_name=repo_name))
312
313 org_repo = _form['org_repo']
314 org_ref = 'rev:merge:%s' % _form['merge_rev']
315 other_repo = _form['other_repo']
316 other_ref = 'rev:ancestor:%s' % _form['ancestor_rev']
317 revisions = reversed(_form['revisions'])
318 reviewers = _form['review_members']
319
320 title = _form['pullrequest_title']
321 description = _form['pullrequest_desc']
322
323 try:
324 pull_request = PullRequestModel().create(
325 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
326 other_ref, revisions, reviewers, title, description
327 )
328 Session().commit()
329 h.flash(_('Successfully opened new pull request'),
330 category='success')
331 except Exception:
332 h.flash(_('Error occurred during sending pull request'),
333 category='error')
334 log.error(traceback.format_exc())
335 return redirect(url('pullrequest_home', repo_name=repo_name))
336
337 return redirect(url('pullrequest_show', repo_name=other_repo,
338 pull_request_id=pull_request.pull_request_id))
339
340 @LoginRequired()
341 @NotAnonymous()
342 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
343 'repository.admin')
344 @jsonify
345 def update(self, repo_name, pull_request_id):
346 pull_request = PullRequest.get_or_404(pull_request_id)
347 if pull_request.is_closed():
348 raise HTTPForbidden()
349 #only owner or admin can update it
350 owner = pull_request.author.user_id == c.rhodecode_user.user_id
351 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
352 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
353 request.POST.get('reviewers_ids', '').split(',')))
354
355 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
356 Session().commit()
357 return True
358 raise HTTPForbidden()
359
360 @LoginRequired()
361 @NotAnonymous()
362 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
363 'repository.admin')
364 @jsonify
365 def delete(self, repo_name, pull_request_id):
366 pull_request = PullRequest.get_or_404(pull_request_id)
367 #only owner can delete it !
368 if pull_request.author.user_id == c.rhodecode_user.user_id:
369 PullRequestModel().delete(pull_request)
370 Session().commit()
371 h.flash(_('Successfully deleted pull request'),
372 category='success')
373 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
374 raise HTTPForbidden()
375
376 @LoginRequired()
377 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
378 'repository.admin')
364 379 def show(self, repo_name, pull_request_id):
365 380 repo_model = RepoModel()
366 381 c.users_array = repo_model.get_users_js()
367 382 c.users_groups_array = repo_model.get_users_groups_js()
368 383 c.pull_request = PullRequest.get_or_404(pull_request_id)
369 384 c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
370 385 cc_model = ChangesetCommentsModel()
371 386 cs_model = ChangesetStatusModel()
372 387 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
373 388 pull_request=c.pull_request,
374 389 with_revisions=True)
375 390
376 391 cs_statuses = defaultdict(list)
377 392 for st in _cs_statuses:
378 393 cs_statuses[st.author.username] += [st]
379 394
380 395 c.pull_request_reviewers = []
381 396 c.pull_request_pending_reviewers = []
382 397 for o in c.pull_request.reviewers:
383 398 st = cs_statuses.get(o.user.username, None)
384 399 if st:
385 400 sorter = lambda k: k.version
386 401 st = [(x, list(y)[0])
387 402 for x, y in (groupby(sorted(st, key=sorter), sorter))]
388 403 else:
389 404 c.pull_request_pending_reviewers.append(o.user)
390 405 c.pull_request_reviewers.append([o.user, st])
391 406
392 407 # pull_requests repo_name we opened it against
393 408 # ie. other_repo must match
394 409 if repo_name != c.pull_request.other_repo.repo_name:
395 410 raise HTTPNotFound
396 411
397 412 # load compare data into template context
398 413 enable_comments = not c.pull_request.is_closed()
399 414 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
400 415
401 416 # inline comments
402 417 c.inline_cnt = 0
403 418 c.inline_comments = cc_model.get_inline_comments(
404 419 c.rhodecode_db_repo.repo_id,
405 420 pull_request=pull_request_id)
406 421 # count inline comments
407 422 for __, lines in c.inline_comments:
408 423 for comments in lines.values():
409 424 c.inline_cnt += len(comments)
410 425 # comments
411 426 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
412 427 pull_request=pull_request_id)
413 428
414 429 try:
415 430 cur_status = c.statuses[c.pull_request.revisions[0]][0]
416 431 except Exception:
417 432 log.error(traceback.format_exc())
418 433 cur_status = 'undefined'
419 434 if c.pull_request.is_closed() and 0:
420 435 c.current_changeset_status = cur_status
421 436 else:
422 437 # changeset(pull-request) status calulation based on reviewers
423 438 c.current_changeset_status = cs_model.calculate_status(
424 439 c.pull_request_reviewers,
425 440 )
426 441 c.changeset_statuses = ChangesetStatus.STATUSES
427 442
428 443 c.as_form = False
429 444 c.ancestor = None # there is one - but right here we don't know which
430 445 return render('/pullrequests/pullrequest_show.html')
431 446
447 @LoginRequired()
432 448 @NotAnonymous()
449 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
450 'repository.admin')
433 451 @jsonify
434 452 def comment(self, repo_name, pull_request_id):
435 453 pull_request = PullRequest.get_or_404(pull_request_id)
436 454 if pull_request.is_closed():
437 455 raise HTTPForbidden()
438 456
439 457 status = request.POST.get('changeset_status')
440 458 change_status = request.POST.get('change_changeset_status')
441 459 text = request.POST.get('text')
442 460 close_pr = request.POST.get('save_close')
443 461
444 462 allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
445 463 if status and change_status and allowed_to_change_status:
446 464 _def = (_('Status change -> %s')
447 465 % ChangesetStatus.get_status_lbl(status))
448 466 if close_pr:
449 467 _def = _('Closing with') + ' ' + _def
450 468 text = text or _def
451 469 comm = ChangesetCommentsModel().create(
452 470 text=text,
453 471 repo=c.rhodecode_db_repo.repo_id,
454 472 user=c.rhodecode_user.user_id,
455 473 pull_request=pull_request_id,
456 474 f_path=request.POST.get('f_path'),
457 475 line_no=request.POST.get('line'),
458 476 status_change=(ChangesetStatus.get_status_lbl(status)
459 477 if status and change_status
460 478 and allowed_to_change_status else None),
461 479 closing_pr=close_pr
462 480 )
463 481
464 482 action_logger(self.rhodecode_user,
465 483 'user_commented_pull_request:%s' % pull_request_id,
466 484 c.rhodecode_db_repo, self.ip_addr, self.sa)
467 485
468 486 if allowed_to_change_status:
469 487 # get status if set !
470 488 if status and change_status:
471 489 ChangesetStatusModel().set_status(
472 490 c.rhodecode_db_repo.repo_id,
473 491 status,
474 492 c.rhodecode_user.user_id,
475 493 comm,
476 494 pull_request=pull_request_id
477 495 )
478 496
479 497 if close_pr:
480 498 if status in ['rejected', 'approved']:
481 499 PullRequestModel().close_pull_request(pull_request_id)
482 500 action_logger(self.rhodecode_user,
483 501 'user_closed_pull_request:%s' % pull_request_id,
484 502 c.rhodecode_db_repo, self.ip_addr, self.sa)
485 503 else:
486 504 h.flash(_('Closing pull request on other statuses than '
487 505 'rejected or approved forbidden'),
488 506 category='warning')
489 507
490 508 Session().commit()
491 509
492 510 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
493 511 return redirect(h.url('pullrequest_show', repo_name=repo_name,
494 512 pull_request_id=pull_request_id))
495 513
496 514 data = {
497 515 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
498 516 }
499 517 if comm:
500 518 c.co = comm
501 519 data.update(comm.get_dict())
502 520 data.update({'rendered_text':
503 521 render('changeset/changeset_comment_block.html')})
504 522
505 523 return data
506 524
525 @LoginRequired()
507 526 @NotAnonymous()
527 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
528 'repository.admin')
508 529 @jsonify
509 530 def delete_comment(self, repo_name, comment_id):
510 531 co = ChangesetComment.get(comment_id)
511 532 if co.pull_request.is_closed():
512 533 #don't allow deleting comments on closed pull request
513 534 raise HTTPForbidden()
514 535
515 536 owner = co.author.user_id == c.rhodecode_user.user_id
516 537 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
517 538 ChangesetCommentsModel().delete(comment=co)
518 539 Session().commit()
519 540 return True
520 541 else:
521 542 raise HTTPForbidden()
@@ -1,146 +1,146 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.search
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Search controller for RhodeCode
7 7
8 8 :created_on: Aug 7, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import logging
26 26 import traceback
27 27 import urllib
28 28 from pylons.i18n.translation import _
29 29 from pylons import request, config, tmpl_context as c
30 30
31 31 from rhodecode.lib.auth import LoginRequired
32 32 from rhodecode.lib.base import BaseRepoController, render
33 33 from rhodecode.lib.indexers import CHGSETS_SCHEMA, SCHEMA, CHGSET_IDX_NAME, \
34 34 IDX_NAME, WhooshResultWrapper
35 35
36 36 from webhelpers.paginate import Page
37 37 from webhelpers.util import update_params
38 38
39 39 from whoosh.index import open_dir, EmptyIndexError
40 40 from whoosh.qparser import QueryParser, QueryParserError
41 41 from whoosh.query import Phrase, Wildcard, Term, Prefix
42 42 from rhodecode.model.repo import RepoModel
43 43 from rhodecode.lib.utils2 import safe_str, safe_int
44 44
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48
49 49 class SearchController(BaseRepoController):
50 50
51 @LoginRequired()
52 51 def __before__(self):
53 52 super(SearchController, self).__before__()
54 53
54 @LoginRequired()
55 55 def index(self, repo_name=None):
56 56 c.repo_name = repo_name
57 57 c.formated_results = []
58 58 c.runtime = ''
59 59 c.cur_query = request.GET.get('q', None)
60 60 c.cur_type = request.GET.get('type', 'content')
61 61 c.cur_search = search_type = {'content': 'content',
62 62 'commit': 'message',
63 63 'path': 'path',
64 64 'repository': 'repository'
65 65 }.get(c.cur_type, 'content')
66 66
67 67 index_name = {
68 68 'content': IDX_NAME,
69 69 'commit': CHGSET_IDX_NAME,
70 70 'path': IDX_NAME
71 71 }.get(c.cur_type, IDX_NAME)
72 72
73 73 schema_defn = {
74 74 'content': SCHEMA,
75 75 'commit': CHGSETS_SCHEMA,
76 76 'path': SCHEMA
77 77 }.get(c.cur_type, SCHEMA)
78 78
79 79 log.debug('IDX: %s' % index_name)
80 80 log.debug('SCHEMA: %s' % schema_defn)
81 81
82 82 if c.cur_query:
83 83 cur_query = c.cur_query.lower()
84 84 log.debug(cur_query)
85 85
86 86 if c.cur_query:
87 87 p = safe_int(request.GET.get('page', 1), 1)
88 88 highlight_items = set()
89 89 try:
90 90 idx = open_dir(config['app_conf']['index_dir'],
91 91 indexname=index_name)
92 92 searcher = idx.searcher()
93 93
94 94 qp = QueryParser(search_type, schema=schema_defn)
95 95 if c.repo_name:
96 96 cur_query = u'repository:%s %s' % (c.repo_name, cur_query)
97 97 try:
98 98 query = qp.parse(unicode(cur_query))
99 99 # extract words for highlight
100 100 if isinstance(query, Phrase):
101 101 highlight_items.update(query.words)
102 102 elif isinstance(query, Prefix):
103 103 highlight_items.add(query.text)
104 104 else:
105 105 for i in query.all_terms():
106 106 if i[0] in ['content', 'message']:
107 107 highlight_items.add(i[1])
108 108
109 109 matcher = query.matcher(searcher)
110 110
111 111 log.debug('query: %s' % query)
112 112 log.debug('hl terms: %s' % highlight_items)
113 113 results = searcher.search(query)
114 114 res_ln = len(results)
115 115 c.runtime = '%s results (%.3f seconds)' % (
116 116 res_ln, results.runtime
117 117 )
118 118
119 119 def url_generator(**kw):
120 120 q = urllib.quote(safe_str(c.cur_query))
121 121 return update_params("?q=%s&type=%s" \
122 122 % (q, safe_str(c.cur_type)), **kw)
123 123 repo_location = RepoModel().repos_path
124 124 c.formated_results = Page(
125 125 WhooshResultWrapper(search_type, searcher, matcher,
126 126 highlight_items, repo_location),
127 127 page=p,
128 128 item_count=res_ln,
129 129 items_per_page=10,
130 130 url=url_generator
131 131 )
132 132
133 133 except QueryParserError:
134 134 c.runtime = _('Invalid search query. Try quoting it.')
135 135 searcher.close()
136 136 except (EmptyIndexError, IOError):
137 137 log.error(traceback.format_exc())
138 138 log.error('Empty Index data')
139 139 c.runtime = _('There is no index to search in. '
140 140 'Please run whoosh indexer')
141 141 except (Exception):
142 142 log.error(traceback.format_exc())
143 143 c.runtime = _('An error occurred during this search operation')
144 144
145 145 # Return a rendered template
146 146 return render('/search/search.html')
@@ -1,107 +1,107 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.shortlog
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Shortlog controller for rhodecode
7 7
8 8 :created_on: Apr 18, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27
28 28 from pylons import tmpl_context as c, request, url
29 29 from pylons.i18n.translation import _
30 30
31 31 from rhodecode.lib import helpers as h
32 32 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
33 33 from rhodecode.lib.base import BaseRepoController, render
34 34 from rhodecode.lib.helpers import RepoPage
35 35 from pylons.controllers.util import redirect
36 36 from rhodecode.lib.utils2 import safe_int
37 37 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError, ChangesetError,\
38 38 RepositoryError
39 39
40 40 log = logging.getLogger(__name__)
41 41
42 42
43 43 class ShortlogController(BaseRepoController):
44 44
45 @LoginRequired()
46 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
47 'repository.admin')
48 45 def __before__(self):
49 46 super(ShortlogController, self).__before__()
50 47
51 48 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
52 49 """
53 50 Safe way to get changeset if error occur it redirects to tip with
54 51 proper message
55 52
56 53 :param rev: revision to fetch
57 54 :param repo_name: repo name to redirect after
58 55 """
59 56
60 57 try:
61 58 return c.rhodecode_repo.get_changeset(rev)
62 59 except RepositoryError, e:
63 60 h.flash(str(e), category='warning')
64 61 redirect(h.url('shortlog_home', repo_name=repo_name))
65 62
63 @LoginRequired()
64 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
65 'repository.admin')
66 66 def index(self, repo_name, revision=None, f_path=None):
67 67 p = safe_int(request.GET.get('page', 1), 1)
68 68 size = safe_int(request.GET.get('size', 20), 20)
69 69 collection = c.rhodecode_repo
70 70 c.file_history = f_path
71 71
72 72 def url_generator(**kw):
73 73 if f_path:
74 74 return url('shortlog_file_home', repo_name=repo_name,
75 75 revision=revision, f_path=f_path, size=size, **kw)
76 76 return url('shortlog_home', repo_name=repo_name, size=size, **kw)
77 77
78 78 if f_path:
79 79 log.debug('generating shortlog for path %s' % f_path)
80 80 # get the history for the file !
81 81 tip_cs = c.rhodecode_repo.get_changeset()
82 82 try:
83 83 collection = tip_cs.get_file_history(f_path)
84 84 except (NodeDoesNotExistError, ChangesetError):
85 85 #this node is not present at tip !
86 86 try:
87 87 cs = self.__get_cs_or_redirect(revision, repo_name)
88 88 collection = cs.get_file_history(f_path)
89 89 except RepositoryError, e:
90 90 h.flash(str(e), category='warning')
91 91 redirect(h.url('shortlog_home', repo_name=repo_name))
92 92 collection = list(reversed(collection))
93 93
94 94 c.repo_changesets = RepoPage(collection, page=p,
95 95 items_per_page=size, url=url_generator)
96 96 page_revisions = [x.raw_id for x in list(c.repo_changesets)]
97 97 c.statuses = c.rhodecode_db_repo.statuses(page_revisions)
98 98
99 99 if not c.repo_changesets:
100 100 h.flash(_('There are no changesets yet'), category='warning')
101 101 return redirect(url('summary_home', repo_name=repo_name))
102 102
103 103 c.shortlog_data = render('shortlog/shortlog_data.html')
104 104 if request.environ.get('HTTP_X_PARTIAL_XHR'):
105 105 return c.shortlog_data
106 106 r = render('shortlog/shortlog.html')
107 107 return r
@@ -1,260 +1,264 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.summary
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Summary controller for Rhodecode
7 7
8 8 :created_on: Apr 18, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import traceback
27 27 import calendar
28 28 import logging
29 29 import urllib
30 30 from time import mktime
31 31 from datetime import timedelta, date
32 32 from urlparse import urlparse
33 33
34 34 from pylons import tmpl_context as c, request, url, config
35 35 from pylons.i18n.translation import _
36 36 from webob.exc import HTTPBadRequest
37 37
38 38 from beaker.cache import cache_region, region_invalidate
39 39
40 40 from rhodecode.lib import helpers as h
41 41 from rhodecode.lib.compat import product
42 42 from rhodecode.lib.vcs.exceptions import ChangesetError, EmptyRepositoryError, \
43 43 NodeDoesNotExistError
44 44 from rhodecode.config.conf import ALL_READMES, ALL_EXTS, LANGUAGES_EXTENSIONS_MAP
45 45 from rhodecode.model.db import Statistics, CacheInvalidation
46 46 from rhodecode.lib.utils import jsonify
47 47 from rhodecode.lib.utils2 import safe_unicode, safe_str
48 48 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
49 49 NotAnonymous
50 50 from rhodecode.lib.base import BaseRepoController, render
51 51 from rhodecode.lib.vcs.backends.base import EmptyChangeset
52 52 from rhodecode.lib.markup_renderer import MarkupRenderer
53 53 from rhodecode.lib.celerylib import run_task
54 54 from rhodecode.lib.celerylib.tasks import get_commits_stats
55 55 from rhodecode.lib.helpers import RepoPage
56 56 from rhodecode.lib.compat import json, OrderedDict
57 57 from rhodecode.lib.vcs.nodes import FileNode
58 58
59 59 log = logging.getLogger(__name__)
60 60
61 61 README_FILES = [''.join([x[0][0], x[1][0]]) for x in
62 62 sorted(list(product(ALL_READMES, ALL_EXTS)),
63 63 key=lambda y:y[0][1] + y[1][1])]
64 64
65 65
66 66 class SummaryController(BaseRepoController):
67 67
68 def __before__(self):
69 super(SummaryController, self).__before__()
70
71 def _get_download_links(self, repo):
72
73 download_l = []
74
75 branches_group = ([], _("Branches"))
76 tags_group = ([], _("Tags"))
77
78 for name, chs in c.rhodecode_repo.branches.items():
79 #chs = chs.split(':')[-1]
80 branches_group[0].append((chs, name),)
81 download_l.append(branches_group)
82
83 for name, chs in c.rhodecode_repo.tags.items():
84 #chs = chs.split(':')[-1]
85 tags_group[0].append((chs, name),)
86 download_l.append(tags_group)
87
88 return download_l
89
90
91 def __get_readme_data(self, db_repo):
92 repo_name = db_repo.repo_name
93
94 @cache_region('long_term')
95 def _get_readme_from_cache(key):
96 readme_data = None
97 readme_file = None
98 log.debug('Looking for README file')
99 try:
100 # get's the landing revision! or tip if fails
101 cs = db_repo.get_landing_changeset()
102 if isinstance(cs, EmptyChangeset):
103 raise EmptyRepositoryError()
104 renderer = MarkupRenderer()
105 for f in README_FILES:
106 try:
107 readme = cs.get_node(f)
108 if not isinstance(readme, FileNode):
109 continue
110 readme_file = f
111 log.debug('Found README file `%s` rendering...' %
112 readme_file)
113 readme_data = renderer.render(readme.content, f)
114 break
115 except NodeDoesNotExistError:
116 continue
117 except ChangesetError:
118 log.error(traceback.format_exc())
119 pass
120 except EmptyRepositoryError:
121 pass
122 except Exception:
123 log.error(traceback.format_exc())
124
125 return readme_data, readme_file
126
127 key = repo_name + '_README'
128 inv = CacheInvalidation.invalidate(key)
129 if inv is not None:
130 region_invalidate(_get_readme_from_cache, None, key)
131 CacheInvalidation.set_valid(inv.cache_key)
132 return _get_readme_from_cache(key)
133
68 134 @LoginRequired()
69 135 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
70 136 'repository.admin')
71 def __before__(self):
72 super(SummaryController, self).__before__()
73
74 137 def index(self, repo_name):
75 138 c.dbrepo = dbrepo = c.rhodecode_db_repo
76 139
77 140 def url_generator(**kw):
78 141 return url('shortlog_home', repo_name=repo_name, size=10, **kw)
79 142
80 143 c.repo_changesets = RepoPage(c.rhodecode_repo, page=1,
81 144 items_per_page=10, url=url_generator)
82 145 page_revisions = [x.raw_id for x in list(c.repo_changesets)]
83 146 c.statuses = c.rhodecode_db_repo.statuses(page_revisions)
84 147
85 148 if self.rhodecode_user.username == 'default':
86 149 # for default(anonymous) user we don't need to pass credentials
87 150 username = ''
88 151 password = ''
89 152 else:
90 153 username = str(self.rhodecode_user.username)
91 154 password = '@'
92 155
93 156 parsed_url = urlparse(url.current(qualified=True))
94 157
95 158 default_clone_uri = '{scheme}://{user}{pass}{netloc}{path}'
96 159
97 160 uri_tmpl = config.get('clone_uri', default_clone_uri)
98 161 uri_tmpl = uri_tmpl.replace('{', '%(').replace('}', ')s')
99 162 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
100 163 uri_dict = {
101 164 'user': urllib.quote(username),
102 165 'pass': password,
103 166 'scheme': parsed_url.scheme,
104 167 'netloc': parsed_url.netloc,
105 168 'path': urllib.quote(safe_str(decoded_path))
106 169 }
107 170
108 171 uri = (uri_tmpl % uri_dict)
109 172 # generate another clone url by id
110 173 uri_dict.update(
111 174 {'path': decoded_path.replace(repo_name, '_%s' % c.dbrepo.repo_id)}
112 175 )
113 176 uri_id = uri_tmpl % uri_dict
114 177
115 178 c.clone_repo_url = uri
116 179 c.clone_repo_url_id = uri_id
117 180 c.repo_tags = OrderedDict()
118 181 for name, hash_ in c.rhodecode_repo.tags.items()[:10]:
119 182 try:
120 183 c.repo_tags[name] = c.rhodecode_repo.get_changeset(hash_)
121 184 except ChangesetError:
122 185 c.repo_tags[name] = EmptyChangeset(hash_)
123 186
124 187 c.repo_branches = OrderedDict()
125 188 for name, hash_ in c.rhodecode_repo.branches.items()[:10]:
126 189 try:
127 190 c.repo_branches[name] = c.rhodecode_repo.get_changeset(hash_)
128 191 except ChangesetError:
129 192 c.repo_branches[name] = EmptyChangeset(hash_)
130 193
131 194 td = date.today() + timedelta(days=1)
132 195 td_1m = td - timedelta(days=calendar.mdays[td.month])
133 196 td_1y = td - timedelta(days=365)
134 197
135 198 ts_min_m = mktime(td_1m.timetuple())
136 199 ts_min_y = mktime(td_1y.timetuple())
137 200 ts_max_y = mktime(td.timetuple())
138 201
139 202 if dbrepo.enable_statistics:
140 203 c.show_stats = True
141 204 c.no_data_msg = _('No data loaded yet')
142 205 recurse_limit = 500 # don't recurse more than 500 times when parsing
143 206 run_task(get_commits_stats, c.dbrepo.repo_name, ts_min_y,
144 207 ts_max_y, recurse_limit)
145 208 else:
146 209 c.show_stats = False
147 210 c.no_data_msg = _('Statistics are disabled for this repository')
148 211 c.ts_min = ts_min_m
149 212 c.ts_max = ts_max_y
150 213
151 214 stats = self.sa.query(Statistics)\
152 215 .filter(Statistics.repository == dbrepo)\
153 216 .scalar()
154 217
155 218 c.stats_percentage = 0
156 219
157 220 if stats and stats.languages:
158 221 c.no_data = False is dbrepo.enable_statistics
159 222 lang_stats_d = json.loads(stats.languages)
160 223 c.commit_data = stats.commit_activity
161 224 c.overview_data = stats.commit_activity_combined
162 225
163 226 lang_stats = ((x, {"count": y,
164 227 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
165 228 for x, y in lang_stats_d.items())
166 229
167 230 c.trending_languages = json.dumps(
168 231 sorted(lang_stats, reverse=True, key=lambda k: k[1])[:10]
169 232 )
170 233 last_rev = stats.stat_on_revision + 1
171 234 c.repo_last_rev = c.rhodecode_repo.count()\
172 235 if c.rhodecode_repo.revisions else 0
173 236 if last_rev == 0 or c.repo_last_rev == 0:
174 237 pass
175 238 else:
176 239 c.stats_percentage = '%.2f' % ((float((last_rev)) /
177 240 c.repo_last_rev) * 100)
178 241 else:
179 242 c.commit_data = json.dumps({})
180 243 c.overview_data = json.dumps([[ts_min_y, 0], [ts_max_y, 10]])
181 244 c.trending_languages = json.dumps({})
182 245 c.no_data = True
183 246
184 247 c.enable_downloads = dbrepo.enable_downloads
185 248 if c.enable_downloads:
186 249 c.download_options = self._get_download_links(c.rhodecode_repo)
187 250
188 251 c.readme_data, c.readme_file = \
189 252 self.__get_readme_data(c.rhodecode_db_repo)
190 253 return render('summary/summary.html')
191 254
255 @LoginRequired()
192 256 @NotAnonymous()
257 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
258 'repository.admin')
193 259 @jsonify
194 260 def repo_size(self, repo_name):
195 261 if request.is_xhr:
196 262 return c.rhodecode_db_repo._repo_size()
197 263 else:
198 264 raise HTTPBadRequest()
199
200 def __get_readme_data(self, db_repo):
201 repo_name = db_repo.repo_name
202
203 @cache_region('long_term')
204 def _get_readme_from_cache(key):
205 readme_data = None
206 readme_file = None
207 log.debug('Looking for README file')
208 try:
209 # get's the landing revision! or tip if fails
210 cs = db_repo.get_landing_changeset()
211 if isinstance(cs, EmptyChangeset):
212 raise EmptyRepositoryError()
213 renderer = MarkupRenderer()
214 for f in README_FILES:
215 try:
216 readme = cs.get_node(f)
217 if not isinstance(readme, FileNode):
218 continue
219 readme_file = f
220 log.debug('Found README file `%s` rendering...' %
221 readme_file)
222 readme_data = renderer.render(readme.content, f)
223 break
224 except NodeDoesNotExistError:
225 continue
226 except ChangesetError:
227 log.error(traceback.format_exc())
228 pass
229 except EmptyRepositoryError:
230 pass
231 except Exception:
232 log.error(traceback.format_exc())
233
234 return readme_data, readme_file
235
236 key = repo_name + '_README'
237 inv = CacheInvalidation.invalidate(key)
238 if inv is not None:
239 region_invalidate(_get_readme_from_cache, None, key)
240 CacheInvalidation.set_valid(inv.cache_key)
241 return _get_readme_from_cache(key)
242
243 def _get_download_links(self, repo):
244
245 download_l = []
246
247 branches_group = ([], _("Branches"))
248 tags_group = ([], _("Tags"))
249
250 for name, chs in c.rhodecode_repo.branches.items():
251 #chs = chs.split(':')[-1]
252 branches_group[0].append((chs, name),)
253 download_l.append(branches_group)
254
255 for name, chs in c.rhodecode_repo.tags.items():
256 #chs = chs.split(':')[-1]
257 tags_group[0].append((chs, name),)
258 download_l.append(tags_group)
259
260 return download_l
@@ -1,53 +1,53 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.tags
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Tags controller for rhodecode
7 7
8 8 :created_on: Apr 21, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import logging
26 26
27 27 from pylons import tmpl_context as c
28 28
29 29 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
30 30 from rhodecode.lib.base import BaseRepoController, render
31 31 from rhodecode.lib.compat import OrderedDict
32 32
33 33 log = logging.getLogger(__name__)
34 34
35 35
36 36 class TagsController(BaseRepoController):
37 37
38 def __before__(self):
39 super(TagsController, self).__before__()
40
38 41 @LoginRequired()
39 42 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
40 43 'repository.admin')
41 def __before__(self):
42 super(TagsController, self).__before__()
43
44 44 def index(self):
45 45 c.repo_tags = OrderedDict()
46 46
47 47 tags = [(name, c.rhodecode_repo.get_changeset(hash_)) for \
48 48 name, hash_ in c.rhodecode_repo.tags.items()]
49 49 ordered_tags = sorted(tags, key=lambda x: x[1].date, reverse=True)
50 50 for name, cs_tag in ordered_tags:
51 51 c.repo_tags[name] = cs_tag
52 52
53 53 return render('tags/tags.html')
General Comments 0
You need to be logged in to leave comments. Login now