##// END OF EJS Templates
report ChangesetDoesNotExistError as an error but don't lose the repo context
Mads Kiilerich -
r3573:881ae12b beta
parent child Browse files
Show More
@@ -1,125 +1,125 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 47 @LoginRequired()
48 48 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
49 49 'repository.admin')
50 50 def __before__(self):
51 51 super(ChangelogController, self).__before__()
52 52 c.affected_files_cut_off = 60
53 53
54 54 def index(self):
55 55 limit = 100
56 56 default = 20
57 57 if request.params.get('size'):
58 58 try:
59 59 int_size = int(request.params.get('size'))
60 60 except ValueError:
61 61 int_size = default
62 62 c.size = max(min(int_size, limit), 1)
63 63 session['changelog_size'] = c.size
64 64 session.save()
65 65 else:
66 66 c.size = int(session.get('changelog_size', default))
67 67 # min size must be 1
68 68 c.size = max(c.size, 1)
69 69 p = safe_int(request.params.get('page', 1), 1)
70 70 branch_name = request.params.get('branch', None)
71 71 try:
72 72 if branch_name:
73 73 collection = [z for z in
74 74 c.rhodecode_repo.get_changesets(start=0,
75 75 branch_name=branch_name)]
76 76 c.total_cs = len(collection)
77 77 else:
78 78 collection = c.rhodecode_repo
79 79 c.total_cs = len(c.rhodecode_repo)
80 80
81 81 c.pagination = RepoPage(collection, page=p, item_count=c.total_cs,
82 82 items_per_page=c.size, branch=branch_name)
83 83 collection = list(c.pagination)
84 84 page_revisions = [x.raw_id for x in collection]
85 85 c.comments = c.rhodecode_db_repo.get_comments(page_revisions)
86 86 c.statuses = c.rhodecode_db_repo.statuses(page_revisions)
87 87 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
88 88 log.error(traceback.format_exc())
89 h.flash(str(e), category='warning')
90 return redirect(url('home'))
89 h.flash(str(e), category='error')
90 return redirect(url('changelog_home', repo_name=c.repo_name))
91 91
92 92 self._graph(c.rhodecode_repo, collection, c.total_cs, c.size, p)
93 93
94 94 c.branch_name = branch_name
95 95 c.branch_filters = [('', _('All Branches'))] + \
96 96 [(k, k) for k in c.rhodecode_repo.branches.keys()]
97 97
98 98 return render('changelog/changelog.html')
99 99
100 100 def changelog_details(self, cs):
101 101 if request.environ.get('HTTP_X_PARTIAL_XHR'):
102 102 c.cs = c.rhodecode_repo.get_changeset(cs)
103 103 return render('changelog/changelog_details.html')
104 104
105 105 def _graph(self, repo, collection, repo_size, size, p):
106 106 """
107 107 Generates a DAG graph for mercurial
108 108
109 109 :param repo: repo instance
110 110 :param size: number of commits to show
111 111 :param p: page number
112 112 """
113 113 if not collection:
114 114 c.jsdata = json.dumps([])
115 115 return
116 116
117 117 data = []
118 118 revs = [x.revision for x in collection]
119 119
120 120 dag = _dagwalker(repo, revs, repo.alias)
121 121 dag = _colored(dag)
122 122 for (id, type, ctx, vtx, edges) in dag:
123 123 data.append(['', vtx, edges])
124 124
125 125 c.jsdata = json.dumps(data)
@@ -1,404 +1,404 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
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 from rhodecode.lib.base import BaseRepoController, render
42 42 from rhodecode.lib.utils import action_logger
43 43 from rhodecode.lib.compat import OrderedDict
44 44 from rhodecode.lib import diffs
45 45 from rhodecode.model.db import ChangesetComment, ChangesetStatus
46 46 from rhodecode.model.comment import ChangesetCommentsModel
47 47 from rhodecode.model.changeset_status import ChangesetStatusModel
48 48 from rhodecode.model.meta import Session
49 49 from rhodecode.model.repo import RepoModel
50 50 from rhodecode.lib.diffs import LimitedDiffContainer
51 51 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
52 52 from rhodecode.lib.vcs.backends.base import EmptyChangeset
53 53 from rhodecode.lib.utils2 import safe_unicode
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57
58 58 def _update_with_GET(params, GET):
59 59 for k in ['diff1', 'diff2', 'diff']:
60 60 params[k] += GET.getall(k)
61 61
62 62
63 63 def anchor_url(revision, path, GET):
64 64 fid = h.FID(revision, path)
65 65 return h.url.current(anchor=fid, **dict(GET))
66 66
67 67
68 68 def get_ignore_ws(fid, GET):
69 69 ig_ws_global = GET.get('ignorews')
70 70 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
71 71 if ig_ws:
72 72 try:
73 73 return int(ig_ws[0].split(':')[-1])
74 74 except:
75 75 pass
76 76 return ig_ws_global
77 77
78 78
79 79 def _ignorews_url(GET, fileid=None):
80 80 fileid = str(fileid) if fileid else None
81 81 params = defaultdict(list)
82 82 _update_with_GET(params, GET)
83 83 lbl = _('show white space')
84 84 ig_ws = get_ignore_ws(fileid, GET)
85 85 ln_ctx = get_line_ctx(fileid, GET)
86 86 # global option
87 87 if fileid is None:
88 88 if ig_ws is None:
89 89 params['ignorews'] += [1]
90 90 lbl = _('ignore white space')
91 91 ctx_key = 'context'
92 92 ctx_val = ln_ctx
93 93 # per file options
94 94 else:
95 95 if ig_ws is None:
96 96 params[fileid] += ['WS:1']
97 97 lbl = _('ignore white space')
98 98
99 99 ctx_key = fileid
100 100 ctx_val = 'C:%s' % ln_ctx
101 101 # if we have passed in ln_ctx pass it along to our params
102 102 if ln_ctx:
103 103 params[ctx_key] += [ctx_val]
104 104
105 105 params['anchor'] = fileid
106 106 img = h.image(h.url('/images/icons/text_strikethrough.png'), lbl, class_='icon')
107 107 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
108 108
109 109
110 110 def get_line_ctx(fid, GET):
111 111 ln_ctx_global = GET.get('context')
112 112 if fid:
113 113 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
114 114 else:
115 115 _ln_ctx = filter(lambda k: k.startswith('C'), GET)
116 116 ln_ctx = GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global
117 117 if ln_ctx:
118 118 ln_ctx = [ln_ctx]
119 119
120 120 if ln_ctx:
121 121 retval = ln_ctx[0].split(':')[-1]
122 122 else:
123 123 retval = ln_ctx_global
124 124
125 125 try:
126 126 return int(retval)
127 127 except:
128 128 return 3
129 129
130 130
131 131 def _context_url(GET, fileid=None):
132 132 """
133 133 Generates url for context lines
134 134
135 135 :param fileid:
136 136 """
137 137
138 138 fileid = str(fileid) if fileid else None
139 139 ig_ws = get_ignore_ws(fileid, GET)
140 140 ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
141 141
142 142 params = defaultdict(list)
143 143 _update_with_GET(params, GET)
144 144
145 145 # global option
146 146 if fileid is None:
147 147 if ln_ctx > 0:
148 148 params['context'] += [ln_ctx]
149 149
150 150 if ig_ws:
151 151 ig_ws_key = 'ignorews'
152 152 ig_ws_val = 1
153 153
154 154 # per file option
155 155 else:
156 156 params[fileid] += ['C:%s' % ln_ctx]
157 157 ig_ws_key = fileid
158 158 ig_ws_val = 'WS:%s' % 1
159 159
160 160 if ig_ws:
161 161 params[ig_ws_key] += [ig_ws_val]
162 162
163 163 lbl = _('%s line context') % ln_ctx
164 164
165 165 params['anchor'] = fileid
166 166 img = h.image(h.url('/images/icons/table_add.png'), lbl, class_='icon')
167 167 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
168 168
169 169
170 170 class ChangesetController(BaseRepoController):
171 171
172 172 @LoginRequired()
173 173 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
174 174 'repository.admin')
175 175 def __before__(self):
176 176 super(ChangesetController, self).__before__()
177 177 c.affected_files_cut_off = 60
178 178 repo_model = RepoModel()
179 179 c.users_array = repo_model.get_users_js()
180 180 c.users_groups_array = repo_model.get_users_groups_js()
181 181
182 182 def index(self, revision, method='show'):
183 183 c.anchor_url = anchor_url
184 184 c.ignorews_url = _ignorews_url
185 185 c.context_url = _context_url
186 186 c.fulldiff = fulldiff = request.GET.get('fulldiff')
187 187 #get ranges of revisions if preset
188 188 rev_range = revision.split('...')[:2]
189 189 enable_comments = True
190 190 try:
191 191 if len(rev_range) == 2:
192 192 enable_comments = False
193 193 rev_start = rev_range[0]
194 194 rev_end = rev_range[1]
195 195 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
196 196 end=rev_end)
197 197 else:
198 198 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
199 199
200 200 c.cs_ranges = list(rev_ranges)
201 201 if not c.cs_ranges:
202 202 raise RepositoryError('Changeset range returned empty result')
203 203
204 204 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
205 205 log.error(traceback.format_exc())
206 h.flash(str(e), category='warning')
207 return redirect(url('home'))
206 h.flash(str(e), category='error')
207 return redirect(url('changeset_home', repo_name=c.repo_name))
208 208
209 209 c.changes = OrderedDict()
210 210
211 211 c.lines_added = 0 # count of lines added
212 212 c.lines_deleted = 0 # count of lines removes
213 213
214 214 c.changeset_statuses = ChangesetStatus.STATUSES
215 215 c.comments = []
216 216 c.statuses = []
217 217 c.inline_comments = []
218 218 c.inline_cnt = 0
219 219
220 220 # Iterate over ranges (default changeset view is always one changeset)
221 221 for changeset in c.cs_ranges:
222 222 inlines = []
223 223 if method == 'show':
224 224 c.statuses.extend([ChangesetStatusModel().get_status(
225 225 c.rhodecode_db_repo.repo_id, changeset.raw_id)])
226 226
227 227 c.comments.extend(ChangesetCommentsModel()\
228 228 .get_comments(c.rhodecode_db_repo.repo_id,
229 229 revision=changeset.raw_id))
230 230
231 231 #comments from PR
232 232 st = ChangesetStatusModel().get_statuses(
233 233 c.rhodecode_db_repo.repo_id, changeset.raw_id,
234 234 with_revisions=True)
235 235 # from associated statuses, check the pull requests, and
236 236 # show comments from them
237 237
238 238 prs = set([x.pull_request for x in
239 239 filter(lambda x: x.pull_request != None, st)])
240 240
241 241 for pr in prs:
242 242 c.comments.extend(pr.comments)
243 243 inlines = ChangesetCommentsModel()\
244 244 .get_inline_comments(c.rhodecode_db_repo.repo_id,
245 245 revision=changeset.raw_id)
246 246 c.inline_comments.extend(inlines)
247 247
248 248 c.changes[changeset.raw_id] = []
249 249
250 250 cs2 = changeset.raw_id
251 251 cs1 = changeset.parents[0].raw_id if changeset.parents else EmptyChangeset()
252 252 context_lcl = get_line_ctx('', request.GET)
253 253 ign_whitespace_lcl = ign_whitespace_lcl = get_ignore_ws('', request.GET)
254 254
255 255 _diff = c.rhodecode_repo.get_diff(cs1, cs2,
256 256 ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
257 257 diff_limit = self.cut_off_limit if not fulldiff else None
258 258 diff_processor = diffs.DiffProcessor(_diff,
259 259 vcs=c.rhodecode_repo.alias,
260 260 format='gitdiff',
261 261 diff_limit=diff_limit)
262 262 cs_changes = OrderedDict()
263 263 if method == 'show':
264 264 _parsed = diff_processor.prepare()
265 265 c.limited_diff = False
266 266 if isinstance(_parsed, LimitedDiffContainer):
267 267 c.limited_diff = True
268 268 for f in _parsed:
269 269 st = f['stats']
270 270 if st[0] != 'b':
271 271 c.lines_added += st[0]
272 272 c.lines_deleted += st[1]
273 273 fid = h.FID(changeset.raw_id, f['filename'])
274 274 diff = diff_processor.as_html(enable_comments=enable_comments,
275 275 parsed_lines=[f])
276 276 cs_changes[fid] = [cs1, cs2, f['operation'], f['filename'],
277 277 diff, st]
278 278 else:
279 279 # downloads/raw we only need RAW diff nothing else
280 280 diff = diff_processor.as_raw()
281 281 cs_changes[''] = [None, None, None, None, diff, None]
282 282 c.changes[changeset.raw_id] = cs_changes
283 283
284 284 #sort comments by how they were generated
285 285 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
286 286
287 287 # count inline comments
288 288 for __, lines in c.inline_comments:
289 289 for comments in lines.values():
290 290 c.inline_cnt += len(comments)
291 291
292 292 if len(c.cs_ranges) == 1:
293 293 c.changeset = c.cs_ranges[0]
294 294 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id
295 295 for x in c.changeset.parents])
296 296 if method == 'download':
297 297 response.content_type = 'text/plain'
298 298 response.content_disposition = 'attachment; filename=%s.diff' \
299 299 % revision[:12]
300 300 return diff
301 301 elif method == 'patch':
302 302 response.content_type = 'text/plain'
303 303 c.diff = safe_unicode(diff)
304 304 return render('changeset/patch_changeset.html')
305 305 elif method == 'raw':
306 306 response.content_type = 'text/plain'
307 307 return diff
308 308 elif method == 'show':
309 309 if len(c.cs_ranges) == 1:
310 310 return render('changeset/changeset.html')
311 311 else:
312 312 return render('changeset/changeset_range.html')
313 313
314 314 def changeset_raw(self, revision):
315 315 return self.index(revision, method='raw')
316 316
317 317 def changeset_patch(self, revision):
318 318 return self.index(revision, method='patch')
319 319
320 320 def changeset_download(self, revision):
321 321 return self.index(revision, method='download')
322 322
323 323 @jsonify
324 324 def comment(self, repo_name, revision):
325 325 status = request.POST.get('changeset_status')
326 326 change_status = request.POST.get('change_changeset_status')
327 327 text = request.POST.get('text')
328 328 if status and change_status:
329 329 text = text or (_('Status change -> %s')
330 330 % ChangesetStatus.get_status_lbl(status))
331 331
332 332 c.co = comm = ChangesetCommentsModel().create(
333 333 text=text,
334 334 repo=c.rhodecode_db_repo.repo_id,
335 335 user=c.rhodecode_user.user_id,
336 336 revision=revision,
337 337 f_path=request.POST.get('f_path'),
338 338 line_no=request.POST.get('line'),
339 339 status_change=(ChangesetStatus.get_status_lbl(status)
340 340 if status and change_status else None)
341 341 )
342 342
343 343 # get status if set !
344 344 if status and change_status:
345 345 # if latest status was from pull request and it's closed
346 346 # disallow changing status !
347 347 # dont_allow_on_closed_pull_request = True !
348 348
349 349 try:
350 350 ChangesetStatusModel().set_status(
351 351 c.rhodecode_db_repo.repo_id,
352 352 status,
353 353 c.rhodecode_user.user_id,
354 354 comm,
355 355 revision=revision,
356 356 dont_allow_on_closed_pull_request=True
357 357 )
358 358 except StatusChangeOnClosedPullRequestError:
359 359 log.error(traceback.format_exc())
360 360 msg = _('Changing status on a changeset associated with '
361 361 'a closed pull request is not allowed')
362 362 h.flash(msg, category='warning')
363 363 return redirect(h.url('changeset_home', repo_name=repo_name,
364 364 revision=revision))
365 365 action_logger(self.rhodecode_user,
366 366 'user_commented_revision:%s' % revision,
367 367 c.rhodecode_db_repo, self.ip_addr, self.sa)
368 368
369 369 Session().commit()
370 370
371 371 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
372 372 return redirect(h.url('changeset_home', repo_name=repo_name,
373 373 revision=revision))
374 374 #only ajax below
375 375 data = {
376 376 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
377 377 }
378 378 if comm:
379 379 data.update(comm.get_dict())
380 380 data.update({'rendered_text':
381 381 render('changeset/changeset_comment_block.html')})
382 382
383 383 return data
384 384
385 385 @jsonify
386 386 def delete_comment(self, repo_name, comment_id):
387 387 co = ChangesetComment.get(comment_id)
388 388 owner = co.author.user_id == c.rhodecode_user.user_id
389 389 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
390 390 ChangesetCommentsModel().delete(comment=co)
391 391 Session().commit()
392 392 return True
393 393 else:
394 394 raise HTTPForbidden()
395 395
396 396 @jsonify
397 397 def changeset_info(self, repo_name, revision):
398 398 if request.is_xhr:
399 399 try:
400 400 return c.rhodecode_repo.get_changeset(revision)
401 401 except ChangesetDoesNotExistError, e:
402 402 return EmptyChangeset(message=str(e))
403 403 else:
404 404 raise HTTPBadRequest()
@@ -1,650 +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
60 60
61 61 log = logging.getLogger(__name__)
62 62
63 63
64 64 class FilesController(BaseRepoController):
65 65
66 66 def __before__(self):
67 67 super(FilesController, self).__before__()
68 68 c.cut_off_limit = self.cut_off_limit
69 69
70 70 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
71 71 """
72 72 Safe way to get changeset if error occur it redirects to tip with
73 73 proper message
74 74
75 75 :param rev: revision to fetch
76 76 :param repo_name: repo name to redirect after
77 77 """
78 78
79 79 try:
80 80 return c.rhodecode_repo.get_changeset(rev)
81 81 except EmptyRepositoryError, e:
82 82 if not redirect_after:
83 83 return None
84 84 url_ = url('files_add_home',
85 85 repo_name=c.repo_name,
86 86 revision=0, f_path='')
87 87 add_new = h.link_to(_('click here to add new file'), url_)
88 88 h.flash(h.literal(_('There are no files yet %s') % add_new),
89 89 category='warning')
90 90 redirect(h.url('summary_home', repo_name=repo_name))
91 91
92 except RepositoryError, e:
93 h.flash(str(e), category='warning')
92 except RepositoryError, e: # including ChangesetDoesNotExistError
93 h.flash(str(e), category='error')
94 94 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
95 95
96 96 def __get_filenode_or_redirect(self, repo_name, cs, path):
97 97 """
98 98 Returns file_node, if error occurs or given path is directory,
99 99 it'll redirect to top level path
100 100
101 101 :param repo_name: repo_name
102 102 :param cs: given changeset
103 103 :param path: path to lookup
104 104 """
105 105
106 106 try:
107 107 file_node = cs.get_node(path)
108 108 if file_node.is_dir():
109 109 raise RepositoryError('given path is a directory')
110 110 except RepositoryError, e:
111 h.flash(str(e), category='warning')
111 h.flash(str(e), category='error')
112 112 redirect(h.url('files_home', repo_name=repo_name,
113 113 revision=cs.raw_id))
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 redirect(url('files_home', repo_name=c.repo_name,
126 126 revision=cs.raw_id, f_path=f_path))
127 127
128 128 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
129 129 c.branch = request.GET.get('branch', None)
130 130 c.f_path = f_path
131 131 c.annotate = annotate
132 132 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
133 133 cur_rev = c.changeset.revision
134 134
135 135 # prev link
136 136 try:
137 137 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
138 138 c.url_prev = url('files_home', repo_name=c.repo_name,
139 139 revision=prev_rev.raw_id, f_path=f_path)
140 140 if c.branch:
141 141 c.url_prev += '?branch=%s' % c.branch
142 142 except (ChangesetDoesNotExistError, VCSError):
143 143 c.url_prev = '#'
144 144
145 145 # next link
146 146 try:
147 147 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
148 148 c.url_next = url('files_home', repo_name=c.repo_name,
149 149 revision=next_rev.raw_id, f_path=f_path)
150 150 if c.branch:
151 151 c.url_next += '?branch=%s' % c.branch
152 152 except (ChangesetDoesNotExistError, VCSError):
153 153 c.url_next = '#'
154 154
155 155 # files or dirs
156 156 try:
157 157 c.file = c.changeset.get_node(f_path)
158 158
159 159 if c.file.is_file():
160 160 c.load_full_history = False
161 161 file_last_cs = c.file.last_changeset
162 162 c.file_changeset = (c.changeset
163 163 if c.changeset.revision < file_last_cs.revision
164 164 else file_last_cs)
165 165 #determine if we're on branch head
166 166 _branches = c.rhodecode_repo.branches
167 167 c.on_branch_head = revision in _branches.keys() + _branches.values()
168 168 _hist = []
169 169 c.file_history = []
170 170 if c.load_full_history:
171 171 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
172 172
173 173 c.authors = []
174 174 for a in set([x.author for x in _hist]):
175 175 c.authors.append((h.email(a), h.person(a)))
176 176 else:
177 177 c.authors = c.file_history = []
178 178 except RepositoryError, e:
179 179 h.flash(str(e), category='warning')
180 180 redirect(h.url('files_home', repo_name=repo_name,
181 181 revision='tip'))
182 182
183 183 if request.environ.get('HTTP_X_PARTIAL_XHR'):
184 184 return render('files/files_ypjax.html')
185 185
186 186 return render('files/files.html')
187 187
188 188 def history(self, repo_name, revision, f_path, annotate=False):
189 189 if request.environ.get('HTTP_X_PARTIAL_XHR'):
190 190 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
191 191 c.f_path = f_path
192 192 c.annotate = annotate
193 193 c.file = c.changeset.get_node(f_path)
194 194 if c.file.is_file():
195 195 file_last_cs = c.file.last_changeset
196 196 c.file_changeset = (c.changeset
197 197 if c.changeset.revision < file_last_cs.revision
198 198 else file_last_cs)
199 199 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
200 200 c.authors = []
201 201 for a in set([x.author for x in _hist]):
202 202 c.authors.append((h.email(a), h.person(a)))
203 203 return render('files/files_history_box.html')
204 204
205 205 @LoginRequired()
206 206 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
207 207 'repository.admin')
208 208 def rawfile(self, repo_name, revision, f_path):
209 209 cs = self.__get_cs_or_redirect(revision, repo_name)
210 210 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
211 211
212 212 response.content_disposition = 'attachment; filename=%s' % \
213 213 safe_str(f_path.split(Repository.url_sep())[-1])
214 214
215 215 response.content_type = file_node.mimetype
216 216 return file_node.content
217 217
218 218 @LoginRequired()
219 219 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
220 220 'repository.admin')
221 221 def raw(self, repo_name, revision, f_path):
222 222 cs = self.__get_cs_or_redirect(revision, repo_name)
223 223 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
224 224
225 225 raw_mimetype_mapping = {
226 226 # map original mimetype to a mimetype used for "show as raw"
227 227 # you can also provide a content-disposition to override the
228 228 # default "attachment" disposition.
229 229 # orig_type: (new_type, new_dispo)
230 230
231 231 # show images inline:
232 232 'image/x-icon': ('image/x-icon', 'inline'),
233 233 'image/png': ('image/png', 'inline'),
234 234 'image/gif': ('image/gif', 'inline'),
235 235 'image/jpeg': ('image/jpeg', 'inline'),
236 236 'image/svg+xml': ('image/svg+xml', 'inline'),
237 237 }
238 238
239 239 mimetype = file_node.mimetype
240 240 try:
241 241 mimetype, dispo = raw_mimetype_mapping[mimetype]
242 242 except KeyError:
243 243 # we don't know anything special about this, handle it safely
244 244 if file_node.is_binary:
245 245 # do same as download raw for binary files
246 246 mimetype, dispo = 'application/octet-stream', 'attachment'
247 247 else:
248 248 # do not just use the original mimetype, but force text/plain,
249 249 # otherwise it would serve text/html and that might be unsafe.
250 250 # Note: underlying vcs library fakes text/plain mimetype if the
251 251 # mimetype can not be determined and it thinks it is not
252 252 # binary.This might lead to erroneous text display in some
253 253 # cases, but helps in other cases, like with text files
254 254 # without extension.
255 255 mimetype, dispo = 'text/plain', 'inline'
256 256
257 257 if dispo == 'attachment':
258 258 dispo = 'attachment; filename=%s' % \
259 259 safe_str(f_path.split(os.sep)[-1])
260 260
261 261 response.content_disposition = dispo
262 262 response.content_type = mimetype
263 263 return file_node.content
264 264
265 265 @LoginRequired()
266 266 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
267 267 def edit(self, repo_name, revision, f_path):
268 268 repo = c.rhodecode_db_repo
269 269 if repo.enable_locking and repo.locked[0]:
270 270 h.flash(_('This repository is has been locked by %s on %s')
271 271 % (h.person_by_id(repo.locked[0]),
272 272 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
273 273 'warning')
274 274 return redirect(h.url('files_home',
275 275 repo_name=repo_name, revision='tip'))
276 276
277 277 # check if revision is a branch identifier- basically we cannot
278 278 # create multiple heads via file editing
279 279 _branches = repo.scm_instance.branches
280 280 # check if revision is a branch name or branch hash
281 281 if revision not in _branches.keys() + _branches.values():
282 282 h.flash(_('You can only edit files with revision '
283 283 'being a valid branch '), category='warning')
284 284 return redirect(h.url('files_home',
285 285 repo_name=repo_name, revision='tip',
286 286 f_path=f_path))
287 287
288 288 r_post = request.POST
289 289
290 290 c.cs = self.__get_cs_or_redirect(revision, repo_name)
291 291 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
292 292
293 293 if c.file.is_binary:
294 294 return redirect(url('files_home', repo_name=c.repo_name,
295 295 revision=c.cs.raw_id, f_path=f_path))
296 296 c.default_message = _('Edited file %s via RhodeCode') % (f_path)
297 297 c.f_path = f_path
298 298
299 299 if r_post:
300 300
301 301 old_content = c.file.content
302 302 sl = old_content.splitlines(1)
303 303 first_line = sl[0] if sl else ''
304 304 # modes: 0 - Unix, 1 - Mac, 2 - DOS
305 305 mode = detect_mode(first_line, 0)
306 306 content = convert_line_endings(r_post.get('content'), mode)
307 307
308 308 message = r_post.get('message') or c.default_message
309 309 author = self.rhodecode_user.full_contact
310 310
311 311 if content == old_content:
312 312 h.flash(_('No changes'),
313 313 category='warning')
314 314 return redirect(url('changeset_home', repo_name=c.repo_name,
315 315 revision='tip'))
316 316 try:
317 317 self.scm_model.commit_change(repo=c.rhodecode_repo,
318 318 repo_name=repo_name, cs=c.cs,
319 319 user=self.rhodecode_user.user_id,
320 320 author=author, message=message,
321 321 content=content, f_path=f_path)
322 322 h.flash(_('Successfully committed to %s') % f_path,
323 323 category='success')
324 324
325 325 except Exception:
326 326 log.error(traceback.format_exc())
327 327 h.flash(_('Error occurred during commit'), category='error')
328 328 return redirect(url('changeset_home',
329 329 repo_name=c.repo_name, revision='tip'))
330 330
331 331 return render('files/files_edit.html')
332 332
333 333 @LoginRequired()
334 334 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
335 335 def add(self, repo_name, revision, f_path):
336 336
337 337 repo = Repository.get_by_repo_name(repo_name)
338 338 if repo.enable_locking and repo.locked[0]:
339 339 h.flash(_('This repository is has been locked by %s on %s')
340 340 % (h.person_by_id(repo.locked[0]),
341 341 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
342 342 'warning')
343 343 return redirect(h.url('files_home',
344 344 repo_name=repo_name, revision='tip'))
345 345
346 346 r_post = request.POST
347 347 c.cs = self.__get_cs_or_redirect(revision, repo_name,
348 348 redirect_after=False)
349 349 if c.cs is None:
350 350 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
351 351 c.default_message = (_('Added file via RhodeCode'))
352 352 c.f_path = f_path
353 353
354 354 if r_post:
355 355 unix_mode = 0
356 356 content = convert_line_endings(r_post.get('content'), unix_mode)
357 357
358 358 message = r_post.get('message') or c.default_message
359 359 filename = r_post.get('filename')
360 360 location = r_post.get('location')
361 361 file_obj = r_post.get('upload_file', None)
362 362
363 363 if file_obj is not None and hasattr(file_obj, 'filename'):
364 364 filename = file_obj.filename
365 365 content = file_obj.file
366 366
367 367 if not content:
368 368 h.flash(_('No content'), category='warning')
369 369 return redirect(url('changeset_home', repo_name=c.repo_name,
370 370 revision='tip'))
371 371 if not filename:
372 372 h.flash(_('No filename'), category='warning')
373 373 return redirect(url('changeset_home', repo_name=c.repo_name,
374 374 revision='tip'))
375 375 if location.startswith('/') or location.startswith('.') or '../' in location:
376 376 h.flash(_('location must be relative path and must not '
377 377 'contain .. in path'), category='warning')
378 378 return redirect(url('changeset_home', repo_name=c.repo_name,
379 379 revision='tip'))
380 380 location = os.path.normpath(location)
381 381 filename = os.path.basename(filename)
382 382 node_path = os.path.join(location, filename)
383 383 author = self.rhodecode_user.full_contact
384 384
385 385 try:
386 386 self.scm_model.create_node(repo=c.rhodecode_repo,
387 387 repo_name=repo_name, cs=c.cs,
388 388 user=self.rhodecode_user.user_id,
389 389 author=author, message=message,
390 390 content=content, f_path=node_path)
391 391 h.flash(_('Successfully committed to %s') % node_path,
392 392 category='success')
393 393 except (NodeError, NodeAlreadyExistsError), e:
394 394 h.flash(_(e), category='error')
395 395 except Exception:
396 396 log.error(traceback.format_exc())
397 397 h.flash(_('Error occurred during commit'), category='error')
398 398 return redirect(url('changeset_home',
399 399 repo_name=c.repo_name, revision='tip'))
400 400
401 401 return render('files/files_add.html')
402 402
403 403 @LoginRequired()
404 404 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
405 405 'repository.admin')
406 406 def archivefile(self, repo_name, fname):
407 407
408 408 fileformat = None
409 409 revision = None
410 410 ext = None
411 411 subrepos = request.GET.get('subrepos') == 'true'
412 412
413 413 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
414 414 archive_spec = fname.split(ext_data[1])
415 415 if len(archive_spec) == 2 and archive_spec[1] == '':
416 416 fileformat = a_type or ext_data[1]
417 417 revision = archive_spec[0]
418 418 ext = ext_data[1]
419 419
420 420 try:
421 421 dbrepo = RepoModel().get_by_repo_name(repo_name)
422 422 if dbrepo.enable_downloads is False:
423 423 return _('downloads disabled')
424 424
425 425 if c.rhodecode_repo.alias == 'hg':
426 426 # patch and reset hooks section of UI config to not run any
427 427 # hooks on fetching archives with subrepos
428 428 for k, v in c.rhodecode_repo._repo.ui.configitems('hooks'):
429 429 c.rhodecode_repo._repo.ui.setconfig('hooks', k, None)
430 430
431 431 cs = c.rhodecode_repo.get_changeset(revision)
432 432 content_type = settings.ARCHIVE_SPECS[fileformat][0]
433 433 except ChangesetDoesNotExistError:
434 434 return _('Unknown revision %s') % revision
435 435 except EmptyRepositoryError:
436 436 return _('Empty repository')
437 437 except (ImproperArchiveTypeError, KeyError):
438 438 return _('Unknown archive type')
439 439 # archive cache
440 440 from rhodecode import CONFIG
441 441 rev_name = cs.raw_id[:12]
442 442 archive_name = '%s-%s%s' % (safe_str(repo_name.replace('/', '_')),
443 443 safe_str(rev_name), ext)
444 444
445 445 use_cached_archive = False # defines if we use cached version of archive
446 446 archive_cache_enabled = CONFIG.get('archive_cache_dir')
447 447 if not subrepos and archive_cache_enabled:
448 448 #check if we it's ok to write
449 449 if not os.path.isdir(CONFIG['archive_cache_dir']):
450 450 os.makedirs(CONFIG['archive_cache_dir'])
451 451 cached_archive_path = os.path.join(CONFIG['archive_cache_dir'], archive_name)
452 452 if os.path.isfile(cached_archive_path):
453 453 log.debug('Found cached archive in %s' % cached_archive_path)
454 454 fd, archive = None, cached_archive_path
455 455 use_cached_archive = True
456 456 else:
457 457 log.debug('Archive %s is not yet cached' % (archive_name))
458 458
459 459 if not use_cached_archive:
460 460 #generate new archive
461 461 try:
462 462 fd, archive = tempfile.mkstemp()
463 463 t = open(archive, 'wb')
464 464 log.debug('Creating new temp archive in %s' % archive)
465 465 cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
466 466 if archive_cache_enabled:
467 467 #if we generated the archive and use cache rename that
468 468 log.debug('Storing new archive in %s' % cached_archive_path)
469 469 shutil.move(archive, cached_archive_path)
470 470 archive = cached_archive_path
471 471 finally:
472 472 t.close()
473 473
474 474 def get_chunked_archive(archive):
475 475 stream = open(archive, 'rb')
476 476 while True:
477 477 data = stream.read(16 * 1024)
478 478 if not data:
479 479 stream.close()
480 480 if fd: # fd means we used temporary file
481 481 os.close(fd)
482 482 if not archive_cache_enabled:
483 483 log.debug('Destroing temp archive %s' % archive)
484 484 os.remove(archive)
485 485 break
486 486 yield data
487 487
488 488 response.content_disposition = str('attachment; filename=%s' % (archive_name))
489 489 response.content_type = str(content_type)
490 490 return get_chunked_archive(archive)
491 491
492 492 @LoginRequired()
493 493 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
494 494 'repository.admin')
495 495 def diff(self, repo_name, f_path):
496 496 ignore_whitespace = request.GET.get('ignorews') == '1'
497 497 line_context = request.GET.get('context', 3)
498 498 diff1 = request.GET.get('diff1', '')
499 499 diff2 = request.GET.get('diff2', '')
500 500 c.action = request.GET.get('diff')
501 501 c.no_changes = diff1 == diff2
502 502 c.f_path = f_path
503 503 c.big_diff = False
504 504 c.anchor_url = anchor_url
505 505 c.ignorews_url = _ignorews_url
506 506 c.context_url = _context_url
507 507 c.changes = OrderedDict()
508 508 c.changes[diff2] = []
509 509
510 510 #special case if we want a show rev only, it's impl here
511 511 #to reduce JS and callbacks
512 512
513 513 if request.GET.get('show_rev'):
514 514 if str2bool(request.GET.get('annotate', 'False')):
515 515 _url = url('files_annotate_home', repo_name=c.repo_name,
516 516 revision=diff1, f_path=c.f_path)
517 517 else:
518 518 _url = url('files_home', repo_name=c.repo_name,
519 519 revision=diff1, f_path=c.f_path)
520 520
521 521 return redirect(_url)
522 522 try:
523 523 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
524 524 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
525 525 try:
526 526 node1 = c.changeset_1.get_node(f_path)
527 527 if node1.is_dir():
528 528 raise NodeError('%s path is a %s not a file'
529 529 % (node1, type(node1)))
530 530 except NodeDoesNotExistError:
531 531 c.changeset_1 = EmptyChangeset(cs=diff1,
532 532 revision=c.changeset_1.revision,
533 533 repo=c.rhodecode_repo)
534 534 node1 = FileNode(f_path, '', changeset=c.changeset_1)
535 535 else:
536 536 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
537 537 node1 = FileNode(f_path, '', changeset=c.changeset_1)
538 538
539 539 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
540 540 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
541 541 try:
542 542 node2 = c.changeset_2.get_node(f_path)
543 543 if node2.is_dir():
544 544 raise NodeError('%s path is a %s not a file'
545 545 % (node2, type(node2)))
546 546 except NodeDoesNotExistError:
547 547 c.changeset_2 = EmptyChangeset(cs=diff2,
548 548 revision=c.changeset_2.revision,
549 549 repo=c.rhodecode_repo)
550 550 node2 = FileNode(f_path, '', changeset=c.changeset_2)
551 551 else:
552 552 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
553 553 node2 = FileNode(f_path, '', changeset=c.changeset_2)
554 554 except (RepositoryError, NodeError):
555 555 log.error(traceback.format_exc())
556 556 return redirect(url('files_home', repo_name=c.repo_name,
557 557 f_path=f_path))
558 558
559 559 if c.action == 'download':
560 560 _diff = diffs.get_gitdiff(node1, node2,
561 561 ignore_whitespace=ignore_whitespace,
562 562 context=line_context)
563 563 diff = diffs.DiffProcessor(_diff, format='gitdiff')
564 564
565 565 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
566 566 response.content_type = 'text/plain'
567 567 response.content_disposition = (
568 568 'attachment; filename=%s' % diff_name
569 569 )
570 570 return diff.as_raw()
571 571
572 572 elif c.action == 'raw':
573 573 _diff = diffs.get_gitdiff(node1, node2,
574 574 ignore_whitespace=ignore_whitespace,
575 575 context=line_context)
576 576 diff = diffs.DiffProcessor(_diff, format='gitdiff')
577 577 response.content_type = 'text/plain'
578 578 return diff.as_raw()
579 579
580 580 else:
581 581 fid = h.FID(diff2, node2.path)
582 582 line_context_lcl = get_line_ctx(fid, request.GET)
583 583 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
584 584
585 585 lim = request.GET.get('fulldiff') or self.cut_off_limit
586 586 _, cs1, cs2, diff, st = diffs.wrapped_diff(filenode_old=node1,
587 587 filenode_new=node2,
588 588 cut_off_limit=lim,
589 589 ignore_whitespace=ign_whitespace_lcl,
590 590 line_context=line_context_lcl,
591 591 enable_comments=False)
592 592 op = ''
593 593 filename = node1.path
594 594 cs_changes = {
595 595 'fid': [cs1, cs2, op, filename, diff, st]
596 596 }
597 597 c.changes = cs_changes
598 598
599 599 return render('files/file_diff.html')
600 600
601 601 def _get_node_history(self, cs, f_path, changesets=None):
602 602 """
603 603 get changesets history for given node
604 604
605 605 :param cs: changeset to calculate history
606 606 :param f_path: path for node to calculate history for
607 607 :param changesets: if passed don't calculate history and take
608 608 changesets defined in this list
609 609 """
610 610 # calculate history based on tip
611 611 tip_cs = c.rhodecode_repo.get_changeset()
612 612 if changesets is None:
613 613 try:
614 614 changesets = tip_cs.get_file_history(f_path)
615 615 except (NodeDoesNotExistError, ChangesetError):
616 616 #this node is not present at tip !
617 617 changesets = cs.get_file_history(f_path)
618 618 hist_l = []
619 619
620 620 changesets_group = ([], _("Changesets"))
621 621 branches_group = ([], _("Branches"))
622 622 tags_group = ([], _("Tags"))
623 623 _hg = cs.repository.alias == 'hg'
624 624 for chs in changesets:
625 625 #_branch = '(%s)' % chs.branch if _hg else ''
626 626 _branch = chs.branch
627 627 n_desc = 'r%s:%s (%s)' % (chs.revision, chs.short_id, _branch)
628 628 changesets_group[0].append((chs.raw_id, n_desc,))
629 629 hist_l.append(changesets_group)
630 630
631 631 for name, chs in c.rhodecode_repo.branches.items():
632 632 branches_group[0].append((chs, name),)
633 633 hist_l.append(branches_group)
634 634
635 635 for name, chs in c.rhodecode_repo.tags.items():
636 636 tags_group[0].append((chs, name),)
637 637 hist_l.append(tags_group)
638 638
639 639 return hist_l, changesets
640 640
641 641 @LoginRequired()
642 642 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
643 643 'repository.admin')
644 644 @jsonify
645 645 def nodelist(self, repo_name, revision, f_path):
646 646 if request.environ.get('HTTP_X_PARTIAL_XHR'):
647 647 cs = self.__get_cs_or_redirect(revision, repo_name)
648 648 _d, _f = ScmModel().get_nodes(repo_name, cs.raw_id, f_path,
649 649 flat=False)
650 650 return {'nodes': _d + _f}
General Comments 0
You need to be logged in to leave comments. Login now