##// END OF EJS Templates
changelog: fix and optimize loading of chunks for file history....
marcink -
r2130:69819530 default
parent child Browse files
Show More
@@ -1,447 +1,450 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20 from rhodecode.apps._base import add_route_with_slash
21 21
22 22
23 23 def includeme(config):
24 24
25 25 # repo creating checks, special cases that aren't repo routes
26 26 config.add_route(
27 27 name='repo_creating',
28 28 pattern='/{repo_name:.*?[^/]}/repo_creating')
29 29
30 30 config.add_route(
31 31 name='repo_creating_check',
32 32 pattern='/{repo_name:.*?[^/]}/repo_creating_check')
33 33
34 34 # Summary
35 35 # NOTE(marcink): one additional route is defined in very bottom, catch
36 36 # all pattern
37 37 config.add_route(
38 38 name='repo_summary_explicit',
39 39 pattern='/{repo_name:.*?[^/]}/summary', repo_route=True)
40 40 config.add_route(
41 41 name='repo_summary_commits',
42 42 pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True)
43 43
44 44 # Commits
45 45 config.add_route(
46 46 name='repo_commit',
47 47 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True)
48 48
49 49 config.add_route(
50 50 name='repo_commit_children',
51 51 pattern='/{repo_name:.*?[^/]}/changeset_children/{commit_id}', repo_route=True)
52 52
53 53 config.add_route(
54 54 name='repo_commit_parents',
55 55 pattern='/{repo_name:.*?[^/]}/changeset_parents/{commit_id}', repo_route=True)
56 56
57 57 config.add_route(
58 58 name='repo_commit_raw',
59 59 pattern='/{repo_name:.*?[^/]}/changeset-diff/{commit_id}', repo_route=True)
60 60
61 61 config.add_route(
62 62 name='repo_commit_patch',
63 63 pattern='/{repo_name:.*?[^/]}/changeset-patch/{commit_id}', repo_route=True)
64 64
65 65 config.add_route(
66 66 name='repo_commit_download',
67 67 pattern='/{repo_name:.*?[^/]}/changeset-download/{commit_id}', repo_route=True)
68 68
69 69 config.add_route(
70 70 name='repo_commit_data',
71 71 pattern='/{repo_name:.*?[^/]}/changeset-data/{commit_id}', repo_route=True)
72 72
73 73 config.add_route(
74 74 name='repo_commit_comment_create',
75 75 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/create', repo_route=True)
76 76
77 77 config.add_route(
78 78 name='repo_commit_comment_preview',
79 79 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/preview', repo_route=True)
80 80
81 81 config.add_route(
82 82 name='repo_commit_comment_delete',
83 83 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/delete', repo_route=True)
84 84
85 85 # still working url for backward compat.
86 86 config.add_route(
87 87 name='repo_commit_raw_deprecated',
88 88 pattern='/{repo_name:.*?[^/]}/raw-changeset/{commit_id}', repo_route=True)
89 89
90 90 # Files
91 91 config.add_route(
92 92 name='repo_archivefile',
93 93 pattern='/{repo_name:.*?[^/]}/archive/{fname}', repo_route=True)
94 94
95 95 config.add_route(
96 96 name='repo_files_diff',
97 97 pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True)
98 98 config.add_route( # legacy route to make old links work
99 99 name='repo_files_diff_2way_redirect',
100 100 pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True)
101 101
102 102 config.add_route(
103 103 name='repo_files',
104 104 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True)
105 105 config.add_route(
106 106 name='repo_files:default_path',
107 107 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True)
108 108 config.add_route(
109 109 name='repo_files:default_commit',
110 110 pattern='/{repo_name:.*?[^/]}/files', repo_route=True)
111 111
112 112 config.add_route(
113 113 name='repo_files:rendered',
114 114 pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True)
115 115
116 116 config.add_route(
117 117 name='repo_files:annotated',
118 118 pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True)
119 119 config.add_route(
120 120 name='repo_files:annotated_previous',
121 121 pattern='/{repo_name:.*?[^/]}/annotate-previous/{commit_id}/{f_path:.*}', repo_route=True)
122 122
123 123 config.add_route(
124 124 name='repo_nodetree_full',
125 125 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/{f_path:.*}', repo_route=True)
126 126 config.add_route(
127 127 name='repo_nodetree_full:default_path',
128 128 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True)
129 129
130 130 config.add_route(
131 131 name='repo_files_nodelist',
132 132 pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True)
133 133
134 134 config.add_route(
135 135 name='repo_file_raw',
136 136 pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True)
137 137
138 138 config.add_route(
139 139 name='repo_file_download',
140 140 pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True)
141 141 config.add_route( # backward compat to keep old links working
142 142 name='repo_file_download:legacy',
143 143 pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}',
144 144 repo_route=True)
145 145
146 146 config.add_route(
147 147 name='repo_file_history',
148 148 pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True)
149 149
150 150 config.add_route(
151 151 name='repo_file_authors',
152 152 pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True)
153 153
154 154 config.add_route(
155 155 name='repo_files_remove_file',
156 156 pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}',
157 157 repo_route=True)
158 158 config.add_route(
159 159 name='repo_files_delete_file',
160 160 pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}',
161 161 repo_route=True)
162 162 config.add_route(
163 163 name='repo_files_edit_file',
164 164 pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}',
165 165 repo_route=True)
166 166 config.add_route(
167 167 name='repo_files_update_file',
168 168 pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}',
169 169 repo_route=True)
170 170 config.add_route(
171 171 name='repo_files_add_file',
172 172 pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}',
173 173 repo_route=True)
174 174 config.add_route(
175 175 name='repo_files_create_file',
176 176 pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}',
177 177 repo_route=True)
178 178
179 179 # Refs data
180 180 config.add_route(
181 181 name='repo_refs_data',
182 182 pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True)
183 183
184 184 config.add_route(
185 185 name='repo_refs_changelog_data',
186 186 pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True)
187 187
188 188 config.add_route(
189 189 name='repo_stats',
190 190 pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True)
191 191
192 192 # Changelog
193 193 config.add_route(
194 194 name='repo_changelog',
195 195 pattern='/{repo_name:.*?[^/]}/changelog', repo_route=True)
196 196 config.add_route(
197 197 name='repo_changelog_file',
198 198 pattern='/{repo_name:.*?[^/]}/changelog/{commit_id}/{f_path:.*}', repo_route=True)
199 199 config.add_route(
200 200 name='repo_changelog_elements',
201 201 pattern='/{repo_name:.*?[^/]}/changelog_elements', repo_route=True)
202 config.add_route(
203 name='repo_changelog_elements_file',
204 pattern='/{repo_name:.*?[^/]}/changelog_elements/{commit_id}/{f_path:.*}', repo_route=True)
202 205
203 206 # Compare
204 207 config.add_route(
205 208 name='repo_compare_select',
206 209 pattern='/{repo_name:.*?[^/]}/compare', repo_route=True)
207 210
208 211 config.add_route(
209 212 name='repo_compare',
210 213 pattern='/{repo_name:.*?[^/]}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}', repo_route=True)
211 214
212 215 # Tags
213 216 config.add_route(
214 217 name='tags_home',
215 218 pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
216 219
217 220 # Branches
218 221 config.add_route(
219 222 name='branches_home',
220 223 pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
221 224
222 225 # Bookmarks
223 226 config.add_route(
224 227 name='bookmarks_home',
225 228 pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
226 229
227 230 # Forks
228 231 config.add_route(
229 232 name='repo_fork_new',
230 233 pattern='/{repo_name:.*?[^/]}/fork', repo_route=True,
231 234 repo_accepted_types=['hg', 'git'])
232 235
233 236 config.add_route(
234 237 name='repo_fork_create',
235 238 pattern='/{repo_name:.*?[^/]}/fork/create', repo_route=True,
236 239 repo_accepted_types=['hg', 'git'])
237 240
238 241 config.add_route(
239 242 name='repo_forks_show_all',
240 243 pattern='/{repo_name:.*?[^/]}/forks', repo_route=True,
241 244 repo_accepted_types=['hg', 'git'])
242 245 config.add_route(
243 246 name='repo_forks_data',
244 247 pattern='/{repo_name:.*?[^/]}/forks/data', repo_route=True,
245 248 repo_accepted_types=['hg', 'git'])
246 249
247 250 # Pull Requests
248 251 config.add_route(
249 252 name='pullrequest_show',
250 253 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}',
251 254 repo_route=True)
252 255
253 256 config.add_route(
254 257 name='pullrequest_show_all',
255 258 pattern='/{repo_name:.*?[^/]}/pull-request',
256 259 repo_route=True, repo_accepted_types=['hg', 'git'])
257 260
258 261 config.add_route(
259 262 name='pullrequest_show_all_data',
260 263 pattern='/{repo_name:.*?[^/]}/pull-request-data',
261 264 repo_route=True, repo_accepted_types=['hg', 'git'])
262 265
263 266 config.add_route(
264 267 name='pullrequest_repo_refs',
265 268 pattern='/{repo_name:.*?[^/]}/pull-request/refs/{target_repo_name:.*?[^/]}',
266 269 repo_route=True)
267 270
268 271 config.add_route(
269 272 name='pullrequest_repo_destinations',
270 273 pattern='/{repo_name:.*?[^/]}/pull-request/repo-destinations',
271 274 repo_route=True)
272 275
273 276 config.add_route(
274 277 name='pullrequest_new',
275 278 pattern='/{repo_name:.*?[^/]}/pull-request/new',
276 279 repo_route=True, repo_accepted_types=['hg', 'git'])
277 280
278 281 config.add_route(
279 282 name='pullrequest_create',
280 283 pattern='/{repo_name:.*?[^/]}/pull-request/create',
281 284 repo_route=True, repo_accepted_types=['hg', 'git'])
282 285
283 286 config.add_route(
284 287 name='pullrequest_update',
285 288 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/update',
286 289 repo_route=True)
287 290
288 291 config.add_route(
289 292 name='pullrequest_merge',
290 293 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/merge',
291 294 repo_route=True)
292 295
293 296 config.add_route(
294 297 name='pullrequest_delete',
295 298 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/delete',
296 299 repo_route=True)
297 300
298 301 config.add_route(
299 302 name='pullrequest_comment_create',
300 303 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment',
301 304 repo_route=True)
302 305
303 306 config.add_route(
304 307 name='pullrequest_comment_delete',
305 308 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete',
306 309 repo_route=True, repo_accepted_types=['hg', 'git'])
307 310
308 311 # Settings
309 312 config.add_route(
310 313 name='edit_repo',
311 314 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
312 315
313 316 # Settings advanced
314 317 config.add_route(
315 318 name='edit_repo_advanced',
316 319 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
317 320 config.add_route(
318 321 name='edit_repo_advanced_delete',
319 322 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
320 323 config.add_route(
321 324 name='edit_repo_advanced_locking',
322 325 pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True)
323 326 config.add_route(
324 327 name='edit_repo_advanced_journal',
325 328 pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True)
326 329 config.add_route(
327 330 name='edit_repo_advanced_fork',
328 331 pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True)
329 332
330 333 # Caches
331 334 config.add_route(
332 335 name='edit_repo_caches',
333 336 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
334 337
335 338 # Permissions
336 339 config.add_route(
337 340 name='edit_repo_perms',
338 341 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
339 342
340 343 # Maintenance
341 344 config.add_route(
342 345 name='edit_repo_maintenance',
343 346 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
344 347
345 348 config.add_route(
346 349 name='edit_repo_maintenance_execute',
347 350 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
348 351
349 352 # Fields
350 353 config.add_route(
351 354 name='edit_repo_fields',
352 355 pattern='/{repo_name:.*?[^/]}/settings/fields', repo_route=True)
353 356 config.add_route(
354 357 name='edit_repo_fields_create',
355 358 pattern='/{repo_name:.*?[^/]}/settings/fields/create', repo_route=True)
356 359 config.add_route(
357 360 name='edit_repo_fields_delete',
358 361 pattern='/{repo_name:.*?[^/]}/settings/fields/{field_id}/delete', repo_route=True)
359 362
360 363 # Locking
361 364 config.add_route(
362 365 name='repo_edit_toggle_locking',
363 366 pattern='/{repo_name:.*?[^/]}/settings/toggle_locking', repo_route=True)
364 367
365 368 # Remote
366 369 config.add_route(
367 370 name='edit_repo_remote',
368 371 pattern='/{repo_name:.*?[^/]}/settings/remote', repo_route=True)
369 372 config.add_route(
370 373 name='edit_repo_remote_pull',
371 374 pattern='/{repo_name:.*?[^/]}/settings/remote/pull', repo_route=True)
372 375
373 376
374 377 # Statistics
375 378 config.add_route(
376 379 name='edit_repo_statistics',
377 380 pattern='/{repo_name:.*?[^/]}/settings/statistics', repo_route=True)
378 381 config.add_route(
379 382 name='edit_repo_statistics_reset',
380 383 pattern='/{repo_name:.*?[^/]}/settings/statistics/update', repo_route=True)
381 384
382 385 # Issue trackers
383 386 config.add_route(
384 387 name='edit_repo_issuetracker',
385 388 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers', repo_route=True)
386 389 config.add_route(
387 390 name='edit_repo_issuetracker_test',
388 391 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/test', repo_route=True)
389 392 config.add_route(
390 393 name='edit_repo_issuetracker_delete',
391 394 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/delete', repo_route=True)
392 395 config.add_route(
393 396 name='edit_repo_issuetracker_update',
394 397 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/update', repo_route=True)
395 398
396 399 # VCS Settings
397 400 config.add_route(
398 401 name='edit_repo_vcs',
399 402 pattern='/{repo_name:.*?[^/]}/settings/vcs', repo_route=True)
400 403 config.add_route(
401 404 name='edit_repo_vcs_update',
402 405 pattern='/{repo_name:.*?[^/]}/settings/vcs/update', repo_route=True)
403 406
404 407 # svn pattern
405 408 config.add_route(
406 409 name='edit_repo_vcs_svn_pattern_delete',
407 410 pattern='/{repo_name:.*?[^/]}/settings/vcs/svn_pattern/delete', repo_route=True)
408 411
409 412 # Repo Review Rules (EE feature)
410 413 config.add_route(
411 414 name='repo_reviewers',
412 415 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
413 416
414 417 config.add_route(
415 418 name='repo_default_reviewers_data',
416 419 pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True)
417 420
418 421 # Strip
419 422 config.add_route(
420 423 name='edit_repo_strip',
421 424 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
422 425
423 426 config.add_route(
424 427 name='strip_check',
425 428 pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True)
426 429
427 430 config.add_route(
428 431 name='strip_execute',
429 432 pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True)
430 433
431 434 # ATOM/RSS Feed
432 435 config.add_route(
433 436 name='rss_feed_home',
434 437 pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True)
435 438
436 439 config.add_route(
437 440 name='atom_feed_home',
438 441 pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True)
439 442
440 443 # NOTE(marcink): needs to be at the end for catch-all
441 444 add_route_with_slash(
442 445 config,
443 446 name='repo_summary',
444 447 pattern='/{repo_name:.*?[^/]}', repo_route=True)
445 448
446 449 # Scan module for configuration decorators.
447 450 config.scan('.views', ignore='.tests')
@@ -1,310 +1,336 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import logging
23 23
24 24 from pyramid.httpexceptions import HTTPNotFound, HTTPFound
25 25 from pyramid.view import view_config
26 26 from pyramid.renderers import render
27 27 from pyramid.response import Response
28 28
29 29 from rhodecode.apps._base import RepoAppView
30 30 import rhodecode.lib.helpers as h
31 31 from rhodecode.lib.auth import (
32 32 LoginRequired, HasRepoPermissionAnyDecorator)
33 33
34 34 from rhodecode.lib.ext_json import json
35 35 from rhodecode.lib.graphmod import _colored, _dagwalker
36 36 from rhodecode.lib.helpers import RepoPage
37 37 from rhodecode.lib.utils2 import safe_int, safe_str
38 38 from rhodecode.lib.vcs.exceptions import (
39 39 RepositoryError, CommitDoesNotExistError,
40 40 CommitError, NodeDoesNotExistError, EmptyRepositoryError)
41 41
42 42 log = logging.getLogger(__name__)
43 43
44 44 DEFAULT_CHANGELOG_SIZE = 20
45 45
46 46
47 47 class RepoChangelogView(RepoAppView):
48 48
49 49 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
50 50 """
51 51 This is a safe way to get commit. If an error occurs it redirects to
52 52 tip with proper message
53 53
54 54 :param commit_id: id of commit to fetch
55 55 :param redirect_after: toggle redirection
56 56 """
57 57 _ = self.request.translate
58 58
59 59 try:
60 60 return self.rhodecode_vcs_repo.get_commit(commit_id)
61 61 except EmptyRepositoryError:
62 62 if not redirect_after:
63 63 return None
64 64
65 65 h.flash(h.literal(
66 66 _('There are no commits yet')), category='warning')
67 67 raise HTTPFound(
68 68 h.route_path('repo_summary', repo_name=self.db_repo_name))
69 69
70 70 except (CommitDoesNotExistError, LookupError):
71 71 msg = _('No such commit exists for this repository')
72 72 h.flash(msg, category='error')
73 73 raise HTTPNotFound()
74 74 except RepositoryError as e:
75 75 h.flash(safe_str(h.escape(e)), category='error')
76 76 raise HTTPNotFound()
77 77
78 78 def _graph(self, repo, commits, prev_data=None, next_data=None):
79 79 """
80 80 Generates a DAG graph for repo
81 81
82 82 :param repo: repo instance
83 83 :param commits: list of commits
84 84 """
85 85 if not commits:
86 86 return json.dumps([])
87 87
88 88 def serialize(commit, parents=True):
89 89 data = dict(
90 90 raw_id=commit.raw_id,
91 91 idx=commit.idx,
92 92 branch=commit.branch,
93 93 )
94 94 if parents:
95 95 data['parents'] = [
96 96 serialize(x, parents=False) for x in commit.parents]
97 97 return data
98 98
99 99 prev_data = prev_data or []
100 100 next_data = next_data or []
101 101
102 102 current = [serialize(x) for x in commits]
103 103 commits = prev_data + current + next_data
104 104
105 105 dag = _dagwalker(repo, commits)
106 106
107 107 data = [[commit_id, vtx, edges, branch]
108 108 for commit_id, vtx, edges, branch in _colored(dag)]
109 109 return json.dumps(data), json.dumps(current)
110 110
111 111 def _check_if_valid_branch(self, branch_name, repo_name, f_path):
112 112 if branch_name not in self.rhodecode_vcs_repo.branches_all:
113 113 h.flash('Branch {} is not found.'.format(h.escape(branch_name)),
114 114 category='warning')
115 115 redirect_url = h.route_path(
116 116 'repo_changelog_file', repo_name=repo_name,
117 117 commit_id=branch_name, f_path=f_path or '')
118 118 raise HTTPFound(redirect_url)
119 119
120 120 def _load_changelog_data(
121 121 self, c, collection, page, chunk_size, branch_name=None,
122 122 dynamic=False, f_path=None, commit_id=None):
123 123
124 124 def url_generator(**kw):
125 125 query_params = {}
126 126 query_params.update(kw)
127 127 if f_path:
128 128 # changelog for file
129 129 return h.route_path(
130 130 'repo_changelog_file',
131 131 repo_name=c.rhodecode_db_repo.repo_name,
132 132 commit_id=commit_id, f_path=f_path,
133 133 _query=query_params)
134 134 else:
135 135 return h.route_path(
136 136 'repo_changelog',
137 137 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
138 138
139 139 c.total_cs = len(collection)
140 140 c.showing_commits = min(chunk_size, c.total_cs)
141 141 c.pagination = RepoPage(collection, page=page, item_count=c.total_cs,
142 142 items_per_page=chunk_size, branch=branch_name,
143 143 url=url_generator)
144 144
145 145 c.next_page = c.pagination.next_page
146 146 c.prev_page = c.pagination.previous_page
147 147
148 148 if dynamic:
149 149 if self.request.GET.get('chunk') != 'next':
150 150 c.next_page = None
151 151 if self.request.GET.get('chunk') != 'prev':
152 152 c.prev_page = None
153 153
154 154 page_commit_ids = [x.raw_id for x in c.pagination]
155 155 c.comments = c.rhodecode_db_repo.get_comments(page_commit_ids)
156 156 c.statuses = c.rhodecode_db_repo.statuses(page_commit_ids)
157 157
158 158 def load_default_context(self):
159 159 c = self._get_local_tmpl_context(include_app_defaults=True)
160 160
161 161 c.rhodecode_repo = self.rhodecode_vcs_repo
162 162 self._register_global_c(c)
163 163 return c
164 164
165 def _get_preload_attrs(self):
166 pre_load = ['author', 'branch', 'date', 'message', 'parents',
167 'obsolete', 'phase', 'hidden']
168 return pre_load
169
165 170 @LoginRequired()
166 171 @HasRepoPermissionAnyDecorator(
167 172 'repository.read', 'repository.write', 'repository.admin')
168 173 @view_config(
169 174 route_name='repo_changelog', request_method='GET',
170 175 renderer='rhodecode:templates/changelog/changelog.mako')
171 176 @view_config(
172 177 route_name='repo_changelog_file', request_method='GET',
173 178 renderer='rhodecode:templates/changelog/changelog.mako')
174 179 def repo_changelog(self):
175 180 c = self.load_default_context()
176 181
177 182 commit_id = self.request.matchdict.get('commit_id')
178 183 f_path = self._get_f_path(self.request.matchdict)
179 184
180 185 chunk_size = 20
181 186
182 187 c.branch_name = branch_name = self.request.GET.get('branch') or ''
183 188 c.book_name = book_name = self.request.GET.get('bookmark') or ''
189 c.f_path = f_path
190 c.commit_id = commit_id
184 191 hist_limit = safe_int(self.request.GET.get('limit')) or None
185 192
186 193 p = safe_int(self.request.GET.get('page', 1), 1)
187 194
188 195 c.selected_name = branch_name or book_name
189 196 if not commit_id and branch_name:
190 197 self._check_if_valid_branch(branch_name, self.db_repo_name, f_path)
191 198
192 199 c.changelog_for_path = f_path
193 pre_load = ['author', 'branch', 'date', 'message', 'parents']
200 pre_load = self._get_preload_attrs()
194 201 commit_ids = []
195 202
196 203 partial_xhr = self.request.environ.get('HTTP_X_PARTIAL_XHR')
197 204
198 205 try:
199 206 if f_path:
200 207 log.debug('generating changelog for path %s', f_path)
201 208 # get the history for the file !
202 209 base_commit = self.rhodecode_vcs_repo.get_commit(commit_id)
210
203 211 try:
204 212 collection = base_commit.get_file_history(
205 213 f_path, limit=hist_limit, pre_load=pre_load)
206 214 if collection and partial_xhr:
207 215 # for ajax call we remove first one since we're looking
208 216 # at it right now in the context of a file commit
209 217 collection.pop(0)
210 218 except (NodeDoesNotExistError, CommitError):
211 219 # this node is not present at tip!
212 220 try:
213 221 commit = self._get_commit_or_redirect(commit_id)
214 222 collection = commit.get_file_history(f_path)
215 223 except RepositoryError as e:
216 224 h.flash(safe_str(e), category='warning')
217 225 redirect_url = h.route_path(
218 226 'repo_changelog', repo_name=self.db_repo_name)
219 227 raise HTTPFound(redirect_url)
220 228 collection = list(reversed(collection))
221 229 else:
222 230 collection = self.rhodecode_vcs_repo.get_commits(
223 231 branch_name=branch_name, pre_load=pre_load)
224 232
225 233 self._load_changelog_data(
226 234 c, collection, p, chunk_size, c.branch_name,
227 235 f_path=f_path, commit_id=commit_id)
228 236
229 237 except EmptyRepositoryError as e:
230 238 h.flash(safe_str(h.escape(e)), category='warning')
231 239 raise HTTPFound(
232 240 h.route_path('repo_summary', repo_name=self.db_repo_name))
233 241 except HTTPFound:
234 242 raise
235 243 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
236 244 log.exception(safe_str(e))
237 245 h.flash(safe_str(h.escape(e)), category='error')
238 246 raise HTTPFound(
239 247 h.route_path('repo_changelog', repo_name=self.db_repo_name))
240 248
241 249 if partial_xhr or self.request.environ.get('HTTP_X_PJAX'):
250 # case when loading dynamic file history in file view
242 251 # loading from ajax, we don't want the first result, it's popped
243 252 # in the code above
244 253 html = render(
245 254 'rhodecode:templates/changelog/changelog_file_history.mako',
246 255 self._get_template_context(c), self.request)
247 256 return Response(html)
248 257
249 258 if not f_path:
250 259 commit_ids = c.pagination
251 260
252 261 c.graph_data, c.graph_commits = self._graph(
253 262 self.rhodecode_vcs_repo, commit_ids)
254 263
255 264 return self._get_template_context(c)
256 265
257 266 @LoginRequired()
258 267 @HasRepoPermissionAnyDecorator(
259 268 'repository.read', 'repository.write', 'repository.admin')
260 269 @view_config(
261 270 route_name='repo_changelog_elements', request_method=('GET', 'POST'),
262 271 renderer='rhodecode:templates/changelog/changelog_elements.mako',
263 272 xhr=True)
273 @view_config(
274 route_name='repo_changelog_elements_file', request_method=('GET', 'POST'),
275 renderer='rhodecode:templates/changelog/changelog_elements.mako',
276 xhr=True)
264 277 def repo_changelog_elements(self):
265 278 c = self.load_default_context()
279 commit_id = self.request.matchdict.get('commit_id')
280 f_path = self._get_f_path(self.request.matchdict)
266 281 chunk_size = 20
282 hist_limit = safe_int(self.request.GET.get('limit')) or None
267 283
268 284 def wrap_for_error(err):
269 285 html = '<tr>' \
270 286 '<td colspan="9" class="alert alert-error">ERROR: {}</td>' \
271 287 '</tr>'.format(err)
272 288 return Response(html)
273 289
274 290 c.branch_name = branch_name = self.request.GET.get('branch') or ''
275 291 c.book_name = book_name = self.request.GET.get('bookmark') or ''
292 c.f_path = f_path
293 c.commit_id = commit_id
276 294
277 295 c.selected_name = branch_name or book_name
278 296 if branch_name and branch_name not in self.rhodecode_vcs_repo.branches_all:
279 297 return wrap_for_error(
280 298 safe_str('Branch: {} is not valid'.format(branch_name)))
281 299
282 pre_load = ['author', 'branch', 'date', 'message', 'parents']
283 collection = self.rhodecode_vcs_repo.get_commits(
284 branch_name=branch_name, pre_load=pre_load)
300 pre_load = self._get_preload_attrs()
301
302 if f_path:
303 base_commit = self.rhodecode_vcs_repo.get_commit(commit_id)
304 collection = base_commit.get_file_history(
305 f_path, limit=hist_limit, pre_load=pre_load)
306 collection = list(reversed(collection))
307 else:
308 collection = self.rhodecode_vcs_repo.get_commits(
309 branch_name=branch_name, pre_load=pre_load)
285 310
286 311 p = safe_int(self.request.GET.get('page', 1), 1)
287 312 try:
288 313 self._load_changelog_data(
289 c, collection, p, chunk_size, dynamic=True)
314 c, collection, p, chunk_size, dynamic=True,
315 f_path=f_path, commit_id=commit_id)
290 316 except EmptyRepositoryError as e:
291 317 return wrap_for_error(safe_str(e))
292 318 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
293 319 log.exception('Failed to fetch commits')
294 320 return wrap_for_error(safe_str(e))
295 321
296 322 prev_data = None
297 323 next_data = None
298 324
299 prev_graph = json.loads(self.request.POST.get('graph', ''))
325 prev_graph = json.loads(self.request.POST.get('graph') or '{}')
300 326
301 327 if self.request.GET.get('chunk') == 'prev':
302 328 next_data = prev_graph
303 329 elif self.request.GET.get('chunk') == 'next':
304 330 prev_data = prev_graph
305 331
306 332 c.graph_data, c.graph_commits = self._graph(
307 333 self.rhodecode_vcs_repo, c.pagination,
308 334 prev_data=prev_data, next_data=next_data)
309 335
310 336 return self._get_template_context(c)
@@ -1,538 +1,545 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2014-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 GIT commit module
23 23 """
24 24
25 25 import re
26 26 import stat
27 27 from ConfigParser import ConfigParser
28 28 from itertools import chain
29 29 from StringIO import StringIO
30 30
31 31 from zope.cachedescriptors.property import Lazy as LazyProperty
32 32
33 33 from rhodecode.lib.datelib import utcdate_fromtimestamp
34 34 from rhodecode.lib.utils import safe_unicode, safe_str
35 35 from rhodecode.lib.utils2 import safe_int
36 36 from rhodecode.lib.vcs.conf import settings
37 37 from rhodecode.lib.vcs.backends import base
38 38 from rhodecode.lib.vcs.exceptions import CommitError, NodeDoesNotExistError
39 39 from rhodecode.lib.vcs.nodes import (
40 40 FileNode, DirNode, NodeKind, RootNode, SubModuleNode,
41 41 ChangedFileNodesGenerator, AddedFileNodesGenerator,
42 42 RemovedFileNodesGenerator, LargeFileNode)
43 43
44 44
45 45 class GitCommit(base.BaseCommit):
46 46 """
47 47 Represents state of the repository at single commit id.
48 48 """
49 49 _author_property = 'author'
50 50 _committer_property = 'committer'
51 51 _date_property = 'commit_time'
52 52 _date_tz_property = 'commit_timezone'
53 53 _message_property = 'message'
54 54 _parents_property = 'parents'
55 55
56 56 _filter_pre_load = [
57 57 # done through a more complex tree walk on parents
58 58 "affected_files",
59 59 # based on repository cached property
60 60 "branch",
61 61 # done through subprocess not remote call
62 62 "children",
63 63 # done through a more complex tree walk on parents
64 64 "status",
65 65 # mercurial specific property not supported here
66 66 "_file_paths",
67 # mercurial specific property not supported here
68 'obsolete',
69 # mercurial specific property not supported here
70 'phase',
71 # mercurial specific property not supported here
72 'hidden'
67 73 ]
68 74
69 75 def __init__(self, repository, raw_id, idx, pre_load=None):
70 76 self.repository = repository
71 77 self._remote = repository._remote
72 78 # TODO: johbo: Tweak of raw_id should not be necessary
73 79 self.raw_id = safe_str(raw_id)
74 80 self.idx = idx
75 81
76 82 self._set_bulk_properties(pre_load)
77 83
78 84 # caches
79 85 self._stat_modes = {} # stat info for paths
80 86 self._paths = {} # path processed with parse_tree
81 87 self.nodes = {}
82 88 self._submodules = None
83 89
84 90 def _set_bulk_properties(self, pre_load):
85 91 if not pre_load:
86 92 return
87 93 pre_load = [entry for entry in pre_load
88 94 if entry not in self._filter_pre_load]
89 95 if not pre_load:
90 96 return
91 97
92 98 result = self._remote.bulk_request(self.raw_id, pre_load)
93 99 for attr, value in result.items():
94 100 if attr in ["author", "message"]:
95 101 if value:
96 102 value = safe_unicode(value)
97 103 elif attr == "date":
98 104 value = utcdate_fromtimestamp(*value)
99 105 elif attr == "parents":
100 106 value = self._make_commits(value)
101 107 self.__dict__[attr] = value
102 108
103 109 @LazyProperty
104 110 def _commit(self):
105 111 return self._remote[self.raw_id]
106 112
107 113 @LazyProperty
108 114 def _tree_id(self):
109 115 return self._remote[self._commit['tree']]['id']
110 116
111 117 @LazyProperty
112 118 def id(self):
113 119 return self.raw_id
114 120
115 121 @LazyProperty
116 122 def short_id(self):
117 123 return self.raw_id[:12]
118 124
119 125 @LazyProperty
120 126 def message(self):
121 127 return safe_unicode(
122 128 self._remote.commit_attribute(self.id, self._message_property))
123 129
124 130 @LazyProperty
125 131 def committer(self):
126 132 return safe_unicode(
127 133 self._remote.commit_attribute(self.id, self._committer_property))
128 134
129 135 @LazyProperty
130 136 def author(self):
131 137 return safe_unicode(
132 138 self._remote.commit_attribute(self.id, self._author_property))
133 139
134 140 @LazyProperty
135 141 def date(self):
136 142 unix_ts, tz = self._remote.get_object_attrs(
137 143 self.raw_id, self._date_property, self._date_tz_property)
138 144 return utcdate_fromtimestamp(unix_ts, tz)
139 145
140 146 @LazyProperty
141 147 def status(self):
142 148 """
143 149 Returns modified, added, removed, deleted files for current commit
144 150 """
145 151 return self.changed, self.added, self.removed
146 152
147 153 @LazyProperty
148 154 def tags(self):
149 155 tags = [safe_unicode(name) for name,
150 156 commit_id in self.repository.tags.iteritems()
151 157 if commit_id == self.raw_id]
152 158 return tags
153 159
154 160 @LazyProperty
155 161 def branch(self):
156 162 for name, commit_id in self.repository.branches.iteritems():
157 163 if commit_id == self.raw_id:
158 164 return safe_unicode(name)
159 165 return None
160 166
161 167 def _get_id_for_path(self, path):
162 168 path = safe_str(path)
163 169 if path in self._paths:
164 170 return self._paths[path]
165 171
166 172 tree_id = self._tree_id
167 173
168 174 path = path.strip('/')
169 175 if path == '':
170 176 data = [tree_id, "tree"]
171 177 self._paths[''] = data
172 178 return data
173 179
174 180 parts = path.split('/')
175 181 dirs, name = parts[:-1], parts[-1]
176 182 cur_dir = ''
177 183
178 184 # initially extract things from root dir
179 185 tree_items = self._remote.tree_items(tree_id)
180 186 self._process_tree_items(tree_items, cur_dir)
181 187
182 188 for dir in dirs:
183 189 if cur_dir:
184 190 cur_dir = '/'.join((cur_dir, dir))
185 191 else:
186 192 cur_dir = dir
187 193 dir_id = None
188 194 for item, stat_, id_, type_ in tree_items:
189 195 if item == dir:
190 196 dir_id = id_
191 197 break
192 198 if dir_id:
193 199 if type_ != "tree":
194 200 raise CommitError('%s is not a directory' % cur_dir)
195 201 # update tree
196 202 tree_items = self._remote.tree_items(dir_id)
197 203 else:
198 204 raise CommitError('%s have not been found' % cur_dir)
199 205
200 206 # cache all items from the given traversed tree
201 207 self._process_tree_items(tree_items, cur_dir)
202 208
203 209 if path not in self._paths:
204 210 raise self.no_node_at_path(path)
205 211
206 212 return self._paths[path]
207 213
208 214 def _process_tree_items(self, items, cur_dir):
209 215 for item, stat_, id_, type_ in items:
210 216 if cur_dir:
211 217 name = '/'.join((cur_dir, item))
212 218 else:
213 219 name = item
214 220 self._paths[name] = [id_, type_]
215 221 self._stat_modes[name] = stat_
216 222
217 223 def _get_kind(self, path):
218 224 path_id, type_ = self._get_id_for_path(path)
219 225 if type_ == 'blob':
220 226 return NodeKind.FILE
221 227 elif type_ == 'tree':
222 228 return NodeKind.DIR
223 229 elif type == 'link':
224 230 return NodeKind.SUBMODULE
225 231 return None
226 232
227 233 def _get_filectx(self, path):
228 234 path = self._fix_path(path)
229 235 if self._get_kind(path) != NodeKind.FILE:
230 236 raise CommitError(
231 237 "File does not exist for commit %s at '%s'" %
232 238 (self.raw_id, path))
233 239 return path
234 240
235 241 def _get_file_nodes(self):
236 242 return chain(*(t[2] for t in self.walk()))
237 243
238 244 @LazyProperty
239 245 def parents(self):
240 246 """
241 247 Returns list of parent commits.
242 248 """
243 249 parent_ids = self._remote.commit_attribute(
244 250 self.id, self._parents_property)
245 251 return self._make_commits(parent_ids)
246 252
247 253 @LazyProperty
248 254 def children(self):
249 255 """
250 256 Returns list of child commits.
251 257 """
252 258 rev_filter = settings.GIT_REV_FILTER
253 259 output, __ = self.repository.run_git_command(
254 260 ['rev-list', '--children'] + rev_filter)
255 261
256 262 child_ids = []
257 263 pat = re.compile(r'^%s' % self.raw_id)
258 264 for l in output.splitlines():
259 265 if pat.match(l):
260 266 found_ids = l.split(' ')[1:]
261 267 child_ids.extend(found_ids)
262 268 return self._make_commits(child_ids)
263 269
264 def _make_commits(self, commit_ids):
265 return [self.repository.get_commit(commit_id=commit_id)
266 for commit_id in commit_ids]
270 def _make_commits(self, commit_ids, pre_load=None):
271 return [
272 self.repository.get_commit(commit_id=commit_id, pre_load=pre_load)
273 for commit_id in commit_ids]
267 274
268 275 def get_file_mode(self, path):
269 276 """
270 277 Returns stat mode of the file at the given `path`.
271 278 """
272 279 path = safe_str(path)
273 280 # ensure path is traversed
274 281 self._get_id_for_path(path)
275 282 return self._stat_modes[path]
276 283
277 284 def is_link(self, path):
278 285 return stat.S_ISLNK(self.get_file_mode(path))
279 286
280 287 def get_file_content(self, path):
281 288 """
282 289 Returns content of the file at given `path`.
283 290 """
284 291 id_, _ = self._get_id_for_path(path)
285 292 return self._remote.blob_as_pretty_string(id_)
286 293
287 294 def get_file_size(self, path):
288 295 """
289 296 Returns size of the file at given `path`.
290 297 """
291 298 id_, _ = self._get_id_for_path(path)
292 299 return self._remote.blob_raw_length(id_)
293 300
294 301 def get_file_history(self, path, limit=None, pre_load=None):
295 302 """
296 303 Returns history of file as reversed list of `GitCommit` objects for
297 304 which file at given `path` has been modified.
298 305
299 306 TODO: This function now uses an underlying 'git' command which works
300 307 quickly but ideally we should replace with an algorithm.
301 308 """
302 309 self._get_filectx(path)
303 310 f_path = safe_str(path)
304 311
305 312 cmd = ['log']
306 313 if limit:
307 314 cmd.extend(['-n', str(safe_int(limit, 0))])
308 315 cmd.extend(['--pretty=format: %H', '-s', self.raw_id, '--', f_path])
309 316
310 317 output, __ = self.repository.run_git_command(cmd)
311 318 commit_ids = re.findall(r'[0-9a-fA-F]{40}', output)
312 319
313 320 return [
314 321 self.repository.get_commit(commit_id=commit_id, pre_load=pre_load)
315 322 for commit_id in commit_ids]
316 323
317 324 # TODO: unused for now potential replacement for subprocess
318 325 def get_file_history_2(self, path, limit=None, pre_load=None):
319 326 """
320 327 Returns history of file as reversed list of `Commit` objects for
321 328 which file at given `path` has been modified.
322 329 """
323 330 self._get_filectx(path)
324 331 f_path = safe_str(path)
325 332
326 333 commit_ids = self._remote.get_file_history(f_path, self.id, limit)
327 334
328 335 return [
329 336 self.repository.get_commit(commit_id=commit_id, pre_load=pre_load)
330 337 for commit_id in commit_ids]
331 338
332 339 def get_file_annotate(self, path, pre_load=None):
333 340 """
334 341 Returns a generator of four element tuples with
335 342 lineno, commit_id, commit lazy loader and line
336 343
337 344 TODO: This function now uses os underlying 'git' command which is
338 345 generally not good. Should be replaced with algorithm iterating
339 346 commits.
340 347 """
341 348 cmd = ['blame', '-l', '--root', '-r', self.raw_id, '--', path]
342 349 # -l ==> outputs long shas (and we need all 40 characters)
343 350 # --root ==> doesn't put '^' character for bounderies
344 351 # -r commit_id ==> blames for the given commit
345 352 output, __ = self.repository.run_git_command(cmd)
346 353
347 354 for i, blame_line in enumerate(output.split('\n')[:-1]):
348 355 line_no = i + 1
349 356 commit_id, line = re.split(r' ', blame_line, 1)
350 357 yield (
351 358 line_no, commit_id,
352 359 lambda: self.repository.get_commit(commit_id=commit_id,
353 360 pre_load=pre_load),
354 361 line)
355 362
356 363 def get_nodes(self, path):
357 364 if self._get_kind(path) != NodeKind.DIR:
358 365 raise CommitError(
359 366 "Directory does not exist for commit %s at "
360 367 " '%s'" % (self.raw_id, path))
361 368 path = self._fix_path(path)
362 369 id_, _ = self._get_id_for_path(path)
363 370 tree_id = self._remote[id_]['id']
364 371 dirnodes = []
365 372 filenodes = []
366 373 alias = self.repository.alias
367 374 for name, stat_, id_, type_ in self._remote.tree_items(tree_id):
368 375 if type_ == 'link':
369 376 url = self._get_submodule_url('/'.join((path, name)))
370 377 dirnodes.append(SubModuleNode(
371 378 name, url=url, commit=id_, alias=alias))
372 379 continue
373 380
374 381 if path != '':
375 382 obj_path = '/'.join((path, name))
376 383 else:
377 384 obj_path = name
378 385 if obj_path not in self._stat_modes:
379 386 self._stat_modes[obj_path] = stat_
380 387
381 388 if type_ == 'tree':
382 389 dirnodes.append(DirNode(obj_path, commit=self))
383 390 elif type_ == 'blob':
384 391 filenodes.append(FileNode(obj_path, commit=self, mode=stat_))
385 392 else:
386 393 raise CommitError(
387 394 "Requested object should be Tree or Blob, is %s", type_)
388 395
389 396 nodes = dirnodes + filenodes
390 397 for node in nodes:
391 398 if node.path not in self.nodes:
392 399 self.nodes[node.path] = node
393 400 nodes.sort()
394 401 return nodes
395 402
396 403 def get_node(self, path, pre_load=None):
397 404 if isinstance(path, unicode):
398 405 path = path.encode('utf-8')
399 406 path = self._fix_path(path)
400 407 if path not in self.nodes:
401 408 try:
402 409 id_, type_ = self._get_id_for_path(path)
403 410 except CommitError:
404 411 raise NodeDoesNotExistError(
405 412 "Cannot find one of parents' directories for a given "
406 413 "path: %s" % path)
407 414
408 415 if type_ == 'link':
409 416 url = self._get_submodule_url(path)
410 417 node = SubModuleNode(path, url=url, commit=id_,
411 418 alias=self.repository.alias)
412 419 elif type_ == 'tree':
413 420 if path == '':
414 421 node = RootNode(commit=self)
415 422 else:
416 423 node = DirNode(path, commit=self)
417 424 elif type_ == 'blob':
418 425 node = FileNode(path, commit=self, pre_load=pre_load)
419 426 else:
420 427 raise self.no_node_at_path(path)
421 428
422 429 # cache node
423 430 self.nodes[path] = node
424 431 return self.nodes[path]
425 432
426 433 def get_largefile_node(self, path):
427 434 id_, _ = self._get_id_for_path(path)
428 435 pointer_spec = self._remote.is_large_file(id_)
429 436
430 437 if pointer_spec:
431 438 # content of that file regular FileNode is the hash of largefile
432 439 file_id = pointer_spec.get('oid_hash')
433 440 if self._remote.in_largefiles_store(file_id):
434 441 lf_path = self._remote.store_path(file_id)
435 442 return LargeFileNode(lf_path, commit=self, org_path=path)
436 443
437 444 @LazyProperty
438 445 def affected_files(self):
439 446 """
440 447 Gets a fast accessible file changes for given commit
441 448 """
442 449 added, modified, deleted = self._changes_cache
443 450 return list(added.union(modified).union(deleted))
444 451
445 452 @LazyProperty
446 453 def _changes_cache(self):
447 454 added = set()
448 455 modified = set()
449 456 deleted = set()
450 457 _r = self._remote
451 458
452 459 parents = self.parents
453 460 if not self.parents:
454 461 parents = [base.EmptyCommit()]
455 462 for parent in parents:
456 463 if isinstance(parent, base.EmptyCommit):
457 464 oid = None
458 465 else:
459 466 oid = parent.raw_id
460 467 changes = _r.tree_changes(oid, self.raw_id)
461 468 for (oldpath, newpath), (_, _), (_, _) in changes:
462 469 if newpath and oldpath:
463 470 modified.add(newpath)
464 471 elif newpath and not oldpath:
465 472 added.add(newpath)
466 473 elif not newpath and oldpath:
467 474 deleted.add(oldpath)
468 475 return added, modified, deleted
469 476
470 477 def _get_paths_for_status(self, status):
471 478 """
472 479 Returns sorted list of paths for given ``status``.
473 480
474 481 :param status: one of: *added*, *modified* or *deleted*
475 482 """
476 483 added, modified, deleted = self._changes_cache
477 484 return sorted({
478 485 'added': list(added),
479 486 'modified': list(modified),
480 487 'deleted': list(deleted)}[status]
481 488 )
482 489
483 490 @LazyProperty
484 491 def added(self):
485 492 """
486 493 Returns list of added ``FileNode`` objects.
487 494 """
488 495 if not self.parents:
489 496 return list(self._get_file_nodes())
490 497 return AddedFileNodesGenerator(
491 498 [n for n in self._get_paths_for_status('added')], self)
492 499
493 500 @LazyProperty
494 501 def changed(self):
495 502 """
496 503 Returns list of modified ``FileNode`` objects.
497 504 """
498 505 if not self.parents:
499 506 return []
500 507 return ChangedFileNodesGenerator(
501 508 [n for n in self._get_paths_for_status('modified')], self)
502 509
503 510 @LazyProperty
504 511 def removed(self):
505 512 """
506 513 Returns list of removed ``FileNode`` objects.
507 514 """
508 515 if not self.parents:
509 516 return []
510 517 return RemovedFileNodesGenerator(
511 518 [n for n in self._get_paths_for_status('deleted')], self)
512 519
513 520 def _get_submodule_url(self, submodule_path):
514 521 git_modules_path = '.gitmodules'
515 522
516 523 if self._submodules is None:
517 524 self._submodules = {}
518 525
519 526 try:
520 527 submodules_node = self.get_node(git_modules_path)
521 528 except NodeDoesNotExistError:
522 529 return None
523 530
524 531 content = submodules_node.content
525 532
526 533 # ConfigParser fails if there are whitespaces
527 534 content = '\n'.join(l.strip() for l in content.split('\n'))
528 535
529 536 parser = ConfigParser()
530 537 parser.readfp(StringIO(content))
531 538
532 539 for section in parser.sections():
533 540 path = parser.get(section, 'path')
534 541 url = parser.get(section, 'url')
535 542 if path and url:
536 543 self._submodules[path.strip('/')] = url
537 544
538 545 return self._submodules.get(submodule_path.strip('/'))
@@ -1,383 +1,388 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2014-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 HG commit module
23 23 """
24 24
25 25 import os
26 26
27 27 from zope.cachedescriptors.property import Lazy as LazyProperty
28 28
29 29 from rhodecode.lib.datelib import utcdate_fromtimestamp
30 30 from rhodecode.lib.utils import safe_str, safe_unicode
31 31 from rhodecode.lib.vcs import path as vcspath
32 32 from rhodecode.lib.vcs.backends import base
33 33 from rhodecode.lib.vcs.backends.hg.diff import MercurialDiff
34 34 from rhodecode.lib.vcs.exceptions import CommitError
35 35 from rhodecode.lib.vcs.nodes import (
36 36 AddedFileNodesGenerator, ChangedFileNodesGenerator, DirNode, FileNode,
37 37 NodeKind, RemovedFileNodesGenerator, RootNode, SubModuleNode,
38 38 LargeFileNode, LARGEFILE_PREFIX)
39 39 from rhodecode.lib.vcs.utils.paths import get_dirs_for_path
40 40
41 41
42 42 class MercurialCommit(base.BaseCommit):
43 43 """
44 44 Represents state of the repository at the single commit.
45 45 """
46 46
47 47 _filter_pre_load = [
48 48 # git specific property not supported here
49 49 "_commit",
50 50 ]
51 51
52 52 def __init__(self, repository, raw_id, idx, pre_load=None):
53 53 raw_id = safe_str(raw_id)
54 54
55 55 self.repository = repository
56 56 self._remote = repository._remote
57 57
58 58 self.raw_id = raw_id
59 59 self.idx = repository._sanitize_commit_idx(idx)
60 60
61 61 self._set_bulk_properties(pre_load)
62 62
63 63 # caches
64 64 self.nodes = {}
65 65
66 66 def _set_bulk_properties(self, pre_load):
67 67 if not pre_load:
68 68 return
69 69 pre_load = [entry for entry in pre_load
70 70 if entry not in self._filter_pre_load]
71 71 if not pre_load:
72 72 return
73 73
74 74 result = self._remote.bulk_request(self.idx, pre_load)
75 75 for attr, value in result.items():
76 76 if attr in ["author", "branch", "message"]:
77 77 value = safe_unicode(value)
78 78 elif attr == "affected_files":
79 79 value = map(safe_unicode, value)
80 80 elif attr == "date":
81 81 value = utcdate_fromtimestamp(*value)
82 82 elif attr in ["children", "parents"]:
83 83 value = self._make_commits(value)
84 elif attr in ["phase"]:
85 value = self._get_phase_text(value)
84 86 self.__dict__[attr] = value
85 87
86 88 @LazyProperty
87 89 def tags(self):
88 90 tags = [name for name, commit_id in self.repository.tags.iteritems()
89 91 if commit_id == self.raw_id]
90 92 return tags
91 93
92 94 @LazyProperty
93 95 def branch(self):
94 96 return safe_unicode(self._remote.ctx_branch(self.idx))
95 97
96 98 @LazyProperty
97 99 def bookmarks(self):
98 100 bookmarks = [
99 101 name for name, commit_id in self.repository.bookmarks.iteritems()
100 102 if commit_id == self.raw_id]
101 103 return bookmarks
102 104
103 105 @LazyProperty
104 106 def message(self):
105 107 return safe_unicode(self._remote.ctx_description(self.idx))
106 108
107 109 @LazyProperty
108 110 def committer(self):
109 111 return safe_unicode(self.author)
110 112
111 113 @LazyProperty
112 114 def author(self):
113 115 return safe_unicode(self._remote.ctx_user(self.idx))
114 116
115 117 @LazyProperty
116 118 def date(self):
117 119 return utcdate_fromtimestamp(*self._remote.ctx_date(self.idx))
118 120
119 121 @LazyProperty
120 122 def status(self):
121 123 """
122 124 Returns modified, added, removed, deleted files for current commit
123 125 """
124 126 return self._remote.ctx_status(self.idx)
125 127
126 128 @LazyProperty
127 129 def _file_paths(self):
128 130 return self._remote.ctx_list(self.idx)
129 131
130 132 @LazyProperty
131 133 def _dir_paths(self):
132 134 p = list(set(get_dirs_for_path(*self._file_paths)))
133 135 p.insert(0, '')
134 136 return p
135 137
136 138 @LazyProperty
137 139 def _paths(self):
138 140 return self._dir_paths + self._file_paths
139 141
140 142 @LazyProperty
141 143 def id(self):
142 144 if self.last:
143 145 return u'tip'
144 146 return self.short_id
145 147
146 148 @LazyProperty
147 149 def short_id(self):
148 150 return self.raw_id[:12]
149 151
150 def _make_commits(self, indexes):
151 return [self.repository.get_commit(commit_idx=idx)
152 def _make_commits(self, indexes, pre_load=None):
153 return [self.repository.get_commit(commit_idx=idx, pre_load=pre_load)
152 154 for idx in indexes if idx >= 0]
153 155
154 156 @LazyProperty
155 157 def parents(self):
156 158 """
157 159 Returns list of parent commits.
158 160 """
159 161 parents = self._remote.ctx_parents(self.idx)
160 162 return self._make_commits(parents)
161 163
164 def _get_phase_text(self, phase_id):
165 return {
166 0: 'public',
167 1: 'draft',
168 2: 'secret',
169 }.get(phase_id) or ''
170
162 171 @LazyProperty
163 172 def phase(self):
164 173 phase_id = self._remote.ctx_phase(self.idx)
165 phase_text = {
166 0: 'public',
167 1: 'draft',
168 2: 'secret',
169 }.get(phase_id) or ''
174 phase_text = self._get_phase_text(phase_id)
170 175
171 176 return safe_unicode(phase_text)
172 177
173 178 @LazyProperty
174 179 def obsolete(self):
175 180 obsolete = self._remote.ctx_obsolete(self.idx)
176 181 return obsolete
177 182
178 183 @LazyProperty
179 184 def hidden(self):
180 185 hidden = self._remote.ctx_hidden(self.idx)
181 186 return hidden
182 187
183 188 @LazyProperty
184 189 def children(self):
185 190 """
186 191 Returns list of child commits.
187 192 """
188 193 children = self._remote.ctx_children(self.idx)
189 194 return self._make_commits(children)
190 195
191 196 def diff(self, ignore_whitespace=True, context=3):
192 197 result = self._remote.ctx_diff(
193 198 self.idx,
194 199 git=True, ignore_whitespace=ignore_whitespace, context=context)
195 200 diff = ''.join(result)
196 201 return MercurialDiff(diff)
197 202
198 203 def _fix_path(self, path):
199 204 """
200 205 Mercurial keeps filenodes as str so we need to encode from unicode
201 206 to str.
202 207 """
203 208 return safe_str(super(MercurialCommit, self)._fix_path(path))
204 209
205 210 def _get_kind(self, path):
206 211 path = self._fix_path(path)
207 212 if path in self._file_paths:
208 213 return NodeKind.FILE
209 214 elif path in self._dir_paths:
210 215 return NodeKind.DIR
211 216 else:
212 217 raise CommitError(
213 218 "Node does not exist at the given path '%s'" % (path, ))
214 219
215 220 def _get_filectx(self, path):
216 221 path = self._fix_path(path)
217 222 if self._get_kind(path) != NodeKind.FILE:
218 223 raise CommitError(
219 224 "File does not exist for idx %s at '%s'" % (self.raw_id, path))
220 225 return path
221 226
222 227 def get_file_mode(self, path):
223 228 """
224 229 Returns stat mode of the file at the given ``path``.
225 230 """
226 231 path = self._get_filectx(path)
227 232 if 'x' in self._remote.fctx_flags(self.idx, path):
228 233 return base.FILEMODE_EXECUTABLE
229 234 else:
230 235 return base.FILEMODE_DEFAULT
231 236
232 237 def is_link(self, path):
233 238 path = self._get_filectx(path)
234 239 return 'l' in self._remote.fctx_flags(self.idx, path)
235 240
236 241 def get_file_content(self, path):
237 242 """
238 243 Returns content of the file at given ``path``.
239 244 """
240 245 path = self._get_filectx(path)
241 246 return self._remote.fctx_data(self.idx, path)
242 247
243 248 def get_file_size(self, path):
244 249 """
245 250 Returns size of the file at given ``path``.
246 251 """
247 252 path = self._get_filectx(path)
248 253 return self._remote.fctx_size(self.idx, path)
249 254
250 255 def get_file_history(self, path, limit=None, pre_load=None):
251 256 """
252 257 Returns history of file as reversed list of `MercurialCommit` objects
253 258 for which file at given ``path`` has been modified.
254 259 """
255 260 path = self._get_filectx(path)
256 261 hist = self._remote.file_history(self.idx, path, limit)
257 262 return [
258 263 self.repository.get_commit(commit_id=commit_id, pre_load=pre_load)
259 264 for commit_id in hist]
260 265
261 266 def get_file_annotate(self, path, pre_load=None):
262 267 """
263 268 Returns a generator of four element tuples with
264 269 lineno, commit_id, commit lazy loader and line
265 270 """
266 271 result = self._remote.fctx_annotate(self.idx, path)
267 272
268 273 for ln_no, commit_id, content in result:
269 274 yield (
270 275 ln_no, commit_id,
271 276 lambda: self.repository.get_commit(commit_id=commit_id,
272 277 pre_load=pre_load),
273 278 content)
274 279
275 280 def get_nodes(self, path):
276 281 """
277 282 Returns combined ``DirNode`` and ``FileNode`` objects list representing
278 283 state of commit at the given ``path``. If node at the given ``path``
279 284 is not instance of ``DirNode``, CommitError would be raised.
280 285 """
281 286
282 287 if self._get_kind(path) != NodeKind.DIR:
283 288 raise CommitError(
284 289 "Directory does not exist for idx %s at '%s'" %
285 290 (self.idx, path))
286 291 path = self._fix_path(path)
287 292
288 293 filenodes = [
289 294 FileNode(f, commit=self) for f in self._file_paths
290 295 if os.path.dirname(f) == path]
291 296 # TODO: johbo: Check if this can be done in a more obvious way
292 297 dirs = path == '' and '' or [
293 298 d for d in self._dir_paths
294 299 if d and vcspath.dirname(d) == path]
295 300 dirnodes = [
296 301 DirNode(d, commit=self) for d in dirs
297 302 if os.path.dirname(d) == path]
298 303
299 304 alias = self.repository.alias
300 305 for k, vals in self._submodules.iteritems():
301 306 loc = vals[0]
302 307 commit = vals[1]
303 308 dirnodes.append(
304 309 SubModuleNode(k, url=loc, commit=commit, alias=alias))
305 310 nodes = dirnodes + filenodes
306 311 # cache nodes
307 312 for node in nodes:
308 313 self.nodes[node.path] = node
309 314 nodes.sort()
310 315
311 316 return nodes
312 317
313 318 def get_node(self, path, pre_load=None):
314 319 """
315 320 Returns `Node` object from the given `path`. If there is no node at
316 321 the given `path`, `NodeDoesNotExistError` would be raised.
317 322 """
318 323 path = self._fix_path(path)
319 324
320 325 if path not in self.nodes:
321 326 if path in self._file_paths:
322 327 node = FileNode(path, commit=self, pre_load=pre_load)
323 328 elif path in self._dir_paths:
324 329 if path == '':
325 330 node = RootNode(commit=self)
326 331 else:
327 332 node = DirNode(path, commit=self)
328 333 else:
329 334 raise self.no_node_at_path(path)
330 335
331 336 # cache node
332 337 self.nodes[path] = node
333 338 return self.nodes[path]
334 339
335 340 def get_largefile_node(self, path):
336 341
337 342 if self._remote.is_large_file(path):
338 343 # content of that file regular FileNode is the hash of largefile
339 344 file_id = self.get_file_content(path).strip()
340 345
341 346 if self._remote.in_largefiles_store(file_id):
342 347 lf_path = self._remote.store_path(file_id)
343 348 return LargeFileNode(lf_path, commit=self, org_path=path)
344 349 elif self._remote.in_user_cache(file_id):
345 350 lf_path = self._remote.store_path(file_id)
346 351 self._remote.link(file_id, path)
347 352 return LargeFileNode(lf_path, commit=self, org_path=path)
348 353
349 354 @LazyProperty
350 355 def _submodules(self):
351 356 """
352 357 Returns a dictionary with submodule information from substate file
353 358 of hg repository.
354 359 """
355 360 return self._remote.ctx_substate(self.idx)
356 361
357 362 @LazyProperty
358 363 def affected_files(self):
359 364 """
360 365 Gets a fast accessible file changes for given commit
361 366 """
362 367 return self._remote.ctx_files(self.idx)
363 368
364 369 @property
365 370 def added(self):
366 371 """
367 372 Returns list of added ``FileNode`` objects.
368 373 """
369 374 return AddedFileNodesGenerator([n for n in self.status[1]], self)
370 375
371 376 @property
372 377 def changed(self):
373 378 """
374 379 Returns list of modified ``FileNode`` objects.
375 380 """
376 381 return ChangedFileNodesGenerator([n for n in self.status[0]], self)
377 382
378 383 @property
379 384 def removed(self):
380 385 """
381 386 Returns list of removed ``FileNode`` objects.
382 387 """
383 388 return RemovedFileNodesGenerator([n for n in self.status[2]], self)
@@ -1,283 +1,284 b''
1 1
2 2 /******************************************************************************
3 3 * *
4 4 * DO NOT CHANGE THIS FILE MANUALLY *
5 5 * *
6 6 * *
7 7 * This file is automatically generated when the app starts up with *
8 8 * generate_js_files = true *
9 9 * *
10 10 * To add a route here pass jsroute=True to the route definition in the app *
11 11 * *
12 12 ******************************************************************************/
13 13 function registerRCRoutes() {
14 14 // routes registration
15 15 pyroutes.register('favicon', '/favicon.ico', []);
16 16 pyroutes.register('robots', '/robots.txt', []);
17 17 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
18 18 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
19 19 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
20 20 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
21 21 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
22 22 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
23 23 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/settings/integrations', ['repo_group_name']);
24 24 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
25 25 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
26 26 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
27 27 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
28 28 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
29 29 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
30 30 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
31 31 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
32 32 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
33 33 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
34 34 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
35 35 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
36 36 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
37 37 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
38 38 pyroutes.register('admin_home', '/_admin', []);
39 39 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
40 40 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
41 41 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
42 42 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
43 43 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
44 44 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
45 45 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
46 46 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
47 47 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
48 48 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
49 49 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
50 50 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
51 51 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
52 52 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
53 53 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
54 54 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
55 55 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
56 56 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
57 57 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
58 58 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
59 59 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
60 60 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
61 61 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
62 62 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
63 63 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
64 64 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
65 65 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
66 66 pyroutes.register('users', '/_admin/users', []);
67 67 pyroutes.register('users_data', '/_admin/users_data', []);
68 68 pyroutes.register('users_create', '/_admin/users/create', []);
69 69 pyroutes.register('users_new', '/_admin/users/new', []);
70 70 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
71 71 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
72 72 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
73 73 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
74 74 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
75 75 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
76 76 pyroutes.register('user_force_password_reset', '/_admin/users/%(user_id)s/password_reset', ['user_id']);
77 77 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
78 78 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
79 79 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
80 80 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
81 81 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
82 82 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
83 83 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
84 84 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
85 85 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
86 86 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
87 87 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
88 88 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
89 89 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
90 90 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
91 91 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
92 92 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
93 93 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
94 94 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
95 95 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
96 96 pyroutes.register('user_groups', '/_admin/user_groups', []);
97 97 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
98 98 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
99 99 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
100 100 pyroutes.register('repos', '/_admin/repos', []);
101 101 pyroutes.register('repo_new', '/_admin/repos/new', []);
102 102 pyroutes.register('repo_create', '/_admin/repos/create', []);
103 103 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
104 104 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
105 105 pyroutes.register('channelstream_proxy', '/_channelstream', []);
106 106 pyroutes.register('login', '/_admin/login', []);
107 107 pyroutes.register('logout', '/_admin/logout', []);
108 108 pyroutes.register('register', '/_admin/register', []);
109 109 pyroutes.register('reset_password', '/_admin/password_reset', []);
110 110 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
111 111 pyroutes.register('home', '/', []);
112 112 pyroutes.register('user_autocomplete_data', '/_users', []);
113 113 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
114 114 pyroutes.register('repo_list_data', '/_repos', []);
115 115 pyroutes.register('goto_switcher_data', '/_goto_data', []);
116 116 pyroutes.register('journal', '/_admin/journal', []);
117 117 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
118 118 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
119 119 pyroutes.register('journal_public', '/_admin/public_journal', []);
120 120 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
121 121 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
122 122 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
123 123 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
124 124 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
125 125 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
126 126 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
127 127 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
128 128 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
129 129 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
130 130 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
131 131 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
132 132 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
133 133 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
134 134 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
135 135 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
136 136 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
137 137 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
138 138 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
139 139 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
140 140 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
141 141 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
142 142 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
143 143 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
144 144 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
145 145 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
146 146 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
147 147 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
148 148 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
149 149 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
150 150 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
151 151 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
152 152 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
153 153 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
154 154 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
155 155 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
156 156 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
157 157 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
158 158 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
159 159 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
160 160 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
161 161 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
162 162 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
163 163 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
164 164 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
165 165 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
166 166 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
167 167 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
168 168 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
169 pyroutes.register('repo_changelog_elements_file', '/%(repo_name)s/changelog_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
169 170 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
170 171 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
171 172 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
172 173 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
173 174 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
174 175 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
175 176 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
176 177 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
177 178 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
178 179 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
179 180 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
180 181 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
181 182 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
182 183 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
183 184 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
184 185 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
185 186 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
186 187 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
187 188 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
188 189 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
189 190 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
190 191 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
191 192 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
192 193 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
193 194 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
194 195 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
195 196 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
196 197 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
197 198 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
198 199 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
199 200 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
200 201 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
201 202 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
202 203 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
203 204 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
204 205 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
205 206 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
206 207 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
207 208 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
208 209 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
209 210 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
210 211 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
211 212 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
212 213 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
213 214 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
214 215 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
215 216 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
216 217 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
217 218 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
218 219 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
219 220 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
220 221 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
221 222 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
222 223 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
223 224 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
224 225 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
225 226 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
226 227 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
227 228 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
228 229 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
229 230 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
230 231 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
231 232 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
232 233 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
233 234 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
234 235 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
235 236 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
236 237 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
237 238 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
238 239 pyroutes.register('search', '/_admin/search', []);
239 240 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
240 241 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
241 242 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
242 243 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
243 244 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
244 245 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
245 246 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
246 247 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
247 248 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
248 249 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
249 250 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
250 251 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
251 252 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
252 253 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
253 254 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
254 255 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
255 256 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
256 257 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
257 258 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
258 259 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
259 260 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
260 261 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
261 262 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
262 263 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
263 264 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
264 265 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
265 266 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
266 267 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
267 268 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
268 269 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
269 270 pyroutes.register('gists_show', '/_admin/gists', []);
270 271 pyroutes.register('gists_new', '/_admin/gists/new', []);
271 272 pyroutes.register('gists_create', '/_admin/gists/create', []);
272 273 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
273 274 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
274 275 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
275 276 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
276 277 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
277 278 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
278 279 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
279 280 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
280 281 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
281 282 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
282 283 pyroutes.register('apiv2', '/_admin/api', []);
283 284 }
@@ -1,173 +1,185 b''
1 1 // # Copyright (C) 2016-2017 RhodeCode GmbH
2 2 // #
3 3 // # This program is free software: you can redistribute it and/or modify
4 4 // # it under the terms of the GNU Affero General Public License, version 3
5 5 // # (only), as published by the Free Software Foundation.
6 6 // #
7 7 // # This program is distributed in the hope that it will be useful,
8 8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 // # GNU General Public License for more details.
11 11 // #
12 12 // # You should have received a copy of the GNU Affero General Public License
13 13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 // #
15 15 // # This program is dual-licensed. If you wish to learn more about the
16 16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19
20 20 var CommitsController = function () {
21 21 var self = this;
22 22 this.$graphCanvas = $('#graph_canvas');
23 23 this.$commitCounter = $('#commit-counter');
24 24
25 25 this.getCurrentGraphData = function () {
26 26 // raw form
27 27 return self.$graphCanvas.data('commits');
28 28 };
29 29
30 30 this.setLabelText = function (graphData) {
31 31 var shown = $('.commit_hash').length;
32 32 var total = self.$commitCounter.data('total');
33 33
34 34 if (shown == 1) {
35 35 var text = _gettext('showing {0} out of {1} commit').format(shown, total);
36 36 } else {
37 37 var text = _gettext('showing {0} out of {1} commits').format(shown, total);
38 38 }
39 39 self.$commitCounter.html(text)
40 40 };
41 41
42 42 this.reloadGraph = function (chunk) {
43 43 chunk = chunk || 'next';
44 44
45 45 // reset state on re-render !
46 46 self.$graphCanvas.html('');
47 47
48 48 var edgeData = $("[data-graph]").data('graph') || this.$graphCanvas.data('graph') || [];
49 49
50 50 // Determine max number of edges per row in graph
51 51 var edgeCount = 1;
52 52 $.each(edgeData, function (i, item) {
53 53 $.each(item[2], function (key, value) {
54 54 if (value[1] > edgeCount) {
55 55 edgeCount = value[1];
56 56 }
57 57 });
58 58 });
59 59
60 60 var x_step = Math.min(10, Math.floor(86 / edgeCount));
61 61 var graph_options = {
62 62 width: 100,
63 63 height: $('#changesets').find('.commits-range').height(),
64 64 x_step: x_step,
65 65 y_step: 42,
66 66 dotRadius: 3.5,
67 67 lineWidth: 2.5
68 68 };
69 69
70 70 var prevCommitsData = this.$graphCanvas.data('commits') || [];
71 71 var nextCommitsData = $("[data-graph]").data('commits') || [];
72 72
73 73 if (chunk == 'next') {
74 74 var commitData = $.merge(prevCommitsData, nextCommitsData);
75 75 } else {
76 76 var commitData = $.merge(nextCommitsData, prevCommitsData);
77 77 }
78 78
79 79 this.$graphCanvas.data('graph', edgeData);
80 80 this.$graphCanvas.data('commits', commitData);
81 81
82 82 // destroy dynamic loaded graph
83 83 $("[data-graph]").remove();
84 84
85 85 this.$graphCanvas.commits(graph_options);
86 86
87 87 this.setLabelText(edgeData);
88 88 if ($('.load-more-commits').find('.prev-commits').get(0)) {
89 89 var padding = 75;
90 90
91 91 } else {
92 92 var padding = 43;
93 93 }
94 94 $('#graph_nodes').css({'padding-top': padding});
95 95 };
96 96
97 this.getChunkUrl = function (page, chunk, branch) {
97 this.getChunkUrl = function (page, chunk, branch, commit_id, f_path) {
98 98 var urlData = {
99 99 'repo_name': templateContext.repo_name,
100 100 'page': page,
101 101 'chunk': chunk
102 102 };
103 103
104 104 if (branch !== undefined && branch !== '') {
105 105 urlData['branch'] = branch;
106 106 }
107 if (commit_id !== undefined && commit_id !== '') {
108 urlData['commit_id'] = commit_id;
109 }
110 if (f_path !== undefined && f_path !== '') {
111 urlData['f_path'] = f_path;
112 }
107 113
108 return pyroutes.url('repo_changelog_elements', urlData);
114 if (urlData['commit_id'] && urlData['f_path']) {
115 return pyroutes.url('repo_changelog_elements_file', urlData);
116 }
117 else {
118 return pyroutes.url('repo_changelog_elements', urlData);
119 }
120
109 121 };
110 122
111 this.loadNext = function (node, page, branch) {
112 var loadUrl = this.getChunkUrl(page, 'next', branch);
123 this.loadNext = function (node, page, branch, commit_id, f_path) {
124 var loadUrl = this.getChunkUrl(page, 'next', branch, commit_id, f_path);
113 125 var postData = {'graph': JSON.stringify(this.getCurrentGraphData())};
114 126
115 127 $.post(loadUrl, postData, function (data) {
116 128 $(node).closest('tbody').append(data);
117 129 $(node).closest('td').remove();
118 130 self.reloadGraph('next');
119 131 })
120 132 };
121 133
122 this.loadPrev = function (node, page, branch) {
123 var loadUrl = this.getChunkUrl(page, 'prev', branch);
134 this.loadPrev = function (node, page, branch, commit_id, f_path) {
135 var loadUrl = this.getChunkUrl(page, 'prev', branch, commit_id, f_path);
124 136 var postData = {'graph': JSON.stringify(this.getCurrentGraphData())};
125 137
126 138 $.post(loadUrl, postData, function (data) {
127 139 $(node).closest('tbody').prepend(data);
128 140 $(node).closest('td').remove();
129 141 self.reloadGraph('prev');
130 142 })
131 143 };
132 144
133 145 this.expandCommit = function (node) {
134 146
135 147 var target_expand = $(node);
136 148 var cid = target_expand.data('commitId');
137 149
138 150 if (target_expand.hasClass('open')) {
139 151 $('#c-' + cid).css({
140 152 'height': '1.5em',
141 153 'white-space': 'nowrap',
142 154 'text-overflow': 'ellipsis',
143 155 'overflow': 'hidden'
144 156 });
145 157 $('#t-' + cid).css({
146 158 'height': 'auto',
147 159 'line-height': '.9em',
148 160 'text-overflow': 'ellipsis',
149 161 'overflow': 'hidden',
150 162 'white-space': 'nowrap'
151 163 });
152 164 target_expand.removeClass('open');
153 165 }
154 166 else {
155 167 $('#c-' + cid).css({
156 168 'height': 'auto',
157 169 'white-space': 'pre-line',
158 170 'text-overflow': 'initial',
159 171 'overflow': 'visible'
160 172 });
161 173 $('#t-' + cid).css({
162 174 'height': 'auto',
163 175 'max-height': 'none',
164 176 'text-overflow': 'initial',
165 177 'overflow': 'visible',
166 178 'white-space': 'normal'
167 179 });
168 180 target_expand.addClass('open');
169 181 }
170 182 // redraw the graph
171 183 self.reloadGraph();
172 184 }
173 185 };
@@ -1,144 +1,144 b''
1 1 ## small box that displays changed/added/removed details fetched by AJAX
2 2 <%namespace name="base" file="/base/base.mako"/>
3 3
4 4
5 5 % if c.prev_page:
6 6 <tr>
7 7 <td colspan="9" class="load-more-commits">
8 <a class="prev-commits" href="#loadPrevCommits" onclick="commitsController.loadPrev(this, ${c.prev_page}, '${c.branch_name}');return false">
8 <a class="prev-commits" href="#loadPrevCommits" onclick="commitsController.loadPrev(this, ${c.prev_page}, '${c.branch_name}', '${c.commit_id}', '${c.f_path}');return false">
9 9 ${_('load previous')}
10 10 </a>
11 11 </td>
12 12 </tr>
13 13 % endif
14 14
15 15 % for cnt,commit in enumerate(c.pagination):
16 16 <tr id="sha_${commit.raw_id}" class="changelogRow container ${'tablerow%s' % (cnt%2)}">
17 17
18 18 <td class="td-checkbox">
19 19 ${h.checkbox(commit.raw_id,class_="commit-range")}
20 20 </td>
21 21 <td class="td-status">
22 22
23 23 %if c.statuses.get(commit.raw_id):
24 24 <div class="changeset-status-ico">
25 25 %if c.statuses.get(commit.raw_id)[2]:
26 26 <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (h.commit_status_lbl(c.statuses.get(commit.raw_id)[0]), c.statuses.get(commit.raw_id)[2])}" href="${h.route_path('pullrequest_show',repo_name=c.statuses.get(commit.raw_id)[3],pull_request_id=c.statuses.get(commit.raw_id)[2])}">
27 27 <div class="${'flag_status {}'.format(c.statuses.get(commit.raw_id)[0])}"></div>
28 28 </a>
29 29 %else:
30 30 <a class="tooltip" title="${_('Commit status: {}').format(h.commit_status_lbl(c.statuses.get(commit.raw_id)[0]))}" href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=commit.raw_id,_anchor='comment-%s' % c.comments[commit.raw_id][0].comment_id)}">
31 31 <div class="${'flag_status {}'.format(c.statuses.get(commit.raw_id)[0])}"></div>
32 32 </a>
33 33 %endif
34 34 </div>
35 35 %else:
36 36 <div class="tooltip flag_status not_reviewed" title="${_('Commit status: Not Reviewed')}"></div>
37 37 %endif
38 38 </td>
39 39 <td class="td-comments comments-col">
40 40 %if c.comments.get(commit.raw_id):
41 41 <a title="${_('Commit has comments')}" href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=commit.raw_id,_anchor='comment-%s' % c.comments[commit.raw_id][0].comment_id)}">
42 42 <i class="icon-comment"></i> ${len(c.comments[commit.raw_id])}
43 43 </a>
44 44 %endif
45 45 </td>
46 46 <td class="td-hash">
47 47 <code>
48 48
49 49 <a href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=commit.raw_id)}">
50 50 <span class="${'commit_hash obsolete' if getattr(commit, 'obsolete', None) else 'commit_hash'}">${h.show_id(commit)}</span>
51 51 </a>
52 52 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${commit.raw_id}" title="${_('Copy the full commit id')}"></i>
53 53 % if hasattr(commit, 'phase'):
54 54 % if commit.phase != 'public':
55 55 <span class="tag phase-${commit.phase} tooltip" title="${_('Commit phase')}">${commit.phase}</span>
56 56 % endif
57 57 % endif
58 58
59 59 ## obsolete commits
60 60 % if hasattr(commit, 'obsolete'):
61 61 % if commit.obsolete:
62 62 <span class="tag obsolete-${commit.obsolete} tooltip" title="${_('Evolve State')}">${_('obsolete')}</span>
63 63 % endif
64 64 % endif
65 65
66 66 ## hidden commits
67 67 % if hasattr(commit, 'hidden'):
68 68 % if commit.hidden:
69 69 <span class="tag obsolete-${commit.hidden} tooltip" title="${_('Evolve State')}">${_('hidden')}</span>
70 70 % endif
71 71 % endif
72 72
73 73 </code>
74 74 </td>
75 75 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_('Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
76 76 <div class="show_more_col">
77 77 <i class="show_more"></i>&nbsp;
78 78 </div>
79 79 </td>
80 80 <td class="td-description mid">
81 81 <div class="log-container truncate-wrap">
82 82 <div class="message truncate" id="c-${commit.raw_id}">${h.urlify_commit_message(commit.message, c.repo_name)}</div>
83 83 </div>
84 84 </td>
85 85
86 86 <td class="td-time">
87 87 ${h.age_component(commit.date)}
88 88 </td>
89 89 <td class="td-user">
90 90 ${base.gravatar_with_user(commit.author)}
91 91 </td>
92 92
93 93 <td class="td-tags tags-col">
94 94 <div id="t-${commit.raw_id}">
95 95
96 96 ## merge
97 97 %if commit.merge:
98 98 <span class="tag mergetag">
99 99 <i class="icon-merge"></i>${_('merge')}
100 100 </span>
101 101 %endif
102 102
103 103 ## branch
104 104 %if commit.branch:
105 105 <span class="tag branchtag" title="${h.tooltip(_('Branch %s') % commit.branch)}">
106 106 <a href="${h.route_path('repo_changelog',repo_name=c.repo_name,_query=dict(branch=commit.branch))}"><i class="icon-code-fork"></i>${h.shorter(commit.branch)}</a>
107 107 </span>
108 108 %endif
109 109
110 110 ## bookmarks
111 111 %if h.is_hg(c.rhodecode_repo):
112 112 %for book in commit.bookmarks:
113 113 <span class="tag booktag" title="${h.tooltip(_('Bookmark %s') % book)}">
114 114 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=commit.raw_id, _query=dict(at=book))}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
115 115 </span>
116 116 %endfor
117 117 %endif
118 118
119 119 ## tags
120 120 %for tag in commit.tags:
121 121 <span class="tag tagtag" title="${h.tooltip(_('Tag %s') % tag)}">
122 122 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=commit.raw_id, _query=dict(at=tag))}"><i class="icon-tag"></i>${h.shorter(tag)}</a>
123 123 </span>
124 124 %endfor
125 125
126 126 </div>
127 127 </td>
128 128 </tr>
129 129 % endfor
130 130
131 131 % if c.next_page:
132 132 <tr>
133 133 <td colspan="9" class="load-more-commits">
134 <a class="next-commits" href="#loadNextCommits" onclick="commitsController.loadNext(this, ${c.next_page}, '${c.branch_name}');return false">
134 <a class="next-commits" href="#loadNextCommits" onclick="commitsController.loadNext(this, ${c.next_page}, '${c.branch_name}', '${c.commit_id}', '${c.f_path}');return false">
135 135 ${_('load next')}
136 136 </a>
137 137 </td>
138 138 </tr>
139 139 % endif
140 140 <tr class="chunk-graph-data" style="display:none"
141 141 data-graph='${c.graph_data|n}'
142 142 data-node='${c.prev_page}:${c.next_page}'
143 143 data-commits='${c.graph_commits|n}'>
144 144 </tr> No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now