##// END OF EJS Templates
comments-history: fixed fetching of history for comments
super-admin -
r4717:39aa18a6 default
parent child Browse files
Show More
@@ -1,1227 +1,1227 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 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 from rhodecode.apps.repository.views.repo_artifacts import RepoArtifactsView
25 25 from rhodecode.apps.repository.views.repo_audit_logs import AuditLogsView
26 26 from rhodecode.apps.repository.views.repo_automation import RepoAutomationView
27 27 from rhodecode.apps.repository.views.repo_bookmarks import RepoBookmarksView
28 28 from rhodecode.apps.repository.views.repo_branch_permissions import RepoSettingsBranchPermissionsView
29 29 from rhodecode.apps.repository.views.repo_branches import RepoBranchesView
30 30 from rhodecode.apps.repository.views.repo_caches import RepoCachesView
31 31 from rhodecode.apps.repository.views.repo_changelog import RepoChangelogView
32 32 from rhodecode.apps.repository.views.repo_checks import RepoChecksView
33 33 from rhodecode.apps.repository.views.repo_commits import RepoCommitsView
34 34 from rhodecode.apps.repository.views.repo_compare import RepoCompareView
35 35 from rhodecode.apps.repository.views.repo_feed import RepoFeedView
36 36 from rhodecode.apps.repository.views.repo_files import RepoFilesView
37 37 from rhodecode.apps.repository.views.repo_forks import RepoForksView
38 38 from rhodecode.apps.repository.views.repo_maintainance import RepoMaintenanceView
39 39 from rhodecode.apps.repository.views.repo_permissions import RepoSettingsPermissionsView
40 40 from rhodecode.apps.repository.views.repo_pull_requests import RepoPullRequestsView
41 41 from rhodecode.apps.repository.views.repo_review_rules import RepoReviewRulesView
42 42 from rhodecode.apps.repository.views.repo_settings import RepoSettingsView
43 43 from rhodecode.apps.repository.views.repo_settings_advanced import RepoSettingsAdvancedView
44 44 from rhodecode.apps.repository.views.repo_settings_fields import RepoSettingsFieldsView
45 45 from rhodecode.apps.repository.views.repo_settings_issue_trackers import RepoSettingsIssueTrackersView
46 46 from rhodecode.apps.repository.views.repo_settings_remote import RepoSettingsRemoteView
47 47 from rhodecode.apps.repository.views.repo_settings_vcs import RepoSettingsVcsView
48 48 from rhodecode.apps.repository.views.repo_strip import RepoStripView
49 49 from rhodecode.apps.repository.views.repo_summary import RepoSummaryView
50 50 from rhodecode.apps.repository.views.repo_tags import RepoTagsView
51 51
52 52 # repo creating checks, special cases that aren't repo routes
53 53 config.add_route(
54 54 name='repo_creating',
55 55 pattern='/{repo_name:.*?[^/]}/repo_creating')
56 56 config.add_view(
57 57 RepoChecksView,
58 58 attr='repo_creating',
59 59 route_name='repo_creating', request_method='GET',
60 60 renderer='rhodecode:templates/admin/repos/repo_creating.mako')
61 61
62 62 config.add_route(
63 63 name='repo_creating_check',
64 64 pattern='/{repo_name:.*?[^/]}/repo_creating_check')
65 65 config.add_view(
66 66 RepoChecksView,
67 67 attr='repo_creating_check',
68 68 route_name='repo_creating_check', request_method='GET',
69 69 renderer='json_ext')
70 70
71 71 # Summary
72 72 # NOTE(marcink): one additional route is defined in very bottom, catch
73 73 # all pattern
74 74 config.add_route(
75 75 name='repo_summary_explicit',
76 76 pattern='/{repo_name:.*?[^/]}/summary', repo_route=True)
77 77 config.add_view(
78 78 RepoSummaryView,
79 79 attr='summary',
80 80 route_name='repo_summary_explicit', request_method='GET',
81 81 renderer='rhodecode:templates/summary/summary.mako')
82 82
83 83 config.add_route(
84 84 name='repo_summary_commits',
85 85 pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True)
86 86 config.add_view(
87 87 RepoSummaryView,
88 88 attr='summary_commits',
89 89 route_name='repo_summary_commits', request_method='GET',
90 90 renderer='rhodecode:templates/summary/summary_commits.mako')
91 91
92 92 # Commits
93 93 config.add_route(
94 94 name='repo_commit',
95 95 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True)
96 96 config.add_view(
97 97 RepoCommitsView,
98 98 attr='repo_commit_show',
99 99 route_name='repo_commit', request_method='GET',
100 100 renderer=None)
101 101
102 102 config.add_route(
103 103 name='repo_commit_children',
104 104 pattern='/{repo_name:.*?[^/]}/changeset_children/{commit_id}', repo_route=True)
105 105 config.add_view(
106 106 RepoCommitsView,
107 107 attr='repo_commit_children',
108 108 route_name='repo_commit_children', request_method='GET',
109 109 renderer='json_ext', xhr=True)
110 110
111 111 config.add_route(
112 112 name='repo_commit_parents',
113 113 pattern='/{repo_name:.*?[^/]}/changeset_parents/{commit_id}', repo_route=True)
114 114 config.add_view(
115 115 RepoCommitsView,
116 116 attr='repo_commit_parents',
117 117 route_name='repo_commit_parents', request_method='GET',
118 118 renderer='json_ext')
119 119
120 120 config.add_route(
121 121 name='repo_commit_raw',
122 122 pattern='/{repo_name:.*?[^/]}/changeset-diff/{commit_id}', repo_route=True)
123 123 config.add_view(
124 124 RepoCommitsView,
125 125 attr='repo_commit_raw',
126 126 route_name='repo_commit_raw', request_method='GET',
127 127 renderer=None)
128 128
129 129 config.add_route(
130 130 name='repo_commit_patch',
131 131 pattern='/{repo_name:.*?[^/]}/changeset-patch/{commit_id}', repo_route=True)
132 132 config.add_view(
133 133 RepoCommitsView,
134 134 attr='repo_commit_patch',
135 135 route_name='repo_commit_patch', request_method='GET',
136 136 renderer=None)
137 137
138 138 config.add_route(
139 139 name='repo_commit_download',
140 140 pattern='/{repo_name:.*?[^/]}/changeset-download/{commit_id}', repo_route=True)
141 141 config.add_view(
142 142 RepoCommitsView,
143 143 attr='repo_commit_download',
144 144 route_name='repo_commit_download', request_method='GET',
145 145 renderer=None)
146 146
147 147 config.add_route(
148 148 name='repo_commit_data',
149 149 pattern='/{repo_name:.*?[^/]}/changeset-data/{commit_id}', repo_route=True)
150 150 config.add_view(
151 151 RepoCommitsView,
152 152 attr='repo_commit_data',
153 153 route_name='repo_commit_data', request_method='GET',
154 154 renderer='json_ext', xhr=True)
155 155
156 156 config.add_route(
157 157 name='repo_commit_comment_create',
158 158 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/create', repo_route=True)
159 159 config.add_view(
160 160 RepoCommitsView,
161 161 attr='repo_commit_comment_create',
162 162 route_name='repo_commit_comment_create', request_method='POST',
163 163 renderer='json_ext')
164 164
165 165 config.add_route(
166 166 name='repo_commit_comment_preview',
167 167 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/preview', repo_route=True)
168 168 config.add_view(
169 169 RepoCommitsView,
170 170 attr='repo_commit_comment_preview',
171 171 route_name='repo_commit_comment_preview', request_method='POST',
172 172 renderer='string', xhr=True)
173 173
174 174 config.add_route(
175 175 name='repo_commit_comment_history_view',
176 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_history_id}/history_view', repo_route=True)
176 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/history_view/{comment_history_id}', repo_route=True)
177 177 config.add_view(
178 178 RepoCommitsView,
179 179 attr='repo_commit_comment_history_view',
180 180 route_name='repo_commit_comment_history_view', request_method='POST',
181 181 renderer='string', xhr=True)
182 182
183 183 config.add_route(
184 184 name='repo_commit_comment_attachment_upload',
185 185 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/attachment_upload', repo_route=True)
186 186 config.add_view(
187 187 RepoCommitsView,
188 188 attr='repo_commit_comment_attachment_upload',
189 189 route_name='repo_commit_comment_attachment_upload', request_method='POST',
190 190 renderer='json_ext', xhr=True)
191 191
192 192 config.add_route(
193 193 name='repo_commit_comment_delete',
194 194 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/delete', repo_route=True)
195 195 config.add_view(
196 196 RepoCommitsView,
197 197 attr='repo_commit_comment_delete',
198 198 route_name='repo_commit_comment_delete', request_method='POST',
199 199 renderer='json_ext')
200 200
201 201 config.add_route(
202 202 name='repo_commit_comment_edit',
203 203 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/edit', repo_route=True)
204 204 config.add_view(
205 205 RepoCommitsView,
206 206 attr='repo_commit_comment_edit',
207 207 route_name='repo_commit_comment_edit', request_method='POST',
208 208 renderer='json_ext')
209 209
210 210 # still working url for backward compat.
211 211 config.add_route(
212 212 name='repo_commit_raw_deprecated',
213 213 pattern='/{repo_name:.*?[^/]}/raw-changeset/{commit_id}', repo_route=True)
214 214 config.add_view(
215 215 RepoCommitsView,
216 216 attr='repo_commit_raw',
217 217 route_name='repo_commit_raw_deprecated', request_method='GET',
218 218 renderer=None)
219 219
220 220 # Files
221 221 config.add_route(
222 222 name='repo_archivefile',
223 223 pattern='/{repo_name:.*?[^/]}/archive/{fname:.*}', repo_route=True)
224 224 config.add_view(
225 225 RepoFilesView,
226 226 attr='repo_archivefile',
227 227 route_name='repo_archivefile', request_method='GET',
228 228 renderer=None)
229 229
230 230 config.add_route(
231 231 name='repo_files_diff',
232 232 pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True)
233 233 config.add_view(
234 234 RepoFilesView,
235 235 attr='repo_files_diff',
236 236 route_name='repo_files_diff', request_method='GET',
237 237 renderer=None)
238 238
239 239 config.add_route( # legacy route to make old links work
240 240 name='repo_files_diff_2way_redirect',
241 241 pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True)
242 242 config.add_view(
243 243 RepoFilesView,
244 244 attr='repo_files_diff_2way_redirect',
245 245 route_name='repo_files_diff_2way_redirect', request_method='GET',
246 246 renderer=None)
247 247
248 248 config.add_route(
249 249 name='repo_files',
250 250 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True)
251 251 config.add_view(
252 252 RepoFilesView,
253 253 attr='repo_files',
254 254 route_name='repo_files', request_method='GET',
255 255 renderer=None)
256 256
257 257 config.add_route(
258 258 name='repo_files:default_path',
259 259 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True)
260 260 config.add_view(
261 261 RepoFilesView,
262 262 attr='repo_files',
263 263 route_name='repo_files:default_path', request_method='GET',
264 264 renderer=None)
265 265
266 266 config.add_route(
267 267 name='repo_files:default_commit',
268 268 pattern='/{repo_name:.*?[^/]}/files', repo_route=True)
269 269 config.add_view(
270 270 RepoFilesView,
271 271 attr='repo_files',
272 272 route_name='repo_files:default_commit', request_method='GET',
273 273 renderer=None)
274 274
275 275 config.add_route(
276 276 name='repo_files:rendered',
277 277 pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True)
278 278 config.add_view(
279 279 RepoFilesView,
280 280 attr='repo_files',
281 281 route_name='repo_files:rendered', request_method='GET',
282 282 renderer=None)
283 283
284 284 config.add_route(
285 285 name='repo_files:annotated',
286 286 pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True)
287 287 config.add_view(
288 288 RepoFilesView,
289 289 attr='repo_files',
290 290 route_name='repo_files:annotated', request_method='GET',
291 291 renderer=None)
292 292
293 293 config.add_route(
294 294 name='repo_files:annotated_previous',
295 295 pattern='/{repo_name:.*?[^/]}/annotate-previous/{commit_id}/{f_path:.*}', repo_route=True)
296 296 config.add_view(
297 297 RepoFilesView,
298 298 attr='repo_files_annotated_previous',
299 299 route_name='repo_files:annotated_previous', request_method='GET',
300 300 renderer=None)
301 301
302 302 config.add_route(
303 303 name='repo_nodetree_full',
304 304 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/{f_path:.*}', repo_route=True)
305 305 config.add_view(
306 306 RepoFilesView,
307 307 attr='repo_nodetree_full',
308 308 route_name='repo_nodetree_full', request_method='GET',
309 309 renderer=None, xhr=True)
310 310
311 311 config.add_route(
312 312 name='repo_nodetree_full:default_path',
313 313 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True)
314 314 config.add_view(
315 315 RepoFilesView,
316 316 attr='repo_nodetree_full',
317 317 route_name='repo_nodetree_full:default_path', request_method='GET',
318 318 renderer=None, xhr=True)
319 319
320 320 config.add_route(
321 321 name='repo_files_nodelist',
322 322 pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True)
323 323 config.add_view(
324 324 RepoFilesView,
325 325 attr='repo_nodelist',
326 326 route_name='repo_files_nodelist', request_method='GET',
327 327 renderer='json_ext', xhr=True)
328 328
329 329 config.add_route(
330 330 name='repo_file_raw',
331 331 pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True)
332 332 config.add_view(
333 333 RepoFilesView,
334 334 attr='repo_file_raw',
335 335 route_name='repo_file_raw', request_method='GET',
336 336 renderer=None)
337 337
338 338 config.add_route(
339 339 name='repo_file_download',
340 340 pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True)
341 341 config.add_view(
342 342 RepoFilesView,
343 343 attr='repo_file_download',
344 344 route_name='repo_file_download', request_method='GET',
345 345 renderer=None)
346 346
347 347 config.add_route( # backward compat to keep old links working
348 348 name='repo_file_download:legacy',
349 349 pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}',
350 350 repo_route=True)
351 351 config.add_view(
352 352 RepoFilesView,
353 353 attr='repo_file_download',
354 354 route_name='repo_file_download:legacy', request_method='GET',
355 355 renderer=None)
356 356
357 357 config.add_route(
358 358 name='repo_file_history',
359 359 pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True)
360 360 config.add_view(
361 361 RepoFilesView,
362 362 attr='repo_file_history',
363 363 route_name='repo_file_history', request_method='GET',
364 364 renderer='json_ext')
365 365
366 366 config.add_route(
367 367 name='repo_file_authors',
368 368 pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True)
369 369 config.add_view(
370 370 RepoFilesView,
371 371 attr='repo_file_authors',
372 372 route_name='repo_file_authors', request_method='GET',
373 373 renderer='rhodecode:templates/files/file_authors_box.mako')
374 374
375 375 config.add_route(
376 376 name='repo_files_check_head',
377 377 pattern='/{repo_name:.*?[^/]}/check_head/{commit_id}/{f_path:.*}',
378 378 repo_route=True)
379 379 config.add_view(
380 380 RepoFilesView,
381 381 attr='repo_files_check_head',
382 382 route_name='repo_files_check_head', request_method='POST',
383 383 renderer='json_ext', xhr=True)
384 384
385 385 config.add_route(
386 386 name='repo_files_remove_file',
387 387 pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}',
388 388 repo_route=True)
389 389 config.add_view(
390 390 RepoFilesView,
391 391 attr='repo_files_remove_file',
392 392 route_name='repo_files_remove_file', request_method='GET',
393 393 renderer='rhodecode:templates/files/files_delete.mako')
394 394
395 395 config.add_route(
396 396 name='repo_files_delete_file',
397 397 pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}',
398 398 repo_route=True)
399 399 config.add_view(
400 400 RepoFilesView,
401 401 attr='repo_files_delete_file',
402 402 route_name='repo_files_delete_file', request_method='POST',
403 403 renderer=None)
404 404
405 405 config.add_route(
406 406 name='repo_files_edit_file',
407 407 pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}',
408 408 repo_route=True)
409 409 config.add_view(
410 410 RepoFilesView,
411 411 attr='repo_files_edit_file',
412 412 route_name='repo_files_edit_file', request_method='GET',
413 413 renderer='rhodecode:templates/files/files_edit.mako')
414 414
415 415 config.add_route(
416 416 name='repo_files_update_file',
417 417 pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}',
418 418 repo_route=True)
419 419 config.add_view(
420 420 RepoFilesView,
421 421 attr='repo_files_update_file',
422 422 route_name='repo_files_update_file', request_method='POST',
423 423 renderer=None)
424 424
425 425 config.add_route(
426 426 name='repo_files_add_file',
427 427 pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}',
428 428 repo_route=True)
429 429 config.add_view(
430 430 RepoFilesView,
431 431 attr='repo_files_add_file',
432 432 route_name='repo_files_add_file', request_method='GET',
433 433 renderer='rhodecode:templates/files/files_add.mako')
434 434
435 435 config.add_route(
436 436 name='repo_files_upload_file',
437 437 pattern='/{repo_name:.*?[^/]}/upload_file/{commit_id}/{f_path:.*}',
438 438 repo_route=True)
439 439 config.add_view(
440 440 RepoFilesView,
441 441 attr='repo_files_add_file',
442 442 route_name='repo_files_upload_file', request_method='GET',
443 443 renderer='rhodecode:templates/files/files_upload.mako')
444 444 config.add_view( # POST creates
445 445 RepoFilesView,
446 446 attr='repo_files_upload_file',
447 447 route_name='repo_files_upload_file', request_method='POST',
448 448 renderer='json_ext')
449 449
450 450 config.add_route(
451 451 name='repo_files_create_file',
452 452 pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}',
453 453 repo_route=True)
454 454 config.add_view( # POST creates
455 455 RepoFilesView,
456 456 attr='repo_files_create_file',
457 457 route_name='repo_files_create_file', request_method='POST',
458 458 renderer=None)
459 459
460 460 # Refs data
461 461 config.add_route(
462 462 name='repo_refs_data',
463 463 pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True)
464 464 config.add_view(
465 465 RepoSummaryView,
466 466 attr='repo_refs_data',
467 467 route_name='repo_refs_data', request_method='GET',
468 468 renderer='json_ext')
469 469
470 470 config.add_route(
471 471 name='repo_refs_changelog_data',
472 472 pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True)
473 473 config.add_view(
474 474 RepoSummaryView,
475 475 attr='repo_refs_changelog_data',
476 476 route_name='repo_refs_changelog_data', request_method='GET',
477 477 renderer='json_ext')
478 478
479 479 config.add_route(
480 480 name='repo_stats',
481 481 pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True)
482 482 config.add_view(
483 483 RepoSummaryView,
484 484 attr='repo_stats',
485 485 route_name='repo_stats', request_method='GET',
486 486 renderer='json_ext')
487 487
488 488 # Commits
489 489 config.add_route(
490 490 name='repo_commits',
491 491 pattern='/{repo_name:.*?[^/]}/commits', repo_route=True)
492 492 config.add_view(
493 493 RepoChangelogView,
494 494 attr='repo_changelog',
495 495 route_name='repo_commits', request_method='GET',
496 496 renderer='rhodecode:templates/commits/changelog.mako')
497 497 # old routes for backward compat
498 498 config.add_view(
499 499 RepoChangelogView,
500 500 attr='repo_changelog',
501 501 route_name='repo_changelog', request_method='GET',
502 502 renderer='rhodecode:templates/commits/changelog.mako')
503 503
504 504 config.add_route(
505 505 name='repo_commits_elements',
506 506 pattern='/{repo_name:.*?[^/]}/commits_elements', repo_route=True)
507 507 config.add_view(
508 508 RepoChangelogView,
509 509 attr='repo_commits_elements',
510 510 route_name='repo_commits_elements', request_method=('GET', 'POST'),
511 511 renderer='rhodecode:templates/commits/changelog_elements.mako',
512 512 xhr=True)
513 513
514 514 config.add_route(
515 515 name='repo_commits_elements_file',
516 516 pattern='/{repo_name:.*?[^/]}/commits_elements/{commit_id}/{f_path:.*}', repo_route=True)
517 517 config.add_view(
518 518 RepoChangelogView,
519 519 attr='repo_commits_elements',
520 520 route_name='repo_commits_elements_file', request_method=('GET', 'POST'),
521 521 renderer='rhodecode:templates/commits/changelog_elements.mako',
522 522 xhr=True)
523 523
524 524 config.add_route(
525 525 name='repo_commits_file',
526 526 pattern='/{repo_name:.*?[^/]}/commits/{commit_id}/{f_path:.*}', repo_route=True)
527 527 config.add_view(
528 528 RepoChangelogView,
529 529 attr='repo_changelog',
530 530 route_name='repo_commits_file', request_method='GET',
531 531 renderer='rhodecode:templates/commits/changelog.mako')
532 532 # old routes for backward compat
533 533 config.add_view(
534 534 RepoChangelogView,
535 535 attr='repo_changelog',
536 536 route_name='repo_changelog_file', request_method='GET',
537 537 renderer='rhodecode:templates/commits/changelog.mako')
538 538
539 539 # Changelog (old deprecated name for commits page)
540 540 config.add_route(
541 541 name='repo_changelog',
542 542 pattern='/{repo_name:.*?[^/]}/changelog', repo_route=True)
543 543 config.add_route(
544 544 name='repo_changelog_file',
545 545 pattern='/{repo_name:.*?[^/]}/changelog/{commit_id}/{f_path:.*}', repo_route=True)
546 546
547 547 # Compare
548 548 config.add_route(
549 549 name='repo_compare_select',
550 550 pattern='/{repo_name:.*?[^/]}/compare', repo_route=True)
551 551 config.add_view(
552 552 RepoCompareView,
553 553 attr='compare_select',
554 554 route_name='repo_compare_select', request_method='GET',
555 555 renderer='rhodecode:templates/compare/compare_diff.mako')
556 556
557 557 config.add_route(
558 558 name='repo_compare',
559 559 pattern='/{repo_name:.*?[^/]}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}', repo_route=True)
560 560 config.add_view(
561 561 RepoCompareView,
562 562 attr='compare',
563 563 route_name='repo_compare', request_method='GET',
564 564 renderer=None)
565 565
566 566 # Tags
567 567 config.add_route(
568 568 name='tags_home',
569 569 pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
570 570 config.add_view(
571 571 RepoTagsView,
572 572 attr='tags',
573 573 route_name='tags_home', request_method='GET',
574 574 renderer='rhodecode:templates/tags/tags.mako')
575 575
576 576 # Branches
577 577 config.add_route(
578 578 name='branches_home',
579 579 pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
580 580 config.add_view(
581 581 RepoBranchesView,
582 582 attr='branches',
583 583 route_name='branches_home', request_method='GET',
584 584 renderer='rhodecode:templates/branches/branches.mako')
585 585
586 586 # Bookmarks
587 587 config.add_route(
588 588 name='bookmarks_home',
589 589 pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
590 590 config.add_view(
591 591 RepoBookmarksView,
592 592 attr='bookmarks',
593 593 route_name='bookmarks_home', request_method='GET',
594 594 renderer='rhodecode:templates/bookmarks/bookmarks.mako')
595 595
596 596 # Forks
597 597 config.add_route(
598 598 name='repo_fork_new',
599 599 pattern='/{repo_name:.*?[^/]}/fork', repo_route=True,
600 600 repo_forbid_when_archived=True,
601 601 repo_accepted_types=['hg', 'git'])
602 602 config.add_view(
603 603 RepoForksView,
604 604 attr='repo_fork_new',
605 605 route_name='repo_fork_new', request_method='GET',
606 606 renderer='rhodecode:templates/forks/forks.mako')
607 607
608 608 config.add_route(
609 609 name='repo_fork_create',
610 610 pattern='/{repo_name:.*?[^/]}/fork/create', repo_route=True,
611 611 repo_forbid_when_archived=True,
612 612 repo_accepted_types=['hg', 'git'])
613 613 config.add_view(
614 614 RepoForksView,
615 615 attr='repo_fork_create',
616 616 route_name='repo_fork_create', request_method='POST',
617 617 renderer='rhodecode:templates/forks/fork.mako')
618 618
619 619 config.add_route(
620 620 name='repo_forks_show_all',
621 621 pattern='/{repo_name:.*?[^/]}/forks', repo_route=True,
622 622 repo_accepted_types=['hg', 'git'])
623 623 config.add_view(
624 624 RepoForksView,
625 625 attr='repo_forks_show_all',
626 626 route_name='repo_forks_show_all', request_method='GET',
627 627 renderer='rhodecode:templates/forks/forks.mako')
628 628
629 629 config.add_route(
630 630 name='repo_forks_data',
631 631 pattern='/{repo_name:.*?[^/]}/forks/data', repo_route=True,
632 632 repo_accepted_types=['hg', 'git'])
633 633 config.add_view(
634 634 RepoForksView,
635 635 attr='repo_forks_data',
636 636 route_name='repo_forks_data', request_method='GET',
637 637 renderer='json_ext', xhr=True)
638 638
639 639 # Pull Requests
640 640 config.add_route(
641 641 name='pullrequest_show',
642 642 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}',
643 643 repo_route=True)
644 644 config.add_view(
645 645 RepoPullRequestsView,
646 646 attr='pull_request_show',
647 647 route_name='pullrequest_show', request_method='GET',
648 648 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
649 649
650 650 config.add_route(
651 651 name='pullrequest_show_all',
652 652 pattern='/{repo_name:.*?[^/]}/pull-request',
653 653 repo_route=True, repo_accepted_types=['hg', 'git'])
654 654 config.add_view(
655 655 RepoPullRequestsView,
656 656 attr='pull_request_list',
657 657 route_name='pullrequest_show_all', request_method='GET',
658 658 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
659 659
660 660 config.add_route(
661 661 name='pullrequest_show_all_data',
662 662 pattern='/{repo_name:.*?[^/]}/pull-request-data',
663 663 repo_route=True, repo_accepted_types=['hg', 'git'])
664 664 config.add_view(
665 665 RepoPullRequestsView,
666 666 attr='pull_request_list_data',
667 667 route_name='pullrequest_show_all_data', request_method='GET',
668 668 renderer='json_ext', xhr=True)
669 669
670 670 config.add_route(
671 671 name='pullrequest_repo_refs',
672 672 pattern='/{repo_name:.*?[^/]}/pull-request/refs/{target_repo_name:.*?[^/]}',
673 673 repo_route=True)
674 674 config.add_view(
675 675 RepoPullRequestsView,
676 676 attr='pull_request_repo_refs',
677 677 route_name='pullrequest_repo_refs', request_method='GET',
678 678 renderer='json_ext', xhr=True)
679 679
680 680 config.add_route(
681 681 name='pullrequest_repo_targets',
682 682 pattern='/{repo_name:.*?[^/]}/pull-request/repo-targets',
683 683 repo_route=True)
684 684 config.add_view(
685 685 RepoPullRequestsView,
686 686 attr='pullrequest_repo_targets',
687 687 route_name='pullrequest_repo_targets', request_method='GET',
688 688 renderer='json_ext', xhr=True)
689 689
690 690 config.add_route(
691 691 name='pullrequest_new',
692 692 pattern='/{repo_name:.*?[^/]}/pull-request/new',
693 693 repo_route=True, repo_accepted_types=['hg', 'git'],
694 694 repo_forbid_when_archived=True)
695 695 config.add_view(
696 696 RepoPullRequestsView,
697 697 attr='pull_request_new',
698 698 route_name='pullrequest_new', request_method='GET',
699 699 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
700 700
701 701 config.add_route(
702 702 name='pullrequest_create',
703 703 pattern='/{repo_name:.*?[^/]}/pull-request/create',
704 704 repo_route=True, repo_accepted_types=['hg', 'git'],
705 705 repo_forbid_when_archived=True)
706 706 config.add_view(
707 707 RepoPullRequestsView,
708 708 attr='pull_request_create',
709 709 route_name='pullrequest_create', request_method='POST',
710 710 renderer=None)
711 711
712 712 config.add_route(
713 713 name='pullrequest_update',
714 714 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/update',
715 715 repo_route=True, repo_forbid_when_archived=True)
716 716 config.add_view(
717 717 RepoPullRequestsView,
718 718 attr='pull_request_update',
719 719 route_name='pullrequest_update', request_method='POST',
720 720 renderer='json_ext')
721 721
722 722 config.add_route(
723 723 name='pullrequest_merge',
724 724 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/merge',
725 725 repo_route=True, repo_forbid_when_archived=True)
726 726 config.add_view(
727 727 RepoPullRequestsView,
728 728 attr='pull_request_merge',
729 729 route_name='pullrequest_merge', request_method='POST',
730 730 renderer='json_ext')
731 731
732 732 config.add_route(
733 733 name='pullrequest_delete',
734 734 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/delete',
735 735 repo_route=True, repo_forbid_when_archived=True)
736 736 config.add_view(
737 737 RepoPullRequestsView,
738 738 attr='pull_request_delete',
739 739 route_name='pullrequest_delete', request_method='POST',
740 740 renderer='json_ext')
741 741
742 742 config.add_route(
743 743 name='pullrequest_comment_create',
744 744 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment',
745 745 repo_route=True)
746 746 config.add_view(
747 747 RepoPullRequestsView,
748 748 attr='pull_request_comment_create',
749 749 route_name='pullrequest_comment_create', request_method='POST',
750 750 renderer='json_ext')
751 751
752 752 config.add_route(
753 753 name='pullrequest_comment_edit',
754 754 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/edit',
755 755 repo_route=True, repo_accepted_types=['hg', 'git'])
756 756 config.add_view(
757 757 RepoPullRequestsView,
758 758 attr='pull_request_comment_edit',
759 759 route_name='pullrequest_comment_edit', request_method='POST',
760 760 renderer='json_ext')
761 761
762 762 config.add_route(
763 763 name='pullrequest_comment_delete',
764 764 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete',
765 765 repo_route=True, repo_accepted_types=['hg', 'git'])
766 766 config.add_view(
767 767 RepoPullRequestsView,
768 768 attr='pull_request_comment_delete',
769 769 route_name='pullrequest_comment_delete', request_method='POST',
770 770 renderer='json_ext')
771 771
772 772 config.add_route(
773 773 name='pullrequest_comments',
774 774 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comments',
775 775 repo_route=True)
776 776 config.add_view(
777 777 RepoPullRequestsView,
778 778 attr='pullrequest_comments',
779 779 route_name='pullrequest_comments', request_method='POST',
780 780 renderer='string_html', xhr=True)
781 781
782 782 config.add_route(
783 783 name='pullrequest_todos',
784 784 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/todos',
785 785 repo_route=True)
786 786 config.add_view(
787 787 RepoPullRequestsView,
788 788 attr='pullrequest_todos',
789 789 route_name='pullrequest_todos', request_method='POST',
790 790 renderer='string_html', xhr=True)
791 791
792 792 config.add_route(
793 793 name='pullrequest_drafts',
794 794 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/drafts',
795 795 repo_route=True)
796 796 config.add_view(
797 797 RepoPullRequestsView,
798 798 attr='pullrequest_drafts',
799 799 route_name='pullrequest_drafts', request_method='POST',
800 800 renderer='string_html', xhr=True)
801 801
802 802 # Artifacts, (EE feature)
803 803 config.add_route(
804 804 name='repo_artifacts_list',
805 805 pattern='/{repo_name:.*?[^/]}/artifacts', repo_route=True)
806 806 config.add_view(
807 807 RepoArtifactsView,
808 808 attr='repo_artifacts',
809 809 route_name='repo_artifacts_list', request_method='GET',
810 810 renderer='rhodecode:templates/artifacts/artifact_list.mako')
811 811
812 812 # Settings
813 813 config.add_route(
814 814 name='edit_repo',
815 815 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
816 816 config.add_view(
817 817 RepoSettingsView,
818 818 attr='edit_settings',
819 819 route_name='edit_repo', request_method='GET',
820 820 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
821 821 # update is POST on edit_repo
822 822 config.add_view(
823 823 RepoSettingsView,
824 824 attr='edit_settings_update',
825 825 route_name='edit_repo', request_method='POST',
826 826 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
827 827
828 828 # Settings advanced
829 829 config.add_route(
830 830 name='edit_repo_advanced',
831 831 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
832 832 config.add_view(
833 833 RepoSettingsAdvancedView,
834 834 attr='edit_advanced',
835 835 route_name='edit_repo_advanced', request_method='GET',
836 836 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
837 837
838 838 config.add_route(
839 839 name='edit_repo_advanced_archive',
840 840 pattern='/{repo_name:.*?[^/]}/settings/advanced/archive', repo_route=True)
841 841 config.add_view(
842 842 RepoSettingsAdvancedView,
843 843 attr='edit_advanced_archive',
844 844 route_name='edit_repo_advanced_archive', request_method='POST',
845 845 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
846 846
847 847 config.add_route(
848 848 name='edit_repo_advanced_delete',
849 849 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
850 850 config.add_view(
851 851 RepoSettingsAdvancedView,
852 852 attr='edit_advanced_delete',
853 853 route_name='edit_repo_advanced_delete', request_method='POST',
854 854 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
855 855
856 856 config.add_route(
857 857 name='edit_repo_advanced_locking',
858 858 pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True)
859 859 config.add_view(
860 860 RepoSettingsAdvancedView,
861 861 attr='edit_advanced_toggle_locking',
862 862 route_name='edit_repo_advanced_locking', request_method='POST',
863 863 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
864 864
865 865 config.add_route(
866 866 name='edit_repo_advanced_journal',
867 867 pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True)
868 868 config.add_view(
869 869 RepoSettingsAdvancedView,
870 870 attr='edit_advanced_journal',
871 871 route_name='edit_repo_advanced_journal', request_method='POST',
872 872 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
873 873
874 874 config.add_route(
875 875 name='edit_repo_advanced_fork',
876 876 pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True)
877 877 config.add_view(
878 878 RepoSettingsAdvancedView,
879 879 attr='edit_advanced_fork',
880 880 route_name='edit_repo_advanced_fork', request_method='POST',
881 881 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
882 882
883 883 config.add_route(
884 884 name='edit_repo_advanced_hooks',
885 885 pattern='/{repo_name:.*?[^/]}/settings/advanced/hooks', repo_route=True)
886 886 config.add_view(
887 887 RepoSettingsAdvancedView,
888 888 attr='edit_advanced_install_hooks',
889 889 route_name='edit_repo_advanced_hooks', request_method='GET',
890 890 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
891 891
892 892 # Caches
893 893 config.add_route(
894 894 name='edit_repo_caches',
895 895 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
896 896 config.add_view(
897 897 RepoCachesView,
898 898 attr='repo_caches',
899 899 route_name='edit_repo_caches', request_method='GET',
900 900 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
901 901 config.add_view(
902 902 RepoCachesView,
903 903 attr='repo_caches_purge',
904 904 route_name='edit_repo_caches', request_method='POST')
905 905
906 906 # Permissions
907 907 config.add_route(
908 908 name='edit_repo_perms',
909 909 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
910 910 config.add_view(
911 911 RepoSettingsPermissionsView,
912 912 attr='edit_permissions',
913 913 route_name='edit_repo_perms', request_method='GET',
914 914 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
915 915 config.add_view(
916 916 RepoSettingsPermissionsView,
917 917 attr='edit_permissions_update',
918 918 route_name='edit_repo_perms', request_method='POST',
919 919 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
920 920
921 921 config.add_route(
922 922 name='edit_repo_perms_set_private',
923 923 pattern='/{repo_name:.*?[^/]}/settings/permissions/set_private', repo_route=True)
924 924 config.add_view(
925 925 RepoSettingsPermissionsView,
926 926 attr='edit_permissions_set_private_repo',
927 927 route_name='edit_repo_perms_set_private', request_method='POST',
928 928 renderer='json_ext')
929 929
930 930 # Permissions Branch (EE feature)
931 931 config.add_route(
932 932 name='edit_repo_perms_branch',
933 933 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions', repo_route=True)
934 934 config.add_view(
935 935 RepoSettingsBranchPermissionsView,
936 936 attr='branch_permissions',
937 937 route_name='edit_repo_perms_branch', request_method='GET',
938 938 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
939 939
940 940 config.add_route(
941 941 name='edit_repo_perms_branch_delete',
942 942 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions/{rule_id}/delete',
943 943 repo_route=True)
944 944 ## Only implemented in EE
945 945
946 946 # Maintenance
947 947 config.add_route(
948 948 name='edit_repo_maintenance',
949 949 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
950 950 config.add_view(
951 951 RepoMaintenanceView,
952 952 attr='repo_maintenance',
953 953 route_name='edit_repo_maintenance', request_method='GET',
954 954 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
955 955
956 956 config.add_route(
957 957 name='edit_repo_maintenance_execute',
958 958 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
959 959 config.add_view(
960 960 RepoMaintenanceView,
961 961 attr='repo_maintenance_execute',
962 962 route_name='edit_repo_maintenance_execute', request_method='GET',
963 963 renderer='json', xhr=True)
964 964
965 965 # Fields
966 966 config.add_route(
967 967 name='edit_repo_fields',
968 968 pattern='/{repo_name:.*?[^/]}/settings/fields', repo_route=True)
969 969 config.add_view(
970 970 RepoSettingsFieldsView,
971 971 attr='repo_field_edit',
972 972 route_name='edit_repo_fields', request_method='GET',
973 973 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
974 974
975 975 config.add_route(
976 976 name='edit_repo_fields_create',
977 977 pattern='/{repo_name:.*?[^/]}/settings/fields/create', repo_route=True)
978 978 config.add_view(
979 979 RepoSettingsFieldsView,
980 980 attr='repo_field_create',
981 981 route_name='edit_repo_fields_create', request_method='POST',
982 982 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
983 983
984 984 config.add_route(
985 985 name='edit_repo_fields_delete',
986 986 pattern='/{repo_name:.*?[^/]}/settings/fields/{field_id}/delete', repo_route=True)
987 987 config.add_view(
988 988 RepoSettingsFieldsView,
989 989 attr='repo_field_delete',
990 990 route_name='edit_repo_fields_delete', request_method='POST',
991 991 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
992 992
993 993 # Locking
994 994 config.add_route(
995 995 name='repo_edit_toggle_locking',
996 996 pattern='/{repo_name:.*?[^/]}/settings/toggle_locking', repo_route=True)
997 997 config.add_view(
998 998 RepoSettingsView,
999 999 attr='edit_advanced_toggle_locking',
1000 1000 route_name='repo_edit_toggle_locking', request_method='GET',
1001 1001 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1002 1002
1003 1003 # Remote
1004 1004 config.add_route(
1005 1005 name='edit_repo_remote',
1006 1006 pattern='/{repo_name:.*?[^/]}/settings/remote', repo_route=True)
1007 1007 config.add_view(
1008 1008 RepoSettingsRemoteView,
1009 1009 attr='repo_remote_edit_form',
1010 1010 route_name='edit_repo_remote', request_method='GET',
1011 1011 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1012 1012
1013 1013 config.add_route(
1014 1014 name='edit_repo_remote_pull',
1015 1015 pattern='/{repo_name:.*?[^/]}/settings/remote/pull', repo_route=True)
1016 1016 config.add_view(
1017 1017 RepoSettingsRemoteView,
1018 1018 attr='repo_remote_pull_changes',
1019 1019 route_name='edit_repo_remote_pull', request_method='POST',
1020 1020 renderer=None)
1021 1021
1022 1022 config.add_route(
1023 1023 name='edit_repo_remote_push',
1024 1024 pattern='/{repo_name:.*?[^/]}/settings/remote/push', repo_route=True)
1025 1025
1026 1026 # Statistics
1027 1027 config.add_route(
1028 1028 name='edit_repo_statistics',
1029 1029 pattern='/{repo_name:.*?[^/]}/settings/statistics', repo_route=True)
1030 1030 config.add_view(
1031 1031 RepoSettingsView,
1032 1032 attr='edit_statistics_form',
1033 1033 route_name='edit_repo_statistics', request_method='GET',
1034 1034 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1035 1035
1036 1036 config.add_route(
1037 1037 name='edit_repo_statistics_reset',
1038 1038 pattern='/{repo_name:.*?[^/]}/settings/statistics/update', repo_route=True)
1039 1039 config.add_view(
1040 1040 RepoSettingsView,
1041 1041 attr='repo_statistics_reset',
1042 1042 route_name='edit_repo_statistics_reset', request_method='POST',
1043 1043 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1044 1044
1045 1045 # Issue trackers
1046 1046 config.add_route(
1047 1047 name='edit_repo_issuetracker',
1048 1048 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers', repo_route=True)
1049 1049 config.add_view(
1050 1050 RepoSettingsIssueTrackersView,
1051 1051 attr='repo_issuetracker',
1052 1052 route_name='edit_repo_issuetracker', request_method='GET',
1053 1053 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1054 1054
1055 1055 config.add_route(
1056 1056 name='edit_repo_issuetracker_test',
1057 1057 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/test', repo_route=True)
1058 1058 config.add_view(
1059 1059 RepoSettingsIssueTrackersView,
1060 1060 attr='repo_issuetracker_test',
1061 1061 route_name='edit_repo_issuetracker_test', request_method='POST',
1062 1062 renderer='string', xhr=True)
1063 1063
1064 1064 config.add_route(
1065 1065 name='edit_repo_issuetracker_delete',
1066 1066 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/delete', repo_route=True)
1067 1067 config.add_view(
1068 1068 RepoSettingsIssueTrackersView,
1069 1069 attr='repo_issuetracker_delete',
1070 1070 route_name='edit_repo_issuetracker_delete', request_method='POST',
1071 1071 renderer='json_ext', xhr=True)
1072 1072
1073 1073 config.add_route(
1074 1074 name='edit_repo_issuetracker_update',
1075 1075 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/update', repo_route=True)
1076 1076 config.add_view(
1077 1077 RepoSettingsIssueTrackersView,
1078 1078 attr='repo_issuetracker_update',
1079 1079 route_name='edit_repo_issuetracker_update', request_method='POST',
1080 1080 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1081 1081
1082 1082 # VCS Settings
1083 1083 config.add_route(
1084 1084 name='edit_repo_vcs',
1085 1085 pattern='/{repo_name:.*?[^/]}/settings/vcs', repo_route=True)
1086 1086 config.add_view(
1087 1087 RepoSettingsVcsView,
1088 1088 attr='repo_vcs_settings',
1089 1089 route_name='edit_repo_vcs', request_method='GET',
1090 1090 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1091 1091
1092 1092 config.add_route(
1093 1093 name='edit_repo_vcs_update',
1094 1094 pattern='/{repo_name:.*?[^/]}/settings/vcs/update', repo_route=True)
1095 1095 config.add_view(
1096 1096 RepoSettingsVcsView,
1097 1097 attr='repo_settings_vcs_update',
1098 1098 route_name='edit_repo_vcs_update', request_method='POST',
1099 1099 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1100 1100
1101 1101 # svn pattern
1102 1102 config.add_route(
1103 1103 name='edit_repo_vcs_svn_pattern_delete',
1104 1104 pattern='/{repo_name:.*?[^/]}/settings/vcs/svn_pattern/delete', repo_route=True)
1105 1105 config.add_view(
1106 1106 RepoSettingsVcsView,
1107 1107 attr='repo_settings_delete_svn_pattern',
1108 1108 route_name='edit_repo_vcs_svn_pattern_delete', request_method='POST',
1109 1109 renderer='json_ext', xhr=True)
1110 1110
1111 1111 # Repo Review Rules (EE feature)
1112 1112 config.add_route(
1113 1113 name='repo_reviewers',
1114 1114 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
1115 1115 config.add_view(
1116 1116 RepoReviewRulesView,
1117 1117 attr='repo_review_rules',
1118 1118 route_name='repo_reviewers', request_method='GET',
1119 1119 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1120 1120
1121 1121 config.add_route(
1122 1122 name='repo_default_reviewers_data',
1123 1123 pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True)
1124 1124 config.add_view(
1125 1125 RepoReviewRulesView,
1126 1126 attr='repo_default_reviewers_data',
1127 1127 route_name='repo_default_reviewers_data', request_method='GET',
1128 1128 renderer='json_ext')
1129 1129
1130 1130 # Repo Automation (EE feature)
1131 1131 config.add_route(
1132 1132 name='repo_automation',
1133 1133 pattern='/{repo_name:.*?[^/]}/settings/automation', repo_route=True)
1134 1134 config.add_view(
1135 1135 RepoAutomationView,
1136 1136 attr='repo_automation',
1137 1137 route_name='repo_automation', request_method='GET',
1138 1138 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1139 1139
1140 1140 # Strip
1141 1141 config.add_route(
1142 1142 name='edit_repo_strip',
1143 1143 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
1144 1144 config.add_view(
1145 1145 RepoStripView,
1146 1146 attr='strip',
1147 1147 route_name='edit_repo_strip', request_method='GET',
1148 1148 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1149 1149
1150 1150 config.add_route(
1151 1151 name='strip_check',
1152 1152 pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True)
1153 1153 config.add_view(
1154 1154 RepoStripView,
1155 1155 attr='strip_check',
1156 1156 route_name='strip_check', request_method='POST',
1157 1157 renderer='json', xhr=True)
1158 1158
1159 1159 config.add_route(
1160 1160 name='strip_execute',
1161 1161 pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True)
1162 1162 config.add_view(
1163 1163 RepoStripView,
1164 1164 attr='strip_execute',
1165 1165 route_name='strip_execute', request_method='POST',
1166 1166 renderer='json', xhr=True)
1167 1167
1168 1168 # Audit logs
1169 1169 config.add_route(
1170 1170 name='edit_repo_audit_logs',
1171 1171 pattern='/{repo_name:.*?[^/]}/settings/audit_logs', repo_route=True)
1172 1172 config.add_view(
1173 1173 AuditLogsView,
1174 1174 attr='repo_audit_logs',
1175 1175 route_name='edit_repo_audit_logs', request_method='GET',
1176 1176 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1177 1177
1178 1178 # ATOM/RSS Feed, shouldn't contain slashes for outlook compatibility
1179 1179 config.add_route(
1180 1180 name='rss_feed_home',
1181 1181 pattern='/{repo_name:.*?[^/]}/feed-rss', repo_route=True)
1182 1182 config.add_view(
1183 1183 RepoFeedView,
1184 1184 attr='rss',
1185 1185 route_name='rss_feed_home', request_method='GET', renderer=None)
1186 1186
1187 1187 config.add_route(
1188 1188 name='rss_feed_home_old',
1189 1189 pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True)
1190 1190 config.add_view(
1191 1191 RepoFeedView,
1192 1192 attr='rss',
1193 1193 route_name='rss_feed_home_old', request_method='GET', renderer=None)
1194 1194
1195 1195 config.add_route(
1196 1196 name='atom_feed_home',
1197 1197 pattern='/{repo_name:.*?[^/]}/feed-atom', repo_route=True)
1198 1198 config.add_view(
1199 1199 RepoFeedView,
1200 1200 attr='atom',
1201 1201 route_name='atom_feed_home', request_method='GET', renderer=None)
1202 1202
1203 1203 config.add_route(
1204 1204 name='atom_feed_home_old',
1205 1205 pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True)
1206 1206 config.add_view(
1207 1207 RepoFeedView,
1208 1208 attr='atom',
1209 1209 route_name='atom_feed_home_old', request_method='GET', renderer=None)
1210 1210
1211 1211 # NOTE(marcink): needs to be at the end for catch-all
1212 1212 add_route_with_slash(
1213 1213 config,
1214 1214 name='repo_summary',
1215 1215 pattern='/{repo_name:.*?[^/]}', repo_route=True)
1216 1216 config.add_view(
1217 1217 RepoSummaryView,
1218 1218 attr='summary',
1219 1219 route_name='repo_summary', request_method='GET',
1220 1220 renderer='rhodecode:templates/summary/summary.mako')
1221 1221
1222 1222 # TODO(marcink): there's no such route??
1223 1223 config.add_view(
1224 1224 RepoSummaryView,
1225 1225 attr='summary',
1226 1226 route_name='repo_summary_slash', request_method='GET',
1227 1227 renderer='rhodecode:templates/summary/summary.mako') No newline at end of file
@@ -1,818 +1,819 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 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 import logging
22 22 import collections
23 23
24 24 from pyramid.httpexceptions import (
25 25 HTTPNotFound, HTTPBadRequest, HTTPFound, HTTPForbidden, HTTPConflict)
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 from rhodecode.apps.file_store import utils as store_utils
31 31 from rhodecode.apps.file_store.exceptions import FileNotAllowedException, FileOverSizeException
32 32
33 33 from rhodecode.lib import diffs, codeblocks, channelstream
34 34 from rhodecode.lib.auth import (
35 35 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
36 36 from rhodecode.lib.ext_json import json
37 37 from rhodecode.lib.compat import OrderedDict
38 38 from rhodecode.lib.diffs import (
39 39 cache_diff, load_cached_diff, diff_cache_exist, get_diff_context,
40 40 get_diff_whitespace_flag)
41 41 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError, CommentVersionMismatch
42 42 import rhodecode.lib.helpers as h
43 43 from rhodecode.lib.utils2 import safe_unicode, str2bool, StrictAttributeDict, safe_str
44 44 from rhodecode.lib.vcs.backends.base import EmptyCommit
45 45 from rhodecode.lib.vcs.exceptions import (
46 46 RepositoryError, CommitDoesNotExistError)
47 47 from rhodecode.model.db import ChangesetComment, ChangesetStatus, FileStore, \
48 48 ChangesetCommentHistory
49 49 from rhodecode.model.changeset_status import ChangesetStatusModel
50 50 from rhodecode.model.comment import CommentsModel
51 51 from rhodecode.model.meta import Session
52 52 from rhodecode.model.settings import VcsSettingsModel
53 53
54 54 log = logging.getLogger(__name__)
55 55
56 56
57 57 def _update_with_GET(params, request):
58 58 for k in ['diff1', 'diff2', 'diff']:
59 59 params[k] += request.GET.getall(k)
60 60
61 61
62 62 class RepoCommitsView(RepoAppView):
63 63 def load_default_context(self):
64 64 c = self._get_local_tmpl_context(include_app_defaults=True)
65 65 c.rhodecode_repo = self.rhodecode_vcs_repo
66 66
67 67 return c
68 68
69 69 def _is_diff_cache_enabled(self, target_repo):
70 70 caching_enabled = self._get_general_setting(
71 71 target_repo, 'rhodecode_diff_cache')
72 72 log.debug('Diff caching enabled: %s', caching_enabled)
73 73 return caching_enabled
74 74
75 75 def _commit(self, commit_id_range, method):
76 76 _ = self.request.translate
77 77 c = self.load_default_context()
78 78 c.fulldiff = self.request.GET.get('fulldiff')
79 79 redirect_to_combined = str2bool(self.request.GET.get('redirect_combined'))
80 80
81 81 # fetch global flags of ignore ws or context lines
82 82 diff_context = get_diff_context(self.request)
83 83 hide_whitespace_changes = get_diff_whitespace_flag(self.request)
84 84
85 85 # diff_limit will cut off the whole diff if the limit is applied
86 86 # otherwise it will just hide the big files from the front-end
87 87 diff_limit = c.visual.cut_off_limit_diff
88 88 file_limit = c.visual.cut_off_limit_file
89 89
90 90 # get ranges of commit ids if preset
91 91 commit_range = commit_id_range.split('...')[:2]
92 92
93 93 try:
94 94 pre_load = ['affected_files', 'author', 'branch', 'date',
95 95 'message', 'parents']
96 96 if self.rhodecode_vcs_repo.alias == 'hg':
97 97 pre_load += ['hidden', 'obsolete', 'phase']
98 98
99 99 if len(commit_range) == 2:
100 100 commits = self.rhodecode_vcs_repo.get_commits(
101 101 start_id=commit_range[0], end_id=commit_range[1],
102 102 pre_load=pre_load, translate_tags=False)
103 103 commits = list(commits)
104 104 else:
105 105 commits = [self.rhodecode_vcs_repo.get_commit(
106 106 commit_id=commit_id_range, pre_load=pre_load)]
107 107
108 108 c.commit_ranges = commits
109 109 if not c.commit_ranges:
110 110 raise RepositoryError('The commit range returned an empty result')
111 111 except CommitDoesNotExistError as e:
112 112 msg = _('No such commit exists. Org exception: `{}`').format(safe_str(e))
113 113 h.flash(msg, category='error')
114 114 raise HTTPNotFound()
115 115 except Exception:
116 116 log.exception("General failure")
117 117 raise HTTPNotFound()
118 118 single_commit = len(c.commit_ranges) == 1
119 119
120 120 if redirect_to_combined and not single_commit:
121 121 source_ref = getattr(c.commit_ranges[0].parents[0]
122 122 if c.commit_ranges[0].parents else h.EmptyCommit(), 'raw_id')
123 123 target_ref = c.commit_ranges[-1].raw_id
124 124 next_url = h.route_path(
125 125 'repo_compare',
126 126 repo_name=c.repo_name,
127 127 source_ref_type='rev',
128 128 source_ref=source_ref,
129 129 target_ref_type='rev',
130 130 target_ref=target_ref)
131 131 raise HTTPFound(next_url)
132 132
133 133 c.changes = OrderedDict()
134 134 c.lines_added = 0
135 135 c.lines_deleted = 0
136 136
137 137 # auto collapse if we have more than limit
138 138 collapse_limit = diffs.DiffProcessor._collapse_commits_over
139 139 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
140 140
141 141 c.commit_statuses = ChangesetStatus.STATUSES
142 142 c.inline_comments = []
143 143 c.files = []
144 144
145 145 c.comments = []
146 146 c.unresolved_comments = []
147 147 c.resolved_comments = []
148 148
149 149 # Single commit
150 150 if single_commit:
151 151 commit = c.commit_ranges[0]
152 152 c.comments = CommentsModel().get_comments(
153 153 self.db_repo.repo_id,
154 154 revision=commit.raw_id)
155 155
156 156 # comments from PR
157 157 statuses = ChangesetStatusModel().get_statuses(
158 158 self.db_repo.repo_id, commit.raw_id,
159 159 with_revisions=True)
160 160
161 161 prs = set()
162 162 reviewers = list()
163 163 reviewers_duplicates = set() # to not have duplicates from multiple votes
164 164 for c_status in statuses:
165 165
166 166 # extract associated pull-requests from votes
167 167 if c_status.pull_request:
168 168 prs.add(c_status.pull_request)
169 169
170 170 # extract reviewers
171 171 _user_id = c_status.author.user_id
172 172 if _user_id not in reviewers_duplicates:
173 173 reviewers.append(
174 174 StrictAttributeDict({
175 175 'user': c_status.author,
176 176
177 177 # fake attributed for commit, page that we don't have
178 178 # but we share the display with PR page
179 179 'mandatory': False,
180 180 'reasons': [],
181 181 'rule_user_group_data': lambda: None
182 182 })
183 183 )
184 184 reviewers_duplicates.add(_user_id)
185 185
186 186 c.reviewers_count = len(reviewers)
187 187 c.observers_count = 0
188 188
189 189 # from associated statuses, check the pull requests, and
190 190 # show comments from them
191 191 for pr in prs:
192 192 c.comments.extend(pr.comments)
193 193
194 194 c.unresolved_comments = CommentsModel()\
195 195 .get_commit_unresolved_todos(commit.raw_id)
196 196 c.resolved_comments = CommentsModel()\
197 197 .get_commit_resolved_todos(commit.raw_id)
198 198
199 199 c.inline_comments_flat = CommentsModel()\
200 200 .get_commit_inline_comments(commit.raw_id)
201 201
202 202 review_statuses = ChangesetStatusModel().aggregate_votes_by_user(
203 203 statuses, reviewers)
204 204
205 205 c.commit_review_status = ChangesetStatus.STATUS_NOT_REVIEWED
206 206
207 207 c.commit_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
208 208
209 209 for review_obj, member, reasons, mandatory, status in review_statuses:
210 210 member_reviewer = h.reviewer_as_json(
211 211 member, reasons=reasons, mandatory=mandatory, role=None,
212 212 user_group=None
213 213 )
214 214
215 215 current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
216 216 member_reviewer['review_status'] = current_review_status
217 217 member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
218 218 member_reviewer['allowed_to_update'] = False
219 219 c.commit_set_reviewers_data_json['reviewers'].append(member_reviewer)
220 220
221 221 c.commit_set_reviewers_data_json = json.dumps(c.commit_set_reviewers_data_json)
222 222
223 223 # NOTE(marcink): this uses the same voting logic as in pull-requests
224 224 c.commit_review_status = ChangesetStatusModel().calculate_status(review_statuses)
225 225 c.commit_broadcast_channel = channelstream.comment_channel(c.repo_name, commit_obj=commit)
226 226
227 227 diff = None
228 228 # Iterate over ranges (default commit view is always one commit)
229 229 for commit in c.commit_ranges:
230 230 c.changes[commit.raw_id] = []
231 231
232 232 commit2 = commit
233 233 commit1 = commit.first_parent
234 234
235 235 if method == 'show':
236 236 inline_comments = CommentsModel().get_inline_comments(
237 237 self.db_repo.repo_id, revision=commit.raw_id)
238 238 c.inline_cnt = len(CommentsModel().get_inline_comments_as_list(
239 239 inline_comments))
240 240 c.inline_comments = inline_comments
241 241
242 242 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(
243 243 self.db_repo)
244 244 cache_file_path = diff_cache_exist(
245 245 cache_path, 'diff', commit.raw_id,
246 246 hide_whitespace_changes, diff_context, c.fulldiff)
247 247
248 248 caching_enabled = self._is_diff_cache_enabled(self.db_repo)
249 249 force_recache = str2bool(self.request.GET.get('force_recache'))
250 250
251 251 cached_diff = None
252 252 if caching_enabled:
253 253 cached_diff = load_cached_diff(cache_file_path)
254 254
255 255 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
256 256 if not force_recache and has_proper_diff_cache:
257 257 diffset = cached_diff['diff']
258 258 else:
259 259 vcs_diff = self.rhodecode_vcs_repo.get_diff(
260 260 commit1, commit2,
261 261 ignore_whitespace=hide_whitespace_changes,
262 262 context=diff_context)
263 263
264 264 diff_processor = diffs.DiffProcessor(
265 265 vcs_diff, format='newdiff', diff_limit=diff_limit,
266 266 file_limit=file_limit, show_full_diff=c.fulldiff)
267 267
268 268 _parsed = diff_processor.prepare()
269 269
270 270 diffset = codeblocks.DiffSet(
271 271 repo_name=self.db_repo_name,
272 272 source_node_getter=codeblocks.diffset_node_getter(commit1),
273 273 target_node_getter=codeblocks.diffset_node_getter(commit2))
274 274
275 275 diffset = self.path_filter.render_patchset_filtered(
276 276 diffset, _parsed, commit1.raw_id, commit2.raw_id)
277 277
278 278 # save cached diff
279 279 if caching_enabled:
280 280 cache_diff(cache_file_path, diffset, None)
281 281
282 282 c.limited_diff = diffset.limited_diff
283 283 c.changes[commit.raw_id] = diffset
284 284 else:
285 285 # TODO(marcink): no cache usage here...
286 286 _diff = self.rhodecode_vcs_repo.get_diff(
287 287 commit1, commit2,
288 288 ignore_whitespace=hide_whitespace_changes, context=diff_context)
289 289 diff_processor = diffs.DiffProcessor(
290 290 _diff, format='newdiff', diff_limit=diff_limit,
291 291 file_limit=file_limit, show_full_diff=c.fulldiff)
292 292 # downloads/raw we only need RAW diff nothing else
293 293 diff = self.path_filter.get_raw_patch(diff_processor)
294 294 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
295 295
296 296 # sort comments by how they were generated
297 297 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
298 298 c.at_version_num = None
299 299
300 300 if len(c.commit_ranges) == 1:
301 301 c.commit = c.commit_ranges[0]
302 302 c.parent_tmpl = ''.join(
303 303 '# Parent %s\n' % x.raw_id for x in c.commit.parents)
304 304
305 305 if method == 'download':
306 306 response = Response(diff)
307 307 response.content_type = 'text/plain'
308 308 response.content_disposition = (
309 309 'attachment; filename=%s.diff' % commit_id_range[:12])
310 310 return response
311 311 elif method == 'patch':
312 312 c.diff = safe_unicode(diff)
313 313 patch = render(
314 314 'rhodecode:templates/changeset/patch_changeset.mako',
315 315 self._get_template_context(c), self.request)
316 316 response = Response(patch)
317 317 response.content_type = 'text/plain'
318 318 return response
319 319 elif method == 'raw':
320 320 response = Response(diff)
321 321 response.content_type = 'text/plain'
322 322 return response
323 323 elif method == 'show':
324 324 if len(c.commit_ranges) == 1:
325 325 html = render(
326 326 'rhodecode:templates/changeset/changeset.mako',
327 327 self._get_template_context(c), self.request)
328 328 return Response(html)
329 329 else:
330 330 c.ancestor = None
331 331 c.target_repo = self.db_repo
332 332 html = render(
333 333 'rhodecode:templates/changeset/changeset_range.mako',
334 334 self._get_template_context(c), self.request)
335 335 return Response(html)
336 336
337 337 raise HTTPBadRequest()
338 338
339 339 @LoginRequired()
340 340 @HasRepoPermissionAnyDecorator(
341 341 'repository.read', 'repository.write', 'repository.admin')
342 342 def repo_commit_show(self):
343 343 commit_id = self.request.matchdict['commit_id']
344 344 return self._commit(commit_id, method='show')
345 345
346 346 @LoginRequired()
347 347 @HasRepoPermissionAnyDecorator(
348 348 'repository.read', 'repository.write', 'repository.admin')
349 349 def repo_commit_raw(self):
350 350 commit_id = self.request.matchdict['commit_id']
351 351 return self._commit(commit_id, method='raw')
352 352
353 353 @LoginRequired()
354 354 @HasRepoPermissionAnyDecorator(
355 355 'repository.read', 'repository.write', 'repository.admin')
356 356 def repo_commit_patch(self):
357 357 commit_id = self.request.matchdict['commit_id']
358 358 return self._commit(commit_id, method='patch')
359 359
360 360 @LoginRequired()
361 361 @HasRepoPermissionAnyDecorator(
362 362 'repository.read', 'repository.write', 'repository.admin')
363 363 def repo_commit_download(self):
364 364 commit_id = self.request.matchdict['commit_id']
365 365 return self._commit(commit_id, method='download')
366 366
367 367 def _commit_comments_create(self, commit_id, comments):
368 368 _ = self.request.translate
369 369 data = {}
370 370 if not comments:
371 371 return
372 372
373 373 commit = self.db_repo.get_commit(commit_id)
374 374
375 375 all_drafts = len([x for x in comments if str2bool(x['is_draft'])]) == len(comments)
376 376 for entry in comments:
377 377 c = self.load_default_context()
378 378 comment_type = entry['comment_type']
379 379 text = entry['text']
380 380 status = entry['status']
381 381 is_draft = str2bool(entry['is_draft'])
382 382 resolves_comment_id = entry['resolves_comment_id']
383 383 f_path = entry['f_path']
384 384 line_no = entry['line']
385 385 target_elem_id = 'file-{}'.format(h.safeid(h.safe_unicode(f_path)))
386 386
387 387 if status:
388 388 text = text or (_('Status change %(transition_icon)s %(status)s')
389 389 % {'transition_icon': '>',
390 390 'status': ChangesetStatus.get_status_lbl(status)})
391 391
392 392 comment = CommentsModel().create(
393 393 text=text,
394 394 repo=self.db_repo.repo_id,
395 395 user=self._rhodecode_db_user.user_id,
396 396 commit_id=commit_id,
397 397 f_path=f_path,
398 398 line_no=line_no,
399 399 status_change=(ChangesetStatus.get_status_lbl(status)
400 400 if status else None),
401 401 status_change_type=status,
402 402 comment_type=comment_type,
403 403 is_draft=is_draft,
404 404 resolves_comment_id=resolves_comment_id,
405 405 auth_user=self._rhodecode_user,
406 406 send_email=not is_draft, # skip notification for draft comments
407 407 )
408 408 is_inline = comment.is_inline
409 409
410 410 # get status if set !
411 411 if status:
412 412 # `dont_allow_on_closed_pull_request = True` means
413 413 # if latest status was from pull request and it's closed
414 414 # disallow changing status !
415 415
416 416 try:
417 417 ChangesetStatusModel().set_status(
418 418 self.db_repo.repo_id,
419 419 status,
420 420 self._rhodecode_db_user.user_id,
421 421 comment,
422 422 revision=commit_id,
423 423 dont_allow_on_closed_pull_request=True
424 424 )
425 425 except StatusChangeOnClosedPullRequestError:
426 426 msg = _('Changing the status of a commit associated with '
427 427 'a closed pull request is not allowed')
428 428 log.exception(msg)
429 429 h.flash(msg, category='warning')
430 430 raise HTTPFound(h.route_path(
431 431 'repo_commit', repo_name=self.db_repo_name,
432 432 commit_id=commit_id))
433 433
434 434 Session().flush()
435 435 # this is somehow required to get access to some relationship
436 436 # loaded on comment
437 437 Session().refresh(comment)
438 438
439 439 # skip notifications for drafts
440 440 if not is_draft:
441 441 CommentsModel().trigger_commit_comment_hook(
442 442 self.db_repo, self._rhodecode_user, 'create',
443 443 data={'comment': comment, 'commit': commit})
444 444
445 445 comment_id = comment.comment_id
446 446 data[comment_id] = {
447 447 'target_id': target_elem_id
448 448 }
449 449 Session().flush()
450 450
451 451 c.co = comment
452 452 c.at_version_num = 0
453 453 c.is_new = True
454 454 rendered_comment = render(
455 455 'rhodecode:templates/changeset/changeset_comment_block.mako',
456 456 self._get_template_context(c), self.request)
457 457
458 458 data[comment_id].update(comment.get_dict())
459 459 data[comment_id].update({'rendered_text': rendered_comment})
460 460
461 461 # finalize, commit and redirect
462 462 Session().commit()
463 463
464 464 # skip channelstream for draft comments
465 465 if not all_drafts:
466 466 comment_broadcast_channel = channelstream.comment_channel(
467 467 self.db_repo_name, commit_obj=commit)
468 468
469 469 comment_data = data
470 470 posted_comment_type = 'inline' if is_inline else 'general'
471 471 if len(data) == 1:
472 472 msg = _('posted {} new {} comment').format(len(data), posted_comment_type)
473 473 else:
474 474 msg = _('posted {} new {} comments').format(len(data), posted_comment_type)
475 475
476 476 channelstream.comment_channelstream_push(
477 477 self.request, comment_broadcast_channel, self._rhodecode_user, msg,
478 478 comment_data=comment_data)
479 479
480 480 return data
481 481
482 482 @LoginRequired()
483 483 @NotAnonymous()
484 484 @HasRepoPermissionAnyDecorator(
485 485 'repository.read', 'repository.write', 'repository.admin')
486 486 @CSRFRequired()
487 487 def repo_commit_comment_create(self):
488 488 _ = self.request.translate
489 489 commit_id = self.request.matchdict['commit_id']
490 490
491 491 multi_commit_ids = []
492 492 for _commit_id in self.request.POST.get('commit_ids', '').split(','):
493 493 if _commit_id not in ['', None, EmptyCommit.raw_id]:
494 494 if _commit_id not in multi_commit_ids:
495 495 multi_commit_ids.append(_commit_id)
496 496
497 497 commit_ids = multi_commit_ids or [commit_id]
498 498
499 499 data = []
500 500 # Multiple comments for each passed commit id
501 501 for current_id in filter(None, commit_ids):
502 502 comment_data = {
503 503 'comment_type': self.request.POST.get('comment_type'),
504 504 'text': self.request.POST.get('text'),
505 505 'status': self.request.POST.get('changeset_status', None),
506 506 'is_draft': self.request.POST.get('draft'),
507 507 'resolves_comment_id': self.request.POST.get('resolves_comment_id', None),
508 508 'close_pull_request': self.request.POST.get('close_pull_request'),
509 509 'f_path': self.request.POST.get('f_path'),
510 510 'line': self.request.POST.get('line'),
511 511 }
512 512 comment = self._commit_comments_create(commit_id=current_id, comments=[comment_data])
513 513 data.append(comment)
514 514
515 515 return data if len(data) > 1 else data[0]
516 516
517 517 @LoginRequired()
518 518 @NotAnonymous()
519 519 @HasRepoPermissionAnyDecorator(
520 520 'repository.read', 'repository.write', 'repository.admin')
521 521 @CSRFRequired()
522 522 def repo_commit_comment_preview(self):
523 523 # Technically a CSRF token is not needed as no state changes with this
524 524 # call. However, as this is a POST is better to have it, so automated
525 525 # tools don't flag it as potential CSRF.
526 526 # Post is required because the payload could be bigger than the maximum
527 527 # allowed by GET.
528 528
529 529 text = self.request.POST.get('text')
530 530 renderer = self.request.POST.get('renderer') or 'rst'
531 531 if text:
532 532 return h.render(text, renderer=renderer, mentions=True,
533 533 repo_name=self.db_repo_name)
534 534 return ''
535 535
536 536 @LoginRequired()
537 537 @HasRepoPermissionAnyDecorator(
538 538 'repository.read', 'repository.write', 'repository.admin')
539 539 @CSRFRequired()
540 540 def repo_commit_comment_history_view(self):
541 541 c = self.load_default_context()
542 comment_id = self.request.matchdict['comment_id']
542 543 comment_history_id = self.request.matchdict['comment_history_id']
543 544
544 comment = ChangesetComment.get_or_404(comment_history_id)
545 comment = ChangesetComment.get_or_404(comment_id)
545 546 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
546 547 if comment.draft and not comment_owner:
547 548 # if we see draft comments history, we only allow this for owner
548 549 raise HTTPNotFound()
549 550
550 551 comment_history = ChangesetCommentHistory.get_or_404(comment_history_id)
551 552 is_repo_comment = comment_history.comment.repo.repo_id == self.db_repo.repo_id
552 553
553 554 if is_repo_comment:
554 555 c.comment_history = comment_history
555 556
556 557 rendered_comment = render(
557 558 'rhodecode:templates/changeset/comment_history.mako',
558 559 self._get_template_context(c), self.request)
559 560 return rendered_comment
560 561 else:
561 562 log.warning('No permissions for user %s to show comment_history_id: %s',
562 563 self._rhodecode_db_user, comment_history_id)
563 564 raise HTTPNotFound()
564 565
565 566 @LoginRequired()
566 567 @NotAnonymous()
567 568 @HasRepoPermissionAnyDecorator(
568 569 'repository.read', 'repository.write', 'repository.admin')
569 570 @CSRFRequired()
570 571 def repo_commit_comment_attachment_upload(self):
571 572 c = self.load_default_context()
572 573 upload_key = 'attachment'
573 574
574 575 file_obj = self.request.POST.get(upload_key)
575 576
576 577 if file_obj is None:
577 578 self.request.response.status = 400
578 579 return {'store_fid': None,
579 580 'access_path': None,
580 581 'error': '{} data field is missing'.format(upload_key)}
581 582
582 583 if not hasattr(file_obj, 'filename'):
583 584 self.request.response.status = 400
584 585 return {'store_fid': None,
585 586 'access_path': None,
586 587 'error': 'filename cannot be read from the data field'}
587 588
588 589 filename = file_obj.filename
589 590 file_display_name = filename
590 591
591 592 metadata = {
592 593 'user_uploaded': {'username': self._rhodecode_user.username,
593 594 'user_id': self._rhodecode_user.user_id,
594 595 'ip': self._rhodecode_user.ip_addr}}
595 596
596 597 # TODO(marcink): allow .ini configuration for allowed_extensions, and file-size
597 598 allowed_extensions = [
598 599 'gif', '.jpeg', '.jpg', '.png', '.docx', '.gz', '.log', '.pdf',
599 600 '.pptx', '.txt', '.xlsx', '.zip']
600 601 max_file_size = 10 * 1024 * 1024 # 10MB, also validated via dropzone.js
601 602
602 603 try:
603 604 storage = store_utils.get_file_storage(self.request.registry.settings)
604 605 store_uid, metadata = storage.save_file(
605 606 file_obj.file, filename, extra_metadata=metadata,
606 607 extensions=allowed_extensions, max_filesize=max_file_size)
607 608 except FileNotAllowedException:
608 609 self.request.response.status = 400
609 610 permitted_extensions = ', '.join(allowed_extensions)
610 611 error_msg = 'File `{}` is not allowed. ' \
611 612 'Only following extensions are permitted: {}'.format(
612 613 filename, permitted_extensions)
613 614 return {'store_fid': None,
614 615 'access_path': None,
615 616 'error': error_msg}
616 617 except FileOverSizeException:
617 618 self.request.response.status = 400
618 619 limit_mb = h.format_byte_size_binary(max_file_size)
619 620 return {'store_fid': None,
620 621 'access_path': None,
621 622 'error': 'File {} is exceeding allowed limit of {}.'.format(
622 623 filename, limit_mb)}
623 624
624 625 try:
625 626 entry = FileStore.create(
626 627 file_uid=store_uid, filename=metadata["filename"],
627 628 file_hash=metadata["sha256"], file_size=metadata["size"],
628 629 file_display_name=file_display_name,
629 630 file_description=u'comment attachment `{}`'.format(safe_unicode(filename)),
630 631 hidden=True, check_acl=True, user_id=self._rhodecode_user.user_id,
631 632 scope_repo_id=self.db_repo.repo_id
632 633 )
633 634 Session().add(entry)
634 635 Session().commit()
635 636 log.debug('Stored upload in DB as %s', entry)
636 637 except Exception:
637 638 log.exception('Failed to store file %s', filename)
638 639 self.request.response.status = 400
639 640 return {'store_fid': None,
640 641 'access_path': None,
641 642 'error': 'File {} failed to store in DB.'.format(filename)}
642 643
643 644 Session().commit()
644 645
645 646 return {
646 647 'store_fid': store_uid,
647 648 'access_path': h.route_path(
648 649 'download_file', fid=store_uid),
649 650 'fqn_access_path': h.route_url(
650 651 'download_file', fid=store_uid),
651 652 'repo_access_path': h.route_path(
652 653 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
653 654 'repo_fqn_access_path': h.route_url(
654 655 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
655 656 }
656 657
657 658 @LoginRequired()
658 659 @NotAnonymous()
659 660 @HasRepoPermissionAnyDecorator(
660 661 'repository.read', 'repository.write', 'repository.admin')
661 662 @CSRFRequired()
662 663 def repo_commit_comment_delete(self):
663 664 commit_id = self.request.matchdict['commit_id']
664 665 comment_id = self.request.matchdict['comment_id']
665 666
666 667 comment = ChangesetComment.get_or_404(comment_id)
667 668 if not comment:
668 669 log.debug('Comment with id:%s not found, skipping', comment_id)
669 670 # comment already deleted in another call probably
670 671 return True
671 672
672 673 if comment.immutable:
673 674 # don't allow deleting comments that are immutable
674 675 raise HTTPForbidden()
675 676
676 677 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
677 678 super_admin = h.HasPermissionAny('hg.admin')()
678 679 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
679 680 is_repo_comment = comment.repo.repo_id == self.db_repo.repo_id
680 681 comment_repo_admin = is_repo_admin and is_repo_comment
681 682
682 683 if comment.draft and not comment_owner:
683 684 # We never allow to delete draft comments for other than owners
684 685 raise HTTPNotFound()
685 686
686 687 if super_admin or comment_owner or comment_repo_admin:
687 688 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
688 689 Session().commit()
689 690 return True
690 691 else:
691 692 log.warning('No permissions for user %s to delete comment_id: %s',
692 693 self._rhodecode_db_user, comment_id)
693 694 raise HTTPNotFound()
694 695
695 696 @LoginRequired()
696 697 @NotAnonymous()
697 698 @HasRepoPermissionAnyDecorator(
698 699 'repository.read', 'repository.write', 'repository.admin')
699 700 @CSRFRequired()
700 701 def repo_commit_comment_edit(self):
701 702 self.load_default_context()
702 703
703 704 commit_id = self.request.matchdict['commit_id']
704 705 comment_id = self.request.matchdict['comment_id']
705 706 comment = ChangesetComment.get_or_404(comment_id)
706 707
707 708 if comment.immutable:
708 709 # don't allow deleting comments that are immutable
709 710 raise HTTPForbidden()
710 711
711 712 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
712 713 super_admin = h.HasPermissionAny('hg.admin')()
713 714 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
714 715 is_repo_comment = comment.repo.repo_id == self.db_repo.repo_id
715 716 comment_repo_admin = is_repo_admin and is_repo_comment
716 717
717 718 if super_admin or comment_owner or comment_repo_admin:
718 719 text = self.request.POST.get('text')
719 720 version = self.request.POST.get('version')
720 721 if text == comment.text:
721 722 log.warning(
722 723 'Comment(repo): '
723 724 'Trying to create new version '
724 725 'with the same comment body {}'.format(
725 726 comment_id,
726 727 )
727 728 )
728 729 raise HTTPNotFound()
729 730
730 731 if version.isdigit():
731 732 version = int(version)
732 733 else:
733 734 log.warning(
734 735 'Comment(repo): Wrong version type {} {} '
735 736 'for comment {}'.format(
736 737 version,
737 738 type(version),
738 739 comment_id,
739 740 )
740 741 )
741 742 raise HTTPNotFound()
742 743
743 744 try:
744 745 comment_history = CommentsModel().edit(
745 746 comment_id=comment_id,
746 747 text=text,
747 748 auth_user=self._rhodecode_user,
748 749 version=version,
749 750 )
750 751 except CommentVersionMismatch:
751 752 raise HTTPConflict()
752 753
753 754 if not comment_history:
754 755 raise HTTPNotFound()
755 756
756 757 if not comment.draft:
757 758 commit = self.db_repo.get_commit(commit_id)
758 759 CommentsModel().trigger_commit_comment_hook(
759 760 self.db_repo, self._rhodecode_user, 'edit',
760 761 data={'comment': comment, 'commit': commit})
761 762
762 763 Session().commit()
763 764 return {
764 765 'comment_history_id': comment_history.comment_history_id,
765 766 'comment_id': comment.comment_id,
766 767 'comment_version': comment_history.version,
767 768 'comment_author_username': comment_history.author.username,
768 769 'comment_author_gravatar': h.gravatar_url(comment_history.author.email, 16),
769 770 'comment_created_on': h.age_component(comment_history.created_on,
770 771 time_is_local=True),
771 772 }
772 773 else:
773 774 log.warning('No permissions for user %s to edit comment_id: %s',
774 775 self._rhodecode_db_user, comment_id)
775 776 raise HTTPNotFound()
776 777
777 778 @LoginRequired()
778 779 @HasRepoPermissionAnyDecorator(
779 780 'repository.read', 'repository.write', 'repository.admin')
780 781 def repo_commit_data(self):
781 782 commit_id = self.request.matchdict['commit_id']
782 783 self.load_default_context()
783 784
784 785 try:
785 786 return self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
786 787 except CommitDoesNotExistError as e:
787 788 return EmptyCommit(message=str(e))
788 789
789 790 @LoginRequired()
790 791 @HasRepoPermissionAnyDecorator(
791 792 'repository.read', 'repository.write', 'repository.admin')
792 793 def repo_commit_children(self):
793 794 commit_id = self.request.matchdict['commit_id']
794 795 self.load_default_context()
795 796
796 797 try:
797 798 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
798 799 children = commit.children
799 800 except CommitDoesNotExistError:
800 801 children = []
801 802
802 803 result = {"results": children}
803 804 return result
804 805
805 806 @LoginRequired()
806 807 @HasRepoPermissionAnyDecorator(
807 808 'repository.read', 'repository.write', 'repository.admin')
808 809 def repo_commit_parents(self):
809 810 commit_id = self.request.matchdict['commit_id']
810 811 self.load_default_context()
811 812
812 813 try:
813 814 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
814 815 parents = commit.parents
815 816 except CommitDoesNotExistError:
816 817 parents = []
817 818 result = {"results": parents}
818 819 return result
@@ -1,411 +1,411 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('admin_artifacts', '/_admin/artifacts', []);
16 16 pyroutes.register('admin_artifacts_data', '/_admin/artifacts-data', []);
17 17 pyroutes.register('admin_artifacts_delete', '/_admin/artifacts/%(uid)s/delete', ['uid']);
18 18 pyroutes.register('admin_artifacts_show_all', '/_admin/artifacts', []);
19 19 pyroutes.register('admin_artifacts_show_info', '/_admin/artifacts/%(uid)s', ['uid']);
20 20 pyroutes.register('admin_artifacts_update', '/_admin/artifacts/%(uid)s/update', ['uid']);
21 21 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
22 22 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
23 23 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
24 24 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
25 25 pyroutes.register('admin_home', '/_admin', []);
26 26 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
27 27 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
28 28 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
29 29 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
30 30 pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []);
31 31 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
32 32 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
33 33 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
34 34 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
35 35 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
36 36 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
37 37 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
38 38 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
39 39 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
40 40 pyroutes.register('admin_settings', '/_admin/settings', []);
41 41 pyroutes.register('admin_settings_automation', '/_admin/settings/automation', []);
42 42 pyroutes.register('admin_settings_automation_update', '/_admin/settings/automation/%(entry_id)s/update', ['entry_id']);
43 43 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
44 44 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
45 45 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
46 46 pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']);
47 47 pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions_delete_all', []);
48 48 pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']);
49 49 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
50 50 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
51 51 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
52 52 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
53 53 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
54 54 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
55 55 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
56 56 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
57 57 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
58 58 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
59 59 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
60 60 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
61 61 pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
62 62 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
63 63 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
64 64 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
65 65 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
66 66 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
67 67 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
68 68 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
69 69 pyroutes.register('admin_settings_scheduler_create', '/_admin/settings/scheduler/create', []);
70 70 pyroutes.register('admin_settings_scheduler_delete', '/_admin/settings/scheduler/%(schedule_id)s/delete', ['schedule_id']);
71 71 pyroutes.register('admin_settings_scheduler_edit', '/_admin/settings/scheduler/%(schedule_id)s', ['schedule_id']);
72 72 pyroutes.register('admin_settings_scheduler_execute', '/_admin/settings/scheduler/%(schedule_id)s/execute', ['schedule_id']);
73 73 pyroutes.register('admin_settings_scheduler_new', '/_admin/settings/scheduler/new', []);
74 74 pyroutes.register('admin_settings_scheduler_show_all', '/_admin/settings/scheduler', []);
75 75 pyroutes.register('admin_settings_scheduler_show_tasks', '/_admin/settings/scheduler/_tasks', []);
76 76 pyroutes.register('admin_settings_scheduler_update', '/_admin/settings/scheduler/%(schedule_id)s/update', ['schedule_id']);
77 77 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
78 78 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
79 79 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
80 80 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
81 81 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
82 82 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
83 83 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
84 84 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
85 85 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
86 86 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
87 87 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
88 88 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
89 89 pyroutes.register('apiv2', '/_admin/api', []);
90 90 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed-atom', ['repo_name']);
91 91 pyroutes.register('atom_feed_home_old', '/%(repo_name)s/feed/atom', ['repo_name']);
92 92 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
93 93 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
94 94 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
95 95 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
96 96 pyroutes.register('channelstream_proxy', '/_channelstream', []);
97 97 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
98 98 pyroutes.register('commit_draft_comments_submit', '/%(repo_name)s/changeset/%(commit_id)s/draft_comments_submit', ['repo_name', 'commit_id']);
99 99 pyroutes.register('debug_style_email', '/_admin/debug_style/email/%(email_id)s', ['email_id']);
100 100 pyroutes.register('debug_style_email_plain_rendered', '/_admin/debug_style/email-rendered/%(email_id)s', ['email_id']);
101 101 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
102 102 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
103 103 pyroutes.register('download_file', '/_file_store/download/%(fid)s', ['fid']);
104 104 pyroutes.register('download_file_by_token', '/_file_store/token-download/%(_auth_token)s/%(fid)s', ['_auth_token', 'fid']);
105 105 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
106 106 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
107 107 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
108 108 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
109 109 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
110 110 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
111 111 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
112 112 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
113 113 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
114 114 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
115 115 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
116 116 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
117 117 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
118 118 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
119 119 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
120 120 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
121 121 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
122 122 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
123 123 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
124 124 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
125 125 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
126 126 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
127 127 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
128 128 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
129 129 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
130 130 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
131 131 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
132 132 pyroutes.register('edit_repo_perms_set_private', '/%(repo_name)s/settings/permissions/set_private', ['repo_name']);
133 133 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
134 134 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
135 135 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
136 136 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
137 137 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
138 138 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
139 139 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
140 140 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
141 141 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
142 142 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
143 143 pyroutes.register('edit_user_audit_logs_download', '/_admin/users/%(user_id)s/edit/audit/download', ['user_id']);
144 144 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
145 145 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
146 146 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
147 147 pyroutes.register('edit_user_auth_tokens_view', '/_admin/users/%(user_id)s/edit/auth_tokens/view', ['user_id']);
148 148 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
149 149 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
150 150 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
151 151 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
152 152 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
153 153 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
154 154 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
155 155 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
156 156 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
157 157 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
158 158 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
159 159 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
160 160 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
161 161 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
162 162 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
163 163 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
164 164 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
165 165 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
166 166 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
167 167 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
168 168 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
169 169 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
170 170 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
171 171 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
172 172 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
173 173 pyroutes.register('favicon', '/favicon.ico', []);
174 174 pyroutes.register('file_preview', '/_file_preview', []);
175 175 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
176 176 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
177 177 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
178 178 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
179 179 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/rev/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
180 180 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/rev/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
181 181 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/rev/%(revision)s', ['gist_id', 'revision']);
182 182 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
183 183 pyroutes.register('gists_create', '/_admin/gists/create', []);
184 184 pyroutes.register('gists_new', '/_admin/gists/new', []);
185 185 pyroutes.register('gists_show', '/_admin/gists', []);
186 186 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
187 187 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
188 188 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
189 189 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
190 190 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
191 191 pyroutes.register('goto_switcher_data', '/_goto_data', []);
192 192 pyroutes.register('home', '/', []);
193 193 pyroutes.register('hovercard_pull_request', '/_hovercard/pull_request/%(pull_request_id)s', ['pull_request_id']);
194 194 pyroutes.register('hovercard_repo_commit', '/_hovercard/commit/%(repo_name)s/%(commit_id)s', ['repo_name', 'commit_id']);
195 195 pyroutes.register('hovercard_user', '/_hovercard/user/%(user_id)s', ['user_id']);
196 196 pyroutes.register('hovercard_user_group', '/_hovercard/user_group/%(user_group_id)s', ['user_group_id']);
197 197 pyroutes.register('hovercard_username', '/_hovercard/username/%(username)s', ['username']);
198 198 pyroutes.register('journal', '/_admin/journal', []);
199 199 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
200 200 pyroutes.register('journal_public', '/_admin/public_journal', []);
201 201 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
202 202 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
203 203 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
204 204 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
205 205 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
206 206 pyroutes.register('login', '/_admin/login', []);
207 207 pyroutes.register('logout', '/_admin/logout', []);
208 208 pyroutes.register('main_page_repo_groups_data', '/_home_repo_groups', []);
209 209 pyroutes.register('main_page_repos_data', '/_home_repos', []);
210 210 pyroutes.register('markup_preview', '/_markup_preview', []);
211 211 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
212 212 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
213 213 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
214 214 pyroutes.register('my_account_auth_tokens_view', '/_admin/my_account/auth_tokens/view', []);
215 215 pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []);
216 216 pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []);
217 217 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
218 218 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
219 219 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
220 220 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
221 221 pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []);
222 222 pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []);
223 223 pyroutes.register('my_account_goto_bookmark', '/_admin/my_account/bookmark/%(bookmark_id)s', ['bookmark_id']);
224 224 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
225 225 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
226 226 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
227 227 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
228 228 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
229 229 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
230 230 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
231 231 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
232 232 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
233 233 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
234 234 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
235 235 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
236 236 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
237 237 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
238 238 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
239 239 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
240 240 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
241 241 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
242 242 pyroutes.register('notifications_mark_all_read', '/_admin/notifications_mark_all_read', []);
243 243 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
244 244 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
245 245 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
246 246 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
247 247 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
248 248 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
249 249 pyroutes.register('plugin_admin_chat', '/_admin/plugin_admin_chat/%(action)s', ['action']);
250 250 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
251 251 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
252 252 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
253 253 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
254 254 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']);
255 255 pyroutes.register('pullrequest_comment_edit', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/edit', ['repo_name', 'pull_request_id', 'comment_id']);
256 256 pyroutes.register('pullrequest_comments', '/%(repo_name)s/pull-request/%(pull_request_id)s/comments', ['repo_name', 'pull_request_id']);
257 257 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
258 258 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
259 259 pyroutes.register('pullrequest_draft_comments_submit', '/%(repo_name)s/pull-request/%(pull_request_id)s/draft_comments_submit', ['repo_name', 'pull_request_id']);
260 260 pyroutes.register('pullrequest_drafts', '/%(repo_name)s/pull-request/%(pull_request_id)s/drafts', ['repo_name', 'pull_request_id']);
261 261 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
262 262 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
263 263 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
264 264 pyroutes.register('pullrequest_repo_targets', '/%(repo_name)s/pull-request/repo-targets', ['repo_name']);
265 265 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
266 266 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
267 267 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
268 268 pyroutes.register('pullrequest_todos', '/%(repo_name)s/pull-request/%(pull_request_id)s/todos', ['repo_name', 'pull_request_id']);
269 269 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
270 270 pyroutes.register('register', '/_admin/register', []);
271 271 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
272 272 pyroutes.register('repo_artifacts_data', '/%(repo_name)s/artifacts_data', ['repo_name']);
273 273 pyroutes.register('repo_artifacts_delete', '/%(repo_name)s/artifacts/delete/%(uid)s', ['repo_name', 'uid']);
274 274 pyroutes.register('repo_artifacts_get', '/%(repo_name)s/artifacts/download/%(uid)s', ['repo_name', 'uid']);
275 275 pyroutes.register('repo_artifacts_info', '/%(repo_name)s/artifacts/info/%(uid)s', ['repo_name', 'uid']);
276 276 pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']);
277 277 pyroutes.register('repo_artifacts_new', '/%(repo_name)s/artifacts/new', ['repo_name']);
278 278 pyroutes.register('repo_artifacts_store', '/%(repo_name)s/artifacts/store', ['repo_name']);
279 279 pyroutes.register('repo_artifacts_stream_script', '/_file_store/stream-upload-script', []);
280 280 pyroutes.register('repo_artifacts_stream_store', '/_file_store/stream-upload', []);
281 281 pyroutes.register('repo_artifacts_update', '/%(repo_name)s/artifacts/update/%(uid)s', ['repo_name', 'uid']);
282 282 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
283 283 pyroutes.register('repo_automation_update', '/%(repo_name)s/settings/automation/%(entry_id)s/update', ['repo_name', 'entry_id']);
284 284 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
285 285 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
286 286 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
287 287 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
288 288 pyroutes.register('repo_commit_comment_attachment_upload', '/%(repo_name)s/changeset/%(commit_id)s/comment/attachment_upload', ['repo_name', 'commit_id']);
289 289 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
290 290 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
291 291 pyroutes.register('repo_commit_comment_edit', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/edit', ['repo_name', 'commit_id', 'comment_id']);
292 pyroutes.register('repo_commit_comment_history_view', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_history_id)s/history_view', ['repo_name', 'commit_id', 'comment_history_id']);
292 pyroutes.register('repo_commit_comment_history_view', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/history_view/%(comment_history_id)s', ['repo_name', 'commit_id', 'comment_id', 'comment_history_id']);
293 293 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
294 294 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
295 295 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
296 296 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
297 297 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
298 298 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
299 299 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
300 300 pyroutes.register('repo_commits', '/%(repo_name)s/commits', ['repo_name']);
301 301 pyroutes.register('repo_commits_elements', '/%(repo_name)s/commits_elements', ['repo_name']);
302 302 pyroutes.register('repo_commits_elements_file', '/%(repo_name)s/commits_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
303 303 pyroutes.register('repo_commits_file', '/%(repo_name)s/commits/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
304 304 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']);
305 305 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
306 306 pyroutes.register('repo_create', '/_admin/repos/create', []);
307 307 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
308 308 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
309 309 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
310 310 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
311 311 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
312 312 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
313 313 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
314 314 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
315 315 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
316 316 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
317 317 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
318 318 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
319 319 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
320 320 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
321 321 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
322 322 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
323 323 pyroutes.register('repo_files_check_head', '/%(repo_name)s/check_head/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
324 324 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
325 325 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
326 326 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
327 327 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
328 328 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
329 329 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
330 330 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
331 331 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
332 332 pyroutes.register('repo_files_upload_file', '/%(repo_name)s/upload_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
333 333 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
334 334 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
335 335 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
336 336 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
337 337 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
338 338 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
339 339 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
340 340 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
341 341 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
342 342 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
343 343 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
344 344 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
345 345 pyroutes.register('repo_group_list_data', '/_repo_groups', []);
346 346 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
347 347 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
348 348 pyroutes.register('repo_groups_data', '/_admin/repo_groups_data', []);
349 349 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
350 350 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
351 351 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
352 352 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
353 353 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
354 354 pyroutes.register('repo_list_data', '/_repos', []);
355 355 pyroutes.register('repo_new', '/_admin/repos/new', []);
356 356 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
357 357 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
358 358 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
359 359 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
360 360 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
361 361 pyroutes.register('repo_reviewers_review_rule_delete', '/%(repo_name)s/settings/review/rules/%(rule_id)s/delete', ['repo_name', 'rule_id']);
362 362 pyroutes.register('repo_reviewers_review_rule_edit', '/%(repo_name)s/settings/review/rules/%(rule_id)s', ['repo_name', 'rule_id']);
363 363 pyroutes.register('repo_reviewers_review_rule_new', '/%(repo_name)s/settings/review/rules/new', ['repo_name']);
364 364 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
365 365 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
366 366 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
367 367 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
368 368 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
369 369 pyroutes.register('repos', '/_admin/repos', []);
370 370 pyroutes.register('repos_data', '/_admin/repos_data', []);
371 371 pyroutes.register('reset_password', '/_admin/password_reset', []);
372 372 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
373 373 pyroutes.register('robots', '/robots.txt', []);
374 374 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed-rss', ['repo_name']);
375 375 pyroutes.register('rss_feed_home_old', '/%(repo_name)s/feed/rss', ['repo_name']);
376 376 pyroutes.register('search', '/_admin/search', []);
377 377 pyroutes.register('search_repo', '/%(repo_name)s/_search', ['repo_name']);
378 378 pyroutes.register('search_repo_alt', '/%(repo_name)s/search', ['repo_name']);
379 379 pyroutes.register('search_repo_group', '/%(repo_group_name)s/_search', ['repo_group_name']);
380 380 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
381 381 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
382 382 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
383 383 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
384 384 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
385 385 pyroutes.register('upload_file', '/_file_store/upload', []);
386 386 pyroutes.register('user_autocomplete_data', '/_users', []);
387 387 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
388 388 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
389 389 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
390 390 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
391 391 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
392 392 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
393 393 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
394 394 pyroutes.register('user_enable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_enable', ['user_id']);
395 395 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
396 396 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
397 397 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
398 398 pyroutes.register('user_groups', '/_admin/user_groups', []);
399 399 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
400 400 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
401 401 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
402 402 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
403 403 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
404 404 pyroutes.register('user_notice_dismiss', '/_admin/users/%(user_id)s/notice_dismiss', ['user_id']);
405 405 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
406 406 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
407 407 pyroutes.register('users', '/_admin/users', []);
408 408 pyroutes.register('users_create', '/_admin/users/create', []);
409 409 pyroutes.register('users_data', '/_admin/users_data', []);
410 410 pyroutes.register('users_new', '/_admin/users/new', []);
411 411 }
@@ -1,1644 +1,1645 b''
1 1 // # Copyright (C) 2010-2020 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 var firefoxAnchorFix = function() {
20 20 // hack to make anchor links behave properly on firefox, in our inline
21 21 // comments generation when comments are injected firefox is misbehaving
22 22 // when jumping to anchor links
23 23 if (location.href.indexOf('#') > -1) {
24 24 location.href += '';
25 25 }
26 26 };
27 27
28 28
29 29 var linkifyComments = function(comments) {
30 30 var firstCommentId = null;
31 31 if (comments) {
32 32 firstCommentId = $(comments[0]).data('comment-id');
33 33 }
34 34
35 35 if (firstCommentId){
36 36 $('#inline-comments-counter').attr('href', '#comment-' + firstCommentId);
37 37 }
38 38 };
39 39
40 40
41 41 var bindToggleButtons = function() {
42 42 $('.comment-toggle').on('click', function() {
43 43 $(this).parent().nextUntil('tr.line').toggle('inline-comments');
44 44 });
45 45 };
46 46
47 47
48 48 var _submitAjaxPOST = function(url, postData, successHandler, failHandler) {
49 49 failHandler = failHandler || function() {};
50 50 postData = toQueryString(postData);
51 51 var request = $.ajax({
52 52 url: url,
53 53 type: 'POST',
54 54 data: postData,
55 55 headers: {'X-PARTIAL-XHR': true}
56 56 })
57 57 .done(function (data) {
58 58 successHandler(data);
59 59 })
60 60 .fail(function (data, textStatus, errorThrown) {
61 61 failHandler(data, textStatus, errorThrown)
62 62 });
63 63 return request;
64 64 };
65 65
66 66
67 67 /* Comment form for main and inline comments */
68 68 (function(mod) {
69 69
70 70 if (typeof exports == "object" && typeof module == "object") {
71 71 // CommonJS
72 72 module.exports = mod();
73 73 }
74 74 else {
75 75 // Plain browser env
76 76 (this || window).CommentForm = mod();
77 77 }
78 78
79 79 })(function() {
80 80 "use strict";
81 81
82 82 function CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId, edit, comment_id) {
83 83
84 84 if (!(this instanceof CommentForm)) {
85 85 return new CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId, edit, comment_id);
86 86 }
87 87
88 88 // bind the element instance to our Form
89 89 $(formElement).get(0).CommentForm = this;
90 90
91 91 this.withLineNo = function(selector) {
92 92 var lineNo = this.lineNo;
93 93 if (lineNo === undefined) {
94 94 return selector
95 95 } else {
96 96 return selector + '_' + lineNo;
97 97 }
98 98 };
99 99
100 100 this.commitId = commitId;
101 101 this.pullRequestId = pullRequestId;
102 102 this.lineNo = lineNo;
103 103 this.initAutocompleteActions = initAutocompleteActions;
104 104
105 105 this.previewButton = this.withLineNo('#preview-btn');
106 106 this.previewContainer = this.withLineNo('#preview-container');
107 107
108 108 this.previewBoxSelector = this.withLineNo('#preview-box');
109 109
110 110 this.editButton = this.withLineNo('#edit-btn');
111 111 this.editContainer = this.withLineNo('#edit-container');
112 112 this.cancelButton = this.withLineNo('#cancel-btn');
113 113 this.commentType = this.withLineNo('#comment_type');
114 114
115 115 this.resolvesId = null;
116 116 this.resolvesActionId = null;
117 117
118 118 this.closesPr = '#close_pull_request';
119 119
120 120 this.cmBox = this.withLineNo('#text');
121 121 this.cm = initCommentBoxCodeMirror(this, this.cmBox, this.initAutocompleteActions);
122 122
123 123 this.statusChange = this.withLineNo('#change_status');
124 124
125 125 this.submitForm = formElement;
126 126
127 127 this.submitButton = $(this.submitForm).find('.submit-comment-action');
128 128 this.submitButtonText = this.submitButton.val();
129 129
130 130 this.submitDraftButton = $(this.submitForm).find('.submit-draft-action');
131 131 this.submitDraftButtonText = this.submitDraftButton.val();
132 132
133 133 this.previewUrl = pyroutes.url('repo_commit_comment_preview',
134 134 {'repo_name': templateContext.repo_name,
135 135 'commit_id': templateContext.commit_data.commit_id});
136 136
137 137 if (edit){
138 138 this.submitDraftButton.hide();
139 139 this.submitButtonText = _gettext('Update Comment');
140 140 $(this.commentType).prop('disabled', true);
141 141 $(this.commentType).addClass('disabled');
142 142 var editInfo =
143 143 '';
144 144 $(editInfo).insertBefore($(this.editButton).parent());
145 145 }
146 146
147 147 if (resolvesCommentId){
148 148 this.resolvesId = '#resolve_comment_{0}'.format(resolvesCommentId);
149 149 this.resolvesActionId = '#resolve_comment_action_{0}'.format(resolvesCommentId);
150 150 $(this.commentType).prop('disabled', true);
151 151 $(this.commentType).addClass('disabled');
152 152
153 153 // disable select
154 154 setTimeout(function() {
155 155 $(self.statusChange).select2('readonly', true);
156 156 }, 10);
157 157
158 158 var resolvedInfo = (
159 159 '<li class="resolve-action">' +
160 160 '<input type="hidden" id="resolve_comment_{0}" name="resolve_comment_{0}" value="{0}">' +
161 161 '<button id="resolve_comment_action_{0}" class="resolve-text btn btn-sm" onclick="return Rhodecode.comments.submitResolution({0})">{1} #{0}</button>' +
162 162 '</li>'
163 163 ).format(resolvesCommentId, _gettext('resolve comment'));
164 164 $(resolvedInfo).insertAfter($(this.commentType).parent());
165 165 }
166 166
167 167 // based on commitId, or pullRequestId decide where do we submit
168 168 // out data
169 169 if (this.commitId){
170 170 var pyurl = 'repo_commit_comment_create';
171 171 if(edit){
172 172 pyurl = 'repo_commit_comment_edit';
173 173 }
174 174 this.submitUrl = pyroutes.url(pyurl,
175 175 {'repo_name': templateContext.repo_name,
176 176 'commit_id': this.commitId,
177 177 'comment_id': comment_id});
178 178 this.selfUrl = pyroutes.url('repo_commit',
179 179 {'repo_name': templateContext.repo_name,
180 180 'commit_id': this.commitId});
181 181
182 182 } else if (this.pullRequestId) {
183 183 var pyurl = 'pullrequest_comment_create';
184 184 if(edit){
185 185 pyurl = 'pullrequest_comment_edit';
186 186 }
187 187 this.submitUrl = pyroutes.url(pyurl,
188 188 {'repo_name': templateContext.repo_name,
189 189 'pull_request_id': this.pullRequestId,
190 190 'comment_id': comment_id});
191 191 this.selfUrl = pyroutes.url('pullrequest_show',
192 192 {'repo_name': templateContext.repo_name,
193 193 'pull_request_id': this.pullRequestId});
194 194
195 195 } else {
196 196 throw new Error(
197 197 'CommentForm requires pullRequestId, or commitId to be specified.')
198 198 }
199 199
200 200 // FUNCTIONS and helpers
201 201 var self = this;
202 202
203 203 this.isInline = function(){
204 204 return this.lineNo && this.lineNo != 'general';
205 205 };
206 206
207 207 this.getCmInstance = function(){
208 208 return this.cm
209 209 };
210 210
211 211 this.setPlaceholder = function(placeholder) {
212 212 var cm = this.getCmInstance();
213 213 if (cm){
214 214 cm.setOption('placeholder', placeholder);
215 215 }
216 216 };
217 217
218 218 this.getCommentStatus = function() {
219 219 return $(this.submitForm).find(this.statusChange).val();
220 220 };
221 221
222 222 this.getCommentType = function() {
223 223 return $(this.submitForm).find(this.commentType).val();
224 224 };
225 225
226 226 this.getDraftState = function () {
227 227 var submitterElem = $(this.submitForm).find('input[type="submit"].submitter');
228 228 var data = $(submitterElem).data('isDraft');
229 229 return data
230 230 }
231 231
232 232 this.getResolvesId = function() {
233 233 return $(this.submitForm).find(this.resolvesId).val() || null;
234 234 };
235 235
236 236 this.getClosePr = function() {
237 237 return $(this.submitForm).find(this.closesPr).val() || null;
238 238 };
239 239
240 240 this.markCommentResolved = function(resolvedCommentId){
241 241 Rhodecode.comments.markCommentResolved(resolvedCommentId)
242 242 };
243 243
244 244 this.isAllowedToSubmit = function() {
245 245 var commentDisabled = $(this.submitButton).prop('disabled');
246 246 var draftDisabled = $(this.submitDraftButton).prop('disabled');
247 247 return !commentDisabled && !draftDisabled;
248 248 };
249 249
250 250 this.initStatusChangeSelector = function(){
251 251 var formatChangeStatus = function(state, escapeMarkup) {
252 252 var originalOption = state.element;
253 253 var tmpl = '<i class="icon-circle review-status-{0}"></i><span>{1}</span>'.format($(originalOption).data('status'), escapeMarkup(state.text));
254 254 return tmpl
255 255 };
256 256 var formatResult = function(result, container, query, escapeMarkup) {
257 257 return formatChangeStatus(result, escapeMarkup);
258 258 };
259 259
260 260 var formatSelection = function(data, container, escapeMarkup) {
261 261 return formatChangeStatus(data, escapeMarkup);
262 262 };
263 263
264 264 $(this.submitForm).find(this.statusChange).select2({
265 265 placeholder: _gettext('Status Review'),
266 266 formatResult: formatResult,
267 267 formatSelection: formatSelection,
268 268 containerCssClass: "drop-menu status_box_menu",
269 269 dropdownCssClass: "drop-menu-dropdown",
270 270 dropdownAutoWidth: true,
271 271 minimumResultsForSearch: -1
272 272 });
273 273
274 274 $(this.submitForm).find(this.statusChange).on('change', function() {
275 275 var status = self.getCommentStatus();
276 276
277 277 if (status && !self.isInline()) {
278 278 $(self.submitButton).prop('disabled', false);
279 279 $(self.submitDraftButton).prop('disabled', false);
280 280 }
281 281
282 282 var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status);
283 283 self.setPlaceholder(placeholderText)
284 284 })
285 285 };
286 286
287 287 // reset the comment form into it's original state
288 288 this.resetCommentFormState = function(content) {
289 289 content = content || '';
290 290
291 291 $(this.editContainer).show();
292 292 $(this.editButton).parent().addClass('active');
293 293
294 294 $(this.previewContainer).hide();
295 295 $(this.previewButton).parent().removeClass('active');
296 296
297 297 this.setActionButtonsDisabled(true);
298 298 self.cm.setValue(content);
299 299 self.cm.setOption("readOnly", false);
300 300
301 301 if (this.resolvesId) {
302 302 // destroy the resolve action
303 303 $(this.resolvesId).parent().remove();
304 304 }
305 305 // reset closingPR flag
306 306 $('.close-pr-input').remove();
307 307
308 308 $(this.statusChange).select2('readonly', false);
309 309 };
310 310
311 311 this.globalSubmitSuccessCallback = function(comment){
312 312 // default behaviour is to call GLOBAL hook, if it's registered.
313 313 if (window.commentFormGlobalSubmitSuccessCallback !== undefined){
314 314 commentFormGlobalSubmitSuccessCallback(comment);
315 315 }
316 316 };
317 317
318 318 this.submitAjaxPOST = function(url, postData, successHandler, failHandler) {
319 319 return _submitAjaxPOST(url, postData, successHandler, failHandler);
320 320 };
321 321
322 322 // overwrite a submitHandler, we need to do it for inline comments
323 323 this.setHandleFormSubmit = function(callback) {
324 324 this.handleFormSubmit = callback;
325 325 };
326 326
327 327 // overwrite a submitSuccessHandler
328 328 this.setGlobalSubmitSuccessCallback = function(callback) {
329 329 this.globalSubmitSuccessCallback = callback;
330 330 };
331 331
332 332 // default handler for for submit for main comments
333 333 this.handleFormSubmit = function() {
334 334 var text = self.cm.getValue();
335 335 var status = self.getCommentStatus();
336 336 var commentType = self.getCommentType();
337 337 var isDraft = self.getDraftState();
338 338 var resolvesCommentId = self.getResolvesId();
339 339 var closePullRequest = self.getClosePr();
340 340
341 341 if (text === "" && !status) {
342 342 return;
343 343 }
344 344
345 345 var excludeCancelBtn = false;
346 346 var submitEvent = true;
347 347 self.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
348 348 self.cm.setOption("readOnly", true);
349 349
350 350 var postData = {
351 351 'text': text,
352 352 'changeset_status': status,
353 353 'comment_type': commentType,
354 354 'csrf_token': CSRF_TOKEN
355 355 };
356 356
357 357 if (resolvesCommentId) {
358 358 postData['resolves_comment_id'] = resolvesCommentId;
359 359 }
360 360
361 361 if (closePullRequest) {
362 362 postData['close_pull_request'] = true;
363 363 }
364 364
365 365 // submitSuccess for general comments
366 366 var submitSuccessCallback = function(json_data) {
367 367 // reload page if we change status for single commit.
368 368 if (status && self.commitId) {
369 369 location.reload(true);
370 370 } else {
371 371 // inject newly created comments, json_data is {<comment_id>: {}}
372 372 Rhodecode.comments.attachGeneralComment(json_data)
373 373
374 374 self.resetCommentFormState();
375 375 timeagoActivate();
376 376 tooltipActivate();
377 377
378 378 // mark visually which comment was resolved
379 379 if (resolvesCommentId) {
380 380 self.markCommentResolved(resolvesCommentId);
381 381 }
382 382 }
383 383
384 384 // run global callback on submit
385 385 self.globalSubmitSuccessCallback({draft: isDraft, comment_id: comment_id});
386 386
387 387 };
388 388 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
389 389 var prefix = "Error while submitting comment.\n"
390 390 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
391 391 ajaxErrorSwal(message);
392 392 self.resetCommentFormState(text);
393 393 };
394 394 self.submitAjaxPOST(
395 395 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
396 396 };
397 397
398 398 this.previewSuccessCallback = function(o) {
399 399 $(self.previewBoxSelector).html(o);
400 400 $(self.previewBoxSelector).removeClass('unloaded');
401 401
402 402 // swap buttons, making preview active
403 403 $(self.previewButton).parent().addClass('active');
404 404 $(self.editButton).parent().removeClass('active');
405 405
406 406 // unlock buttons
407 407 self.setActionButtonsDisabled(false);
408 408 };
409 409
410 410 this.setActionButtonsDisabled = function(state, excludeCancelBtn, submitEvent) {
411 411 excludeCancelBtn = excludeCancelBtn || false;
412 412 submitEvent = submitEvent || false;
413 413
414 414 $(this.editButton).prop('disabled', state);
415 415 $(this.previewButton).prop('disabled', state);
416 416
417 417 if (!excludeCancelBtn) {
418 418 $(this.cancelButton).prop('disabled', state);
419 419 }
420 420
421 421 var submitState = state;
422 422 if (!submitEvent && this.getCommentStatus() && !self.isInline()) {
423 423 // if the value of commit review status is set, we allow
424 424 // submit button, but only on Main form, isInline means inline
425 425 submitState = false
426 426 }
427 427
428 428 $(this.submitButton).prop('disabled', submitState);
429 429 $(this.submitDraftButton).prop('disabled', submitState);
430 430
431 431 if (submitEvent) {
432 432 var isDraft = self.getDraftState();
433 433
434 434 if (isDraft) {
435 435 $(this.submitDraftButton).val(_gettext('Saving Draft...'));
436 436 } else {
437 437 $(this.submitButton).val(_gettext('Submitting...'));
438 438 }
439 439
440 440 } else {
441 441 $(this.submitButton).val(this.submitButtonText);
442 442 $(this.submitDraftButton).val(this.submitDraftButtonText);
443 443 }
444 444
445 445 };
446 446
447 447 // lock preview/edit/submit buttons on load, but exclude cancel button
448 448 var excludeCancelBtn = true;
449 449 this.setActionButtonsDisabled(true, excludeCancelBtn);
450 450
451 451 // anonymous users don't have access to initialized CM instance
452 452 if (this.cm !== undefined){
453 453 this.cm.on('change', function(cMirror) {
454 454 if (cMirror.getValue() === "") {
455 455 self.setActionButtonsDisabled(true, excludeCancelBtn)
456 456 } else {
457 457 self.setActionButtonsDisabled(false, excludeCancelBtn)
458 458 }
459 459 });
460 460 }
461 461
462 462 $(this.editButton).on('click', function(e) {
463 463 e.preventDefault();
464 464
465 465 $(self.previewButton).parent().removeClass('active');
466 466 $(self.previewContainer).hide();
467 467
468 468 $(self.editButton).parent().addClass('active');
469 469 $(self.editContainer).show();
470 470
471 471 });
472 472
473 473 $(this.previewButton).on('click', function(e) {
474 474 e.preventDefault();
475 475 var text = self.cm.getValue();
476 476
477 477 if (text === "") {
478 478 return;
479 479 }
480 480
481 481 var postData = {
482 482 'text': text,
483 483 'renderer': templateContext.visual.default_renderer,
484 484 'csrf_token': CSRF_TOKEN
485 485 };
486 486
487 487 // lock ALL buttons on preview
488 488 self.setActionButtonsDisabled(true);
489 489
490 490 $(self.previewBoxSelector).addClass('unloaded');
491 491 $(self.previewBoxSelector).html(_gettext('Loading ...'));
492 492
493 493 $(self.editContainer).hide();
494 494 $(self.previewContainer).show();
495 495
496 496 // by default we reset state of comment preserving the text
497 497 var previewFailCallback = function(jqXHR, textStatus, errorThrown) {
498 498 var prefix = "Error while preview of comment.\n"
499 499 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
500 500 ajaxErrorSwal(message);
501 501
502 502 self.resetCommentFormState(text)
503 503 };
504 504 self.submitAjaxPOST(
505 505 self.previewUrl, postData, self.previewSuccessCallback,
506 506 previewFailCallback);
507 507
508 508 $(self.previewButton).parent().addClass('active');
509 509 $(self.editButton).parent().removeClass('active');
510 510 });
511 511
512 512 $(this.submitForm).submit(function(e) {
513 513 e.preventDefault();
514 514 var allowedToSubmit = self.isAllowedToSubmit();
515 515 if (!allowedToSubmit){
516 516 return false;
517 517 }
518 518
519 519 self.handleFormSubmit();
520 520 });
521 521
522 522 }
523 523
524 524 return CommentForm;
525 525 });
526 526
527 527 /* selector for comment versions */
528 528 var initVersionSelector = function(selector, initialData) {
529 529
530 530 var formatResult = function(result, container, query, escapeMarkup) {
531 531
532 532 return renderTemplate('commentVersion', {
533 533 show_disabled: true,
534 534 version: result.comment_version,
535 535 user_name: result.comment_author_username,
536 536 gravatar_url: result.comment_author_gravatar,
537 537 size: 16,
538 538 timeago_component: result.comment_created_on,
539 539 })
540 540 };
541 541
542 542 $(selector).select2({
543 543 placeholder: "Edited",
544 544 containerCssClass: "drop-menu-comment-history",
545 545 dropdownCssClass: "drop-menu-dropdown",
546 546 dropdownAutoWidth: true,
547 547 minimumResultsForSearch: -1,
548 548 data: initialData,
549 549 formatResult: formatResult,
550 550 });
551 551
552 552 $(selector).on('select2-selecting', function (e) {
553 553 // hide the mast as we later do preventDefault()
554 554 $("#select2-drop-mask").click();
555 555 e.preventDefault();
556 556 e.choice.action();
557 557 });
558 558
559 559 $(selector).on("select2-open", function() {
560 560 timeagoActivate();
561 561 });
562 562 };
563 563
564 564 /* comments controller */
565 565 var CommentsController = function() {
566 566 var mainComment = '#text';
567 567 var self = this;
568 568
569 569 this.showVersion = function (comment_id, comment_history_id) {
570 570
571 571 var historyViewUrl = pyroutes.url(
572 572 'repo_commit_comment_history_view',
573 573 {
574 574 'repo_name': templateContext.repo_name,
575 'commit_id': comment_id,
575 'commit_id': null, // We don't need to check the commit data here...
576 'comment_id': comment_id,
576 577 'comment_history_id': comment_history_id,
577 578 }
578 579 );
579 580 successRenderCommit = function (data) {
580 581 SwalNoAnimation.fire({
581 582 html: data,
582 583 title: '',
583 584 });
584 585 };
585 586 failRenderCommit = function () {
586 587 SwalNoAnimation.fire({
587 588 html: 'Error while loading comment history',
588 589 title: '',
589 590 });
590 591 };
591 592 _submitAjaxPOST(
592 593 historyViewUrl, {'csrf_token': CSRF_TOKEN},
593 594 successRenderCommit,
594 595 failRenderCommit
595 596 );
596 597 };
597 598
598 599 this.getLineNumber = function(node) {
599 600 var $node = $(node);
600 601 var lineNo = $node.closest('td').attr('data-line-no');
601 602 if (lineNo === undefined && $node.data('commentInline')){
602 603 lineNo = $node.data('commentLineNo')
603 604 }
604 605
605 606 return lineNo
606 607 };
607 608
608 609 this.scrollToComment = function(node, offset, outdated) {
609 610 if (offset === undefined) {
610 611 offset = 0;
611 612 }
612 613 var outdated = outdated || false;
613 614 var klass = outdated ? 'div.comment-outdated' : 'div.comment-current';
614 615
615 616 if (!node) {
616 617 node = $('.comment-selected');
617 618 if (!node.length) {
618 619 node = $('comment-current')
619 620 }
620 621 }
621 622
622 623 $wrapper = $(node).closest('div.comment');
623 624
624 625 // show hidden comment when referenced.
625 626 if (!$wrapper.is(':visible')){
626 627 $wrapper.show();
627 628 }
628 629
629 630 $comment = $(node).closest(klass);
630 631 $comments = $(klass);
631 632
632 633 $('.comment-selected').removeClass('comment-selected');
633 634
634 635 var nextIdx = $(klass).index($comment) + offset;
635 636 if (nextIdx >= $comments.length) {
636 637 nextIdx = 0;
637 638 }
638 639 var $next = $(klass).eq(nextIdx);
639 640
640 641 var $cb = $next.closest('.cb');
641 642 $cb.removeClass('cb-collapsed');
642 643
643 644 var $filediffCollapseState = $cb.closest('.filediff').prev();
644 645 $filediffCollapseState.prop('checked', false);
645 646 $next.addClass('comment-selected');
646 647 scrollToElement($next);
647 648 return false;
648 649 };
649 650
650 651 this.nextComment = function(node) {
651 652 return self.scrollToComment(node, 1);
652 653 };
653 654
654 655 this.prevComment = function(node) {
655 656 return self.scrollToComment(node, -1);
656 657 };
657 658
658 659 this.nextOutdatedComment = function(node) {
659 660 return self.scrollToComment(node, 1, true);
660 661 };
661 662
662 663 this.prevOutdatedComment = function(node) {
663 664 return self.scrollToComment(node, -1, true);
664 665 };
665 666
666 667 this.cancelComment = function (node) {
667 668 var $node = $(node);
668 669 var edit = $(this).attr('edit');
669 670 var $inlineComments = $node.closest('div.inline-comments');
670 671
671 672 if (edit) {
672 673 var $general_comments = null;
673 674 if (!$inlineComments.length) {
674 675 $general_comments = $('#comments');
675 676 var $comment = $general_comments.parent().find('div.comment:hidden');
676 677 // show hidden general comment form
677 678 $('#cb-comment-general-form-placeholder').show();
678 679 } else {
679 680 var $comment = $inlineComments.find('div.comment:hidden');
680 681 }
681 682 $comment.show();
682 683 }
683 684 var $replyWrapper = $node.closest('.comment-inline-form').closest('.reply-thread-container-wrapper')
684 685 $replyWrapper.removeClass('comment-form-active');
685 686
686 687 var lastComment = $inlineComments.find('.comment-inline').last();
687 688 if ($(lastComment).hasClass('comment-outdated')) {
688 689 $replyWrapper.hide();
689 690 }
690 691
691 692 $node.closest('.comment-inline-form').remove();
692 693 return false;
693 694 };
694 695
695 696 this._deleteComment = function(node) {
696 697 var $node = $(node);
697 698 var $td = $node.closest('td');
698 699 var $comment = $node.closest('.comment');
699 700 var comment_id = $($comment).data('commentId');
700 701 var isDraft = $($comment).data('commentDraft');
701 702
702 703 var pullRequestId = templateContext.pull_request_data.pull_request_id;
703 704 var commitId = templateContext.commit_data.commit_id;
704 705
705 706 if (pullRequestId) {
706 707 var url = pyroutes.url('pullrequest_comment_delete', {"comment_id": comment_id, "repo_name": templateContext.repo_name, "pull_request_id": pullRequestId})
707 708 } else if (commitId) {
708 709 var url = pyroutes.url('repo_commit_comment_delete', {"comment_id": comment_id, "repo_name": templateContext.repo_name, "commit_id": commitId})
709 710 }
710 711
711 712 var postData = {
712 713 'csrf_token': CSRF_TOKEN
713 714 };
714 715
715 716 $comment.addClass('comment-deleting');
716 717 $comment.hide('fast');
717 718
718 719 var success = function(response) {
719 720 $comment.remove();
720 721
721 722 if (window.updateSticky !== undefined) {
722 723 // potentially our comments change the active window size, so we
723 724 // notify sticky elements
724 725 updateSticky()
725 726 }
726 727
727 728 if (window.refreshAllComments !== undefined && !isDraft) {
728 729 // if we have this handler, run it, and refresh all comments boxes
729 730 refreshAllComments()
730 731 }
731 732 else if (window.refreshDraftComments !== undefined && isDraft) {
732 733 // if we have this handler, run it, and refresh all comments boxes
733 734 refreshDraftComments();
734 735 }
735 736 return false;
736 737 };
737 738
738 739 var failure = function(jqXHR, textStatus, errorThrown) {
739 740 var prefix = "Error while deleting this comment.\n"
740 741 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
741 742 ajaxErrorSwal(message);
742 743
743 744 $comment.show('fast');
744 745 $comment.removeClass('comment-deleting');
745 746 return false;
746 747 };
747 748 ajaxPOST(url, postData, success, failure);
748 749
749 750 }
750 751
751 752 this.deleteComment = function(node) {
752 753 var $comment = $(node).closest('.comment');
753 754 var comment_id = $comment.attr('data-comment-id');
754 755
755 756 SwalNoAnimation.fire({
756 757 title: 'Delete this comment?',
757 758 icon: 'warning',
758 759 showCancelButton: true,
759 760 confirmButtonText: _gettext('Yes, delete comment #{0}!').format(comment_id),
760 761
761 762 }).then(function(result) {
762 763 if (result.value) {
763 764 self._deleteComment(node);
764 765 }
765 766 })
766 767 };
767 768
768 769 this._finalizeDrafts = function(commentIds) {
769 770
770 771 var pullRequestId = templateContext.pull_request_data.pull_request_id;
771 772 var commitId = templateContext.commit_data.commit_id;
772 773
773 774 if (pullRequestId) {
774 775 var url = pyroutes.url('pullrequest_draft_comments_submit', {"repo_name": templateContext.repo_name, "pull_request_id": pullRequestId})
775 776 } else if (commitId) {
776 777 var url = pyroutes.url('commit_draft_comments_submit', {"repo_name": templateContext.repo_name, "commit_id": commitId})
777 778 }
778 779
779 780 // remove the drafts so we can lock them before submit.
780 781 $.each(commentIds, function(idx, val){
781 782 $('#comment-{0}'.format(val)).remove();
782 783 })
783 784
784 785 var postData = {'comments': commentIds, 'csrf_token': CSRF_TOKEN};
785 786
786 787 var submitSuccessCallback = function(json_data) {
787 788 self.attachInlineComment(json_data);
788 789
789 790 if (window.refreshDraftComments !== undefined) {
790 791 // if we have this handler, run it, and refresh all comments boxes
791 792 refreshDraftComments()
792 793 }
793 794
794 795 if (window.refreshAllComments !== undefined) {
795 796 // if we have this handler, run it, and refresh all comments boxes
796 797 refreshAllComments()
797 798 }
798 799
799 800 return false;
800 801 };
801 802
802 803 ajaxPOST(url, postData, submitSuccessCallback)
803 804
804 805 }
805 806
806 807 this.finalizeDrafts = function(commentIds, callback) {
807 808
808 809 SwalNoAnimation.fire({
809 810 title: _ngettext('Submit {0} draft comment.', 'Submit {0} draft comments.', commentIds.length).format(commentIds.length),
810 811 icon: 'warning',
811 812 showCancelButton: true,
812 813 confirmButtonText: _gettext('Yes'),
813 814
814 815 }).then(function(result) {
815 816 if (result.value) {
816 817 if (callback !== undefined) {
817 818 callback(result)
818 819 }
819 820 self._finalizeDrafts(commentIds);
820 821 }
821 822 })
822 823 };
823 824
824 825 this.toggleWideMode = function (node) {
825 826
826 827 if ($('#content').hasClass('wrapper')) {
827 828 $('#content').removeClass("wrapper");
828 829 $('#content').addClass("wide-mode-wrapper");
829 830 $(node).addClass('btn-success');
830 831 return true
831 832 } else {
832 833 $('#content').removeClass("wide-mode-wrapper");
833 834 $('#content').addClass("wrapper");
834 835 $(node).removeClass('btn-success');
835 836 return false
836 837 }
837 838
838 839 };
839 840
840 841 /**
841 842 * Turn off/on all comments in file diff
842 843 */
843 844 this.toggleDiffComments = function(node) {
844 845 // Find closes filediff container
845 846 var $filediff = $(node).closest('.filediff');
846 847 if ($(node).hasClass('toggle-on')) {
847 848 var show = false;
848 849 } else if ($(node).hasClass('toggle-off')) {
849 850 var show = true;
850 851 }
851 852
852 853 // Toggle each individual comment block, so we can un-toggle single ones
853 854 $.each($filediff.find('.toggle-comment-action'), function(idx, val) {
854 855 self.toggleLineComments($(val), show)
855 856 })
856 857
857 858 // since we change the height of the diff container that has anchor points for upper
858 859 // sticky header, we need to tell it to re-calculate those
859 860 if (window.updateSticky !== undefined) {
860 861 // potentially our comments change the active window size, so we
861 862 // notify sticky elements
862 863 updateSticky()
863 864 }
864 865
865 866 return false;
866 867 }
867 868
868 869 this.toggleLineComments = function(node, show) {
869 870
870 871 var trElem = $(node).closest('tr')
871 872
872 873 if (show === true) {
873 874 // mark outdated comments as visible before the toggle;
874 875 $(trElem).find('.comment-outdated').show();
875 876 $(trElem).removeClass('hide-line-comments');
876 877 } else if (show === false) {
877 878 $(trElem).find('.comment-outdated').hide();
878 879 $(trElem).addClass('hide-line-comments');
879 880 } else {
880 881 // mark outdated comments as visible before the toggle;
881 882 $(trElem).find('.comment-outdated').show();
882 883 $(trElem).toggleClass('hide-line-comments');
883 884 }
884 885
885 886 // since we change the height of the diff container that has anchor points for upper
886 887 // sticky header, we need to tell it to re-calculate those
887 888 if (window.updateSticky !== undefined) {
888 889 // potentially our comments change the active window size, so we
889 890 // notify sticky elements
890 891 updateSticky()
891 892 }
892 893
893 894 };
894 895
895 896 this.createCommentForm = function(formElement, lineno, placeholderText, initAutocompleteActions, resolvesCommentId, edit, comment_id){
896 897 var pullRequestId = templateContext.pull_request_data.pull_request_id;
897 898 var commitId = templateContext.commit_data.commit_id;
898 899
899 900 var commentForm = new CommentForm(
900 901 formElement, commitId, pullRequestId, lineno, initAutocompleteActions, resolvesCommentId, edit, comment_id);
901 902 var cm = commentForm.getCmInstance();
902 903
903 904 if (resolvesCommentId){
904 905 placeholderText = _gettext('Leave a resolution comment, or click resolve button to resolve TODO comment #{0}').format(resolvesCommentId);
905 906 }
906 907
907 908 setTimeout(function() {
908 909 // callbacks
909 910 if (cm !== undefined) {
910 911 commentForm.setPlaceholder(placeholderText);
911 912 if (commentForm.isInline()) {
912 913 cm.focus();
913 914 cm.refresh();
914 915 }
915 916 }
916 917 }, 10);
917 918
918 919 // trigger scrolldown to the resolve comment, since it might be away
919 920 // from the clicked
920 921 if (resolvesCommentId){
921 922 var actionNode = $(commentForm.resolvesActionId).offset();
922 923
923 924 setTimeout(function() {
924 925 if (actionNode) {
925 926 $('body, html').animate({scrollTop: actionNode.top}, 10);
926 927 }
927 928 }, 100);
928 929 }
929 930
930 931 // add dropzone support
931 932 var insertAttachmentText = function (cm, attachmentName, attachmentStoreUrl, isRendered) {
932 933 var renderer = templateContext.visual.default_renderer;
933 934 if (renderer == 'rst') {
934 935 var attachmentUrl = '`#{0} <{1}>`_'.format(attachmentName, attachmentStoreUrl);
935 936 if (isRendered){
936 937 attachmentUrl = '\n.. image:: {0}'.format(attachmentStoreUrl);
937 938 }
938 939 } else if (renderer == 'markdown') {
939 940 var attachmentUrl = '[{0}]({1})'.format(attachmentName, attachmentStoreUrl);
940 941 if (isRendered){
941 942 attachmentUrl = '!' + attachmentUrl;
942 943 }
943 944 } else {
944 945 var attachmentUrl = '{}'.format(attachmentStoreUrl);
945 946 }
946 947 cm.replaceRange(attachmentUrl+'\n', CodeMirror.Pos(cm.lastLine()));
947 948
948 949 return false;
949 950 };
950 951
951 952 //see: https://www.dropzonejs.com/#configuration
952 953 var storeUrl = pyroutes.url('repo_commit_comment_attachment_upload',
953 954 {'repo_name': templateContext.repo_name,
954 955 'commit_id': templateContext.commit_data.commit_id})
955 956
956 957 var previewTmpl = $(formElement).find('.comment-attachment-uploader-template').get(0);
957 958 if (previewTmpl !== undefined){
958 959 var selectLink = $(formElement).find('.pick-attachment').get(0);
959 960 $(formElement).find('.comment-attachment-uploader').dropzone({
960 961 url: storeUrl,
961 962 headers: {"X-CSRF-Token": CSRF_TOKEN},
962 963 paramName: function () {
963 964 return "attachment"
964 965 }, // The name that will be used to transfer the file
965 966 clickable: selectLink,
966 967 parallelUploads: 1,
967 968 maxFiles: 10,
968 969 maxFilesize: templateContext.attachment_store.max_file_size_mb,
969 970 uploadMultiple: false,
970 971 autoProcessQueue: true, // if false queue will not be processed automatically.
971 972 createImageThumbnails: false,
972 973 previewTemplate: previewTmpl.innerHTML,
973 974
974 975 accept: function (file, done) {
975 976 done();
976 977 },
977 978 init: function () {
978 979
979 980 this.on("sending", function (file, xhr, formData) {
980 981 $(formElement).find('.comment-attachment-uploader').find('.dropzone-text').hide();
981 982 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').show();
982 983 });
983 984
984 985 this.on("success", function (file, response) {
985 986 $(formElement).find('.comment-attachment-uploader').find('.dropzone-text').show();
986 987 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').hide();
987 988
988 989 var isRendered = false;
989 990 var ext = file.name.split('.').pop();
990 991 var imageExts = templateContext.attachment_store.image_ext;
991 992 if (imageExts.indexOf(ext) !== -1){
992 993 isRendered = true;
993 994 }
994 995
995 996 insertAttachmentText(cm, file.name, response.repo_fqn_access_path, isRendered)
996 997 });
997 998
998 999 this.on("error", function (file, errorMessage, xhr) {
999 1000 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').hide();
1000 1001
1001 1002 var error = null;
1002 1003
1003 1004 if (xhr !== undefined){
1004 1005 var httpStatus = xhr.status + " " + xhr.statusText;
1005 1006 if (xhr !== undefined && xhr.status >= 500) {
1006 1007 error = httpStatus;
1007 1008 }
1008 1009 }
1009 1010
1010 1011 if (error === null) {
1011 1012 error = errorMessage.error || errorMessage || httpStatus;
1012 1013 }
1013 1014 $(file.previewElement).find('.dz-error-message').html('ERROR: {0}'.format(error));
1014 1015
1015 1016 });
1016 1017 }
1017 1018 });
1018 1019 }
1019 1020 return commentForm;
1020 1021 };
1021 1022
1022 1023 this.createGeneralComment = function (lineNo, placeholderText, resolvesCommentId) {
1023 1024
1024 1025 var tmpl = $('#cb-comment-general-form-template').html();
1025 1026 tmpl = tmpl.format(null, 'general');
1026 1027 var $form = $(tmpl);
1027 1028
1028 1029 var $formPlaceholder = $('#cb-comment-general-form-placeholder');
1029 1030 var curForm = $formPlaceholder.find('form');
1030 1031 if (curForm){
1031 1032 curForm.remove();
1032 1033 }
1033 1034 $formPlaceholder.append($form);
1034 1035
1035 1036 var _form = $($form[0]);
1036 1037 var autocompleteActions = ['approve', 'reject', 'as_note', 'as_todo'];
1037 1038 var edit = false;
1038 1039 var comment_id = null;
1039 1040 var commentForm = this.createCommentForm(
1040 1041 _form, lineNo, placeholderText, autocompleteActions, resolvesCommentId, edit, comment_id);
1041 1042 commentForm.initStatusChangeSelector();
1042 1043
1043 1044 return commentForm;
1044 1045 };
1045 1046
1046 1047 this.editComment = function(node, line_no, f_path) {
1047 1048 self.edit = true;
1048 1049 var $node = $(node);
1049 1050 var $td = $node.closest('td');
1050 1051
1051 1052 var $comment = $(node).closest('.comment');
1052 1053 var comment_id = $($comment).data('commentId');
1053 1054 var isDraft = $($comment).data('commentDraft');
1054 1055 var $editForm = null
1055 1056
1056 1057 var $comments = $node.closest('div.inline-comments');
1057 1058 var $general_comments = null;
1058 1059
1059 1060 if($comments.length){
1060 1061 // inline comments setup
1061 1062 $editForm = $comments.find('.comment-inline-form');
1062 1063 line_no = self.getLineNumber(node)
1063 1064 }
1064 1065 else{
1065 1066 // general comments setup
1066 1067 $comments = $('#comments');
1067 1068 $editForm = $comments.find('.comment-inline-form');
1068 1069 line_no = $comment[0].id
1069 1070 $('#cb-comment-general-form-placeholder').hide();
1070 1071 }
1071 1072
1072 1073 if ($editForm.length === 0) {
1073 1074
1074 1075 // unhide all comments if they are hidden for a proper REPLY mode
1075 1076 var $filediff = $node.closest('.filediff');
1076 1077 $filediff.removeClass('hide-comments');
1077 1078
1078 1079 $editForm = self.createNewFormWrapper(f_path, line_no);
1079 1080 if(f_path && line_no) {
1080 1081 $editForm.addClass('comment-inline-form-edit')
1081 1082 }
1082 1083
1083 1084 $comment.after($editForm)
1084 1085
1085 1086 var _form = $($editForm[0]).find('form');
1086 1087 var autocompleteActions = ['as_note',];
1087 1088 var commentForm = this.createCommentForm(
1088 1089 _form, line_no, '', autocompleteActions, resolvesCommentId,
1089 1090 this.edit, comment_id);
1090 1091 var old_comment_text_binary = $comment.attr('data-comment-text');
1091 1092 var old_comment_text = b64DecodeUnicode(old_comment_text_binary);
1092 1093 commentForm.cm.setValue(old_comment_text);
1093 1094 $comment.hide();
1094 1095 tooltipActivate();
1095 1096
1096 1097 // set a CUSTOM submit handler for inline comment edit action.
1097 1098 commentForm.setHandleFormSubmit(function(o) {
1098 1099 var text = commentForm.cm.getValue();
1099 1100 var commentType = commentForm.getCommentType();
1100 1101
1101 1102 if (text === "") {
1102 1103 return;
1103 1104 }
1104 1105
1105 1106 if (old_comment_text == text) {
1106 1107 SwalNoAnimation.fire({
1107 1108 title: 'Unable to edit comment',
1108 1109 html: _gettext('Comment body was not changed.'),
1109 1110 });
1110 1111 return;
1111 1112 }
1112 1113 var excludeCancelBtn = false;
1113 1114 var submitEvent = true;
1114 1115 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
1115 1116 commentForm.cm.setOption("readOnly", true);
1116 1117
1117 1118 // Read last version known
1118 1119 var versionSelector = $('#comment_versions_{0}'.format(comment_id));
1119 1120 var version = versionSelector.data('lastVersion');
1120 1121
1121 1122 if (!version) {
1122 1123 version = 0;
1123 1124 }
1124 1125
1125 1126 var postData = {
1126 1127 'text': text,
1127 1128 'f_path': f_path,
1128 1129 'line': line_no,
1129 1130 'comment_type': commentType,
1130 1131 'draft': isDraft,
1131 1132 'version': version,
1132 1133 'csrf_token': CSRF_TOKEN
1133 1134 };
1134 1135
1135 1136 var submitSuccessCallback = function(json_data) {
1136 1137 $editForm.remove();
1137 1138 $comment.show();
1138 1139 var postData = {
1139 1140 'text': text,
1140 1141 'renderer': $comment.attr('data-comment-renderer'),
1141 1142 'csrf_token': CSRF_TOKEN
1142 1143 };
1143 1144
1144 1145 /* Inject new edited version selector */
1145 1146 var updateCommentVersionDropDown = function () {
1146 1147 var versionSelectId = '#comment_versions_'+comment_id;
1147 1148 var preLoadVersionData = [
1148 1149 {
1149 1150 id: json_data['comment_version'],
1150 1151 text: "v{0}".format(json_data['comment_version']),
1151 1152 action: function () {
1152 1153 Rhodecode.comments.showVersion(
1153 1154 json_data['comment_id'],
1154 1155 json_data['comment_history_id']
1155 1156 )
1156 1157 },
1157 1158 comment_version: json_data['comment_version'],
1158 1159 comment_author_username: json_data['comment_author_username'],
1159 1160 comment_author_gravatar: json_data['comment_author_gravatar'],
1160 1161 comment_created_on: json_data['comment_created_on'],
1161 1162 },
1162 1163 ]
1163 1164
1164 1165
1165 1166 if ($(versionSelectId).data('select2')) {
1166 1167 var oldData = $(versionSelectId).data('select2').opts.data.results;
1167 1168 $(versionSelectId).select2("destroy");
1168 1169 preLoadVersionData = oldData.concat(preLoadVersionData)
1169 1170 }
1170 1171
1171 1172 initVersionSelector(versionSelectId, {results: preLoadVersionData});
1172 1173
1173 1174 $comment.attr('data-comment-text', utf8ToB64(text));
1174 1175
1175 1176 var versionSelector = $('#comment_versions_'+comment_id);
1176 1177
1177 1178 // set lastVersion so we know our last edit version
1178 1179 versionSelector.data('lastVersion', json_data['comment_version'])
1179 1180 versionSelector.parent().show();
1180 1181 }
1181 1182 updateCommentVersionDropDown();
1182 1183
1183 1184 // by default we reset state of comment preserving the text
1184 1185 var failRenderCommit = function(jqXHR, textStatus, errorThrown) {
1185 1186 var prefix = "Error while editing this comment.\n"
1186 1187 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1187 1188 ajaxErrorSwal(message);
1188 1189 };
1189 1190
1190 1191 var successRenderCommit = function(o){
1191 1192 $comment.show();
1192 1193 $comment[0].lastElementChild.innerHTML = o;
1193 1194 };
1194 1195
1195 1196 var previewUrl = pyroutes.url(
1196 1197 'repo_commit_comment_preview',
1197 1198 {'repo_name': templateContext.repo_name,
1198 1199 'commit_id': templateContext.commit_data.commit_id});
1199 1200
1200 1201 _submitAjaxPOST(
1201 1202 previewUrl, postData, successRenderCommit, failRenderCommit
1202 1203 );
1203 1204
1204 1205 try {
1205 1206 var html = json_data.rendered_text;
1206 1207 var lineno = json_data.line_no;
1207 1208 var target_id = json_data.target_id;
1208 1209
1209 1210 $comments.find('.cb-comment-add-button').before(html);
1210 1211
1211 1212 // run global callback on submit
1212 1213 commentForm.globalSubmitSuccessCallback({draft: isDraft, comment_id: comment_id});
1213 1214
1214 1215 } catch (e) {
1215 1216 console.error(e);
1216 1217 }
1217 1218
1218 1219 // re trigger the linkification of next/prev navigation
1219 1220 linkifyComments($('.inline-comment-injected'));
1220 1221 timeagoActivate();
1221 1222 tooltipActivate();
1222 1223
1223 1224 if (window.updateSticky !== undefined) {
1224 1225 // potentially our comments change the active window size, so we
1225 1226 // notify sticky elements
1226 1227 updateSticky()
1227 1228 }
1228 1229
1229 1230 if (window.refreshAllComments !== undefined && !isDraft) {
1230 1231 // if we have this handler, run it, and refresh all comments boxes
1231 1232 refreshAllComments()
1232 1233 }
1233 1234 else if (window.refreshDraftComments !== undefined && isDraft) {
1234 1235 // if we have this handler, run it, and refresh all comments boxes
1235 1236 refreshDraftComments();
1236 1237 }
1237 1238
1238 1239 commentForm.setActionButtonsDisabled(false);
1239 1240
1240 1241 };
1241 1242
1242 1243 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
1243 1244 var prefix = "Error while editing comment.\n"
1244 1245 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1245 1246 if (jqXHR.status == 409){
1246 1247 message = 'This comment was probably changed somewhere else. Please reload the content of this comment.'
1247 1248 ajaxErrorSwal(message, 'Comment version mismatch.');
1248 1249 } else {
1249 1250 ajaxErrorSwal(message);
1250 1251 }
1251 1252
1252 1253 commentForm.resetCommentFormState(text)
1253 1254 };
1254 1255 commentForm.submitAjaxPOST(
1255 1256 commentForm.submitUrl, postData,
1256 1257 submitSuccessCallback,
1257 1258 submitFailCallback);
1258 1259 });
1259 1260 }
1260 1261
1261 1262 $editForm.addClass('comment-inline-form-open');
1262 1263 };
1263 1264
1264 1265 this.attachComment = function(json_data) {
1265 1266 var self = this;
1266 1267 $.each(json_data, function(idx, val) {
1267 1268 var json_data_elem = [val]
1268 1269 var isInline = val.comment_f_path && val.comment_lineno
1269 1270
1270 1271 if (isInline) {
1271 1272 self.attachInlineComment(json_data_elem)
1272 1273 } else {
1273 1274 self.attachGeneralComment(json_data_elem)
1274 1275 }
1275 1276 })
1276 1277
1277 1278 }
1278 1279
1279 1280 this.attachGeneralComment = function(json_data) {
1280 1281 $.each(json_data, function(idx, val) {
1281 1282 $('#injected_page_comments').append(val.rendered_text);
1282 1283 })
1283 1284 }
1284 1285
1285 1286 this.attachInlineComment = function(json_data) {
1286 1287
1287 1288 $.each(json_data, function (idx, val) {
1288 1289 var line_qry = '*[data-line-no="{0}"]'.format(val.line_no);
1289 1290 var html = val.rendered_text;
1290 1291 var $inlineComments = $('#' + val.target_id)
1291 1292 .find(line_qry)
1292 1293 .find('.inline-comments');
1293 1294
1294 1295 var lastComment = $inlineComments.find('.comment-inline').last();
1295 1296
1296 1297 if (lastComment.length === 0) {
1297 1298 // first comment, we append simply
1298 1299 $inlineComments.find('.reply-thread-container-wrapper').before(html);
1299 1300 } else {
1300 1301 $(lastComment).after(html)
1301 1302 }
1302 1303
1303 1304 })
1304 1305
1305 1306 };
1306 1307
1307 1308 this.createNewFormWrapper = function(f_path, line_no) {
1308 1309 // create a new reply HTML form from template
1309 1310 var tmpl = $('#cb-comment-inline-form-template').html();
1310 1311 tmpl = tmpl.format(escapeHtml(f_path), line_no);
1311 1312 return $(tmpl);
1312 1313 }
1313 1314
1314 1315 this.markCommentResolved = function(commentId) {
1315 1316 $('#comment-label-{0}'.format(commentId)).find('.resolved').show();
1316 1317 $('#comment-label-{0}'.format(commentId)).find('.resolve').hide();
1317 1318 };
1318 1319
1319 1320 this.createComment = function(node, f_path, line_no, resolutionComment) {
1320 1321 self.edit = false;
1321 1322 var $node = $(node);
1322 1323 var $td = $node.closest('td');
1323 1324 var resolvesCommentId = resolutionComment || null;
1324 1325
1325 1326 var $replyForm = $td.find('.comment-inline-form');
1326 1327
1327 1328 // if form isn't existing, we're generating a new one and injecting it.
1328 1329 if ($replyForm.length === 0) {
1329 1330
1330 1331 // unhide/expand all comments if they are hidden for a proper REPLY mode
1331 1332 self.toggleLineComments($node, true);
1332 1333
1333 1334 $replyForm = self.createNewFormWrapper(f_path, line_no);
1334 1335
1335 1336 var $comments = $td.find('.inline-comments');
1336 1337
1337 1338 // There aren't any comments, we init the `.inline-comments` with `reply-thread-container` first
1338 1339 if ($comments.length===0) {
1339 1340 var replBtn = '<button class="cb-comment-add-button" onclick="return Rhodecode.comments.createComment(this, \'{0}\', \'{1}\', null)">Reply...</button>'.format(escapeHtml(f_path), line_no)
1340 1341 var $reply_container = $('#cb-comments-inline-container-template')
1341 1342 $reply_container.find('button.cb-comment-add-button').replaceWith(replBtn);
1342 1343 $td.append($($reply_container).html());
1343 1344 }
1344 1345
1345 1346 // default comment button exists, so we prepend the form for leaving initial comment
1346 1347 $td.find('.cb-comment-add-button').before($replyForm);
1347 1348 // set marker, that we have a open form
1348 1349 var $replyWrapper = $td.find('.reply-thread-container-wrapper')
1349 1350 $replyWrapper.addClass('comment-form-active');
1350 1351
1351 1352 var lastComment = $comments.find('.comment-inline').last();
1352 1353 if ($(lastComment).hasClass('comment-outdated')) {
1353 1354 $replyWrapper.show();
1354 1355 }
1355 1356
1356 1357 var _form = $($replyForm[0]).find('form');
1357 1358 var autocompleteActions = ['as_note', 'as_todo'];
1358 1359 var comment_id=null;
1359 1360 var placeholderText = _gettext('Leave a comment on file {0} line {1}.').format(f_path, line_no);
1360 1361 var commentForm = self.createCommentForm(
1361 1362 _form, line_no, placeholderText, autocompleteActions, resolvesCommentId,
1362 1363 self.edit, comment_id);
1363 1364
1364 1365 // set a CUSTOM submit handler for inline comments.
1365 1366 commentForm.setHandleFormSubmit(function(o) {
1366 1367 var text = commentForm.cm.getValue();
1367 1368 var commentType = commentForm.getCommentType();
1368 1369 var resolvesCommentId = commentForm.getResolvesId();
1369 1370 var isDraft = commentForm.getDraftState();
1370 1371
1371 1372 if (text === "") {
1372 1373 return;
1373 1374 }
1374 1375
1375 1376 if (line_no === undefined) {
1376 1377 alert('Error: unable to fetch line number for this inline comment !');
1377 1378 return;
1378 1379 }
1379 1380
1380 1381 if (f_path === undefined) {
1381 1382 alert('Error: unable to fetch file path for this inline comment !');
1382 1383 return;
1383 1384 }
1384 1385
1385 1386 var excludeCancelBtn = false;
1386 1387 var submitEvent = true;
1387 1388 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
1388 1389 commentForm.cm.setOption("readOnly", true);
1389 1390 var postData = {
1390 1391 'text': text,
1391 1392 'f_path': f_path,
1392 1393 'line': line_no,
1393 1394 'comment_type': commentType,
1394 1395 'draft': isDraft,
1395 1396 'csrf_token': CSRF_TOKEN
1396 1397 };
1397 1398 if (resolvesCommentId){
1398 1399 postData['resolves_comment_id'] = resolvesCommentId;
1399 1400 }
1400 1401
1401 1402 // submitSuccess for inline commits
1402 1403 var submitSuccessCallback = function(json_data) {
1403 1404
1404 1405 $replyForm.remove();
1405 1406 $td.find('.reply-thread-container-wrapper').removeClass('comment-form-active');
1406 1407
1407 1408 try {
1408 1409
1409 1410 // inject newly created comments, json_data is {<comment_id>: {}}
1410 1411 self.attachInlineComment(json_data)
1411 1412
1412 1413 //mark visually which comment was resolved
1413 1414 if (resolvesCommentId) {
1414 1415 self.markCommentResolved(resolvesCommentId);
1415 1416 }
1416 1417
1417 1418 // run global callback on submit
1418 1419 commentForm.globalSubmitSuccessCallback({
1419 1420 draft: isDraft,
1420 1421 comment_id: comment_id
1421 1422 });
1422 1423
1423 1424 } catch (e) {
1424 1425 console.error(e);
1425 1426 }
1426 1427
1427 1428 if (window.updateSticky !== undefined) {
1428 1429 // potentially our comments change the active window size, so we
1429 1430 // notify sticky elements
1430 1431 updateSticky()
1431 1432 }
1432 1433
1433 1434 if (window.refreshAllComments !== undefined && !isDraft) {
1434 1435 // if we have this handler, run it, and refresh all comments boxes
1435 1436 refreshAllComments()
1436 1437 }
1437 1438 else if (window.refreshDraftComments !== undefined && isDraft) {
1438 1439 // if we have this handler, run it, and refresh all comments boxes
1439 1440 refreshDraftComments();
1440 1441 }
1441 1442
1442 1443 commentForm.setActionButtonsDisabled(false);
1443 1444
1444 1445 // re trigger the linkification of next/prev navigation
1445 1446 linkifyComments($('.inline-comment-injected'));
1446 1447 timeagoActivate();
1447 1448 tooltipActivate();
1448 1449 };
1449 1450
1450 1451 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
1451 1452 var prefix = "Error while submitting comment.\n"
1452 1453 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1453 1454 ajaxErrorSwal(message);
1454 1455 commentForm.resetCommentFormState(text)
1455 1456 };
1456 1457
1457 1458 commentForm.submitAjaxPOST(
1458 1459 commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback);
1459 1460 });
1460 1461 }
1461 1462
1462 1463 // Finally "open" our reply form, since we know there are comments and we have the "attached" old form
1463 1464 $replyForm.addClass('comment-inline-form-open');
1464 1465 tooltipActivate();
1465 1466 };
1466 1467
1467 1468 this.createResolutionComment = function(commentId){
1468 1469 // hide the trigger text
1469 1470 $('#resolve-comment-{0}'.format(commentId)).hide();
1470 1471
1471 1472 var comment = $('#comment-'+commentId);
1472 1473 var commentData = comment.data();
1473 1474
1474 1475 if (commentData.commentInline) {
1475 1476 var f_path = commentData.commentFPath;
1476 1477 var line_no = commentData.commentLineNo;
1477 1478 this.createComment(comment, f_path, line_no, commentId)
1478 1479 } else {
1479 1480 this.createGeneralComment('general', "$placeholder", commentId)
1480 1481 }
1481 1482
1482 1483 return false;
1483 1484 };
1484 1485
1485 1486 this.submitResolution = function(commentId){
1486 1487 var form = $('#resolve_comment_{0}'.format(commentId)).closest('form');
1487 1488 var commentForm = form.get(0).CommentForm;
1488 1489
1489 1490 var cm = commentForm.getCmInstance();
1490 1491 var renderer = templateContext.visual.default_renderer;
1491 1492 if (renderer == 'rst'){
1492 1493 var commentUrl = '`#{0} <{1}#comment-{0}>`_'.format(commentId, commentForm.selfUrl);
1493 1494 } else if (renderer == 'markdown') {
1494 1495 var commentUrl = '[#{0}]({1}#comment-{0})'.format(commentId, commentForm.selfUrl);
1495 1496 } else {
1496 1497 var commentUrl = '{1}#comment-{0}'.format(commentId, commentForm.selfUrl);
1497 1498 }
1498 1499
1499 1500 cm.setValue(_gettext('TODO from comment {0} was fixed.').format(commentUrl));
1500 1501 form.submit();
1501 1502 return false;
1502 1503 };
1503 1504
1504 1505 this.resolveTodo = function (elem, todoId) {
1505 1506 var commentId = todoId;
1506 1507
1507 1508 SwalNoAnimation.fire({
1508 1509 title: 'Resolve TODO {0}'.format(todoId),
1509 1510 showCancelButton: true,
1510 1511 confirmButtonText: _gettext('Yes'),
1511 1512 showLoaderOnConfirm: true,
1512 1513
1513 1514 allowOutsideClick: function () {
1514 1515 !Swal.isLoading()
1515 1516 },
1516 1517 preConfirm: function () {
1517 1518 var comment = $('#comment-' + commentId);
1518 1519 var commentData = comment.data();
1519 1520
1520 1521 var f_path = null
1521 1522 var line_no = null
1522 1523 if (commentData.commentInline) {
1523 1524 f_path = commentData.commentFPath;
1524 1525 line_no = commentData.commentLineNo;
1525 1526 }
1526 1527
1527 1528 var renderer = templateContext.visual.default_renderer;
1528 1529 var commentBoxUrl = '{1}#comment-{0}'.format(commentId);
1529 1530
1530 1531 // Pull request case
1531 1532 if (templateContext.pull_request_data.pull_request_id !== null) {
1532 1533 var commentUrl = pyroutes.url('pullrequest_comment_create',
1533 1534 {
1534 1535 'repo_name': templateContext.repo_name,
1535 1536 'pull_request_id': templateContext.pull_request_data.pull_request_id,
1536 1537 'comment_id': commentId
1537 1538 });
1538 1539 } else {
1539 1540 var commentUrl = pyroutes.url('repo_commit_comment_create',
1540 1541 {
1541 1542 'repo_name': templateContext.repo_name,
1542 1543 'commit_id': templateContext.commit_data.commit_id,
1543 1544 'comment_id': commentId
1544 1545 });
1545 1546 }
1546 1547
1547 1548 if (renderer === 'rst') {
1548 1549 commentBoxUrl = '`#{0} <{1}#comment-{0}>`_'.format(commentId, commentUrl);
1549 1550 } else if (renderer === 'markdown') {
1550 1551 commentBoxUrl = '[#{0}]({1}#comment-{0})'.format(commentId, commentUrl);
1551 1552 }
1552 1553 var resolveText = _gettext('TODO from comment {0} was fixed.').format(commentBoxUrl);
1553 1554
1554 1555 var postData = {
1555 1556 text: resolveText,
1556 1557 comment_type: 'note',
1557 1558 draft: false,
1558 1559 csrf_token: CSRF_TOKEN,
1559 1560 resolves_comment_id: commentId
1560 1561 }
1561 1562 if (commentData.commentInline) {
1562 1563 postData['f_path'] = f_path;
1563 1564 postData['line'] = line_no;
1564 1565 }
1565 1566
1566 1567 return new Promise(function (resolve, reject) {
1567 1568 $.ajax({
1568 1569 type: 'POST',
1569 1570 data: postData,
1570 1571 url: commentUrl,
1571 1572 headers: {'X-PARTIAL-XHR': true}
1572 1573 })
1573 1574 .done(function (data) {
1574 1575 resolve(data);
1575 1576 })
1576 1577 .fail(function (jqXHR, textStatus, errorThrown) {
1577 1578 var prefix = "Error while resolving TODO.\n"
1578 1579 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1579 1580 ajaxErrorSwal(message);
1580 1581 });
1581 1582 })
1582 1583 }
1583 1584
1584 1585 })
1585 1586 .then(function (result) {
1586 1587 var success = function (json_data) {
1587 1588 resolvesCommentId = commentId;
1588 1589 var commentResolved = json_data[Object.keys(json_data)[0]]
1589 1590
1590 1591 try {
1591 1592
1592 1593 if (commentResolved.f_path) {
1593 1594 // inject newly created comments, json_data is {<comment_id>: {}}
1594 1595 self.attachInlineComment(json_data)
1595 1596 } else {
1596 1597 self.attachGeneralComment(json_data)
1597 1598 }
1598 1599
1599 1600 //mark visually which comment was resolved
1600 1601 if (resolvesCommentId) {
1601 1602 self.markCommentResolved(resolvesCommentId);
1602 1603 }
1603 1604
1604 1605 // run global callback on submit
1605 1606 if (window.commentFormGlobalSubmitSuccessCallback !== undefined) {
1606 1607 commentFormGlobalSubmitSuccessCallback({
1607 1608 draft: false,
1608 1609 comment_id: commentId
1609 1610 });
1610 1611 }
1611 1612
1612 1613 } catch (e) {
1613 1614 console.error(e);
1614 1615 }
1615 1616
1616 1617 if (window.updateSticky !== undefined) {
1617 1618 // potentially our comments change the active window size, so we
1618 1619 // notify sticky elements
1619 1620 updateSticky()
1620 1621 }
1621 1622
1622 1623 if (window.refreshAllComments !== undefined) {
1623 1624 // if we have this handler, run it, and refresh all comments boxes
1624 1625 refreshAllComments()
1625 1626 }
1626 1627 // re trigger the linkification of next/prev navigation
1627 1628 linkifyComments($('.inline-comment-injected'));
1628 1629 timeagoActivate();
1629 1630 tooltipActivate();
1630 1631 };
1631 1632
1632 1633 if (result.value) {
1633 1634 $(elem).remove();
1634 1635 success(result.value)
1635 1636 }
1636 1637 })
1637 1638 };
1638 1639
1639 1640 };
1640 1641
1641 1642 window.commentHelp = function(renderer) {
1642 1643 var funcData = {'renderer': renderer}
1643 1644 return renderTemplate('commentHelpHovercard', funcData)
1644 1645 }
General Comments 0
You need to be logged in to leave comments. Login now