##// END OF EJS Templates
pull-requests: change the naming from #NUM into !NUM....
dan -
r4039:6942c656 default
parent child Browse files
Show More
@@ -1,76 +1,76 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2014-2019 RhodeCode GmbH
3 # Copyright (C) 2014-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Internal settings for vcs-lib
22 Internal settings for vcs-lib
23 """
23 """
24
24
25 # list of default encoding used in safe_unicode/safe_str methods
25 # list of default encoding used in safe_unicode/safe_str methods
26 DEFAULT_ENCODINGS = ['utf8']
26 DEFAULT_ENCODINGS = ['utf8']
27
27
28
28
29 # Compatibility version when creating SVN repositories. None means newest.
29 # Compatibility version when creating SVN repositories. None means newest.
30 # Other available options are: pre-1.4-compatible, pre-1.5-compatible,
30 # Other available options are: pre-1.4-compatible, pre-1.5-compatible,
31 # pre-1.6-compatible, pre-1.8-compatible
31 # pre-1.6-compatible, pre-1.8-compatible
32 SVN_COMPATIBLE_VERSION = None
32 SVN_COMPATIBLE_VERSION = None
33
33
34 ALIASES = ['hg', 'git', 'svn']
34 ALIASES = ['hg', 'git', 'svn']
35
35
36 BACKENDS = {
36 BACKENDS = {
37 'hg': 'rhodecode.lib.vcs.backends.hg.MercurialRepository',
37 'hg': 'rhodecode.lib.vcs.backends.hg.MercurialRepository',
38 'git': 'rhodecode.lib.vcs.backends.git.GitRepository',
38 'git': 'rhodecode.lib.vcs.backends.git.GitRepository',
39 'svn': 'rhodecode.lib.vcs.backends.svn.SubversionRepository',
39 'svn': 'rhodecode.lib.vcs.backends.svn.SubversionRepository',
40 }
40 }
41
41
42
42
43 ARCHIVE_SPECS = [
43 ARCHIVE_SPECS = [
44 ('tbz2', 'application/x-bzip2', 'tbz2'),
44 ('tbz2', 'application/x-bzip2', 'tbz2'),
45 ('tbz2', 'application/x-bzip2', '.tar.bz2'),
45 ('tbz2', 'application/x-bzip2', '.tar.bz2'),
46
46
47 ('tgz', 'application/x-gzip', '.tgz'),
47 ('tgz', 'application/x-gzip', '.tgz'),
48 ('tgz', 'application/x-gzip', '.tar.gz'),
48 ('tgz', 'application/x-gzip', '.tar.gz'),
49
49
50 ('zip', 'application/zip', '.zip'),
50 ('zip', 'application/zip', '.zip'),
51 ]
51 ]
52
52
53 HOOKS_PROTOCOL = None
53 HOOKS_PROTOCOL = None
54 HOOKS_DIRECT_CALLS = False
54 HOOKS_DIRECT_CALLS = False
55 HOOKS_HOST = '127.0.0.1'
55 HOOKS_HOST = '127.0.0.1'
56
56
57
57
58 MERGE_MESSAGE_TMPL = (
58 MERGE_MESSAGE_TMPL = (
59 u'Merge pull request #{pr_id} from {source_repo} {source_ref_name}\n\n '
59 u'Merge pull request !{pr_id} from {source_repo} {source_ref_name}\n\n '
60 u'{pr_title}')
60 u'{pr_title}')
61 MERGE_DRY_RUN_MESSAGE = 'dry_run_merge_message_from_rhodecode'
61 MERGE_DRY_RUN_MESSAGE = 'dry_run_merge_message_from_rhodecode'
62 MERGE_DRY_RUN_USER = 'Dry-Run User'
62 MERGE_DRY_RUN_USER = 'Dry-Run User'
63 MERGE_DRY_RUN_EMAIL = 'dry-run-merge@rhodecode.com'
63 MERGE_DRY_RUN_EMAIL = 'dry-run-merge@rhodecode.com'
64
64
65
65
66 def available_aliases():
66 def available_aliases():
67 """
67 """
68 Mercurial is required for the system to work, so in case vcs.backends does
68 Mercurial is required for the system to work, so in case vcs.backends does
69 not include it, we make sure it will be available internally
69 not include it, we make sure it will be available internally
70 TODO: anderson: refactor vcs.backends so it won't be necessary, VCS server
70 TODO: anderson: refactor vcs.backends so it won't be necessary, VCS server
71 should be responsible to dictate available backends.
71 should be responsible to dictate available backends.
72 """
72 """
73 aliases = ALIASES[:]
73 aliases = ALIASES[:]
74 if 'hg' not in aliases:
74 if 'hg' not in aliases:
75 aliases += ['hg']
75 aliases += ['hg']
76 return aliases
76 return aliases
@@ -1,94 +1,94 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <div class="panel panel-default">
3 <div class="panel panel-default">
4 <div class="panel-body">
4 <div class="panel-body">
5 %if c.closed:
5 %if c.closed:
6 ${h.checkbox('show_closed',checked="checked", label=_('Show Closed Pull Requests'))}
6 ${h.checkbox('show_closed',checked="checked", label=_('Show Closed Pull Requests'))}
7 %else:
7 %else:
8 ${h.checkbox('show_closed',label=_('Show Closed Pull Requests'))}
8 ${h.checkbox('show_closed',label=_('Show Closed Pull Requests'))}
9 %endif
9 %endif
10 </div>
10 </div>
11 </div>
11 </div>
12
12
13 <div class="panel panel-default">
13 <div class="panel panel-default">
14 <div class="panel-heading">
14 <div class="panel-heading">
15 <h3 class="panel-title">${_('Pull Requests You Participate In')}</h3>
15 <h3 class="panel-title">${_('Pull Requests You Participate In')}</h3>
16 </div>
16 </div>
17 <div class="panel-body panel-body-min-height">
17 <div class="panel-body panel-body-min-height">
18 <table id="pull_request_list_table" class="display"></table>
18 <table id="pull_request_list_table" class="display"></table>
19 </div>
19 </div>
20 </div>
20 </div>
21
21
22 <script type="text/javascript">
22 <script type="text/javascript">
23 $(document).ready(function() {
23 $(document).ready(function() {
24
24
25 $('#show_closed').on('click', function(e){
25 $('#show_closed').on('click', function(e){
26 if($(this).is(":checked")){
26 if($(this).is(":checked")){
27 window.location = "${h.route_path('my_account_pullrequests', _query={'pr_show_closed':1})}";
27 window.location = "${h.route_path('my_account_pullrequests', _query={'pr_show_closed':1})}";
28 }
28 }
29 else{
29 else{
30 window.location = "${h.route_path('my_account_pullrequests')}";
30 window.location = "${h.route_path('my_account_pullrequests')}";
31 }
31 }
32 });
32 });
33
33
34 var $pullRequestListTable = $('#pull_request_list_table');
34 var $pullRequestListTable = $('#pull_request_list_table');
35
35
36 // participating object list
36 // participating object list
37 $pullRequestListTable.DataTable({
37 $pullRequestListTable.DataTable({
38 processing: true,
38 processing: true,
39 serverSide: true,
39 serverSide: true,
40 ajax: {
40 ajax: {
41 "url": "${h.route_path('my_account_pullrequests_data')}",
41 "url": "${h.route_path('my_account_pullrequests_data')}",
42 "data": function (d) {
42 "data": function (d) {
43 d.closed = "${c.closed}";
43 d.closed = "${c.closed}";
44 }
44 }
45 },
45 },
46 dom: 'rtp',
46 dom: 'rtp',
47 pageLength: ${c.visual.dashboard_items},
47 pageLength: ${c.visual.dashboard_items},
48 order: [[ 2, "desc" ]],
48 order: [[ 2, "desc" ]],
49 columns: [
49 columns: [
50 { data: {"_": "status",
50 { data: {"_": "status",
51 "sort": "status"}, title: "", className: "td-status", orderable: false},
51 "sort": "status"}, title: "", className: "td-status", orderable: false},
52 { data: {"_": "target_repo",
52 { data: {"_": "target_repo",
53 "sort": "target_repo"}, title: "${_('Target Repo')}", className: "td-targetrepo", orderable: false},
53 "sort": "target_repo"}, title: "${_('Target Repo')}", className: "td-targetrepo", orderable: false},
54 { data: {"_": "name",
54 { data: {"_": "name",
55 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname", "type": "num" },
55 "sort": "name_raw"}, title: "${_('Id')}", className: "td-componentname", "type": "num" },
56 { data: {"_": "title",
57 "sort": "title"}, title: "${_('Title')}", className: "td-description" },
56 { data: {"_": "author",
58 { data: {"_": "author",
57 "sort": "author_raw"}, title: "${_('Author')}", className: "td-user", orderable: false },
59 "sort": "author_raw"}, title: "${_('Author')}", className: "td-user", orderable: false },
58 { data: {"_": "title",
59 "sort": "title"}, title: "${_('Title')}", className: "td-description" },
60 { data: {"_": "comments",
60 { data: {"_": "comments",
61 "sort": "comments_raw"}, title: "", className: "td-comments", orderable: false},
61 "sort": "comments_raw"}, title: "", className: "td-comments", orderable: false},
62 { data: {"_": "updated_on",
62 { data: {"_": "updated_on",
63 "sort": "updated_on_raw"}, title: "${_('Last Update')}", className: "td-time" }
63 "sort": "updated_on_raw"}, title: "${_('Last Update')}", className: "td-time" }
64 ],
64 ],
65 language: {
65 language: {
66 paginate: DEFAULT_GRID_PAGINATION,
66 paginate: DEFAULT_GRID_PAGINATION,
67 sProcessing: _gettext('loading...'),
67 sProcessing: _gettext('loading...'),
68 emptyTable: _gettext("There are currently no open pull requests requiring your participation.")
68 emptyTable: _gettext("There are currently no open pull requests requiring your participation.")
69 },
69 },
70 "drawCallback": function( settings, json ) {
70 "drawCallback": function( settings, json ) {
71 timeagoActivate();
71 timeagoActivate();
72 tooltipActivate();
72 tooltipActivate();
73 },
73 },
74 "createdRow": function ( row, data, index ) {
74 "createdRow": function ( row, data, index ) {
75 if (data['closed']) {
75 if (data['closed']) {
76 $(row).addClass('closed');
76 $(row).addClass('closed');
77 }
77 }
78 if (data['owned']) {
78 if (data['owned']) {
79 $(row).addClass('owned');
79 $(row).addClass('owned');
80 }
80 }
81 if (data['state'] !== 'created') {
81 if (data['state'] !== 'created') {
82 $(row).addClass('state-' + data['state']);
82 $(row).addClass('state-' + data['state']);
83 }
83 }
84 }
84 }
85 });
85 });
86 $pullRequestListTable.on('xhr.dt', function(e, settings, json, xhr){
86 $pullRequestListTable.on('xhr.dt', function(e, settings, json, xhr){
87 $pullRequestListTable.css('opacity', 1);
87 $pullRequestListTable.css('opacity', 1);
88 });
88 });
89
89
90 $pullRequestListTable.on('preXhr.dt', function(e, settings, data){
90 $pullRequestListTable.on('preXhr.dt', function(e, settings, data){
91 $pullRequestListTable.css('opacity', 0.3);
91 $pullRequestListTable.css('opacity', 0.3);
92 });
92 });
93 });
93 });
94 </script>
94 </script>
@@ -1,420 +1,420 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 ## usage:
2 ## usage:
3 ## <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
3 ## <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
4 ## ${comment.comment_block(comment)}
4 ## ${comment.comment_block(comment)}
5 ##
5 ##
6 <%namespace name="base" file="/base/base.mako"/>
6 <%namespace name="base" file="/base/base.mako"/>
7
7
8 <%def name="comment_block(comment, inline=False)">
8 <%def name="comment_block(comment, inline=False)">
9 <% pr_index_ver = comment.get_index_version(getattr(c, 'versions', [])) %>
9 <% pr_index_ver = comment.get_index_version(getattr(c, 'versions', [])) %>
10 <% latest_ver = len(getattr(c, 'versions', [])) %>
10 <% latest_ver = len(getattr(c, 'versions', [])) %>
11 % if inline:
11 % if inline:
12 <% outdated_at_ver = comment.outdated_at_version(getattr(c, 'at_version_num', None)) %>
12 <% outdated_at_ver = comment.outdated_at_version(getattr(c, 'at_version_num', None)) %>
13 % else:
13 % else:
14 <% outdated_at_ver = comment.older_than_version(getattr(c, 'at_version_num', None)) %>
14 <% outdated_at_ver = comment.older_than_version(getattr(c, 'at_version_num', None)) %>
15 % endif
15 % endif
16
16
17
17
18 <div class="comment
18 <div class="comment
19 ${'comment-inline' if inline else 'comment-general'}
19 ${'comment-inline' if inline else 'comment-general'}
20 ${'comment-outdated' if outdated_at_ver else 'comment-current'}"
20 ${'comment-outdated' if outdated_at_ver else 'comment-current'}"
21 id="comment-${comment.comment_id}"
21 id="comment-${comment.comment_id}"
22 line="${comment.line_no}"
22 line="${comment.line_no}"
23 data-comment-id="${comment.comment_id}"
23 data-comment-id="${comment.comment_id}"
24 data-comment-type="${comment.comment_type}"
24 data-comment-type="${comment.comment_type}"
25 data-comment-line-no="${comment.line_no}"
25 data-comment-line-no="${comment.line_no}"
26 data-comment-inline=${h.json.dumps(inline)}
26 data-comment-inline=${h.json.dumps(inline)}
27 style="${'display: none;' if outdated_at_ver else ''}">
27 style="${'display: none;' if outdated_at_ver else ''}">
28
28
29 <div class="meta">
29 <div class="meta">
30 <div class="comment-type-label">
30 <div class="comment-type-label">
31 <div class="comment-label ${comment.comment_type or 'note'}" id="comment-label-${comment.comment_id}" title="line: ${comment.line_no}">
31 <div class="comment-label ${comment.comment_type or 'note'}" id="comment-label-${comment.comment_id}" title="line: ${comment.line_no}">
32 % if comment.comment_type == 'todo':
32 % if comment.comment_type == 'todo':
33 % if comment.resolved:
33 % if comment.resolved:
34 <div class="resolved tooltip" title="${_('Resolved by comment #{}').format(comment.resolved.comment_id)}">
34 <div class="resolved tooltip" title="${_('Resolved by comment #{}').format(comment.resolved.comment_id)}">
35 <a href="#comment-${comment.resolved.comment_id}">${comment.comment_type}</a>
35 <a href="#comment-${comment.resolved.comment_id}">${comment.comment_type}</a>
36 </div>
36 </div>
37 % else:
37 % else:
38 <div class="resolved tooltip" style="display: none">
38 <div class="resolved tooltip" style="display: none">
39 <span>${comment.comment_type}</span>
39 <span>${comment.comment_type}</span>
40 </div>
40 </div>
41 <div class="resolve tooltip" onclick="return Rhodecode.comments.createResolutionComment(${comment.comment_id});" title="${_('Click to resolve this comment')}">
41 <div class="resolve tooltip" onclick="return Rhodecode.comments.createResolutionComment(${comment.comment_id});" title="${_('Click to resolve this comment')}">
42 ${comment.comment_type}
42 ${comment.comment_type}
43 </div>
43 </div>
44 % endif
44 % endif
45 % else:
45 % else:
46 % if comment.resolved_comment:
46 % if comment.resolved_comment:
47 fix
47 fix
48 <a href="#comment-${comment.resolved_comment.comment_id}" onclick="Rhodecode.comments.scrollToComment($('#comment-${comment.resolved_comment.comment_id}'), 0, ${h.json.dumps(comment.resolved_comment.outdated)})">
48 <a href="#comment-${comment.resolved_comment.comment_id}" onclick="Rhodecode.comments.scrollToComment($('#comment-${comment.resolved_comment.comment_id}'), 0, ${h.json.dumps(comment.resolved_comment.outdated)})">
49 <span style="text-decoration: line-through">#${comment.resolved_comment.comment_id}</span>
49 <span style="text-decoration: line-through">#${comment.resolved_comment.comment_id}</span>
50 </a>
50 </a>
51 % else:
51 % else:
52 ${comment.comment_type or 'note'}
52 ${comment.comment_type or 'note'}
53 % endif
53 % endif
54 % endif
54 % endif
55 </div>
55 </div>
56 </div>
56 </div>
57
57
58 <div class="author ${'author-inline' if inline else 'author-general'}">
58 <div class="author ${'author-inline' if inline else 'author-general'}">
59 ${base.gravatar_with_user(comment.author.email, 16, tooltip=True)}
59 ${base.gravatar_with_user(comment.author.email, 16, tooltip=True)}
60 </div>
60 </div>
61 <div class="date">
61 <div class="date">
62 ${h.age_component(comment.modified_at, time_is_local=True)}
62 ${h.age_component(comment.modified_at, time_is_local=True)}
63 </div>
63 </div>
64 % if inline:
64 % if inline:
65 <span></span>
65 <span></span>
66 % else:
66 % else:
67 <div class="status-change">
67 <div class="status-change">
68 % if comment.pull_request:
68 % if comment.pull_request:
69 <a href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id)}">
69 <a href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id)}">
70 % if comment.status_change:
70 % if comment.status_change:
71 ${_('pull request #%s') % comment.pull_request.pull_request_id}:
71 ${_('pull request !{}').format(comment.pull_request.pull_request_id)}:
72 % else:
72 % else:
73 ${_('pull request #%s') % comment.pull_request.pull_request_id}
73 ${_('pull request !{}').format(comment.pull_request.pull_request_id)}
74 % endif
74 % endif
75 </a>
75 </a>
76 % else:
76 % else:
77 % if comment.status_change:
77 % if comment.status_change:
78 ${_('Status change on commit')}:
78 ${_('Status change on commit')}:
79 % endif
79 % endif
80 % endif
80 % endif
81 </div>
81 </div>
82 % endif
82 % endif
83
83
84 % if comment.status_change:
84 % if comment.status_change:
85 <i class="icon-circle review-status-${comment.status_change[0].status}"></i>
85 <i class="icon-circle review-status-${comment.status_change[0].status}"></i>
86 <div title="${_('Commit status')}" class="changeset-status-lbl">
86 <div title="${_('Commit status')}" class="changeset-status-lbl">
87 ${comment.status_change[0].status_lbl}
87 ${comment.status_change[0].status_lbl}
88 </div>
88 </div>
89 % endif
89 % endif
90
90
91 <a class="permalink" href="#comment-${comment.comment_id}"> &para;</a>
91 <a class="permalink" href="#comment-${comment.comment_id}"> &para;</a>
92
92
93 <div class="comment-links-block">
93 <div class="comment-links-block">
94 % if comment.pull_request and comment.pull_request.author.user_id == comment.author.user_id:
94 % if comment.pull_request and comment.pull_request.author.user_id == comment.author.user_id:
95 <span class="tag authortag tooltip" title="${_('Pull request author')}">
95 <span class="tag authortag tooltip" title="${_('Pull request author')}">
96 ${_('author')}
96 ${_('author')}
97 </span>
97 </span>
98 |
98 |
99 % endif
99 % endif
100 % if inline:
100 % if inline:
101 <div class="pr-version-inline">
101 <div class="pr-version-inline">
102 <a href="${request.current_route_path(_query=dict(version=comment.pull_request_version_id), _anchor='comment-{}'.format(comment.comment_id))}">
102 <a href="${request.current_route_path(_query=dict(version=comment.pull_request_version_id), _anchor='comment-{}'.format(comment.comment_id))}">
103 % if outdated_at_ver:
103 % if outdated_at_ver:
104 <code class="pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
104 <code class="pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
105 outdated ${'v{}'.format(pr_index_ver)} |
105 outdated ${'v{}'.format(pr_index_ver)} |
106 </code>
106 </code>
107 % elif pr_index_ver:
107 % elif pr_index_ver:
108 <code class="pr-version-num" title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
108 <code class="pr-version-num" title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
109 ${'v{}'.format(pr_index_ver)} |
109 ${'v{}'.format(pr_index_ver)} |
110 </code>
110 </code>
111 % endif
111 % endif
112 </a>
112 </a>
113 </div>
113 </div>
114 % else:
114 % else:
115 % if comment.pull_request_version_id and pr_index_ver:
115 % if comment.pull_request_version_id and pr_index_ver:
116 |
116 |
117 <div class="pr-version">
117 <div class="pr-version">
118 % if comment.outdated:
118 % if comment.outdated:
119 <a href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}">
119 <a href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}">
120 ${_('Outdated comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}
120 ${_('Outdated comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}
121 </a>
121 </a>
122 % else:
122 % else:
123 <div title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
123 <div title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
124 <a href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id, version=comment.pull_request_version_id)}">
124 <a href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id, version=comment.pull_request_version_id)}">
125 <code class="pr-version-num">
125 <code class="pr-version-num">
126 ${'v{}'.format(pr_index_ver)}
126 ${'v{}'.format(pr_index_ver)}
127 </code>
127 </code>
128 </a>
128 </a>
129 </div>
129 </div>
130 % endif
130 % endif
131 </div>
131 </div>
132 % endif
132 % endif
133 % endif
133 % endif
134
134
135 ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed
135 ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed
136 ## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated
136 ## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated
137 %if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())):
137 %if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())):
138 ## permissions to delete
138 ## permissions to delete
139 %if c.is_super_admin or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id:
139 %if c.is_super_admin or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id:
140 ## TODO: dan: add edit comment here
140 ## TODO: dan: add edit comment here
141 <a onclick="return Rhodecode.comments.deleteComment(this);" class="delete-comment"> ${_('Delete')}</a>
141 <a onclick="return Rhodecode.comments.deleteComment(this);" class="delete-comment"> ${_('Delete')}</a>
142 %else:
142 %else:
143 <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
143 <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
144 %endif
144 %endif
145 %else:
145 %else:
146 <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
146 <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
147 %endif
147 %endif
148
148
149 % if outdated_at_ver:
149 % if outdated_at_ver:
150 | <a onclick="return Rhodecode.comments.prevOutdatedComment(this);" class="prev-comment"> ${_('Prev')}</a>
150 | <a onclick="return Rhodecode.comments.prevOutdatedComment(this);" class="prev-comment"> ${_('Prev')}</a>
151 | <a onclick="return Rhodecode.comments.nextOutdatedComment(this);" class="next-comment"> ${_('Next')}</a>
151 | <a onclick="return Rhodecode.comments.nextOutdatedComment(this);" class="next-comment"> ${_('Next')}</a>
152 % else:
152 % else:
153 | <a onclick="return Rhodecode.comments.prevComment(this);" class="prev-comment"> ${_('Prev')}</a>
153 | <a onclick="return Rhodecode.comments.prevComment(this);" class="prev-comment"> ${_('Prev')}</a>
154 | <a onclick="return Rhodecode.comments.nextComment(this);" class="next-comment"> ${_('Next')}</a>
154 | <a onclick="return Rhodecode.comments.nextComment(this);" class="next-comment"> ${_('Next')}</a>
155 % endif
155 % endif
156
156
157 </div>
157 </div>
158 </div>
158 </div>
159 <div class="text">
159 <div class="text">
160 ${h.render(comment.text, renderer=comment.renderer, mentions=True)}
160 ${h.render(comment.text, renderer=comment.renderer, mentions=True)}
161 </div>
161 </div>
162
162
163 </div>
163 </div>
164 </%def>
164 </%def>
165
165
166 ## generate main comments
166 ## generate main comments
167 <%def name="generate_comments(comments, include_pull_request=False, is_pull_request=False)">
167 <%def name="generate_comments(comments, include_pull_request=False, is_pull_request=False)">
168 <div class="general-comments" id="comments">
168 <div class="general-comments" id="comments">
169 %for comment in comments:
169 %for comment in comments:
170 <div id="comment-tr-${comment.comment_id}">
170 <div id="comment-tr-${comment.comment_id}">
171 ## only render comments that are not from pull request, or from
171 ## only render comments that are not from pull request, or from
172 ## pull request and a status change
172 ## pull request and a status change
173 %if not comment.pull_request or (comment.pull_request and comment.status_change) or include_pull_request:
173 %if not comment.pull_request or (comment.pull_request and comment.status_change) or include_pull_request:
174 ${comment_block(comment)}
174 ${comment_block(comment)}
175 %endif
175 %endif
176 </div>
176 </div>
177 %endfor
177 %endfor
178 ## to anchor ajax comments
178 ## to anchor ajax comments
179 <div id="injected_page_comments"></div>
179 <div id="injected_page_comments"></div>
180 </div>
180 </div>
181 </%def>
181 </%def>
182
182
183
183
184 <%def name="comments(post_url, cur_status, is_pull_request=False, is_compare=False, change_status=True, form_extras=None)">
184 <%def name="comments(post_url, cur_status, is_pull_request=False, is_compare=False, change_status=True, form_extras=None)">
185
185
186 <div class="comments">
186 <div class="comments">
187 <%
187 <%
188 if is_pull_request:
188 if is_pull_request:
189 placeholder = _('Leave a comment on this Pull Request.')
189 placeholder = _('Leave a comment on this Pull Request.')
190 elif is_compare:
190 elif is_compare:
191 placeholder = _('Leave a comment on {} commits in this range.').format(len(form_extras))
191 placeholder = _('Leave a comment on {} commits in this range.').format(len(form_extras))
192 else:
192 else:
193 placeholder = _('Leave a comment on this Commit.')
193 placeholder = _('Leave a comment on this Commit.')
194 %>
194 %>
195
195
196 % if c.rhodecode_user.username != h.DEFAULT_USER:
196 % if c.rhodecode_user.username != h.DEFAULT_USER:
197 <div class="js-template" id="cb-comment-general-form-template">
197 <div class="js-template" id="cb-comment-general-form-template">
198 ## template generated for injection
198 ## template generated for injection
199 ${comment_form(form_type='general', review_statuses=c.commit_statuses, form_extras=form_extras)}
199 ${comment_form(form_type='general', review_statuses=c.commit_statuses, form_extras=form_extras)}
200 </div>
200 </div>
201
201
202 <div id="cb-comment-general-form-placeholder" class="comment-form ac">
202 <div id="cb-comment-general-form-placeholder" class="comment-form ac">
203 ## inject form here
203 ## inject form here
204 </div>
204 </div>
205 <script type="text/javascript">
205 <script type="text/javascript">
206 var lineNo = 'general';
206 var lineNo = 'general';
207 var resolvesCommentId = null;
207 var resolvesCommentId = null;
208 var generalCommentForm = Rhodecode.comments.createGeneralComment(
208 var generalCommentForm = Rhodecode.comments.createGeneralComment(
209 lineNo, "${placeholder}", resolvesCommentId);
209 lineNo, "${placeholder}", resolvesCommentId);
210
210
211 // set custom success callback on rangeCommit
211 // set custom success callback on rangeCommit
212 % if is_compare:
212 % if is_compare:
213 generalCommentForm.setHandleFormSubmit(function(o) {
213 generalCommentForm.setHandleFormSubmit(function(o) {
214 var self = generalCommentForm;
214 var self = generalCommentForm;
215
215
216 var text = self.cm.getValue();
216 var text = self.cm.getValue();
217 var status = self.getCommentStatus();
217 var status = self.getCommentStatus();
218 var commentType = self.getCommentType();
218 var commentType = self.getCommentType();
219
219
220 if (text === "" && !status) {
220 if (text === "" && !status) {
221 return;
221 return;
222 }
222 }
223
223
224 // we can pick which commits we want to make the comment by
224 // we can pick which commits we want to make the comment by
225 // selecting them via click on preview pane, this will alter the hidden inputs
225 // selecting them via click on preview pane, this will alter the hidden inputs
226 var cherryPicked = $('#changeset_compare_view_content .compare_select.hl').length > 0;
226 var cherryPicked = $('#changeset_compare_view_content .compare_select.hl').length > 0;
227
227
228 var commitIds = [];
228 var commitIds = [];
229 $('#changeset_compare_view_content .compare_select').each(function(el) {
229 $('#changeset_compare_view_content .compare_select').each(function(el) {
230 var commitId = this.id.replace('row-', '');
230 var commitId = this.id.replace('row-', '');
231 if ($(this).hasClass('hl') || !cherryPicked) {
231 if ($(this).hasClass('hl') || !cherryPicked) {
232 $("input[data-commit-id='{0}']".format(commitId)).val(commitId);
232 $("input[data-commit-id='{0}']".format(commitId)).val(commitId);
233 commitIds.push(commitId);
233 commitIds.push(commitId);
234 } else {
234 } else {
235 $("input[data-commit-id='{0}']".format(commitId)).val('')
235 $("input[data-commit-id='{0}']".format(commitId)).val('')
236 }
236 }
237 });
237 });
238
238
239 self.setActionButtonsDisabled(true);
239 self.setActionButtonsDisabled(true);
240 self.cm.setOption("readOnly", true);
240 self.cm.setOption("readOnly", true);
241 var postData = {
241 var postData = {
242 'text': text,
242 'text': text,
243 'changeset_status': status,
243 'changeset_status': status,
244 'comment_type': commentType,
244 'comment_type': commentType,
245 'commit_ids': commitIds,
245 'commit_ids': commitIds,
246 'csrf_token': CSRF_TOKEN
246 'csrf_token': CSRF_TOKEN
247 };
247 };
248
248
249 var submitSuccessCallback = function(o) {
249 var submitSuccessCallback = function(o) {
250 location.reload(true);
250 location.reload(true);
251 };
251 };
252 var submitFailCallback = function(){
252 var submitFailCallback = function(){
253 self.resetCommentFormState(text)
253 self.resetCommentFormState(text)
254 };
254 };
255 self.submitAjaxPOST(
255 self.submitAjaxPOST(
256 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
256 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
257 });
257 });
258 % endif
258 % endif
259
259
260 </script>
260 </script>
261 % else:
261 % else:
262 ## form state when not logged in
262 ## form state when not logged in
263 <div class="comment-form ac">
263 <div class="comment-form ac">
264
264
265 <div class="comment-area">
265 <div class="comment-area">
266 <div class="comment-area-header">
266 <div class="comment-area-header">
267 <ul class="nav-links clearfix">
267 <ul class="nav-links clearfix">
268 <li class="active">
268 <li class="active">
269 <a class="disabled" href="#edit-btn" disabled="disabled" onclick="return false">${_('Write')}</a>
269 <a class="disabled" href="#edit-btn" disabled="disabled" onclick="return false">${_('Write')}</a>
270 </li>
270 </li>
271 <li class="">
271 <li class="">
272 <a class="disabled" href="#preview-btn" disabled="disabled" onclick="return false">${_('Preview')}</a>
272 <a class="disabled" href="#preview-btn" disabled="disabled" onclick="return false">${_('Preview')}</a>
273 </li>
273 </li>
274 </ul>
274 </ul>
275 </div>
275 </div>
276
276
277 <div class="comment-area-write" style="display: block;">
277 <div class="comment-area-write" style="display: block;">
278 <div id="edit-container">
278 <div id="edit-container">
279 <div style="padding: 40px 0">
279 <div style="padding: 40px 0">
280 ${_('You need to be logged in to leave comments.')}
280 ${_('You need to be logged in to leave comments.')}
281 <a href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">${_('Login now')}</a>
281 <a href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">${_('Login now')}</a>
282 </div>
282 </div>
283 </div>
283 </div>
284 <div id="preview-container" class="clearfix" style="display: none;">
284 <div id="preview-container" class="clearfix" style="display: none;">
285 <div id="preview-box" class="preview-box"></div>
285 <div id="preview-box" class="preview-box"></div>
286 </div>
286 </div>
287 </div>
287 </div>
288
288
289 <div class="comment-area-footer">
289 <div class="comment-area-footer">
290 <div class="toolbar">
290 <div class="toolbar">
291 <div class="toolbar-text">
291 <div class="toolbar-text">
292 </div>
292 </div>
293 </div>
293 </div>
294 </div>
294 </div>
295 </div>
295 </div>
296
296
297 <div class="comment-footer">
297 <div class="comment-footer">
298 </div>
298 </div>
299
299
300 </div>
300 </div>
301 % endif
301 % endif
302
302
303 <script type="text/javascript">
303 <script type="text/javascript">
304 bindToggleButtons();
304 bindToggleButtons();
305 </script>
305 </script>
306 </div>
306 </div>
307 </%def>
307 </%def>
308
308
309
309
310 <%def name="comment_form(form_type, form_id='', lineno_id='{1}', review_statuses=None, form_extras=None)">
310 <%def name="comment_form(form_type, form_id='', lineno_id='{1}', review_statuses=None, form_extras=None)">
311
311
312 ## comment injected based on assumption that user is logged in
312 ## comment injected based on assumption that user is logged in
313 <form ${'id="{}"'.format(form_id) if form_id else '' |n} action="#" method="GET">
313 <form ${'id="{}"'.format(form_id) if form_id else '' |n} action="#" method="GET">
314
314
315 <div class="comment-area">
315 <div class="comment-area">
316 <div class="comment-area-header">
316 <div class="comment-area-header">
317 <ul class="nav-links clearfix">
317 <ul class="nav-links clearfix">
318 <li class="active">
318 <li class="active">
319 <a href="#edit-btn" tabindex="-1" id="edit-btn_${lineno_id}">${_('Write')}</a>
319 <a href="#edit-btn" tabindex="-1" id="edit-btn_${lineno_id}">${_('Write')}</a>
320 </li>
320 </li>
321 <li class="">
321 <li class="">
322 <a href="#preview-btn" tabindex="-1" id="preview-btn_${lineno_id}">${_('Preview')}</a>
322 <a href="#preview-btn" tabindex="-1" id="preview-btn_${lineno_id}">${_('Preview')}</a>
323 </li>
323 </li>
324 <li class="pull-right">
324 <li class="pull-right">
325 <select class="comment-type" id="comment_type_${lineno_id}" name="comment_type">
325 <select class="comment-type" id="comment_type_${lineno_id}" name="comment_type">
326 % for val in c.visual.comment_types:
326 % for val in c.visual.comment_types:
327 <option value="${val}">${val.upper()}</option>
327 <option value="${val}">${val.upper()}</option>
328 % endfor
328 % endfor
329 </select>
329 </select>
330 </li>
330 </li>
331 </ul>
331 </ul>
332 </div>
332 </div>
333
333
334 <div class="comment-area-write" style="display: block;">
334 <div class="comment-area-write" style="display: block;">
335 <div id="edit-container_${lineno_id}">
335 <div id="edit-container_${lineno_id}">
336 <textarea id="text_${lineno_id}" name="text" class="comment-block-ta ac-input"></textarea>
336 <textarea id="text_${lineno_id}" name="text" class="comment-block-ta ac-input"></textarea>
337 </div>
337 </div>
338 <div id="preview-container_${lineno_id}" class="clearfix" style="display: none;">
338 <div id="preview-container_${lineno_id}" class="clearfix" style="display: none;">
339 <div id="preview-box_${lineno_id}" class="preview-box"></div>
339 <div id="preview-box_${lineno_id}" class="preview-box"></div>
340 </div>
340 </div>
341 </div>
341 </div>
342
342
343 <div class="comment-area-footer comment-attachment-uploader">
343 <div class="comment-area-footer comment-attachment-uploader">
344 <div class="toolbar">
344 <div class="toolbar">
345 <div class="toolbar-text">
345 <div class="toolbar-text">
346 ${(_('Comments parsed using %s syntax with %s, and %s actions support.') % (
346 ${(_('Comments parsed using %s syntax with %s, and %s actions support.') % (
347 ('<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
347 ('<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
348 ('<span class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user')),
348 ('<span class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user')),
349 ('<span class="tooltip" title="%s">`/`</span>' % _('Start typing with / for certain actions to be triggered via text box.'))
349 ('<span class="tooltip" title="%s">`/`</span>' % _('Start typing with / for certain actions to be triggered via text box.'))
350 )
350 )
351 )|n}
351 )|n}
352 </div>
352 </div>
353
353
354 <div class="comment-attachment-text">
354 <div class="comment-attachment-text">
355 <div class="dropzone-text">
355 <div class="dropzone-text">
356 ${_("Drag'n Drop files here or")} <span class="link pick-attachment">${_('Choose your files')}</span>.<br>
356 ${_("Drag'n Drop files here or")} <span class="link pick-attachment">${_('Choose your files')}</span>.<br>
357 </div>
357 </div>
358 <div class="dropzone-upload" style="display:none">
358 <div class="dropzone-upload" style="display:none">
359 <i class="icon-spin animate-spin"></i> ${_('uploading...')}
359 <i class="icon-spin animate-spin"></i> ${_('uploading...')}
360 </div>
360 </div>
361 </div>
361 </div>
362
362
363 ## comments dropzone template, empty on purpose
363 ## comments dropzone template, empty on purpose
364 <div style="display: none" class="comment-attachment-uploader-template">
364 <div style="display: none" class="comment-attachment-uploader-template">
365 <div class="dz-file-preview" style="margin: 0">
365 <div class="dz-file-preview" style="margin: 0">
366 <div class="dz-error-message"></div>
366 <div class="dz-error-message"></div>
367 </div>
367 </div>
368 </div>
368 </div>
369
369
370 </div>
370 </div>
371 </div>
371 </div>
372 </div>
372 </div>
373
373
374 <div class="comment-footer">
374 <div class="comment-footer">
375
375
376 % if review_statuses:
376 % if review_statuses:
377 <div class="status_box">
377 <div class="status_box">
378 <select id="change_status_${lineno_id}" name="changeset_status">
378 <select id="change_status_${lineno_id}" name="changeset_status">
379 <option></option> ## Placeholder
379 <option></option> ## Placeholder
380 % for status, lbl in review_statuses:
380 % for status, lbl in review_statuses:
381 <option value="${status}" data-status="${status}">${lbl}</option>
381 <option value="${status}" data-status="${status}">${lbl}</option>
382 %if is_pull_request and change_status and status in ('approved', 'rejected'):
382 %if is_pull_request and change_status and status in ('approved', 'rejected'):
383 <option value="${status}_closed" data-status="${status}">${lbl} & ${_('Closed')}</option>
383 <option value="${status}_closed" data-status="${status}">${lbl} & ${_('Closed')}</option>
384 %endif
384 %endif
385 % endfor
385 % endfor
386 </select>
386 </select>
387 </div>
387 </div>
388 % endif
388 % endif
389
389
390 ## inject extra inputs into the form
390 ## inject extra inputs into the form
391 % if form_extras and isinstance(form_extras, (list, tuple)):
391 % if form_extras and isinstance(form_extras, (list, tuple)):
392 <div id="comment_form_extras">
392 <div id="comment_form_extras">
393 % for form_ex_el in form_extras:
393 % for form_ex_el in form_extras:
394 ${form_ex_el|n}
394 ${form_ex_el|n}
395 % endfor
395 % endfor
396 </div>
396 </div>
397 % endif
397 % endif
398
398
399 <div class="action-buttons">
399 <div class="action-buttons">
400 ## inline for has a file, and line-number together with cancel hide button.
400 ## inline for has a file, and line-number together with cancel hide button.
401 % if form_type == 'inline':
401 % if form_type == 'inline':
402 <input type="hidden" name="f_path" value="{0}">
402 <input type="hidden" name="f_path" value="{0}">
403 <input type="hidden" name="line" value="${lineno_id}">
403 <input type="hidden" name="line" value="${lineno_id}">
404 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
404 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
405 ${_('Cancel')}
405 ${_('Cancel')}
406 </button>
406 </button>
407 % endif
407 % endif
408
408
409 % if form_type != 'inline':
409 % if form_type != 'inline':
410 <div class="action-buttons-extra"></div>
410 <div class="action-buttons-extra"></div>
411 % endif
411 % endif
412
412
413 ${h.submit('save', _('Comment'), class_='btn btn-success comment-button-input')}
413 ${h.submit('save', _('Comment'), class_='btn btn-success comment-button-input')}
414
414
415 </div>
415 </div>
416 </div>
416 </div>
417
417
418 </form>
418 </form>
419
419
420 </%def> No newline at end of file
420 </%def>
@@ -1,469 +1,469 b''
1 ## DATA TABLE RE USABLE ELEMENTS
1 ## DATA TABLE RE USABLE ELEMENTS
2 ## usage:
2 ## usage:
3 ## <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
3 ## <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
4 <%namespace name="base" file="/base/base.mako"/>
4 <%namespace name="base" file="/base/base.mako"/>
5
5
6 <%def name="metatags_help()">
6 <%def name="metatags_help()">
7 <table>
7 <table>
8 <%
8 <%
9 example_tags = [
9 example_tags = [
10 ('state','[stable]'),
10 ('state','[stable]'),
11 ('state','[stale]'),
11 ('state','[stale]'),
12 ('state','[featured]'),
12 ('state','[featured]'),
13 ('state','[dev]'),
13 ('state','[dev]'),
14 ('state','[dead]'),
14 ('state','[dead]'),
15 ('state','[deprecated]'),
15 ('state','[deprecated]'),
16
16
17 ('label','[personal]'),
17 ('label','[personal]'),
18 ('generic','[v2.0.0]'),
18 ('generic','[v2.0.0]'),
19
19
20 ('lang','[lang =&gt; JavaScript]'),
20 ('lang','[lang =&gt; JavaScript]'),
21 ('license','[license =&gt; LicenseName]'),
21 ('license','[license =&gt; LicenseName]'),
22
22
23 ('ref','[requires =&gt; RepoName]'),
23 ('ref','[requires =&gt; RepoName]'),
24 ('ref','[recommends =&gt; GroupName]'),
24 ('ref','[recommends =&gt; GroupName]'),
25 ('ref','[conflicts =&gt; SomeName]'),
25 ('ref','[conflicts =&gt; SomeName]'),
26 ('ref','[base =&gt; SomeName]'),
26 ('ref','[base =&gt; SomeName]'),
27 ('url','[url =&gt; [linkName](https://rhodecode.com)]'),
27 ('url','[url =&gt; [linkName](https://rhodecode.com)]'),
28 ('see','[see =&gt; http://rhodecode.com]'),
28 ('see','[see =&gt; http://rhodecode.com]'),
29 ]
29 ]
30 %>
30 %>
31 % for tag_type, tag in example_tags:
31 % for tag_type, tag in example_tags:
32 <tr>
32 <tr>
33 <td>${tag|n}</td>
33 <td>${tag|n}</td>
34 <td>${h.style_metatag(tag_type, tag)|n}</td>
34 <td>${h.style_metatag(tag_type, tag)|n}</td>
35 </tr>
35 </tr>
36 % endfor
36 % endfor
37 </table>
37 </table>
38 </%def>
38 </%def>
39
39
40 <%def name="render_description(description, stylify_metatags)">
40 <%def name="render_description(description, stylify_metatags)">
41 <%
41 <%
42 tags = []
42 tags = []
43 if stylify_metatags:
43 if stylify_metatags:
44 tags, description = h.extract_metatags(description)
44 tags, description = h.extract_metatags(description)
45 %>
45 %>
46 % for tag_type, tag in tags:
46 % for tag_type, tag in tags:
47 ${h.style_metatag(tag_type, tag)|n,trim}
47 ${h.style_metatag(tag_type, tag)|n,trim}
48 % endfor
48 % endfor
49 <code style="white-space: pre-wrap">${description}</code>
49 <code style="white-space: pre-wrap">${description}</code>
50 </%def>
50 </%def>
51
51
52 ## REPOSITORY RENDERERS
52 ## REPOSITORY RENDERERS
53 <%def name="quick_menu(repo_name)">
53 <%def name="quick_menu(repo_name)">
54 <i class="icon-more"></i>
54 <i class="icon-more"></i>
55 <div class="menu_items_container hidden">
55 <div class="menu_items_container hidden">
56 <ul class="menu_items">
56 <ul class="menu_items">
57 <li>
57 <li>
58 <a title="${_('Summary')}" href="${h.route_path('repo_summary',repo_name=repo_name)}">
58 <a title="${_('Summary')}" href="${h.route_path('repo_summary',repo_name=repo_name)}">
59 <span>${_('Summary')}</span>
59 <span>${_('Summary')}</span>
60 </a>
60 </a>
61 </li>
61 </li>
62 <li>
62 <li>
63 <a title="${_('Commits')}" href="${h.route_path('repo_commits',repo_name=repo_name)}">
63 <a title="${_('Commits')}" href="${h.route_path('repo_commits',repo_name=repo_name)}">
64 <span>${_('Commits')}</span>
64 <span>${_('Commits')}</span>
65 </a>
65 </a>
66 </li>
66 </li>
67 <li>
67 <li>
68 <a title="${_('Files')}" href="${h.route_path('repo_files:default_commit',repo_name=repo_name)}">
68 <a title="${_('Files')}" href="${h.route_path('repo_files:default_commit',repo_name=repo_name)}">
69 <span>${_('Files')}</span>
69 <span>${_('Files')}</span>
70 </a>
70 </a>
71 </li>
71 </li>
72 <li>
72 <li>
73 <a title="${_('Fork')}" href="${h.route_path('repo_fork_new',repo_name=repo_name)}">
73 <a title="${_('Fork')}" href="${h.route_path('repo_fork_new',repo_name=repo_name)}">
74 <span>${_('Fork')}</span>
74 <span>${_('Fork')}</span>
75 </a>
75 </a>
76 </li>
76 </li>
77 </ul>
77 </ul>
78 </div>
78 </div>
79 </%def>
79 </%def>
80
80
81 <%def name="repo_name(name,rtype,rstate,private,archived,fork_of,short_name=False,admin=False)">
81 <%def name="repo_name(name,rtype,rstate,private,archived,fork_of,short_name=False,admin=False)">
82 <%
82 <%
83 def get_name(name,short_name=short_name):
83 def get_name(name,short_name=short_name):
84 if short_name:
84 if short_name:
85 return name.split('/')[-1]
85 return name.split('/')[-1]
86 else:
86 else:
87 return name
87 return name
88 %>
88 %>
89 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
89 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
90 ##NAME
90 ##NAME
91 <a href="${h.route_path('edit_repo',repo_name=name) if admin else h.route_path('repo_summary',repo_name=name)}">
91 <a href="${h.route_path('edit_repo',repo_name=name) if admin else h.route_path('repo_summary',repo_name=name)}">
92
92
93 ##TYPE OF REPO
93 ##TYPE OF REPO
94 %if h.is_hg(rtype):
94 %if h.is_hg(rtype):
95 <span title="${_('Mercurial repository')}"><i class="icon-hg" style="font-size: 14px;"></i></span>
95 <span title="${_('Mercurial repository')}"><i class="icon-hg" style="font-size: 14px;"></i></span>
96 %elif h.is_git(rtype):
96 %elif h.is_git(rtype):
97 <span title="${_('Git repository')}"><i class="icon-git" style="font-size: 14px"></i></span>
97 <span title="${_('Git repository')}"><i class="icon-git" style="font-size: 14px"></i></span>
98 %elif h.is_svn(rtype):
98 %elif h.is_svn(rtype):
99 <span title="${_('Subversion repository')}"><i class="icon-svn" style="font-size: 14px"></i></span>
99 <span title="${_('Subversion repository')}"><i class="icon-svn" style="font-size: 14px"></i></span>
100 %endif
100 %endif
101
101
102 ##PRIVATE/PUBLIC
102 ##PRIVATE/PUBLIC
103 %if private is True and c.visual.show_private_icon:
103 %if private is True and c.visual.show_private_icon:
104 <i class="icon-lock" title="${_('Private repository')}"></i>
104 <i class="icon-lock" title="${_('Private repository')}"></i>
105 %elif private is False and c.visual.show_public_icon:
105 %elif private is False and c.visual.show_public_icon:
106 <i class="icon-unlock-alt" title="${_('Public repository')}"></i>
106 <i class="icon-unlock-alt" title="${_('Public repository')}"></i>
107 %else:
107 %else:
108 <span></span>
108 <span></span>
109 %endif
109 %endif
110 ${get_name(name)}
110 ${get_name(name)}
111 </a>
111 </a>
112 %if fork_of:
112 %if fork_of:
113 <a href="${h.route_path('repo_summary',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
113 <a href="${h.route_path('repo_summary',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
114 %endif
114 %endif
115 %if rstate == 'repo_state_pending':
115 %if rstate == 'repo_state_pending':
116 <span class="creation_in_progress tooltip" title="${_('This repository is being created in a background task')}">
116 <span class="creation_in_progress tooltip" title="${_('This repository is being created in a background task')}">
117 (${_('creating...')})
117 (${_('creating...')})
118 </span>
118 </span>
119 %endif
119 %endif
120
120
121 </div>
121 </div>
122 </%def>
122 </%def>
123
123
124 <%def name="repo_desc(description, stylify_metatags)">
124 <%def name="repo_desc(description, stylify_metatags)">
125 <%
125 <%
126 tags, description = h.extract_metatags(description)
126 tags, description = h.extract_metatags(description)
127 %>
127 %>
128
128
129 <div class="truncate-wrap">
129 <div class="truncate-wrap">
130 % if stylify_metatags:
130 % if stylify_metatags:
131 % for tag_type, tag in tags:
131 % for tag_type, tag in tags:
132 ${h.style_metatag(tag_type, tag)|n}
132 ${h.style_metatag(tag_type, tag)|n}
133 % endfor
133 % endfor
134 % endif
134 % endif
135 ${description}
135 ${description}
136 </div>
136 </div>
137
137
138 </%def>
138 </%def>
139
139
140 <%def name="last_change(last_change)">
140 <%def name="last_change(last_change)">
141 ${h.age_component(last_change, time_is_local=True)}
141 ${h.age_component(last_change, time_is_local=True)}
142 </%def>
142 </%def>
143
143
144 <%def name="revision(repo_name, rev, commit_id, author, last_msg, commit_date)">
144 <%def name="revision(repo_name, rev, commit_id, author, last_msg, commit_date)">
145 <div>
145 <div>
146 %if rev >= 0:
146 %if rev >= 0:
147 <code><a class="tooltip-hovercard" data-hovercard-alt=${h.tooltip(last_msg)} data-hovercard-url="${h.route_path('hovercard_repo_commit', repo_name=repo_name, commit_id=commit_id)}" href="${h.route_path('repo_commit',repo_name=repo_name,commit_id=commit_id)}">${'r{}:{}'.format(rev,h.short_id(commit_id))}</a></code>
147 <code><a class="tooltip-hovercard" data-hovercard-alt=${h.tooltip(last_msg)} data-hovercard-url="${h.route_path('hovercard_repo_commit', repo_name=repo_name, commit_id=commit_id)}" href="${h.route_path('repo_commit',repo_name=repo_name,commit_id=commit_id)}">${'r{}:{}'.format(rev,h.short_id(commit_id))}</a></code>
148 %else:
148 %else:
149 ${_('No commits yet')}
149 ${_('No commits yet')}
150 %endif
150 %endif
151 </div>
151 </div>
152 </%def>
152 </%def>
153
153
154 <%def name="rss(name)">
154 <%def name="rss(name)">
155 %if c.rhodecode_user.username != h.DEFAULT_USER:
155 %if c.rhodecode_user.username != h.DEFAULT_USER:
156 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
156 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
157 %else:
157 %else:
158 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
158 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
159 %endif
159 %endif
160 </%def>
160 </%def>
161
161
162 <%def name="atom(name)">
162 <%def name="atom(name)">
163 %if c.rhodecode_user.username != h.DEFAULT_USER:
163 %if c.rhodecode_user.username != h.DEFAULT_USER:
164 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
164 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
165 %else:
165 %else:
166 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
166 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
167 %endif
167 %endif
168 </%def>
168 </%def>
169
169
170 <%def name="repo_actions(repo_name, super_user=True)">
170 <%def name="repo_actions(repo_name, super_user=True)">
171 <div>
171 <div>
172 <div class="grid_edit">
172 <div class="grid_edit">
173 <a href="${h.route_path('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
173 <a href="${h.route_path('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
174 Edit
174 Edit
175 </a>
175 </a>
176 </div>
176 </div>
177 <div class="grid_delete">
177 <div class="grid_delete">
178 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=repo_name), request=request)}
178 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=repo_name), request=request)}
179 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
179 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
180 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
180 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
181 ${h.end_form()}
181 ${h.end_form()}
182 </div>
182 </div>
183 </div>
183 </div>
184 </%def>
184 </%def>
185
185
186 <%def name="repo_state(repo_state)">
186 <%def name="repo_state(repo_state)">
187 <div>
187 <div>
188 %if repo_state == 'repo_state_pending':
188 %if repo_state == 'repo_state_pending':
189 <div class="tag tag4">${_('Creating')}</div>
189 <div class="tag tag4">${_('Creating')}</div>
190 %elif repo_state == 'repo_state_created':
190 %elif repo_state == 'repo_state_created':
191 <div class="tag tag1">${_('Created')}</div>
191 <div class="tag tag1">${_('Created')}</div>
192 %else:
192 %else:
193 <div class="tag alert2" title="${h.tooltip(repo_state)}">invalid</div>
193 <div class="tag alert2" title="${h.tooltip(repo_state)}">invalid</div>
194 %endif
194 %endif
195 </div>
195 </div>
196 </%def>
196 </%def>
197
197
198
198
199 ## REPO GROUP RENDERERS
199 ## REPO GROUP RENDERERS
200 <%def name="quick_repo_group_menu(repo_group_name)">
200 <%def name="quick_repo_group_menu(repo_group_name)">
201 <i class="icon-more"></i>
201 <i class="icon-more"></i>
202 <div class="menu_items_container hidden">
202 <div class="menu_items_container hidden">
203 <ul class="menu_items">
203 <ul class="menu_items">
204 <li>
204 <li>
205 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">${_('Summary')}</a>
205 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">${_('Summary')}</a>
206 </li>
206 </li>
207
207
208 </ul>
208 </ul>
209 </div>
209 </div>
210 </%def>
210 </%def>
211
211
212 <%def name="repo_group_name(repo_group_name, children_groups=None)">
212 <%def name="repo_group_name(repo_group_name, children_groups=None)">
213 <div>
213 <div>
214 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">
214 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">
215 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
215 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
216 %if children_groups:
216 %if children_groups:
217 ${h.literal(' &raquo; '.join(children_groups))}
217 ${h.literal(' &raquo; '.join(children_groups))}
218 %else:
218 %else:
219 ${repo_group_name}
219 ${repo_group_name}
220 %endif
220 %endif
221 </a>
221 </a>
222 </div>
222 </div>
223 </%def>
223 </%def>
224
224
225 <%def name="repo_group_desc(description, personal, stylify_metatags)">
225 <%def name="repo_group_desc(description, personal, stylify_metatags)">
226
226
227 <%
227 <%
228 if stylify_metatags:
228 if stylify_metatags:
229 tags, description = h.extract_metatags(description)
229 tags, description = h.extract_metatags(description)
230 %>
230 %>
231
231
232 <div class="truncate-wrap">
232 <div class="truncate-wrap">
233 % if personal:
233 % if personal:
234 <div class="metatag" tag="personal">${_('personal')}</div>
234 <div class="metatag" tag="personal">${_('personal')}</div>
235 % endif
235 % endif
236
236
237 % if stylify_metatags:
237 % if stylify_metatags:
238 % for tag_type, tag in tags:
238 % for tag_type, tag in tags:
239 ${h.style_metatag(tag_type, tag)|n}
239 ${h.style_metatag(tag_type, tag)|n}
240 % endfor
240 % endfor
241 % endif
241 % endif
242 ${description}
242 ${description}
243 </div>
243 </div>
244
244
245 </%def>
245 </%def>
246
246
247 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
247 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
248 <div class="grid_edit">
248 <div class="grid_edit">
249 <a href="${h.route_path('edit_repo_group',repo_group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
249 <a href="${h.route_path('edit_repo_group',repo_group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
250 </div>
250 </div>
251 <div class="grid_delete">
251 <div class="grid_delete">
252 ${h.secure_form(h.route_path('edit_repo_group_advanced_delete', repo_group_name=repo_group_name), request=request)}
252 ${h.secure_form(h.route_path('edit_repo_group_advanced_delete', repo_group_name=repo_group_name), request=request)}
253 ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger",
253 ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger",
254 onclick="return confirm('"+_ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
254 onclick="return confirm('"+_ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
255 ${h.end_form()}
255 ${h.end_form()}
256 </div>
256 </div>
257 </%def>
257 </%def>
258
258
259
259
260 <%def name="user_actions(user_id, username)">
260 <%def name="user_actions(user_id, username)">
261 <div class="grid_edit">
261 <div class="grid_edit">
262 <a href="${h.route_path('user_edit',user_id=user_id)}" title="${_('Edit')}">
262 <a href="${h.route_path('user_edit',user_id=user_id)}" title="${_('Edit')}">
263 ${_('Edit')}
263 ${_('Edit')}
264 </a>
264 </a>
265 </div>
265 </div>
266 <div class="grid_delete">
266 <div class="grid_delete">
267 ${h.secure_form(h.route_path('user_delete', user_id=user_id), request=request)}
267 ${h.secure_form(h.route_path('user_delete', user_id=user_id), request=request)}
268 ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger",
268 ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger",
269 onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
269 onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
270 ${h.end_form()}
270 ${h.end_form()}
271 </div>
271 </div>
272 </%def>
272 </%def>
273
273
274 <%def name="user_group_actions(user_group_id, user_group_name)">
274 <%def name="user_group_actions(user_group_id, user_group_name)">
275 <div class="grid_edit">
275 <div class="grid_edit">
276 <a href="${h.route_path('edit_user_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
276 <a href="${h.route_path('edit_user_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
277 </div>
277 </div>
278 <div class="grid_delete">
278 <div class="grid_delete">
279 ${h.secure_form(h.route_path('user_groups_delete', user_group_id=user_group_id), request=request)}
279 ${h.secure_form(h.route_path('user_groups_delete', user_group_id=user_group_id), request=request)}
280 ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger",
280 ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger",
281 onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
281 onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
282 ${h.end_form()}
282 ${h.end_form()}
283 </div>
283 </div>
284 </%def>
284 </%def>
285
285
286
286
287 <%def name="user_name(user_id, username)">
287 <%def name="user_name(user_id, username)">
288 ${h.link_to(h.person(username, 'username_or_name_or_email'), h.route_path('user_edit', user_id=user_id))}
288 ${h.link_to(h.person(username, 'username_or_name_or_email'), h.route_path('user_edit', user_id=user_id))}
289 </%def>
289 </%def>
290
290
291 <%def name="user_profile(username)">
291 <%def name="user_profile(username)">
292 ${base.gravatar_with_user(username, 16, tooltip=True)}
292 ${base.gravatar_with_user(username, 16, tooltip=True)}
293 </%def>
293 </%def>
294
294
295 <%def name="user_group_name(user_group_name)">
295 <%def name="user_group_name(user_group_name)">
296 <div>
296 <div>
297 <i class="icon-user-group" title="${_('User group')}"></i>
297 <i class="icon-user-group" title="${_('User group')}"></i>
298 ${h.link_to_group(user_group_name)}
298 ${h.link_to_group(user_group_name)}
299 </div>
299 </div>
300 </%def>
300 </%def>
301
301
302
302
303 ## GISTS
303 ## GISTS
304
304
305 <%def name="gist_gravatar(full_contact)">
305 <%def name="gist_gravatar(full_contact)">
306 <div class="gist_gravatar">
306 <div class="gist_gravatar">
307 ${base.gravatar(full_contact, 30)}
307 ${base.gravatar(full_contact, 30)}
308 </div>
308 </div>
309 </%def>
309 </%def>
310
310
311 <%def name="gist_access_id(gist_access_id, full_contact)">
311 <%def name="gist_access_id(gist_access_id, full_contact)">
312 <div>
312 <div>
313 <b>
313 <b>
314 <a href="${h.route_path('gist_show', gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
314 <a href="${h.route_path('gist_show', gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
315 </b>
315 </b>
316 </div>
316 </div>
317 </%def>
317 </%def>
318
318
319 <%def name="gist_author(full_contact, created_on, expires)">
319 <%def name="gist_author(full_contact, created_on, expires)">
320 ${base.gravatar_with_user(full_contact, 16, tooltip=True)}
320 ${base.gravatar_with_user(full_contact, 16, tooltip=True)}
321 </%def>
321 </%def>
322
322
323
323
324 <%def name="gist_created(created_on)">
324 <%def name="gist_created(created_on)">
325 <div class="created">
325 <div class="created">
326 ${h.age_component(created_on, time_is_local=True)}
326 ${h.age_component(created_on, time_is_local=True)}
327 </div>
327 </div>
328 </%def>
328 </%def>
329
329
330 <%def name="gist_expires(expires)">
330 <%def name="gist_expires(expires)">
331 <div class="created">
331 <div class="created">
332 %if expires == -1:
332 %if expires == -1:
333 ${_('never')}
333 ${_('never')}
334 %else:
334 %else:
335 ${h.age_component(h.time_to_utcdatetime(expires))}
335 ${h.age_component(h.time_to_utcdatetime(expires))}
336 %endif
336 %endif
337 </div>
337 </div>
338 </%def>
338 </%def>
339
339
340 <%def name="gist_type(gist_type)">
340 <%def name="gist_type(gist_type)">
341 %if gist_type != 'public':
341 %if gist_type != 'public':
342 <div class="tag">${_('Private')}</div>
342 <div class="tag">${_('Private')}</div>
343 %endif
343 %endif
344 </%def>
344 </%def>
345
345
346 <%def name="gist_description(gist_description)">
346 <%def name="gist_description(gist_description)">
347 ${gist_description}
347 ${gist_description}
348 </%def>
348 </%def>
349
349
350
350
351 ## PULL REQUESTS GRID RENDERERS
351 ## PULL REQUESTS GRID RENDERERS
352
352
353 <%def name="pullrequest_target_repo(repo_name)">
353 <%def name="pullrequest_target_repo(repo_name)">
354 <div class="truncate">
354 <div class="truncate">
355 ${h.link_to(repo_name,h.route_path('repo_summary',repo_name=repo_name))}
355 ${h.link_to(repo_name,h.route_path('repo_summary',repo_name=repo_name))}
356 </div>
356 </div>
357 </%def>
357 </%def>
358
358
359 <%def name="pullrequest_status(status)">
359 <%def name="pullrequest_status(status)">
360 <i class="icon-circle review-status-${status}"></i>
360 <i class="icon-circle review-status-${status}"></i>
361 </%def>
361 </%def>
362
362
363 <%def name="pullrequest_title(title, description)">
363 <%def name="pullrequest_title(title, description)">
364 ${title}
364 ${title}
365 </%def>
365 </%def>
366
366
367 <%def name="pullrequest_comments(comments_nr)">
367 <%def name="pullrequest_comments(comments_nr)">
368 <i class="icon-comment"></i> ${comments_nr}
368 <i class="icon-comment"></i> ${comments_nr}
369 </%def>
369 </%def>
370
370
371 <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)">
371 <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)">
372 <a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
372 <a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
373 % if short:
373 % if short:
374 #${pull_request_id}
374 !${pull_request_id}
375 % else:
375 % else:
376 ${_('Pull request #%(pr_number)s') % {'pr_number': pull_request_id,}}
376 ${_('Pull request !{}').format(pull_request_id)}
377 % endif
377 % endif
378 </a>
378 </a>
379 </%def>
379 </%def>
380
380
381 <%def name="pullrequest_updated_on(updated_on)">
381 <%def name="pullrequest_updated_on(updated_on)">
382 ${h.age_component(h.time_to_utcdatetime(updated_on))}
382 ${h.age_component(h.time_to_utcdatetime(updated_on))}
383 </%def>
383 </%def>
384
384
385 <%def name="pullrequest_author(full_contact)">
385 <%def name="pullrequest_author(full_contact)">
386 ${base.gravatar_with_user(full_contact, 16, tooltip=True)}
386 ${base.gravatar_with_user(full_contact, 16, tooltip=True)}
387 </%def>
387 </%def>
388
388
389
389
390 ## ARTIFACT RENDERERS
390 ## ARTIFACT RENDERERS
391 <%def name="repo_artifact_name(repo_name, file_uid, artifact_display_name)">
391 <%def name="repo_artifact_name(repo_name, file_uid, artifact_display_name)">
392 <a href="${h.route_path('repo_artifacts_get', repo_name=repo_name, uid=file_uid)}">
392 <a href="${h.route_path('repo_artifacts_get', repo_name=repo_name, uid=file_uid)}">
393 ${artifact_display_name or '_EMPTY_NAME_'}
393 ${artifact_display_name or '_EMPTY_NAME_'}
394 </a>
394 </a>
395 </%def>
395 </%def>
396
396
397 <%def name="repo_artifact_uid(repo_name, file_uid)">
397 <%def name="repo_artifact_uid(repo_name, file_uid)">
398 <code>${h.shorter(file_uid, size=24, prefix=True)}</code>
398 <code>${h.shorter(file_uid, size=24, prefix=True)}</code>
399 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${h.route_url('repo_artifacts_get', repo_name=repo_name, uid=file_uid)}" title="${_('Copy the full url')}"></i>
399 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${h.route_url('repo_artifacts_get', repo_name=repo_name, uid=file_uid)}" title="${_('Copy the full url')}"></i>
400 </%def>
400 </%def>
401
401
402 <%def name="repo_artifact_sha256(artifact_sha256)">
402 <%def name="repo_artifact_sha256(artifact_sha256)">
403 <div class="code">${h.shorter(artifact_sha256, 12)}<i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${artifact_sha256}" title="${_('Copy the sha256 ({})').format(artifact_sha256)}"></i></div>
403 <div class="code">${h.shorter(artifact_sha256, 12)}<i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${artifact_sha256}" title="${_('Copy the sha256 ({})').format(artifact_sha256)}"></i></div>
404 </%def>
404 </%def>
405
405
406 <%def name="repo_artifact_actions(repo_name, file_store_id, file_uid)">
406 <%def name="repo_artifact_actions(repo_name, file_store_id, file_uid)">
407 ## <div class="grid_edit">
407 ## <div class="grid_edit">
408 ## <a href="#Edit" title="${_('Edit')}">${_('Edit')}</a>
408 ## <a href="#Edit" title="${_('Edit')}">${_('Edit')}</a>
409 ## </div>
409 ## </div>
410 <div class="grid_edit">
410 <div class="grid_edit">
411 <a href="${h.route_path('repo_artifacts_info', repo_name=repo_name, uid=file_store_id)}" title="${_('Info')}">${_('Info')}</a>
411 <a href="${h.route_path('repo_artifacts_info', repo_name=repo_name, uid=file_store_id)}" title="${_('Info')}">${_('Info')}</a>
412 </div>
412 </div>
413 % if h.HasRepoPermissionAny('repository.admin')(c.repo_name):
413 % if h.HasRepoPermissionAny('repository.admin')(c.repo_name):
414 <div class="grid_delete">
414 <div class="grid_delete">
415 ${h.secure_form(h.route_path('repo_artifacts_delete', repo_name=repo_name, uid=file_store_id), request=request)}
415 ${h.secure_form(h.route_path('repo_artifacts_delete', repo_name=repo_name, uid=file_store_id), request=request)}
416 ${h.submit('remove_',_('Delete'),id="remove_artifact_%s" % file_store_id, class_="btn btn-link btn-danger",
416 ${h.submit('remove_',_('Delete'),id="remove_artifact_%s" % file_store_id, class_="btn btn-link btn-danger",
417 onclick="return confirm('"+_('Confirm to delete this artifact: %s') % file_uid+"');")}
417 onclick="return confirm('"+_('Confirm to delete this artifact: %s') % file_uid+"');")}
418 ${h.end_form()}
418 ${h.end_form()}
419 </div>
419 </div>
420 % endif
420 % endif
421 </%def>
421 </%def>
422
422
423 <%def name="markup_form(form_id, form_text='', help_text=None)">
423 <%def name="markup_form(form_id, form_text='', help_text=None)">
424
424
425 <div class="markup-form">
425 <div class="markup-form">
426 <div class="markup-form-area">
426 <div class="markup-form-area">
427 <div class="markup-form-area-header">
427 <div class="markup-form-area-header">
428 <ul class="nav-links clearfix">
428 <ul class="nav-links clearfix">
429 <li class="active">
429 <li class="active">
430 <a href="#edit-text" tabindex="-1" id="edit-btn_${form_id}">${_('Write')}</a>
430 <a href="#edit-text" tabindex="-1" id="edit-btn_${form_id}">${_('Write')}</a>
431 </li>
431 </li>
432 <li class="">
432 <li class="">
433 <a href="#preview-text" tabindex="-1" id="preview-btn_${form_id}">${_('Preview')}</a>
433 <a href="#preview-text" tabindex="-1" id="preview-btn_${form_id}">${_('Preview')}</a>
434 </li>
434 </li>
435 </ul>
435 </ul>
436 </div>
436 </div>
437
437
438 <div class="markup-form-area-write" style="display: block;">
438 <div class="markup-form-area-write" style="display: block;">
439 <div id="edit-container_${form_id}">
439 <div id="edit-container_${form_id}">
440 <textarea id="${form_id}" name="${form_id}" class="comment-block-ta ac-input">${form_text if form_text else ''}</textarea>
440 <textarea id="${form_id}" name="${form_id}" class="comment-block-ta ac-input">${form_text if form_text else ''}</textarea>
441 </div>
441 </div>
442 <div id="preview-container_${form_id}" class="clearfix" style="display: none;">
442 <div id="preview-container_${form_id}" class="clearfix" style="display: none;">
443 <div id="preview-box_${form_id}" class="preview-box"></div>
443 <div id="preview-box_${form_id}" class="preview-box"></div>
444 </div>
444 </div>
445 </div>
445 </div>
446
446
447 <div class="markup-form-area-footer">
447 <div class="markup-form-area-footer">
448 <div class="toolbar">
448 <div class="toolbar">
449 <div class="toolbar-text">
449 <div class="toolbar-text">
450 ${(_('Parsed using %s syntax') % (
450 ${(_('Parsed using %s syntax') % (
451 ('<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
451 ('<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
452 )
452 )
453 )|n}
453 )|n}
454 </div>
454 </div>
455 </div>
455 </div>
456 </div>
456 </div>
457 </div>
457 </div>
458
458
459 <div class="markup-form-footer">
459 <div class="markup-form-footer">
460 % if help_text:
460 % if help_text:
461 <span class="help-block">${help_text}</span>
461 <span class="help-block">${help_text}</span>
462 % endif
462 % endif
463 </div>
463 </div>
464 </div>
464 </div>
465 <script type="text/javascript">
465 <script type="text/javascript">
466 new MarkupForm('${form_id}');
466 new MarkupForm('${form_id}');
467 </script>
467 </script>
468
468
469 </%def>
469 </%def>
@@ -1,804 +1,804 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
3 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
3 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
4
4
5 <%def name="title()">
5 <%def name="title()">
6 ${_('%s Pull Request #%s') % (c.repo_name, c.pull_request.pull_request_id)}
6 ${_('{} Pull Request !{}').format(c.repo_name, c.pull_request.pull_request_id)}
7 %if c.rhodecode_name:
7 %if c.rhodecode_name:
8 &middot; ${h.branding(c.rhodecode_name)}
8 &middot; ${h.branding(c.rhodecode_name)}
9 %endif
9 %endif
10 </%def>
10 </%def>
11
11
12 <%def name="breadcrumbs_links()">
12 <%def name="breadcrumbs_links()">
13 <span id="pr-title">
13 <span id="pr-title">
14 ${c.pull_request.title}
14 ${c.pull_request.title}
15 %if c.pull_request.is_closed():
15 %if c.pull_request.is_closed():
16 (${_('Closed')})
16 (${_('Closed')})
17 %endif
17 %endif
18 </span>
18 </span>
19 <div id="pr-title-edit" class="input" style="display: none;">
19 <div id="pr-title-edit" class="input" style="display: none;">
20 ${h.text('pullrequest_title', id_="pr-title-input", class_="large", value=c.pull_request.title)}
20 ${h.text('pullrequest_title', id_="pr-title-input", class_="large", value=c.pull_request.title)}
21 </div>
21 </div>
22 </%def>
22 </%def>
23
23
24 <%def name="menu_bar_nav()">
24 <%def name="menu_bar_nav()">
25 ${self.menu_items(active='repositories')}
25 ${self.menu_items(active='repositories')}
26 </%def>
26 </%def>
27
27
28 <%def name="menu_bar_subnav()">
28 <%def name="menu_bar_subnav()">
29 ${self.repo_menu(active='showpullrequest')}
29 ${self.repo_menu(active='showpullrequest')}
30 </%def>
30 </%def>
31
31
32 <%def name="main()">
32 <%def name="main()">
33
33
34 <script type="text/javascript">
34 <script type="text/javascript">
35 // TODO: marcink switch this to pyroutes
35 // TODO: marcink switch this to pyroutes
36 AJAX_COMMENT_DELETE_URL = "${h.route_path('pullrequest_comment_delete',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id,comment_id='__COMMENT_ID__')}";
36 AJAX_COMMENT_DELETE_URL = "${h.route_path('pullrequest_comment_delete',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id,comment_id='__COMMENT_ID__')}";
37 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
37 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
38 </script>
38 </script>
39 <div class="box">
39 <div class="box">
40
40
41 ${self.breadcrumbs()}
41 ${self.breadcrumbs()}
42
42
43 <div class="box pr-summary">
43 <div class="box pr-summary">
44
44
45 <div class="summary-details block-left">
45 <div class="summary-details block-left">
46 <% summary = lambda n:{False:'summary-short'}.get(n) %>
46 <% summary = lambda n:{False:'summary-short'}.get(n) %>
47 <div class="pr-details-title">
47 <div class="pr-details-title">
48 <a href="${h.route_path('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">${_('Pull request #%s') % c.pull_request.pull_request_id}</a> ${_('From')} ${h.format_date(c.pull_request.created_on)}
48 <a href="${h.route_path('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">${_('Pull request !{}').format(c.pull_request.pull_request_id)}</a> ${_('From')} ${h.format_date(c.pull_request.created_on)}
49 %if c.allowed_to_update:
49 %if c.allowed_to_update:
50 <div id="delete_pullrequest" class="pull-right action_button ${'' if c.allowed_to_delete else 'disabled' }" style="clear:inherit;padding: 0">
50 <div id="delete_pullrequest" class="pull-right action_button ${'' if c.allowed_to_delete else 'disabled' }" style="clear:inherit;padding: 0">
51 % if c.allowed_to_delete:
51 % if c.allowed_to_delete:
52 ${h.secure_form(h.route_path('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id), request=request)}
52 ${h.secure_form(h.route_path('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id), request=request)}
53 ${h.submit('remove_%s' % c.pull_request.pull_request_id, _('Delete'),
53 ${h.submit('remove_%s' % c.pull_request.pull_request_id, _('Delete'),
54 class_="btn btn-link btn-danger no-margin",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
54 class_="btn btn-link btn-danger no-margin",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
55 ${h.end_form()}
55 ${h.end_form()}
56 % else:
56 % else:
57 ${_('Delete')}
57 ${_('Delete')}
58 % endif
58 % endif
59 </div>
59 </div>
60 <div id="open_edit_pullrequest" class="pull-right action_button">${_('Edit')}</div>
60 <div id="open_edit_pullrequest" class="pull-right action_button">${_('Edit')}</div>
61 <div id="close_edit_pullrequest" class="pull-right action_button" style="display: none;padding: 0">${_('Cancel')}</div>
61 <div id="close_edit_pullrequest" class="pull-right action_button" style="display: none;padding: 0">${_('Cancel')}</div>
62 %endif
62 %endif
63 </div>
63 </div>
64
64
65 <div id="summary" class="fields pr-details-content">
65 <div id="summary" class="fields pr-details-content">
66 <div class="field">
66 <div class="field">
67 <div class="label-summary">
67 <div class="label-summary">
68 <label>${_('Source')}:</label>
68 <label>${_('Source')}:</label>
69 </div>
69 </div>
70 <div class="input">
70 <div class="input">
71 <div class="pr-origininfo">
71 <div class="pr-origininfo">
72 ## branch link is only valid if it is a branch
72 ## branch link is only valid if it is a branch
73 <span class="tag">
73 <span class="tag">
74 %if c.pull_request.source_ref_parts.type == 'branch':
74 %if c.pull_request.source_ref_parts.type == 'branch':
75 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.source_repo.repo_name, _query=dict(branch=c.pull_request.source_ref_parts.name))}">${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}</a>
75 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.source_repo.repo_name, _query=dict(branch=c.pull_request.source_ref_parts.name))}">${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}</a>
76 %else:
76 %else:
77 ${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}
77 ${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}
78 %endif
78 %endif
79 </span>
79 </span>
80 <span class="clone-url">
80 <span class="clone-url">
81 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.clone_url()}</a>
81 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.clone_url()}</a>
82 </span>
82 </span>
83 <br/>
83 <br/>
84 % if c.ancestor_commit:
84 % if c.ancestor_commit:
85 ${_('Common ancestor')}:
85 ${_('Common ancestor')}:
86 <code><a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=c.ancestor_commit.raw_id)}">${h.show_id(c.ancestor_commit)}</a></code>
86 <code><a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=c.ancestor_commit.raw_id)}">${h.show_id(c.ancestor_commit)}</a></code>
87 % endif
87 % endif
88 </div>
88 </div>
89 %if h.is_hg(c.pull_request.source_repo):
89 %if h.is_hg(c.pull_request.source_repo):
90 <% clone_url = 'hg pull -r {} {}'.format(h.short_id(c.source_ref), c.pull_request.source_repo.clone_url()) %>
90 <% clone_url = 'hg pull -r {} {}'.format(h.short_id(c.source_ref), c.pull_request.source_repo.clone_url()) %>
91 %elif h.is_git(c.pull_request.source_repo):
91 %elif h.is_git(c.pull_request.source_repo):
92 <% clone_url = 'git pull {} {}'.format(c.pull_request.source_repo.clone_url(), c.pull_request.source_ref_parts.name) %>
92 <% clone_url = 'git pull {} {}'.format(c.pull_request.source_repo.clone_url(), c.pull_request.source_ref_parts.name) %>
93 %endif
93 %endif
94
94
95 <div class="">
95 <div class="">
96 <input type="text" class="input-monospace pr-pullinfo" value="${clone_url}" readonly="readonly">
96 <input type="text" class="input-monospace pr-pullinfo" value="${clone_url}" readonly="readonly">
97 <i class="tooltip icon-clipboard clipboard-action pull-right pr-pullinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the pull url')}"></i>
97 <i class="tooltip icon-clipboard clipboard-action pull-right pr-pullinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the pull url')}"></i>
98 </div>
98 </div>
99
99
100 </div>
100 </div>
101 </div>
101 </div>
102 <div class="field">
102 <div class="field">
103 <div class="label-summary">
103 <div class="label-summary">
104 <label>${_('Target')}:</label>
104 <label>${_('Target')}:</label>
105 </div>
105 </div>
106 <div class="input">
106 <div class="input">
107 <div class="pr-targetinfo">
107 <div class="pr-targetinfo">
108 ## branch link is only valid if it is a branch
108 ## branch link is only valid if it is a branch
109 <span class="tag">
109 <span class="tag">
110 %if c.pull_request.target_ref_parts.type == 'branch':
110 %if c.pull_request.target_ref_parts.type == 'branch':
111 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.target_repo.repo_name, _query=dict(branch=c.pull_request.target_ref_parts.name))}">${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}</a>
111 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.target_repo.repo_name, _query=dict(branch=c.pull_request.target_ref_parts.name))}">${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}</a>
112 %else:
112 %else:
113 ${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}
113 ${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}
114 %endif
114 %endif
115 </span>
115 </span>
116 <span class="clone-url">
116 <span class="clone-url">
117 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.clone_url()}</a>
117 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.clone_url()}</a>
118 </span>
118 </span>
119 </div>
119 </div>
120 </div>
120 </div>
121 </div>
121 </div>
122
122
123 ## Link to the shadow repository.
123 ## Link to the shadow repository.
124 <div class="field">
124 <div class="field">
125 <div class="label-summary">
125 <div class="label-summary">
126 <label>${_('Merge')}:</label>
126 <label>${_('Merge')}:</label>
127 </div>
127 </div>
128 <div class="input">
128 <div class="input">
129 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
129 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
130 %if h.is_hg(c.pull_request.target_repo):
130 %if h.is_hg(c.pull_request.target_repo):
131 <% clone_url = 'hg clone --update {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
131 <% clone_url = 'hg clone --update {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
132 %elif h.is_git(c.pull_request.target_repo):
132 %elif h.is_git(c.pull_request.target_repo):
133 <% clone_url = 'git clone --branch {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
133 <% clone_url = 'git clone --branch {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
134 %endif
134 %endif
135 <div class="">
135 <div class="">
136 <input type="text" class="input-monospace pr-mergeinfo" value="${clone_url}" readonly="readonly">
136 <input type="text" class="input-monospace pr-mergeinfo" value="${clone_url}" readonly="readonly">
137 <i class="tooltip icon-clipboard clipboard-action pull-right pr-mergeinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the clone url')}"></i>
137 <i class="tooltip icon-clipboard clipboard-action pull-right pr-mergeinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the clone url')}"></i>
138 </div>
138 </div>
139 % else:
139 % else:
140 <div class="">
140 <div class="">
141 ${_('Shadow repository data not available')}.
141 ${_('Shadow repository data not available')}.
142 </div>
142 </div>
143 % endif
143 % endif
144 </div>
144 </div>
145 </div>
145 </div>
146
146
147 <div class="field">
147 <div class="field">
148 <div class="label-summary">
148 <div class="label-summary">
149 <label>${_('Review')}:</label>
149 <label>${_('Review')}:</label>
150 </div>
150 </div>
151 <div class="input">
151 <div class="input">
152 %if c.pull_request_review_status:
152 %if c.pull_request_review_status:
153 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
153 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
154 <span class="changeset-status-lbl tooltip">
154 <span class="changeset-status-lbl tooltip">
155 %if c.pull_request.is_closed():
155 %if c.pull_request.is_closed():
156 ${_('Closed')},
156 ${_('Closed')},
157 %endif
157 %endif
158 ${h.commit_status_lbl(c.pull_request_review_status)}
158 ${h.commit_status_lbl(c.pull_request_review_status)}
159 </span>
159 </span>
160 - ${_ungettext('calculated based on %s reviewer vote', 'calculated based on %s reviewers votes', len(c.pull_request_reviewers)) % len(c.pull_request_reviewers)}
160 - ${_ungettext('calculated based on %s reviewer vote', 'calculated based on %s reviewers votes', len(c.pull_request_reviewers)) % len(c.pull_request_reviewers)}
161 %endif
161 %endif
162 </div>
162 </div>
163 </div>
163 </div>
164 <div class="field">
164 <div class="field">
165 <div class="pr-description-label label-summary" title="${_('Rendered using {} renderer').format(c.renderer)}">
165 <div class="pr-description-label label-summary" title="${_('Rendered using {} renderer').format(c.renderer)}">
166 <label>${_('Description')}:</label>
166 <label>${_('Description')}:</label>
167 </div>
167 </div>
168 <div id="pr-desc" class="input">
168 <div id="pr-desc" class="input">
169 <div class="pr-description">${h.render(c.pull_request.description, renderer=c.renderer)}</div>
169 <div class="pr-description">${h.render(c.pull_request.description, renderer=c.renderer)}</div>
170 </div>
170 </div>
171 <div id="pr-desc-edit" class="input textarea editor" style="display: none;">
171 <div id="pr-desc-edit" class="input textarea editor" style="display: none;">
172 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
172 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
173 ${dt.markup_form('pr-description-input', form_text=c.pull_request.description)}
173 ${dt.markup_form('pr-description-input', form_text=c.pull_request.description)}
174 </div>
174 </div>
175 </div>
175 </div>
176
176
177 <div class="field">
177 <div class="field">
178 <div class="label-summary">
178 <div class="label-summary">
179 <label>${_('Versions')}:</label>
179 <label>${_('Versions')}:</label>
180 </div>
180 </div>
181
181
182 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
182 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
183 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
183 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
184
184
185 <div class="pr-versions">
185 <div class="pr-versions">
186 % if c.show_version_changes:
186 % if c.show_version_changes:
187 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
187 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
188 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
188 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
189 <a id="show-pr-versions" class="input" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
189 <a id="show-pr-versions" class="input" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
190 data-toggle-on="${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}"
190 data-toggle-on="${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}"
191 data-toggle-off="${_('Hide all versions of this pull request')}">
191 data-toggle-off="${_('Hide all versions of this pull request')}">
192 ${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}
192 ${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}
193 </a>
193 </a>
194 <table>
194 <table>
195 ## SHOW ALL VERSIONS OF PR
195 ## SHOW ALL VERSIONS OF PR
196 <% ver_pr = None %>
196 <% ver_pr = None %>
197
197
198 % for data in reversed(list(enumerate(c.versions, 1))):
198 % for data in reversed(list(enumerate(c.versions, 1))):
199 <% ver_pos = data[0] %>
199 <% ver_pos = data[0] %>
200 <% ver = data[1] %>
200 <% ver = data[1] %>
201 <% ver_pr = ver.pull_request_version_id %>
201 <% ver_pr = ver.pull_request_version_id %>
202 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
202 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
203
203
204 <tr class="version-pr" style="display: ${display_row}">
204 <tr class="version-pr" style="display: ${display_row}">
205 <td>
205 <td>
206 <code>
206 <code>
207 <a href="${request.current_route_path(_query=dict(version=ver_pr or 'latest'))}">v${ver_pos}</a>
207 <a href="${request.current_route_path(_query=dict(version=ver_pr or 'latest'))}">v${ver_pos}</a>
208 </code>
208 </code>
209 </td>
209 </td>
210 <td>
210 <td>
211 <input ${'checked="checked"' if c.from_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
211 <input ${'checked="checked"' if c.from_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
212 <input ${'checked="checked"' if c.at_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_target" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
212 <input ${'checked="checked"' if c.at_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_target" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
213 </td>
213 </td>
214 <td>
214 <td>
215 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
215 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
216 <i class="tooltip icon-circle review-status-${review_status}" title="${_('Your review status at this version')}"></i>
216 <i class="tooltip icon-circle review-status-${review_status}" title="${_('Your review status at this version')}"></i>
217 </div>
217 </div>
218 </td>
218 </td>
219 <td>
219 <td>
220 % if c.at_version_num != ver_pr:
220 % if c.at_version_num != ver_pr:
221 <i class="icon-comment"></i>
221 <i class="icon-comment"></i>
222 <code class="tooltip" title="${_('Comment from pull request version v{0}, general:{1} inline:{2}').format(ver_pos, len(c.comment_versions[ver_pr]['at']), len(c.inline_versions[ver_pr]['at']))}">
222 <code class="tooltip" title="${_('Comment from pull request version v{0}, general:{1} inline:{2}').format(ver_pos, len(c.comment_versions[ver_pr]['at']), len(c.inline_versions[ver_pr]['at']))}">
223 G:${len(c.comment_versions[ver_pr]['at'])} / I:${len(c.inline_versions[ver_pr]['at'])}
223 G:${len(c.comment_versions[ver_pr]['at'])} / I:${len(c.inline_versions[ver_pr]['at'])}
224 </code>
224 </code>
225 % endif
225 % endif
226 </td>
226 </td>
227 <td>
227 <td>
228 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
228 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
229 </td>
229 </td>
230 <td>
230 <td>
231 ${h.age_component(ver.updated_on, time_is_local=True)}
231 ${h.age_component(ver.updated_on, time_is_local=True)}
232 </td>
232 </td>
233 </tr>
233 </tr>
234 % endfor
234 % endfor
235
235
236 <tr>
236 <tr>
237 <td colspan="6">
237 <td colspan="6">
238 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
238 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
239 data-label-text-locked="${_('select versions to show changes')}"
239 data-label-text-locked="${_('select versions to show changes')}"
240 data-label-text-diff="${_('show changes between versions')}"
240 data-label-text-diff="${_('show changes between versions')}"
241 data-label-text-show="${_('show pull request for this version')}"
241 data-label-text-show="${_('show pull request for this version')}"
242 >
242 >
243 ${_('select versions to show changes')}
243 ${_('select versions to show changes')}
244 </button>
244 </button>
245 </td>
245 </td>
246 </tr>
246 </tr>
247 </table>
247 </table>
248 % else:
248 % else:
249 <div class="input">
249 <div class="input">
250 ${_('Pull request versions not available')}.
250 ${_('Pull request versions not available')}.
251 </div>
251 </div>
252 % endif
252 % endif
253 </div>
253 </div>
254 </div>
254 </div>
255
255
256 <div id="pr-save" class="field" style="display: none;">
256 <div id="pr-save" class="field" style="display: none;">
257 <div class="label-summary"></div>
257 <div class="label-summary"></div>
258 <div class="input">
258 <div class="input">
259 <span id="edit_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</span>
259 <span id="edit_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</span>
260 </div>
260 </div>
261 </div>
261 </div>
262 </div>
262 </div>
263 </div>
263 </div>
264 <div>
264 <div>
265 ## AUTHOR
265 ## AUTHOR
266 <div class="reviewers-title block-right">
266 <div class="reviewers-title block-right">
267 <div class="pr-details-title">
267 <div class="pr-details-title">
268 ${_('Author of this pull request')}
268 ${_('Author of this pull request')}
269 </div>
269 </div>
270 </div>
270 </div>
271 <div class="block-right pr-details-content reviewers">
271 <div class="block-right pr-details-content reviewers">
272 <ul class="group_members">
272 <ul class="group_members">
273 <li>
273 <li>
274 ${self.gravatar_with_user(c.pull_request.author.email, 16, tooltip=True)}
274 ${self.gravatar_with_user(c.pull_request.author.email, 16, tooltip=True)}
275 </li>
275 </li>
276 </ul>
276 </ul>
277 </div>
277 </div>
278
278
279 ## REVIEW RULES
279 ## REVIEW RULES
280 <div id="review_rules" style="display: none" class="reviewers-title block-right">
280 <div id="review_rules" style="display: none" class="reviewers-title block-right">
281 <div class="pr-details-title">
281 <div class="pr-details-title">
282 ${_('Reviewer rules')}
282 ${_('Reviewer rules')}
283 %if c.allowed_to_update:
283 %if c.allowed_to_update:
284 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
284 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
285 %endif
285 %endif
286 </div>
286 </div>
287 <div class="pr-reviewer-rules">
287 <div class="pr-reviewer-rules">
288 ## review rules will be appended here, by default reviewers logic
288 ## review rules will be appended here, by default reviewers logic
289 </div>
289 </div>
290 <input id="review_data" type="hidden" name="review_data" value="">
290 <input id="review_data" type="hidden" name="review_data" value="">
291 </div>
291 </div>
292
292
293 ## REVIEWERS
293 ## REVIEWERS
294 <div class="reviewers-title block-right">
294 <div class="reviewers-title block-right">
295 <div class="pr-details-title">
295 <div class="pr-details-title">
296 ${_('Pull request reviewers')}
296 ${_('Pull request reviewers')}
297 %if c.allowed_to_update:
297 %if c.allowed_to_update:
298 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
298 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
299 %endif
299 %endif
300 </div>
300 </div>
301 </div>
301 </div>
302 <div id="reviewers" class="block-right pr-details-content reviewers">
302 <div id="reviewers" class="block-right pr-details-content reviewers">
303
303
304 ## members redering block
304 ## members redering block
305 <input type="hidden" name="__start__" value="review_members:sequence">
305 <input type="hidden" name="__start__" value="review_members:sequence">
306 <ul id="review_members" class="group_members">
306 <ul id="review_members" class="group_members">
307
307
308 % for review_obj, member, reasons, mandatory, status in c.pull_request_reviewers:
308 % for review_obj, member, reasons, mandatory, status in c.pull_request_reviewers:
309 <script>
309 <script>
310 var member = ${h.json.dumps(h.reviewer_as_json(member, reasons=reasons, mandatory=mandatory, user_group=review_obj.rule_user_group_data()))|n};
310 var member = ${h.json.dumps(h.reviewer_as_json(member, reasons=reasons, mandatory=mandatory, user_group=review_obj.rule_user_group_data()))|n};
311 var status = "${(status[0][1].status if status else 'not_reviewed')}";
311 var status = "${(status[0][1].status if status else 'not_reviewed')}";
312 var status_lbl = "${h.commit_status_lbl(status[0][1].status if status else 'not_reviewed')}";
312 var status_lbl = "${h.commit_status_lbl(status[0][1].status if status else 'not_reviewed')}";
313 var allowed_to_update = ${h.json.dumps(c.allowed_to_update)};
313 var allowed_to_update = ${h.json.dumps(c.allowed_to_update)};
314
314
315 var entry = renderTemplate('reviewMemberEntry', {
315 var entry = renderTemplate('reviewMemberEntry', {
316 'member': member,
316 'member': member,
317 'mandatory': member.mandatory,
317 'mandatory': member.mandatory,
318 'reasons': member.reasons,
318 'reasons': member.reasons,
319 'allowed_to_update': allowed_to_update,
319 'allowed_to_update': allowed_to_update,
320 'review_status': status,
320 'review_status': status,
321 'review_status_label': status_lbl,
321 'review_status_label': status_lbl,
322 'user_group': member.user_group,
322 'user_group': member.user_group,
323 'create': false
323 'create': false
324 });
324 });
325 $('#review_members').append(entry)
325 $('#review_members').append(entry)
326 </script>
326 </script>
327
327
328 % endfor
328 % endfor
329
329
330 </ul>
330 </ul>
331
331
332 <input type="hidden" name="__end__" value="review_members:sequence">
332 <input type="hidden" name="__end__" value="review_members:sequence">
333 ## end members redering block
333 ## end members redering block
334
334
335 %if not c.pull_request.is_closed():
335 %if not c.pull_request.is_closed():
336 <div id="add_reviewer" class="ac" style="display: none;">
336 <div id="add_reviewer" class="ac" style="display: none;">
337 %if c.allowed_to_update:
337 %if c.allowed_to_update:
338 % if not c.forbid_adding_reviewers:
338 % if not c.forbid_adding_reviewers:
339 <div id="add_reviewer_input" class="reviewer_ac">
339 <div id="add_reviewer_input" class="reviewer_ac">
340 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
340 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
341 <div id="reviewers_container"></div>
341 <div id="reviewers_container"></div>
342 </div>
342 </div>
343 % endif
343 % endif
344 <div class="pull-right">
344 <div class="pull-right">
345 <button id="update_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</button>
345 <button id="update_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</button>
346 </div>
346 </div>
347 %endif
347 %endif
348 </div>
348 </div>
349 %endif
349 %endif
350 </div>
350 </div>
351 </div>
351 </div>
352 </div>
352 </div>
353 <div class="box">
353 <div class="box">
354 ##DIFF
354 ##DIFF
355 <div class="table" >
355 <div class="table" >
356 <div id="changeset_compare_view_content">
356 <div id="changeset_compare_view_content">
357 ##CS
357 ##CS
358 % if c.missing_requirements:
358 % if c.missing_requirements:
359 <div class="box">
359 <div class="box">
360 <div class="alert alert-warning">
360 <div class="alert alert-warning">
361 <div>
361 <div>
362 <strong>${_('Missing requirements:')}</strong>
362 <strong>${_('Missing requirements:')}</strong>
363 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
363 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
364 </div>
364 </div>
365 </div>
365 </div>
366 </div>
366 </div>
367 % elif c.missing_commits:
367 % elif c.missing_commits:
368 <div class="box">
368 <div class="box">
369 <div class="alert alert-warning">
369 <div class="alert alert-warning">
370 <div>
370 <div>
371 <strong>${_('Missing commits')}:</strong>
371 <strong>${_('Missing commits')}:</strong>
372 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}
372 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}
373 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}
373 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}
374 ${_('Consider doing a {force_refresh_url} in case you think this is an error.').format(force_refresh_url=h.link_to('force refresh', h.current_route_path(request, force_refresh='1')))|n}
374 ${_('Consider doing a {force_refresh_url} in case you think this is an error.').format(force_refresh_url=h.link_to('force refresh', h.current_route_path(request, force_refresh='1')))|n}
375 </div>
375 </div>
376 </div>
376 </div>
377 </div>
377 </div>
378 % endif
378 % endif
379
379
380 <div class="compare_view_commits_title">
380 <div class="compare_view_commits_title">
381 % if not c.compare_mode:
381 % if not c.compare_mode:
382
382
383 % if c.at_version_pos:
383 % if c.at_version_pos:
384 <h4>
384 <h4>
385 ${_('Showing changes at v%d, commenting is disabled.') % c.at_version_pos}
385 ${_('Showing changes at v%d, commenting is disabled.') % c.at_version_pos}
386 </h4>
386 </h4>
387 % endif
387 % endif
388
388
389 <div class="pull-left">
389 <div class="pull-left">
390 <div class="btn-group">
390 <div class="btn-group">
391 <a
391 <a
392 class="btn"
392 class="btn"
393 href="#"
393 href="#"
394 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
394 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
395 ${_ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
395 ${_ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
396 </a>
396 </a>
397 <a
397 <a
398 class="btn"
398 class="btn"
399 href="#"
399 href="#"
400 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
400 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
401 ${_ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
401 ${_ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
402 </a>
402 </a>
403 </div>
403 </div>
404 </div>
404 </div>
405
405
406 <div class="pull-right">
406 <div class="pull-right">
407 % if c.allowed_to_update and not c.pull_request.is_closed():
407 % if c.allowed_to_update and not c.pull_request.is_closed():
408 <a id="update_commits" class="btn btn-primary no-margin pull-right">${_('Update commits')}</a>
408 <a id="update_commits" class="btn btn-primary no-margin pull-right">${_('Update commits')}</a>
409 % else:
409 % else:
410 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
410 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
411 % endif
411 % endif
412
412
413 </div>
413 </div>
414 % endif
414 % endif
415 </div>
415 </div>
416
416
417 % if not c.missing_commits:
417 % if not c.missing_commits:
418 % if c.compare_mode:
418 % if c.compare_mode:
419 % if c.at_version:
419 % if c.at_version:
420 <h4>
420 <h4>
421 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_pos, ver_to=c.at_version_pos if c.at_version_pos else 'latest')}:
421 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_pos, ver_to=c.at_version_pos if c.at_version_pos else 'latest')}:
422 </h4>
422 </h4>
423
423
424 <div class="subtitle-compare">
424 <div class="subtitle-compare">
425 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
425 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
426 </div>
426 </div>
427
427
428 <div class="container">
428 <div class="container">
429 <table class="rctable compare_view_commits">
429 <table class="rctable compare_view_commits">
430 <tr>
430 <tr>
431 <th></th>
431 <th></th>
432 <th>${_('Time')}</th>
432 <th>${_('Time')}</th>
433 <th>${_('Author')}</th>
433 <th>${_('Author')}</th>
434 <th>${_('Commit')}</th>
434 <th>${_('Commit')}</th>
435 <th></th>
435 <th></th>
436 <th>${_('Description')}</th>
436 <th>${_('Description')}</th>
437 </tr>
437 </tr>
438
438
439 % for c_type, commit in c.commit_changes:
439 % for c_type, commit in c.commit_changes:
440 % if c_type in ['a', 'r']:
440 % if c_type in ['a', 'r']:
441 <%
441 <%
442 if c_type == 'a':
442 if c_type == 'a':
443 cc_title = _('Commit added in displayed changes')
443 cc_title = _('Commit added in displayed changes')
444 elif c_type == 'r':
444 elif c_type == 'r':
445 cc_title = _('Commit removed in displayed changes')
445 cc_title = _('Commit removed in displayed changes')
446 else:
446 else:
447 cc_title = ''
447 cc_title = ''
448 %>
448 %>
449 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
449 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
450 <td>
450 <td>
451 <div class="commit-change-indicator color-${c_type}-border">
451 <div class="commit-change-indicator color-${c_type}-border">
452 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
452 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
453 ${c_type.upper()}
453 ${c_type.upper()}
454 </div>
454 </div>
455 </div>
455 </div>
456 </td>
456 </td>
457 <td class="td-time">
457 <td class="td-time">
458 ${h.age_component(commit.date)}
458 ${h.age_component(commit.date)}
459 </td>
459 </td>
460 <td class="td-user">
460 <td class="td-user">
461 ${base.gravatar_with_user(commit.author, 16, tooltip=True)}
461 ${base.gravatar_with_user(commit.author, 16, tooltip=True)}
462 </td>
462 </td>
463 <td class="td-hash">
463 <td class="td-hash">
464 <code>
464 <code>
465 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=commit.raw_id)}">
465 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=commit.raw_id)}">
466 r${commit.idx}:${h.short_id(commit.raw_id)}
466 r${commit.idx}:${h.short_id(commit.raw_id)}
467 </a>
467 </a>
468 ${h.hidden('revisions', commit.raw_id)}
468 ${h.hidden('revisions', commit.raw_id)}
469 </code>
469 </code>
470 </td>
470 </td>
471 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
471 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
472 <i class="icon-expand-linked"></i>
472 <i class="icon-expand-linked"></i>
473 </td>
473 </td>
474 <td class="mid td-description">
474 <td class="mid td-description">
475 <div class="log-container truncate-wrap">
475 <div class="log-container truncate-wrap">
476 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">${h.urlify_commit_message(commit.message, c.repo_name)}</div>
476 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">${h.urlify_commit_message(commit.message, c.repo_name)}</div>
477 </div>
477 </div>
478 </td>
478 </td>
479 </tr>
479 </tr>
480 % endif
480 % endif
481 % endfor
481 % endfor
482 </table>
482 </table>
483 </div>
483 </div>
484
484
485 % endif
485 % endif
486
486
487 % else:
487 % else:
488 <%include file="/compare/compare_commits.mako" />
488 <%include file="/compare/compare_commits.mako" />
489 % endif
489 % endif
490
490
491 <div class="cs_files">
491 <div class="cs_files">
492 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
492 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
493 % if c.at_version:
493 % if c.at_version:
494 <% c.inline_cnt = len(c.inline_versions[c.at_version_num]['display']) %>
494 <% c.inline_cnt = len(c.inline_versions[c.at_version_num]['display']) %>
495 <% c.comments = c.comment_versions[c.at_version_num]['display'] %>
495 <% c.comments = c.comment_versions[c.at_version_num]['display'] %>
496 % else:
496 % else:
497 <% c.inline_cnt = len(c.inline_versions[c.at_version_num]['until']) %>
497 <% c.inline_cnt = len(c.inline_versions[c.at_version_num]['until']) %>
498 <% c.comments = c.comment_versions[c.at_version_num]['until'] %>
498 <% c.comments = c.comment_versions[c.at_version_num]['until'] %>
499 % endif
499 % endif
500
500
501 <%
501 <%
502 pr_menu_data = {
502 pr_menu_data = {
503 'outdated_comm_count_ver': outdated_comm_count_ver
503 'outdated_comm_count_ver': outdated_comm_count_ver
504 }
504 }
505 %>
505 %>
506
506
507 ${cbdiffs.render_diffset_menu(c.diffset, range_diff_on=c.range_diff_on)}
507 ${cbdiffs.render_diffset_menu(c.diffset, range_diff_on=c.range_diff_on)}
508
508
509 % if c.range_diff_on:
509 % if c.range_diff_on:
510 % for commit in c.commit_ranges:
510 % for commit in c.commit_ranges:
511 ${cbdiffs.render_diffset(
511 ${cbdiffs.render_diffset(
512 c.changes[commit.raw_id],
512 c.changes[commit.raw_id],
513 commit=commit, use_comments=True,
513 commit=commit, use_comments=True,
514 collapse_when_files_over=5,
514 collapse_when_files_over=5,
515 disable_new_comments=True,
515 disable_new_comments=True,
516 deleted_files_comments=c.deleted_files_comments,
516 deleted_files_comments=c.deleted_files_comments,
517 inline_comments=c.inline_comments,
517 inline_comments=c.inline_comments,
518 pull_request_menu=pr_menu_data)}
518 pull_request_menu=pr_menu_data)}
519 % endfor
519 % endfor
520 % else:
520 % else:
521 ${cbdiffs.render_diffset(
521 ${cbdiffs.render_diffset(
522 c.diffset, use_comments=True,
522 c.diffset, use_comments=True,
523 collapse_when_files_over=30,
523 collapse_when_files_over=30,
524 disable_new_comments=not c.allowed_to_comment,
524 disable_new_comments=not c.allowed_to_comment,
525 deleted_files_comments=c.deleted_files_comments,
525 deleted_files_comments=c.deleted_files_comments,
526 inline_comments=c.inline_comments,
526 inline_comments=c.inline_comments,
527 pull_request_menu=pr_menu_data)}
527 pull_request_menu=pr_menu_data)}
528 % endif
528 % endif
529
529
530 </div>
530 </div>
531 % else:
531 % else:
532 ## skipping commits we need to clear the view for missing commits
532 ## skipping commits we need to clear the view for missing commits
533 <div style="clear:both;"></div>
533 <div style="clear:both;"></div>
534 % endif
534 % endif
535
535
536 </div>
536 </div>
537 </div>
537 </div>
538
538
539 ## template for inline comment form
539 ## template for inline comment form
540 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
540 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
541
541
542 ## comments heading with count
542 ## comments heading with count
543 <div class="comments-heading">
543 <div class="comments-heading">
544 <i class="icon-comment"></i>
544 <i class="icon-comment"></i>
545 ${_('Comments')} ${len(c.comments)}
545 ${_('Comments')} ${len(c.comments)}
546 </div>
546 </div>
547
547
548 ## render general comments
548 ## render general comments
549 <div id="comment-tr-show">
549 <div id="comment-tr-show">
550 % if general_outdated_comm_count_ver:
550 % if general_outdated_comm_count_ver:
551 <div class="info-box">
551 <div class="info-box">
552 % if general_outdated_comm_count_ver == 1:
552 % if general_outdated_comm_count_ver == 1:
553 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
553 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
554 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
554 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
555 % else:
555 % else:
556 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
556 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
557 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
557 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
558 % endif
558 % endif
559 </div>
559 </div>
560 % endif
560 % endif
561 </div>
561 </div>
562
562
563 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
563 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
564
564
565 % if not c.pull_request.is_closed():
565 % if not c.pull_request.is_closed():
566 ## merge status, and merge action
566 ## merge status, and merge action
567 <div class="pull-request-merge">
567 <div class="pull-request-merge">
568 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
568 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
569 </div>
569 </div>
570
570
571 ## main comment form and it status
571 ## main comment form and it status
572 ${comment.comments(h.route_path('pullrequest_comment_create', repo_name=c.repo_name,
572 ${comment.comments(h.route_path('pullrequest_comment_create', repo_name=c.repo_name,
573 pull_request_id=c.pull_request.pull_request_id),
573 pull_request_id=c.pull_request.pull_request_id),
574 c.pull_request_review_status,
574 c.pull_request_review_status,
575 is_pull_request=True, change_status=c.allowed_to_change_status)}
575 is_pull_request=True, change_status=c.allowed_to_change_status)}
576 %endif
576 %endif
577
577
578 <script type="text/javascript">
578 <script type="text/javascript">
579 if (location.hash) {
579 if (location.hash) {
580 var result = splitDelimitedHash(location.hash);
580 var result = splitDelimitedHash(location.hash);
581 var line = $('html').find(result.loc);
581 var line = $('html').find(result.loc);
582 // show hidden comments if we use location.hash
582 // show hidden comments if we use location.hash
583 if (line.hasClass('comment-general')) {
583 if (line.hasClass('comment-general')) {
584 $(line).show();
584 $(line).show();
585 } else if (line.hasClass('comment-inline')) {
585 } else if (line.hasClass('comment-inline')) {
586 $(line).show();
586 $(line).show();
587 var $cb = $(line).closest('.cb');
587 var $cb = $(line).closest('.cb');
588 $cb.removeClass('cb-collapsed')
588 $cb.removeClass('cb-collapsed')
589 }
589 }
590 if (line.length > 0){
590 if (line.length > 0){
591 offsetScroll(line, 70);
591 offsetScroll(line, 70);
592 }
592 }
593 }
593 }
594
594
595 versionController = new VersionController();
595 versionController = new VersionController();
596 versionController.init();
596 versionController.init();
597
597
598 reviewersController = new ReviewersController();
598 reviewersController = new ReviewersController();
599 commitsController = new CommitsController();
599 commitsController = new CommitsController();
600
600
601 $(function(){
601 $(function(){
602
602
603 // custom code mirror
603 // custom code mirror
604 var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
604 var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
605
605
606 var PRDetails = {
606 var PRDetails = {
607 editButton: $('#open_edit_pullrequest'),
607 editButton: $('#open_edit_pullrequest'),
608 closeButton: $('#close_edit_pullrequest'),
608 closeButton: $('#close_edit_pullrequest'),
609 deleteButton: $('#delete_pullrequest'),
609 deleteButton: $('#delete_pullrequest'),
610 viewFields: $('#pr-desc, #pr-title'),
610 viewFields: $('#pr-desc, #pr-title'),
611 editFields: $('#pr-desc-edit, #pr-title-edit, #pr-save'),
611 editFields: $('#pr-desc-edit, #pr-title-edit, #pr-save'),
612
612
613 init: function() {
613 init: function() {
614 var that = this;
614 var that = this;
615 this.editButton.on('click', function(e) { that.edit(); });
615 this.editButton.on('click', function(e) { that.edit(); });
616 this.closeButton.on('click', function(e) { that.view(); });
616 this.closeButton.on('click', function(e) { that.view(); });
617 },
617 },
618
618
619 edit: function(event) {
619 edit: function(event) {
620 this.viewFields.hide();
620 this.viewFields.hide();
621 this.editButton.hide();
621 this.editButton.hide();
622 this.deleteButton.hide();
622 this.deleteButton.hide();
623 this.closeButton.show();
623 this.closeButton.show();
624 this.editFields.show();
624 this.editFields.show();
625 codeMirrorInstance.refresh();
625 codeMirrorInstance.refresh();
626 },
626 },
627
627
628 view: function(event) {
628 view: function(event) {
629 this.editButton.show();
629 this.editButton.show();
630 this.deleteButton.show();
630 this.deleteButton.show();
631 this.editFields.hide();
631 this.editFields.hide();
632 this.closeButton.hide();
632 this.closeButton.hide();
633 this.viewFields.show();
633 this.viewFields.show();
634 }
634 }
635 };
635 };
636
636
637 var ReviewersPanel = {
637 var ReviewersPanel = {
638 editButton: $('#open_edit_reviewers'),
638 editButton: $('#open_edit_reviewers'),
639 closeButton: $('#close_edit_reviewers'),
639 closeButton: $('#close_edit_reviewers'),
640 addButton: $('#add_reviewer'),
640 addButton: $('#add_reviewer'),
641 removeButtons: $('.reviewer_member_remove,.reviewer_member_mandatory_remove'),
641 removeButtons: $('.reviewer_member_remove,.reviewer_member_mandatory_remove'),
642
642
643 init: function() {
643 init: function() {
644 var self = this;
644 var self = this;
645 this.editButton.on('click', function(e) { self.edit(); });
645 this.editButton.on('click', function(e) { self.edit(); });
646 this.closeButton.on('click', function(e) { self.close(); });
646 this.closeButton.on('click', function(e) { self.close(); });
647 },
647 },
648
648
649 edit: function(event) {
649 edit: function(event) {
650 this.editButton.hide();
650 this.editButton.hide();
651 this.closeButton.show();
651 this.closeButton.show();
652 this.addButton.show();
652 this.addButton.show();
653 this.removeButtons.css('visibility', 'visible');
653 this.removeButtons.css('visibility', 'visible');
654 // review rules
654 // review rules
655 reviewersController.loadReviewRules(
655 reviewersController.loadReviewRules(
656 ${c.pull_request.reviewer_data_json | n});
656 ${c.pull_request.reviewer_data_json | n});
657 },
657 },
658
658
659 close: function(event) {
659 close: function(event) {
660 this.editButton.show();
660 this.editButton.show();
661 this.closeButton.hide();
661 this.closeButton.hide();
662 this.addButton.hide();
662 this.addButton.hide();
663 this.removeButtons.css('visibility', 'hidden');
663 this.removeButtons.css('visibility', 'hidden');
664 // hide review rules
664 // hide review rules
665 reviewersController.hideReviewRules()
665 reviewersController.hideReviewRules()
666 }
666 }
667 };
667 };
668
668
669 PRDetails.init();
669 PRDetails.init();
670 ReviewersPanel.init();
670 ReviewersPanel.init();
671
671
672 showOutdated = function(self){
672 showOutdated = function(self){
673 $('.comment-inline.comment-outdated').show();
673 $('.comment-inline.comment-outdated').show();
674 $('.filediff-outdated').show();
674 $('.filediff-outdated').show();
675 $('.showOutdatedComments').hide();
675 $('.showOutdatedComments').hide();
676 $('.hideOutdatedComments').show();
676 $('.hideOutdatedComments').show();
677 };
677 };
678
678
679 hideOutdated = function(self){
679 hideOutdated = function(self){
680 $('.comment-inline.comment-outdated').hide();
680 $('.comment-inline.comment-outdated').hide();
681 $('.filediff-outdated').hide();
681 $('.filediff-outdated').hide();
682 $('.hideOutdatedComments').hide();
682 $('.hideOutdatedComments').hide();
683 $('.showOutdatedComments').show();
683 $('.showOutdatedComments').show();
684 };
684 };
685
685
686 refreshMergeChecks = function(){
686 refreshMergeChecks = function(){
687 var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
687 var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
688 $('.pull-request-merge').css('opacity', 0.3);
688 $('.pull-request-merge').css('opacity', 0.3);
689 $('.action-buttons-extra').css('opacity', 0.3);
689 $('.action-buttons-extra').css('opacity', 0.3);
690
690
691 $('.pull-request-merge').load(
691 $('.pull-request-merge').load(
692 loadUrl, function() {
692 loadUrl, function() {
693 $('.pull-request-merge').css('opacity', 1);
693 $('.pull-request-merge').css('opacity', 1);
694
694
695 $('.action-buttons-extra').css('opacity', 1);
695 $('.action-buttons-extra').css('opacity', 1);
696 }
696 }
697 );
697 );
698 };
698 };
699
699
700 closePullRequest = function (status) {
700 closePullRequest = function (status) {
701 // inject closing flag
701 // inject closing flag
702 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
702 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
703 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
703 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
704 $(generalCommentForm.submitForm).submit();
704 $(generalCommentForm.submitForm).submit();
705 };
705 };
706
706
707 $('#show-outdated-comments').on('click', function(e){
707 $('#show-outdated-comments').on('click', function(e){
708 var button = $(this);
708 var button = $(this);
709 var outdated = $('.comment-outdated');
709 var outdated = $('.comment-outdated');
710
710
711 if (button.html() === "(Show)") {
711 if (button.html() === "(Show)") {
712 button.html("(Hide)");
712 button.html("(Hide)");
713 outdated.show();
713 outdated.show();
714 } else {
714 } else {
715 button.html("(Show)");
715 button.html("(Show)");
716 outdated.hide();
716 outdated.hide();
717 }
717 }
718 });
718 });
719
719
720 $('.show-inline-comments').on('change', function(e){
720 $('.show-inline-comments').on('change', function(e){
721 var show = 'none';
721 var show = 'none';
722 var target = e.currentTarget;
722 var target = e.currentTarget;
723 if(target.checked){
723 if(target.checked){
724 show = ''
724 show = ''
725 }
725 }
726 var boxid = $(target).attr('id_for');
726 var boxid = $(target).attr('id_for');
727 var comments = $('#{0} .inline-comments'.format(boxid));
727 var comments = $('#{0} .inline-comments'.format(boxid));
728 var fn_display = function(idx){
728 var fn_display = function(idx){
729 $(this).css('display', show);
729 $(this).css('display', show);
730 };
730 };
731 $(comments).each(fn_display);
731 $(comments).each(fn_display);
732 var btns = $('#{0} .inline-comments-button'.format(boxid));
732 var btns = $('#{0} .inline-comments-button'.format(boxid));
733 $(btns).each(fn_display);
733 $(btns).each(fn_display);
734 });
734 });
735
735
736 $('#merge_pull_request_form').submit(function() {
736 $('#merge_pull_request_form').submit(function() {
737 if (!$('#merge_pull_request').attr('disabled')) {
737 if (!$('#merge_pull_request').attr('disabled')) {
738 $('#merge_pull_request').attr('disabled', 'disabled');
738 $('#merge_pull_request').attr('disabled', 'disabled');
739 }
739 }
740 return true;
740 return true;
741 });
741 });
742
742
743 $('#edit_pull_request').on('click', function(e){
743 $('#edit_pull_request').on('click', function(e){
744 var title = $('#pr-title-input').val();
744 var title = $('#pr-title-input').val();
745 var description = codeMirrorInstance.getValue();
745 var description = codeMirrorInstance.getValue();
746 var renderer = $('#pr-renderer-input').val();
746 var renderer = $('#pr-renderer-input').val();
747 editPullRequest(
747 editPullRequest(
748 "${c.repo_name}", "${c.pull_request.pull_request_id}",
748 "${c.repo_name}", "${c.pull_request.pull_request_id}",
749 title, description, renderer);
749 title, description, renderer);
750 });
750 });
751
751
752 $('#update_pull_request').on('click', function(e){
752 $('#update_pull_request').on('click', function(e){
753 $(this).attr('disabled', 'disabled');
753 $(this).attr('disabled', 'disabled');
754 $(this).addClass('disabled');
754 $(this).addClass('disabled');
755 $(this).html(_gettext('Saving...'));
755 $(this).html(_gettext('Saving...'));
756 reviewersController.updateReviewers(
756 reviewersController.updateReviewers(
757 "${c.repo_name}", "${c.pull_request.pull_request_id}");
757 "${c.repo_name}", "${c.pull_request.pull_request_id}");
758 });
758 });
759
759
760 $('#update_commits').on('click', function(e){
760 $('#update_commits').on('click', function(e){
761 var isDisabled = !$(e.currentTarget).attr('disabled');
761 var isDisabled = !$(e.currentTarget).attr('disabled');
762 $(e.currentTarget).attr('disabled', 'disabled');
762 $(e.currentTarget).attr('disabled', 'disabled');
763 $(e.currentTarget).addClass('disabled');
763 $(e.currentTarget).addClass('disabled');
764 $(e.currentTarget).removeClass('btn-primary');
764 $(e.currentTarget).removeClass('btn-primary');
765 $(e.currentTarget).text(_gettext('Updating...'));
765 $(e.currentTarget).text(_gettext('Updating...'));
766 if(isDisabled){
766 if(isDisabled){
767 updateCommits(
767 updateCommits(
768 "${c.repo_name}", "${c.pull_request.pull_request_id}");
768 "${c.repo_name}", "${c.pull_request.pull_request_id}");
769 }
769 }
770 });
770 });
771 // fixing issue with caches on firefox
771 // fixing issue with caches on firefox
772 $('#update_commits').removeAttr("disabled");
772 $('#update_commits').removeAttr("disabled");
773
773
774 $('.show-inline-comments').on('click', function(e){
774 $('.show-inline-comments').on('click', function(e){
775 var boxid = $(this).attr('data-comment-id');
775 var boxid = $(this).attr('data-comment-id');
776 var button = $(this);
776 var button = $(this);
777
777
778 if(button.hasClass("comments-visible")) {
778 if(button.hasClass("comments-visible")) {
779 $('#{0} .inline-comments'.format(boxid)).each(function(index){
779 $('#{0} .inline-comments'.format(boxid)).each(function(index){
780 $(this).hide();
780 $(this).hide();
781 });
781 });
782 button.removeClass("comments-visible");
782 button.removeClass("comments-visible");
783 } else {
783 } else {
784 $('#{0} .inline-comments'.format(boxid)).each(function(index){
784 $('#{0} .inline-comments'.format(boxid)).each(function(index){
785 $(this).show();
785 $(this).show();
786 });
786 });
787 button.addClass("comments-visible");
787 button.addClass("comments-visible");
788 }
788 }
789 });
789 });
790
790
791 // register submit callback on commentForm form to track TODOs
791 // register submit callback on commentForm form to track TODOs
792 window.commentFormGlobalSubmitSuccessCallback = function(){
792 window.commentFormGlobalSubmitSuccessCallback = function(){
793 refreshMergeChecks();
793 refreshMergeChecks();
794 };
794 };
795
795
796 ReviewerAutoComplete('#user');
796 ReviewerAutoComplete('#user');
797
797
798 })
798 })
799 </script>
799 </script>
800
800
801 </div>
801 </div>
802 </div>
802 </div>
803
803
804 </%def>
804 </%def>
@@ -1,121 +1,121 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${_('%s Pull Requests') % c.repo_name}
4 ${_('%s Pull Requests') % c.repo_name}
5 %if c.rhodecode_name:
5 %if c.rhodecode_name:
6 &middot; ${h.branding(c.rhodecode_name)}
6 &middot; ${h.branding(c.rhodecode_name)}
7 %endif
7 %endif
8 </%def>
8 </%def>
9
9
10 <%def name="breadcrumbs_links()"></%def>
10 <%def name="breadcrumbs_links()"></%def>
11
11
12 <%def name="menu_bar_nav()">
12 <%def name="menu_bar_nav()">
13 ${self.menu_items(active='repositories')}
13 ${self.menu_items(active='repositories')}
14 </%def>
14 </%def>
15
15
16
16
17 <%def name="menu_bar_subnav()">
17 <%def name="menu_bar_subnav()">
18 ${self.repo_menu(active='showpullrequest')}
18 ${self.repo_menu(active='showpullrequest')}
19 </%def>
19 </%def>
20
20
21
21
22 <%def name="main()">
22 <%def name="main()">
23
23
24 <div class="box">
24 <div class="box">
25 <div class="title">
25 <div class="title">
26 <ul class="button-links">
26 <ul class="button-links">
27 <li class="btn ${('active' if c.active=='open' else '')}"><a href="${h.route_path('pullrequest_show_all',repo_name=c.repo_name, _query={'source':0})}">${_('Opened')}</a></li>
27 <li class="btn ${('active' if c.active=='open' else '')}"><a href="${h.route_path('pullrequest_show_all',repo_name=c.repo_name, _query={'source':0})}">${_('Opened')}</a></li>
28 <li class="btn ${('active' if c.active=='my' else '')}"><a href="${h.route_path('pullrequest_show_all',repo_name=c.repo_name, _query={'source':0,'my':1})}">${_('Opened by me')}</a></li>
28 <li class="btn ${('active' if c.active=='my' else '')}"><a href="${h.route_path('pullrequest_show_all',repo_name=c.repo_name, _query={'source':0,'my':1})}">${_('Opened by me')}</a></li>
29 <li class="btn ${('active' if c.active=='awaiting' else '')}"><a href="${h.route_path('pullrequest_show_all',repo_name=c.repo_name, _query={'source':0,'awaiting_review':1})}">${_('Awaiting review')}</a></li>
29 <li class="btn ${('active' if c.active=='awaiting' else '')}"><a href="${h.route_path('pullrequest_show_all',repo_name=c.repo_name, _query={'source':0,'awaiting_review':1})}">${_('Awaiting review')}</a></li>
30 <li class="btn ${('active' if c.active=='awaiting_my' else '')}"><a href="${h.route_path('pullrequest_show_all',repo_name=c.repo_name, _query={'source':0,'awaiting_my_review':1})}">${_('Awaiting my review')}</a></li>
30 <li class="btn ${('active' if c.active=='awaiting_my' else '')}"><a href="${h.route_path('pullrequest_show_all',repo_name=c.repo_name, _query={'source':0,'awaiting_my_review':1})}">${_('Awaiting my review')}</a></li>
31 <li class="btn ${('active' if c.active=='closed' else '')}"><a href="${h.route_path('pullrequest_show_all',repo_name=c.repo_name, _query={'source':0,'closed':1})}">${_('Closed')}</a></li>
31 <li class="btn ${('active' if c.active=='closed' else '')}"><a href="${h.route_path('pullrequest_show_all',repo_name=c.repo_name, _query={'source':0,'closed':1})}">${_('Closed')}</a></li>
32 <li class="btn ${('active' if c.active=='source' else '')}"><a href="${h.route_path('pullrequest_show_all',repo_name=c.repo_name, _query={'source':1})}">${_('From this repo')}</a></li>
32 <li class="btn ${('active' if c.active=='source' else '')}"><a href="${h.route_path('pullrequest_show_all',repo_name=c.repo_name, _query={'source':1})}">${_('From this repo')}</a></li>
33 </ul>
33 </ul>
34
34
35 <ul class="links">
35 <ul class="links">
36 % if c.rhodecode_user.username != h.DEFAULT_USER:
36 % if c.rhodecode_user.username != h.DEFAULT_USER:
37 <li>
37 <li>
38 <span>
38 <span>
39 <a id="open_new_pull_request" class="btn btn-small btn-success" href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">
39 <a id="open_new_pull_request" class="btn btn-small btn-success" href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">
40 ${_('Open new Pull Request')}
40 ${_('Open new Pull Request')}
41 </a>
41 </a>
42 </span>
42 </span>
43 </li>
43 </li>
44 % endif
44 % endif
45 </ul>
45 </ul>
46
46
47 </div>
47 </div>
48
48
49 <div class="main-content-full-width">
49 <div class="main-content-full-width">
50 <table id="pull_request_list_table" class="display"></table>
50 <table id="pull_request_list_table" class="display"></table>
51 </div>
51 </div>
52
52
53 </div>
53 </div>
54
54
55 <script type="text/javascript">
55 <script type="text/javascript">
56 $(document).ready(function() {
56 $(document).ready(function() {
57
57
58 var $pullRequestListTable = $('#pull_request_list_table');
58 var $pullRequestListTable = $('#pull_request_list_table');
59
59
60 // object list
60 // object list
61 $pullRequestListTable.DataTable({
61 $pullRequestListTable.DataTable({
62 processing: true,
62 processing: true,
63 serverSide: true,
63 serverSide: true,
64 ajax: {
64 ajax: {
65 "url": "${h.route_path('pullrequest_show_all_data', repo_name=c.repo_name)}",
65 "url": "${h.route_path('pullrequest_show_all_data', repo_name=c.repo_name)}",
66 "data": function (d) {
66 "data": function (d) {
67 d.source = "${c.source}";
67 d.source = "${c.source}";
68 d.closed = "${c.closed}";
68 d.closed = "${c.closed}";
69 d.my = "${c.my}";
69 d.my = "${c.my}";
70 d.awaiting_review = "${c.awaiting_review}";
70 d.awaiting_review = "${c.awaiting_review}";
71 d.awaiting_my_review = "${c.awaiting_my_review}";
71 d.awaiting_my_review = "${c.awaiting_my_review}";
72 }
72 }
73 },
73 },
74 dom: 'rtp',
74 dom: 'rtp',
75 pageLength: ${c.visual.dashboard_items},
75 pageLength: ${c.visual.dashboard_items},
76 order: [[ 1, "desc" ]],
76 order: [[ 1, "desc" ]],
77 columns: [
77 columns: [
78 { data: {"_": "status",
78 { data: {"_": "status",
79 "sort": "status"}, title: "", className: "td-status", orderable: false},
79 "sort": "status"}, title: "", className: "td-status", orderable: false},
80 { data: {"_": "name",
80 { data: {"_": "name",
81 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname", "type": "num" },
81 "sort": "name_raw"}, title: "${_('Id')}", className: "td-componentname", "type": "num" },
82 { data: {"_": "title",
83 "sort": "title"}, title: "${_('Title')}", className: "td-description" },
82 { data: {"_": "author",
84 { data: {"_": "author",
83 "sort": "author_raw"}, title: "${_('Author')}", className: "td-user", orderable: false },
85 "sort": "author_raw"}, title: "${_('Author')}", className: "td-user", orderable: false },
84 { data: {"_": "title",
85 "sort": "title"}, title: "${_('Title')}", className: "td-description" },
86 { data: {"_": "comments",
86 { data: {"_": "comments",
87 "sort": "comments_raw"}, title: "", className: "td-comments", orderable: false},
87 "sort": "comments_raw"}, title: "", className: "td-comments", orderable: false},
88 { data: {"_": "updated_on",
88 { data: {"_": "updated_on",
89 "sort": "updated_on_raw"}, title: "${_('Last Update')}", className: "td-time" }
89 "sort": "updated_on_raw"}, title: "${_('Last Update')}", className: "td-time" }
90 ],
90 ],
91 language: {
91 language: {
92 paginate: DEFAULT_GRID_PAGINATION,
92 paginate: DEFAULT_GRID_PAGINATION,
93 sProcessing: _gettext('loading...'),
93 sProcessing: _gettext('loading...'),
94 emptyTable: _gettext("No pull requests available yet.")
94 emptyTable: _gettext("No pull requests available yet.")
95 },
95 },
96 "drawCallback": function( settings, json ) {
96 "drawCallback": function( settings, json ) {
97 timeagoActivate();
97 timeagoActivate();
98 tooltipActivate();
98 tooltipActivate();
99 },
99 },
100 "createdRow": function ( row, data, index ) {
100 "createdRow": function ( row, data, index ) {
101 if (data['closed']) {
101 if (data['closed']) {
102 $(row).addClass('closed');
102 $(row).addClass('closed');
103 }
103 }
104 if (data['state'] !== 'created') {
104 if (data['state'] !== 'created') {
105 $(row).addClass('state-' + data['state']);
105 $(row).addClass('state-' + data['state']);
106 }
106 }
107 }
107 }
108 });
108 });
109
109
110 $pullRequestListTable.on('xhr.dt', function(e, settings, json, xhr){
110 $pullRequestListTable.on('xhr.dt', function(e, settings, json, xhr){
111 $pullRequestListTable.css('opacity', 1);
111 $pullRequestListTable.css('opacity', 1);
112 });
112 });
113
113
114 $pullRequestListTable.on('preXhr.dt', function(e, settings, data){
114 $pullRequestListTable.on('preXhr.dt', function(e, settings, data){
115 $pullRequestListTable.css('opacity', 0.3);
115 $pullRequestListTable.css('opacity', 0.3);
116 });
116 });
117
117
118 });
118 });
119
119
120 </script>
120 </script>
121 </%def>
121 </%def>
@@ -1,964 +1,964 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23 import textwrap
23 import textwrap
24
24
25 import rhodecode
25 import rhodecode
26 from rhodecode.lib.utils2 import safe_unicode
26 from rhodecode.lib.utils2 import safe_unicode
27 from rhodecode.lib.vcs.backends import get_backend
27 from rhodecode.lib.vcs.backends import get_backend
28 from rhodecode.lib.vcs.backends.base import (
28 from rhodecode.lib.vcs.backends.base import (
29 MergeResponse, MergeFailureReason, Reference)
29 MergeResponse, MergeFailureReason, Reference)
30 from rhodecode.lib.vcs.exceptions import RepositoryError
30 from rhodecode.lib.vcs.exceptions import RepositoryError
31 from rhodecode.lib.vcs.nodes import FileNode
31 from rhodecode.lib.vcs.nodes import FileNode
32 from rhodecode.model.comment import CommentsModel
32 from rhodecode.model.comment import CommentsModel
33 from rhodecode.model.db import PullRequest, Session
33 from rhodecode.model.db import PullRequest, Session
34 from rhodecode.model.pull_request import PullRequestModel
34 from rhodecode.model.pull_request import PullRequestModel
35 from rhodecode.model.user import UserModel
35 from rhodecode.model.user import UserModel
36 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
36 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
37
37
38
38
39 pytestmark = [
39 pytestmark = [
40 pytest.mark.backends("git", "hg"),
40 pytest.mark.backends("git", "hg"),
41 ]
41 ]
42
42
43
43
44 @pytest.mark.usefixtures('config_stub')
44 @pytest.mark.usefixtures('config_stub')
45 class TestPullRequestModel(object):
45 class TestPullRequestModel(object):
46
46
47 @pytest.fixture()
47 @pytest.fixture()
48 def pull_request(self, request, backend, pr_util):
48 def pull_request(self, request, backend, pr_util):
49 """
49 """
50 A pull request combined with multiples patches.
50 A pull request combined with multiples patches.
51 """
51 """
52 BackendClass = get_backend(backend.alias)
52 BackendClass = get_backend(backend.alias)
53 merge_resp = MergeResponse(
53 merge_resp = MergeResponse(
54 False, False, None, MergeFailureReason.UNKNOWN,
54 False, False, None, MergeFailureReason.UNKNOWN,
55 metadata={'exception': 'MockError'})
55 metadata={'exception': 'MockError'})
56 self.merge_patcher = mock.patch.object(
56 self.merge_patcher = mock.patch.object(
57 BackendClass, 'merge', return_value=merge_resp)
57 BackendClass, 'merge', return_value=merge_resp)
58 self.workspace_remove_patcher = mock.patch.object(
58 self.workspace_remove_patcher = mock.patch.object(
59 BackendClass, 'cleanup_merge_workspace')
59 BackendClass, 'cleanup_merge_workspace')
60
60
61 self.workspace_remove_mock = self.workspace_remove_patcher.start()
61 self.workspace_remove_mock = self.workspace_remove_patcher.start()
62 self.merge_mock = self.merge_patcher.start()
62 self.merge_mock = self.merge_patcher.start()
63 self.comment_patcher = mock.patch(
63 self.comment_patcher = mock.patch(
64 'rhodecode.model.changeset_status.ChangesetStatusModel.set_status')
64 'rhodecode.model.changeset_status.ChangesetStatusModel.set_status')
65 self.comment_patcher.start()
65 self.comment_patcher.start()
66 self.notification_patcher = mock.patch(
66 self.notification_patcher = mock.patch(
67 'rhodecode.model.notification.NotificationModel.create')
67 'rhodecode.model.notification.NotificationModel.create')
68 self.notification_patcher.start()
68 self.notification_patcher.start()
69 self.helper_patcher = mock.patch(
69 self.helper_patcher = mock.patch(
70 'rhodecode.lib.helpers.route_path')
70 'rhodecode.lib.helpers.route_path')
71 self.helper_patcher.start()
71 self.helper_patcher.start()
72
72
73 self.hook_patcher = mock.patch.object(PullRequestModel,
73 self.hook_patcher = mock.patch.object(PullRequestModel,
74 'trigger_pull_request_hook')
74 'trigger_pull_request_hook')
75 self.hook_mock = self.hook_patcher.start()
75 self.hook_mock = self.hook_patcher.start()
76
76
77 self.invalidation_patcher = mock.patch(
77 self.invalidation_patcher = mock.patch(
78 'rhodecode.model.pull_request.ScmModel.mark_for_invalidation')
78 'rhodecode.model.pull_request.ScmModel.mark_for_invalidation')
79 self.invalidation_mock = self.invalidation_patcher.start()
79 self.invalidation_mock = self.invalidation_patcher.start()
80
80
81 self.pull_request = pr_util.create_pull_request(
81 self.pull_request = pr_util.create_pull_request(
82 mergeable=True, name_suffix=u'Δ…Δ‡')
82 mergeable=True, name_suffix=u'Δ…Δ‡')
83 self.source_commit = self.pull_request.source_ref_parts.commit_id
83 self.source_commit = self.pull_request.source_ref_parts.commit_id
84 self.target_commit = self.pull_request.target_ref_parts.commit_id
84 self.target_commit = self.pull_request.target_ref_parts.commit_id
85 self.workspace_id = 'pr-%s' % self.pull_request.pull_request_id
85 self.workspace_id = 'pr-%s' % self.pull_request.pull_request_id
86 self.repo_id = self.pull_request.target_repo.repo_id
86 self.repo_id = self.pull_request.target_repo.repo_id
87
87
88 @request.addfinalizer
88 @request.addfinalizer
89 def cleanup_pull_request():
89 def cleanup_pull_request():
90 calls = [mock.call(
90 calls = [mock.call(
91 self.pull_request, self.pull_request.author, 'create')]
91 self.pull_request, self.pull_request.author, 'create')]
92 self.hook_mock.assert_has_calls(calls)
92 self.hook_mock.assert_has_calls(calls)
93
93
94 self.workspace_remove_patcher.stop()
94 self.workspace_remove_patcher.stop()
95 self.merge_patcher.stop()
95 self.merge_patcher.stop()
96 self.comment_patcher.stop()
96 self.comment_patcher.stop()
97 self.notification_patcher.stop()
97 self.notification_patcher.stop()
98 self.helper_patcher.stop()
98 self.helper_patcher.stop()
99 self.hook_patcher.stop()
99 self.hook_patcher.stop()
100 self.invalidation_patcher.stop()
100 self.invalidation_patcher.stop()
101
101
102 return self.pull_request
102 return self.pull_request
103
103
104 def test_get_all(self, pull_request):
104 def test_get_all(self, pull_request):
105 prs = PullRequestModel().get_all(pull_request.target_repo)
105 prs = PullRequestModel().get_all(pull_request.target_repo)
106 assert isinstance(prs, list)
106 assert isinstance(prs, list)
107 assert len(prs) == 1
107 assert len(prs) == 1
108
108
109 def test_count_all(self, pull_request):
109 def test_count_all(self, pull_request):
110 pr_count = PullRequestModel().count_all(pull_request.target_repo)
110 pr_count = PullRequestModel().count_all(pull_request.target_repo)
111 assert pr_count == 1
111 assert pr_count == 1
112
112
113 def test_get_awaiting_review(self, pull_request):
113 def test_get_awaiting_review(self, pull_request):
114 prs = PullRequestModel().get_awaiting_review(pull_request.target_repo)
114 prs = PullRequestModel().get_awaiting_review(pull_request.target_repo)
115 assert isinstance(prs, list)
115 assert isinstance(prs, list)
116 assert len(prs) == 1
116 assert len(prs) == 1
117
117
118 def test_count_awaiting_review(self, pull_request):
118 def test_count_awaiting_review(self, pull_request):
119 pr_count = PullRequestModel().count_awaiting_review(
119 pr_count = PullRequestModel().count_awaiting_review(
120 pull_request.target_repo)
120 pull_request.target_repo)
121 assert pr_count == 1
121 assert pr_count == 1
122
122
123 def test_get_awaiting_my_review(self, pull_request):
123 def test_get_awaiting_my_review(self, pull_request):
124 PullRequestModel().update_reviewers(
124 PullRequestModel().update_reviewers(
125 pull_request, [(pull_request.author, ['author'], False, [])],
125 pull_request, [(pull_request.author, ['author'], False, [])],
126 pull_request.author)
126 pull_request.author)
127 Session().commit()
127 Session().commit()
128
128
129 prs = PullRequestModel().get_awaiting_my_review(
129 prs = PullRequestModel().get_awaiting_my_review(
130 pull_request.target_repo, user_id=pull_request.author.user_id)
130 pull_request.target_repo, user_id=pull_request.author.user_id)
131 assert isinstance(prs, list)
131 assert isinstance(prs, list)
132 assert len(prs) == 1
132 assert len(prs) == 1
133
133
134 def test_count_awaiting_my_review(self, pull_request):
134 def test_count_awaiting_my_review(self, pull_request):
135 PullRequestModel().update_reviewers(
135 PullRequestModel().update_reviewers(
136 pull_request, [(pull_request.author, ['author'], False, [])],
136 pull_request, [(pull_request.author, ['author'], False, [])],
137 pull_request.author)
137 pull_request.author)
138 Session().commit()
138 Session().commit()
139
139
140 pr_count = PullRequestModel().count_awaiting_my_review(
140 pr_count = PullRequestModel().count_awaiting_my_review(
141 pull_request.target_repo, user_id=pull_request.author.user_id)
141 pull_request.target_repo, user_id=pull_request.author.user_id)
142 assert pr_count == 1
142 assert pr_count == 1
143
143
144 def test_delete_calls_cleanup_merge(self, pull_request):
144 def test_delete_calls_cleanup_merge(self, pull_request):
145 repo_id = pull_request.target_repo.repo_id
145 repo_id = pull_request.target_repo.repo_id
146 PullRequestModel().delete(pull_request, pull_request.author)
146 PullRequestModel().delete(pull_request, pull_request.author)
147 Session().commit()
147 Session().commit()
148
148
149 self.workspace_remove_mock.assert_called_once_with(
149 self.workspace_remove_mock.assert_called_once_with(
150 repo_id, self.workspace_id)
150 repo_id, self.workspace_id)
151
151
152 def test_close_calls_cleanup_and_hook(self, pull_request):
152 def test_close_calls_cleanup_and_hook(self, pull_request):
153 PullRequestModel().close_pull_request(
153 PullRequestModel().close_pull_request(
154 pull_request, pull_request.author)
154 pull_request, pull_request.author)
155 Session().commit()
155 Session().commit()
156
156
157 repo_id = pull_request.target_repo.repo_id
157 repo_id = pull_request.target_repo.repo_id
158
158
159 self.workspace_remove_mock.assert_called_once_with(
159 self.workspace_remove_mock.assert_called_once_with(
160 repo_id, self.workspace_id)
160 repo_id, self.workspace_id)
161 self.hook_mock.assert_called_with(
161 self.hook_mock.assert_called_with(
162 self.pull_request, self.pull_request.author, 'close')
162 self.pull_request, self.pull_request.author, 'close')
163
163
164 def test_merge_status(self, pull_request):
164 def test_merge_status(self, pull_request):
165 self.merge_mock.return_value = MergeResponse(
165 self.merge_mock.return_value = MergeResponse(
166 True, False, None, MergeFailureReason.NONE)
166 True, False, None, MergeFailureReason.NONE)
167
167
168 assert pull_request._last_merge_source_rev is None
168 assert pull_request._last_merge_source_rev is None
169 assert pull_request._last_merge_target_rev is None
169 assert pull_request._last_merge_target_rev is None
170 assert pull_request.last_merge_status is None
170 assert pull_request.last_merge_status is None
171
171
172 status, msg = PullRequestModel().merge_status(pull_request)
172 status, msg = PullRequestModel().merge_status(pull_request)
173 assert status is True
173 assert status is True
174 assert msg == 'This pull request can be automatically merged.'
174 assert msg == 'This pull request can be automatically merged.'
175 self.merge_mock.assert_called_with(
175 self.merge_mock.assert_called_with(
176 self.repo_id, self.workspace_id,
176 self.repo_id, self.workspace_id,
177 pull_request.target_ref_parts,
177 pull_request.target_ref_parts,
178 pull_request.source_repo.scm_instance(),
178 pull_request.source_repo.scm_instance(),
179 pull_request.source_ref_parts, dry_run=True,
179 pull_request.source_ref_parts, dry_run=True,
180 use_rebase=False, close_branch=False)
180 use_rebase=False, close_branch=False)
181
181
182 assert pull_request._last_merge_source_rev == self.source_commit
182 assert pull_request._last_merge_source_rev == self.source_commit
183 assert pull_request._last_merge_target_rev == self.target_commit
183 assert pull_request._last_merge_target_rev == self.target_commit
184 assert pull_request.last_merge_status is MergeFailureReason.NONE
184 assert pull_request.last_merge_status is MergeFailureReason.NONE
185
185
186 self.merge_mock.reset_mock()
186 self.merge_mock.reset_mock()
187 status, msg = PullRequestModel().merge_status(pull_request)
187 status, msg = PullRequestModel().merge_status(pull_request)
188 assert status is True
188 assert status is True
189 assert msg == 'This pull request can be automatically merged.'
189 assert msg == 'This pull request can be automatically merged.'
190 assert self.merge_mock.called is False
190 assert self.merge_mock.called is False
191
191
192 def test_merge_status_known_failure(self, pull_request):
192 def test_merge_status_known_failure(self, pull_request):
193 self.merge_mock.return_value = MergeResponse(
193 self.merge_mock.return_value = MergeResponse(
194 False, False, None, MergeFailureReason.MERGE_FAILED)
194 False, False, None, MergeFailureReason.MERGE_FAILED)
195
195
196 assert pull_request._last_merge_source_rev is None
196 assert pull_request._last_merge_source_rev is None
197 assert pull_request._last_merge_target_rev is None
197 assert pull_request._last_merge_target_rev is None
198 assert pull_request.last_merge_status is None
198 assert pull_request.last_merge_status is None
199
199
200 status, msg = PullRequestModel().merge_status(pull_request)
200 status, msg = PullRequestModel().merge_status(pull_request)
201 assert status is False
201 assert status is False
202 assert msg == 'This pull request cannot be merged because of merge conflicts.'
202 assert msg == 'This pull request cannot be merged because of merge conflicts.'
203 self.merge_mock.assert_called_with(
203 self.merge_mock.assert_called_with(
204 self.repo_id, self.workspace_id,
204 self.repo_id, self.workspace_id,
205 pull_request.target_ref_parts,
205 pull_request.target_ref_parts,
206 pull_request.source_repo.scm_instance(),
206 pull_request.source_repo.scm_instance(),
207 pull_request.source_ref_parts, dry_run=True,
207 pull_request.source_ref_parts, dry_run=True,
208 use_rebase=False, close_branch=False)
208 use_rebase=False, close_branch=False)
209
209
210 assert pull_request._last_merge_source_rev == self.source_commit
210 assert pull_request._last_merge_source_rev == self.source_commit
211 assert pull_request._last_merge_target_rev == self.target_commit
211 assert pull_request._last_merge_target_rev == self.target_commit
212 assert (
212 assert (
213 pull_request.last_merge_status is MergeFailureReason.MERGE_FAILED)
213 pull_request.last_merge_status is MergeFailureReason.MERGE_FAILED)
214
214
215 self.merge_mock.reset_mock()
215 self.merge_mock.reset_mock()
216 status, msg = PullRequestModel().merge_status(pull_request)
216 status, msg = PullRequestModel().merge_status(pull_request)
217 assert status is False
217 assert status is False
218 assert msg == 'This pull request cannot be merged because of merge conflicts.'
218 assert msg == 'This pull request cannot be merged because of merge conflicts.'
219 assert self.merge_mock.called is False
219 assert self.merge_mock.called is False
220
220
221 def test_merge_status_unknown_failure(self, pull_request):
221 def test_merge_status_unknown_failure(self, pull_request):
222 self.merge_mock.return_value = MergeResponse(
222 self.merge_mock.return_value = MergeResponse(
223 False, False, None, MergeFailureReason.UNKNOWN,
223 False, False, None, MergeFailureReason.UNKNOWN,
224 metadata={'exception': 'MockError'})
224 metadata={'exception': 'MockError'})
225
225
226 assert pull_request._last_merge_source_rev is None
226 assert pull_request._last_merge_source_rev is None
227 assert pull_request._last_merge_target_rev is None
227 assert pull_request._last_merge_target_rev is None
228 assert pull_request.last_merge_status is None
228 assert pull_request.last_merge_status is None
229
229
230 status, msg = PullRequestModel().merge_status(pull_request)
230 status, msg = PullRequestModel().merge_status(pull_request)
231 assert status is False
231 assert status is False
232 assert msg == (
232 assert msg == (
233 'This pull request cannot be merged because of an unhandled exception. '
233 'This pull request cannot be merged because of an unhandled exception. '
234 'MockError')
234 'MockError')
235 self.merge_mock.assert_called_with(
235 self.merge_mock.assert_called_with(
236 self.repo_id, self.workspace_id,
236 self.repo_id, self.workspace_id,
237 pull_request.target_ref_parts,
237 pull_request.target_ref_parts,
238 pull_request.source_repo.scm_instance(),
238 pull_request.source_repo.scm_instance(),
239 pull_request.source_ref_parts, dry_run=True,
239 pull_request.source_ref_parts, dry_run=True,
240 use_rebase=False, close_branch=False)
240 use_rebase=False, close_branch=False)
241
241
242 assert pull_request._last_merge_source_rev is None
242 assert pull_request._last_merge_source_rev is None
243 assert pull_request._last_merge_target_rev is None
243 assert pull_request._last_merge_target_rev is None
244 assert pull_request.last_merge_status is None
244 assert pull_request.last_merge_status is None
245
245
246 self.merge_mock.reset_mock()
246 self.merge_mock.reset_mock()
247 status, msg = PullRequestModel().merge_status(pull_request)
247 status, msg = PullRequestModel().merge_status(pull_request)
248 assert status is False
248 assert status is False
249 assert msg == (
249 assert msg == (
250 'This pull request cannot be merged because of an unhandled exception. '
250 'This pull request cannot be merged because of an unhandled exception. '
251 'MockError')
251 'MockError')
252 assert self.merge_mock.called is True
252 assert self.merge_mock.called is True
253
253
254 def test_merge_status_when_target_is_locked(self, pull_request):
254 def test_merge_status_when_target_is_locked(self, pull_request):
255 pull_request.target_repo.locked = [1, u'12345.50', 'lock_web']
255 pull_request.target_repo.locked = [1, u'12345.50', 'lock_web']
256 status, msg = PullRequestModel().merge_status(pull_request)
256 status, msg = PullRequestModel().merge_status(pull_request)
257 assert status is False
257 assert status is False
258 assert msg == (
258 assert msg == (
259 'This pull request cannot be merged because the target repository '
259 'This pull request cannot be merged because the target repository '
260 'is locked by user:1.')
260 'is locked by user:1.')
261
261
262 def test_merge_status_requirements_check_target(self, pull_request):
262 def test_merge_status_requirements_check_target(self, pull_request):
263
263
264 def has_largefiles(self, repo):
264 def has_largefiles(self, repo):
265 return repo == pull_request.source_repo
265 return repo == pull_request.source_repo
266
266
267 patcher = mock.patch.object(PullRequestModel, '_has_largefiles', has_largefiles)
267 patcher = mock.patch.object(PullRequestModel, '_has_largefiles', has_largefiles)
268 with patcher:
268 with patcher:
269 status, msg = PullRequestModel().merge_status(pull_request)
269 status, msg = PullRequestModel().merge_status(pull_request)
270
270
271 assert status is False
271 assert status is False
272 assert msg == 'Target repository large files support is disabled.'
272 assert msg == 'Target repository large files support is disabled.'
273
273
274 def test_merge_status_requirements_check_source(self, pull_request):
274 def test_merge_status_requirements_check_source(self, pull_request):
275
275
276 def has_largefiles(self, repo):
276 def has_largefiles(self, repo):
277 return repo == pull_request.target_repo
277 return repo == pull_request.target_repo
278
278
279 patcher = mock.patch.object(PullRequestModel, '_has_largefiles', has_largefiles)
279 patcher = mock.patch.object(PullRequestModel, '_has_largefiles', has_largefiles)
280 with patcher:
280 with patcher:
281 status, msg = PullRequestModel().merge_status(pull_request)
281 status, msg = PullRequestModel().merge_status(pull_request)
282
282
283 assert status is False
283 assert status is False
284 assert msg == 'Source repository large files support is disabled.'
284 assert msg == 'Source repository large files support is disabled.'
285
285
286 def test_merge(self, pull_request, merge_extras):
286 def test_merge(self, pull_request, merge_extras):
287 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
287 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
288 merge_ref = Reference(
288 merge_ref = Reference(
289 'type', 'name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
289 'type', 'name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
290 self.merge_mock.return_value = MergeResponse(
290 self.merge_mock.return_value = MergeResponse(
291 True, True, merge_ref, MergeFailureReason.NONE)
291 True, True, merge_ref, MergeFailureReason.NONE)
292
292
293 merge_extras['repository'] = pull_request.target_repo.repo_name
293 merge_extras['repository'] = pull_request.target_repo.repo_name
294 PullRequestModel().merge_repo(
294 PullRequestModel().merge_repo(
295 pull_request, pull_request.author, extras=merge_extras)
295 pull_request, pull_request.author, extras=merge_extras)
296 Session().commit()
296 Session().commit()
297
297
298 message = (
298 message = (
299 u'Merge pull request #{pr_id} from {source_repo} {source_ref_name}'
299 u'Merge pull request !{pr_id} from {source_repo} {source_ref_name}'
300 u'\n\n {pr_title}'.format(
300 u'\n\n {pr_title}'.format(
301 pr_id=pull_request.pull_request_id,
301 pr_id=pull_request.pull_request_id,
302 source_repo=safe_unicode(
302 source_repo=safe_unicode(
303 pull_request.source_repo.scm_instance().name),
303 pull_request.source_repo.scm_instance().name),
304 source_ref_name=pull_request.source_ref_parts.name,
304 source_ref_name=pull_request.source_ref_parts.name,
305 pr_title=safe_unicode(pull_request.title)
305 pr_title=safe_unicode(pull_request.title)
306 )
306 )
307 )
307 )
308 self.merge_mock.assert_called_with(
308 self.merge_mock.assert_called_with(
309 self.repo_id, self.workspace_id,
309 self.repo_id, self.workspace_id,
310 pull_request.target_ref_parts,
310 pull_request.target_ref_parts,
311 pull_request.source_repo.scm_instance(),
311 pull_request.source_repo.scm_instance(),
312 pull_request.source_ref_parts,
312 pull_request.source_ref_parts,
313 user_name=user.short_contact, user_email=user.email, message=message,
313 user_name=user.short_contact, user_email=user.email, message=message,
314 use_rebase=False, close_branch=False
314 use_rebase=False, close_branch=False
315 )
315 )
316 self.invalidation_mock.assert_called_once_with(
316 self.invalidation_mock.assert_called_once_with(
317 pull_request.target_repo.repo_name)
317 pull_request.target_repo.repo_name)
318
318
319 self.hook_mock.assert_called_with(
319 self.hook_mock.assert_called_with(
320 self.pull_request, self.pull_request.author, 'merge')
320 self.pull_request, self.pull_request.author, 'merge')
321
321
322 pull_request = PullRequest.get(pull_request.pull_request_id)
322 pull_request = PullRequest.get(pull_request.pull_request_id)
323 assert pull_request.merge_rev == '6126b7bfcc82ad2d3deaee22af926b082ce54cc6'
323 assert pull_request.merge_rev == '6126b7bfcc82ad2d3deaee22af926b082ce54cc6'
324
324
325 def test_merge_with_status_lock(self, pull_request, merge_extras):
325 def test_merge_with_status_lock(self, pull_request, merge_extras):
326 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
326 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
327 merge_ref = Reference(
327 merge_ref = Reference(
328 'type', 'name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
328 'type', 'name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
329 self.merge_mock.return_value = MergeResponse(
329 self.merge_mock.return_value = MergeResponse(
330 True, True, merge_ref, MergeFailureReason.NONE)
330 True, True, merge_ref, MergeFailureReason.NONE)
331
331
332 merge_extras['repository'] = pull_request.target_repo.repo_name
332 merge_extras['repository'] = pull_request.target_repo.repo_name
333
333
334 with pull_request.set_state(PullRequest.STATE_UPDATING):
334 with pull_request.set_state(PullRequest.STATE_UPDATING):
335 assert pull_request.pull_request_state == PullRequest.STATE_UPDATING
335 assert pull_request.pull_request_state == PullRequest.STATE_UPDATING
336 PullRequestModel().merge_repo(
336 PullRequestModel().merge_repo(
337 pull_request, pull_request.author, extras=merge_extras)
337 pull_request, pull_request.author, extras=merge_extras)
338 Session().commit()
338 Session().commit()
339
339
340 assert pull_request.pull_request_state == PullRequest.STATE_CREATED
340 assert pull_request.pull_request_state == PullRequest.STATE_CREATED
341
341
342 message = (
342 message = (
343 u'Merge pull request #{pr_id} from {source_repo} {source_ref_name}'
343 u'Merge pull request !{pr_id} from {source_repo} {source_ref_name}'
344 u'\n\n {pr_title}'.format(
344 u'\n\n {pr_title}'.format(
345 pr_id=pull_request.pull_request_id,
345 pr_id=pull_request.pull_request_id,
346 source_repo=safe_unicode(
346 source_repo=safe_unicode(
347 pull_request.source_repo.scm_instance().name),
347 pull_request.source_repo.scm_instance().name),
348 source_ref_name=pull_request.source_ref_parts.name,
348 source_ref_name=pull_request.source_ref_parts.name,
349 pr_title=safe_unicode(pull_request.title)
349 pr_title=safe_unicode(pull_request.title)
350 )
350 )
351 )
351 )
352 self.merge_mock.assert_called_with(
352 self.merge_mock.assert_called_with(
353 self.repo_id, self.workspace_id,
353 self.repo_id, self.workspace_id,
354 pull_request.target_ref_parts,
354 pull_request.target_ref_parts,
355 pull_request.source_repo.scm_instance(),
355 pull_request.source_repo.scm_instance(),
356 pull_request.source_ref_parts,
356 pull_request.source_ref_parts,
357 user_name=user.short_contact, user_email=user.email, message=message,
357 user_name=user.short_contact, user_email=user.email, message=message,
358 use_rebase=False, close_branch=False
358 use_rebase=False, close_branch=False
359 )
359 )
360 self.invalidation_mock.assert_called_once_with(
360 self.invalidation_mock.assert_called_once_with(
361 pull_request.target_repo.repo_name)
361 pull_request.target_repo.repo_name)
362
362
363 self.hook_mock.assert_called_with(
363 self.hook_mock.assert_called_with(
364 self.pull_request, self.pull_request.author, 'merge')
364 self.pull_request, self.pull_request.author, 'merge')
365
365
366 pull_request = PullRequest.get(pull_request.pull_request_id)
366 pull_request = PullRequest.get(pull_request.pull_request_id)
367 assert pull_request.merge_rev == '6126b7bfcc82ad2d3deaee22af926b082ce54cc6'
367 assert pull_request.merge_rev == '6126b7bfcc82ad2d3deaee22af926b082ce54cc6'
368
368
369 def test_merge_failed(self, pull_request, merge_extras):
369 def test_merge_failed(self, pull_request, merge_extras):
370 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
370 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
371 merge_ref = Reference(
371 merge_ref = Reference(
372 'type', 'name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
372 'type', 'name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
373 self.merge_mock.return_value = MergeResponse(
373 self.merge_mock.return_value = MergeResponse(
374 False, False, merge_ref, MergeFailureReason.MERGE_FAILED)
374 False, False, merge_ref, MergeFailureReason.MERGE_FAILED)
375
375
376 merge_extras['repository'] = pull_request.target_repo.repo_name
376 merge_extras['repository'] = pull_request.target_repo.repo_name
377 PullRequestModel().merge_repo(
377 PullRequestModel().merge_repo(
378 pull_request, pull_request.author, extras=merge_extras)
378 pull_request, pull_request.author, extras=merge_extras)
379 Session().commit()
379 Session().commit()
380
380
381 message = (
381 message = (
382 u'Merge pull request #{pr_id} from {source_repo} {source_ref_name}'
382 u'Merge pull request !{pr_id} from {source_repo} {source_ref_name}'
383 u'\n\n {pr_title}'.format(
383 u'\n\n {pr_title}'.format(
384 pr_id=pull_request.pull_request_id,
384 pr_id=pull_request.pull_request_id,
385 source_repo=safe_unicode(
385 source_repo=safe_unicode(
386 pull_request.source_repo.scm_instance().name),
386 pull_request.source_repo.scm_instance().name),
387 source_ref_name=pull_request.source_ref_parts.name,
387 source_ref_name=pull_request.source_ref_parts.name,
388 pr_title=safe_unicode(pull_request.title)
388 pr_title=safe_unicode(pull_request.title)
389 )
389 )
390 )
390 )
391 self.merge_mock.assert_called_with(
391 self.merge_mock.assert_called_with(
392 self.repo_id, self.workspace_id,
392 self.repo_id, self.workspace_id,
393 pull_request.target_ref_parts,
393 pull_request.target_ref_parts,
394 pull_request.source_repo.scm_instance(),
394 pull_request.source_repo.scm_instance(),
395 pull_request.source_ref_parts,
395 pull_request.source_ref_parts,
396 user_name=user.short_contact, user_email=user.email, message=message,
396 user_name=user.short_contact, user_email=user.email, message=message,
397 use_rebase=False, close_branch=False
397 use_rebase=False, close_branch=False
398 )
398 )
399
399
400 pull_request = PullRequest.get(pull_request.pull_request_id)
400 pull_request = PullRequest.get(pull_request.pull_request_id)
401 assert self.invalidation_mock.called is False
401 assert self.invalidation_mock.called is False
402 assert pull_request.merge_rev is None
402 assert pull_request.merge_rev is None
403
403
404 def test_get_commit_ids(self, pull_request):
404 def test_get_commit_ids(self, pull_request):
405 # The PR has been not merget yet, so expect an exception
405 # The PR has been not merget yet, so expect an exception
406 with pytest.raises(ValueError):
406 with pytest.raises(ValueError):
407 PullRequestModel()._get_commit_ids(pull_request)
407 PullRequestModel()._get_commit_ids(pull_request)
408
408
409 # Merge revision is in the revisions list
409 # Merge revision is in the revisions list
410 pull_request.merge_rev = pull_request.revisions[0]
410 pull_request.merge_rev = pull_request.revisions[0]
411 commit_ids = PullRequestModel()._get_commit_ids(pull_request)
411 commit_ids = PullRequestModel()._get_commit_ids(pull_request)
412 assert commit_ids == pull_request.revisions
412 assert commit_ids == pull_request.revisions
413
413
414 # Merge revision is not in the revisions list
414 # Merge revision is not in the revisions list
415 pull_request.merge_rev = 'f000' * 10
415 pull_request.merge_rev = 'f000' * 10
416 commit_ids = PullRequestModel()._get_commit_ids(pull_request)
416 commit_ids = PullRequestModel()._get_commit_ids(pull_request)
417 assert commit_ids == pull_request.revisions + [pull_request.merge_rev]
417 assert commit_ids == pull_request.revisions + [pull_request.merge_rev]
418
418
419 def test_get_diff_from_pr_version(self, pull_request):
419 def test_get_diff_from_pr_version(self, pull_request):
420 source_repo = pull_request.source_repo
420 source_repo = pull_request.source_repo
421 source_ref_id = pull_request.source_ref_parts.commit_id
421 source_ref_id = pull_request.source_ref_parts.commit_id
422 target_ref_id = pull_request.target_ref_parts.commit_id
422 target_ref_id = pull_request.target_ref_parts.commit_id
423 diff = PullRequestModel()._get_diff_from_pr_or_version(
423 diff = PullRequestModel()._get_diff_from_pr_or_version(
424 source_repo, source_ref_id, target_ref_id,
424 source_repo, source_ref_id, target_ref_id,
425 hide_whitespace_changes=False, diff_context=6)
425 hide_whitespace_changes=False, diff_context=6)
426 assert 'file_1' in diff.raw
426 assert 'file_1' in diff.raw
427
427
428 def test_generate_title_returns_unicode(self):
428 def test_generate_title_returns_unicode(self):
429 title = PullRequestModel().generate_pullrequest_title(
429 title = PullRequestModel().generate_pullrequest_title(
430 source='source-dummy',
430 source='source-dummy',
431 source_ref='source-ref-dummy',
431 source_ref='source-ref-dummy',
432 target='target-dummy',
432 target='target-dummy',
433 )
433 )
434 assert type(title) == unicode
434 assert type(title) == unicode
435
435
436
436
437 @pytest.mark.usefixtures('config_stub')
437 @pytest.mark.usefixtures('config_stub')
438 class TestIntegrationMerge(object):
438 class TestIntegrationMerge(object):
439 @pytest.mark.parametrize('extra_config', (
439 @pytest.mark.parametrize('extra_config', (
440 {'vcs.hooks.protocol': 'http', 'vcs.hooks.direct_calls': False},
440 {'vcs.hooks.protocol': 'http', 'vcs.hooks.direct_calls': False},
441 ))
441 ))
442 def test_merge_triggers_push_hooks(
442 def test_merge_triggers_push_hooks(
443 self, pr_util, user_admin, capture_rcextensions, merge_extras,
443 self, pr_util, user_admin, capture_rcextensions, merge_extras,
444 extra_config):
444 extra_config):
445
445
446 pull_request = pr_util.create_pull_request(
446 pull_request = pr_util.create_pull_request(
447 approved=True, mergeable=True)
447 approved=True, mergeable=True)
448 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
448 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
449 merge_extras['repository'] = pull_request.target_repo.repo_name
449 merge_extras['repository'] = pull_request.target_repo.repo_name
450 Session().commit()
450 Session().commit()
451
451
452 with mock.patch.dict(rhodecode.CONFIG, extra_config, clear=False):
452 with mock.patch.dict(rhodecode.CONFIG, extra_config, clear=False):
453 merge_state = PullRequestModel().merge_repo(
453 merge_state = PullRequestModel().merge_repo(
454 pull_request, user_admin, extras=merge_extras)
454 pull_request, user_admin, extras=merge_extras)
455 Session().commit()
455 Session().commit()
456
456
457 assert merge_state.executed
457 assert merge_state.executed
458 assert '_pre_push_hook' in capture_rcextensions
458 assert '_pre_push_hook' in capture_rcextensions
459 assert '_push_hook' in capture_rcextensions
459 assert '_push_hook' in capture_rcextensions
460
460
461 def test_merge_can_be_rejected_by_pre_push_hook(
461 def test_merge_can_be_rejected_by_pre_push_hook(
462 self, pr_util, user_admin, capture_rcextensions, merge_extras):
462 self, pr_util, user_admin, capture_rcextensions, merge_extras):
463 pull_request = pr_util.create_pull_request(
463 pull_request = pr_util.create_pull_request(
464 approved=True, mergeable=True)
464 approved=True, mergeable=True)
465 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
465 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
466 merge_extras['repository'] = pull_request.target_repo.repo_name
466 merge_extras['repository'] = pull_request.target_repo.repo_name
467 Session().commit()
467 Session().commit()
468
468
469 with mock.patch('rhodecode.EXTENSIONS.PRE_PUSH_HOOK') as pre_pull:
469 with mock.patch('rhodecode.EXTENSIONS.PRE_PUSH_HOOK') as pre_pull:
470 pre_pull.side_effect = RepositoryError("Disallow push!")
470 pre_pull.side_effect = RepositoryError("Disallow push!")
471 merge_status = PullRequestModel().merge_repo(
471 merge_status = PullRequestModel().merge_repo(
472 pull_request, user_admin, extras=merge_extras)
472 pull_request, user_admin, extras=merge_extras)
473 Session().commit()
473 Session().commit()
474
474
475 assert not merge_status.executed
475 assert not merge_status.executed
476 assert 'pre_push' not in capture_rcextensions
476 assert 'pre_push' not in capture_rcextensions
477 assert 'post_push' not in capture_rcextensions
477 assert 'post_push' not in capture_rcextensions
478
478
479 def test_merge_fails_if_target_is_locked(
479 def test_merge_fails_if_target_is_locked(
480 self, pr_util, user_regular, merge_extras):
480 self, pr_util, user_regular, merge_extras):
481 pull_request = pr_util.create_pull_request(
481 pull_request = pr_util.create_pull_request(
482 approved=True, mergeable=True)
482 approved=True, mergeable=True)
483 locked_by = [user_regular.user_id + 1, 12345.50, 'lock_web']
483 locked_by = [user_regular.user_id + 1, 12345.50, 'lock_web']
484 pull_request.target_repo.locked = locked_by
484 pull_request.target_repo.locked = locked_by
485 # TODO: johbo: Check if this can work based on the database, currently
485 # TODO: johbo: Check if this can work based on the database, currently
486 # all data is pre-computed, that's why just updating the DB is not
486 # all data is pre-computed, that's why just updating the DB is not
487 # enough.
487 # enough.
488 merge_extras['locked_by'] = locked_by
488 merge_extras['locked_by'] = locked_by
489 merge_extras['repository'] = pull_request.target_repo.repo_name
489 merge_extras['repository'] = pull_request.target_repo.repo_name
490 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
490 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
491 Session().commit()
491 Session().commit()
492 merge_status = PullRequestModel().merge_repo(
492 merge_status = PullRequestModel().merge_repo(
493 pull_request, user_regular, extras=merge_extras)
493 pull_request, user_regular, extras=merge_extras)
494 Session().commit()
494 Session().commit()
495
495
496 assert not merge_status.executed
496 assert not merge_status.executed
497
497
498
498
499 @pytest.mark.parametrize('use_outdated, inlines_count, outdated_count', [
499 @pytest.mark.parametrize('use_outdated, inlines_count, outdated_count', [
500 (False, 1, 0),
500 (False, 1, 0),
501 (True, 0, 1),
501 (True, 0, 1),
502 ])
502 ])
503 def test_outdated_comments(
503 def test_outdated_comments(
504 pr_util, use_outdated, inlines_count, outdated_count, config_stub):
504 pr_util, use_outdated, inlines_count, outdated_count, config_stub):
505 pull_request = pr_util.create_pull_request()
505 pull_request = pr_util.create_pull_request()
506 pr_util.create_inline_comment(file_path='not_in_updated_diff')
506 pr_util.create_inline_comment(file_path='not_in_updated_diff')
507
507
508 with outdated_comments_patcher(use_outdated) as outdated_comment_mock:
508 with outdated_comments_patcher(use_outdated) as outdated_comment_mock:
509 pr_util.add_one_commit()
509 pr_util.add_one_commit()
510 assert_inline_comments(
510 assert_inline_comments(
511 pull_request, visible=inlines_count, outdated=outdated_count)
511 pull_request, visible=inlines_count, outdated=outdated_count)
512 outdated_comment_mock.assert_called_with(pull_request)
512 outdated_comment_mock.assert_called_with(pull_request)
513
513
514
514
515 @pytest.mark.parametrize('mr_type, expected_msg', [
515 @pytest.mark.parametrize('mr_type, expected_msg', [
516 (MergeFailureReason.NONE,
516 (MergeFailureReason.NONE,
517 'This pull request can be automatically merged.'),
517 'This pull request can be automatically merged.'),
518 (MergeFailureReason.UNKNOWN,
518 (MergeFailureReason.UNKNOWN,
519 'This pull request cannot be merged because of an unhandled exception. CRASH'),
519 'This pull request cannot be merged because of an unhandled exception. CRASH'),
520 (MergeFailureReason.MERGE_FAILED,
520 (MergeFailureReason.MERGE_FAILED,
521 'This pull request cannot be merged because of merge conflicts.'),
521 'This pull request cannot be merged because of merge conflicts.'),
522 (MergeFailureReason.PUSH_FAILED,
522 (MergeFailureReason.PUSH_FAILED,
523 'This pull request could not be merged because push to target:`some-repo@merge_commit` failed.'),
523 'This pull request could not be merged because push to target:`some-repo@merge_commit` failed.'),
524 (MergeFailureReason.TARGET_IS_NOT_HEAD,
524 (MergeFailureReason.TARGET_IS_NOT_HEAD,
525 'This pull request cannot be merged because the target `ref_name` is not a head.'),
525 'This pull request cannot be merged because the target `ref_name` is not a head.'),
526 (MergeFailureReason.HG_SOURCE_HAS_MORE_BRANCHES,
526 (MergeFailureReason.HG_SOURCE_HAS_MORE_BRANCHES,
527 'This pull request cannot be merged because the source contains more branches than the target.'),
527 'This pull request cannot be merged because the source contains more branches than the target.'),
528 (MergeFailureReason.HG_TARGET_HAS_MULTIPLE_HEADS,
528 (MergeFailureReason.HG_TARGET_HAS_MULTIPLE_HEADS,
529 'This pull request cannot be merged because the target `ref_name` has multiple heads: `a,b,c`.'),
529 'This pull request cannot be merged because the target `ref_name` has multiple heads: `a,b,c`.'),
530 (MergeFailureReason.TARGET_IS_LOCKED,
530 (MergeFailureReason.TARGET_IS_LOCKED,
531 'This pull request cannot be merged because the target repository is locked by user:123.'),
531 'This pull request cannot be merged because the target repository is locked by user:123.'),
532 (MergeFailureReason.MISSING_TARGET_REF,
532 (MergeFailureReason.MISSING_TARGET_REF,
533 'This pull request cannot be merged because the target reference `ref_name` is missing.'),
533 'This pull request cannot be merged because the target reference `ref_name` is missing.'),
534 (MergeFailureReason.MISSING_SOURCE_REF,
534 (MergeFailureReason.MISSING_SOURCE_REF,
535 'This pull request cannot be merged because the source reference `ref_name` is missing.'),
535 'This pull request cannot be merged because the source reference `ref_name` is missing.'),
536 (MergeFailureReason.SUBREPO_MERGE_FAILED,
536 (MergeFailureReason.SUBREPO_MERGE_FAILED,
537 'This pull request cannot be merged because of conflicts related to sub repositories.'),
537 'This pull request cannot be merged because of conflicts related to sub repositories.'),
538
538
539 ])
539 ])
540 def test_merge_response_message(mr_type, expected_msg):
540 def test_merge_response_message(mr_type, expected_msg):
541 merge_ref = Reference('type', 'ref_name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
541 merge_ref = Reference('type', 'ref_name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
542 metadata = {
542 metadata = {
543 'exception': "CRASH",
543 'exception': "CRASH",
544 'target': 'some-repo',
544 'target': 'some-repo',
545 'merge_commit': 'merge_commit',
545 'merge_commit': 'merge_commit',
546 'target_ref': merge_ref,
546 'target_ref': merge_ref,
547 'source_ref': merge_ref,
547 'source_ref': merge_ref,
548 'heads': ','.join(['a', 'b', 'c']),
548 'heads': ','.join(['a', 'b', 'c']),
549 'locked_by': 'user:123'}
549 'locked_by': 'user:123'}
550
550
551 merge_response = MergeResponse(True, True, merge_ref, mr_type, metadata=metadata)
551 merge_response = MergeResponse(True, True, merge_ref, mr_type, metadata=metadata)
552 assert merge_response.merge_status_message == expected_msg
552 assert merge_response.merge_status_message == expected_msg
553
553
554
554
555 @pytest.fixture()
555 @pytest.fixture()
556 def merge_extras(user_regular):
556 def merge_extras(user_regular):
557 """
557 """
558 Context for the vcs operation when running a merge.
558 Context for the vcs operation when running a merge.
559 """
559 """
560 extras = {
560 extras = {
561 'ip': '127.0.0.1',
561 'ip': '127.0.0.1',
562 'username': user_regular.username,
562 'username': user_regular.username,
563 'user_id': user_regular.user_id,
563 'user_id': user_regular.user_id,
564 'action': 'push',
564 'action': 'push',
565 'repository': 'fake_target_repo_name',
565 'repository': 'fake_target_repo_name',
566 'scm': 'git',
566 'scm': 'git',
567 'config': 'fake_config_ini_path',
567 'config': 'fake_config_ini_path',
568 'repo_store': '',
568 'repo_store': '',
569 'make_lock': None,
569 'make_lock': None,
570 'locked_by': [None, None, None],
570 'locked_by': [None, None, None],
571 'server_url': 'http://test.example.com:5000',
571 'server_url': 'http://test.example.com:5000',
572 'hooks': ['push', 'pull'],
572 'hooks': ['push', 'pull'],
573 'is_shadow_repo': False,
573 'is_shadow_repo': False,
574 }
574 }
575 return extras
575 return extras
576
576
577
577
578 @pytest.mark.usefixtures('config_stub')
578 @pytest.mark.usefixtures('config_stub')
579 class TestUpdateCommentHandling(object):
579 class TestUpdateCommentHandling(object):
580
580
581 @pytest.fixture(autouse=True, scope='class')
581 @pytest.fixture(autouse=True, scope='class')
582 def enable_outdated_comments(self, request, baseapp):
582 def enable_outdated_comments(self, request, baseapp):
583 config_patch = mock.patch.dict(
583 config_patch = mock.patch.dict(
584 'rhodecode.CONFIG', {'rhodecode_use_outdated_comments': True})
584 'rhodecode.CONFIG', {'rhodecode_use_outdated_comments': True})
585 config_patch.start()
585 config_patch.start()
586
586
587 @request.addfinalizer
587 @request.addfinalizer
588 def cleanup():
588 def cleanup():
589 config_patch.stop()
589 config_patch.stop()
590
590
591 def test_comment_stays_unflagged_on_unchanged_diff(self, pr_util):
591 def test_comment_stays_unflagged_on_unchanged_diff(self, pr_util):
592 commits = [
592 commits = [
593 {'message': 'a'},
593 {'message': 'a'},
594 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
594 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
595 {'message': 'c', 'added': [FileNode('file_c', 'test_content\n')]},
595 {'message': 'c', 'added': [FileNode('file_c', 'test_content\n')]},
596 ]
596 ]
597 pull_request = pr_util.create_pull_request(
597 pull_request = pr_util.create_pull_request(
598 commits=commits, target_head='a', source_head='b', revisions=['b'])
598 commits=commits, target_head='a', source_head='b', revisions=['b'])
599 pr_util.create_inline_comment(file_path='file_b')
599 pr_util.create_inline_comment(file_path='file_b')
600 pr_util.add_one_commit(head='c')
600 pr_util.add_one_commit(head='c')
601
601
602 assert_inline_comments(pull_request, visible=1, outdated=0)
602 assert_inline_comments(pull_request, visible=1, outdated=0)
603
603
604 def test_comment_stays_unflagged_on_change_above(self, pr_util):
604 def test_comment_stays_unflagged_on_change_above(self, pr_util):
605 original_content = ''.join(
605 original_content = ''.join(
606 ['line {}\n'.format(x) for x in range(1, 11)])
606 ['line {}\n'.format(x) for x in range(1, 11)])
607 updated_content = 'new_line_at_top\n' + original_content
607 updated_content = 'new_line_at_top\n' + original_content
608 commits = [
608 commits = [
609 {'message': 'a'},
609 {'message': 'a'},
610 {'message': 'b', 'added': [FileNode('file_b', original_content)]},
610 {'message': 'b', 'added': [FileNode('file_b', original_content)]},
611 {'message': 'c', 'changed': [FileNode('file_b', updated_content)]},
611 {'message': 'c', 'changed': [FileNode('file_b', updated_content)]},
612 ]
612 ]
613 pull_request = pr_util.create_pull_request(
613 pull_request = pr_util.create_pull_request(
614 commits=commits, target_head='a', source_head='b', revisions=['b'])
614 commits=commits, target_head='a', source_head='b', revisions=['b'])
615
615
616 with outdated_comments_patcher():
616 with outdated_comments_patcher():
617 comment = pr_util.create_inline_comment(
617 comment = pr_util.create_inline_comment(
618 line_no=u'n8', file_path='file_b')
618 line_no=u'n8', file_path='file_b')
619 pr_util.add_one_commit(head='c')
619 pr_util.add_one_commit(head='c')
620
620
621 assert_inline_comments(pull_request, visible=1, outdated=0)
621 assert_inline_comments(pull_request, visible=1, outdated=0)
622 assert comment.line_no == u'n9'
622 assert comment.line_no == u'n9'
623
623
624 def test_comment_stays_unflagged_on_change_below(self, pr_util):
624 def test_comment_stays_unflagged_on_change_below(self, pr_util):
625 original_content = ''.join(['line {}\n'.format(x) for x in range(10)])
625 original_content = ''.join(['line {}\n'.format(x) for x in range(10)])
626 updated_content = original_content + 'new_line_at_end\n'
626 updated_content = original_content + 'new_line_at_end\n'
627 commits = [
627 commits = [
628 {'message': 'a'},
628 {'message': 'a'},
629 {'message': 'b', 'added': [FileNode('file_b', original_content)]},
629 {'message': 'b', 'added': [FileNode('file_b', original_content)]},
630 {'message': 'c', 'changed': [FileNode('file_b', updated_content)]},
630 {'message': 'c', 'changed': [FileNode('file_b', updated_content)]},
631 ]
631 ]
632 pull_request = pr_util.create_pull_request(
632 pull_request = pr_util.create_pull_request(
633 commits=commits, target_head='a', source_head='b', revisions=['b'])
633 commits=commits, target_head='a', source_head='b', revisions=['b'])
634 pr_util.create_inline_comment(file_path='file_b')
634 pr_util.create_inline_comment(file_path='file_b')
635 pr_util.add_one_commit(head='c')
635 pr_util.add_one_commit(head='c')
636
636
637 assert_inline_comments(pull_request, visible=1, outdated=0)
637 assert_inline_comments(pull_request, visible=1, outdated=0)
638
638
639 @pytest.mark.parametrize('line_no', ['n4', 'o4', 'n10', 'o9'])
639 @pytest.mark.parametrize('line_no', ['n4', 'o4', 'n10', 'o9'])
640 def test_comment_flagged_on_change_around_context(self, pr_util, line_no):
640 def test_comment_flagged_on_change_around_context(self, pr_util, line_no):
641 base_lines = ['line {}\n'.format(x) for x in range(1, 13)]
641 base_lines = ['line {}\n'.format(x) for x in range(1, 13)]
642 change_lines = list(base_lines)
642 change_lines = list(base_lines)
643 change_lines.insert(6, 'line 6a added\n')
643 change_lines.insert(6, 'line 6a added\n')
644
644
645 # Changes on the last line of sight
645 # Changes on the last line of sight
646 update_lines = list(change_lines)
646 update_lines = list(change_lines)
647 update_lines[0] = 'line 1 changed\n'
647 update_lines[0] = 'line 1 changed\n'
648 update_lines[-1] = 'line 12 changed\n'
648 update_lines[-1] = 'line 12 changed\n'
649
649
650 def file_b(lines):
650 def file_b(lines):
651 return FileNode('file_b', ''.join(lines))
651 return FileNode('file_b', ''.join(lines))
652
652
653 commits = [
653 commits = [
654 {'message': 'a', 'added': [file_b(base_lines)]},
654 {'message': 'a', 'added': [file_b(base_lines)]},
655 {'message': 'b', 'changed': [file_b(change_lines)]},
655 {'message': 'b', 'changed': [file_b(change_lines)]},
656 {'message': 'c', 'changed': [file_b(update_lines)]},
656 {'message': 'c', 'changed': [file_b(update_lines)]},
657 ]
657 ]
658
658
659 pull_request = pr_util.create_pull_request(
659 pull_request = pr_util.create_pull_request(
660 commits=commits, target_head='a', source_head='b', revisions=['b'])
660 commits=commits, target_head='a', source_head='b', revisions=['b'])
661 pr_util.create_inline_comment(line_no=line_no, file_path='file_b')
661 pr_util.create_inline_comment(line_no=line_no, file_path='file_b')
662
662
663 with outdated_comments_patcher():
663 with outdated_comments_patcher():
664 pr_util.add_one_commit(head='c')
664 pr_util.add_one_commit(head='c')
665 assert_inline_comments(pull_request, visible=0, outdated=1)
665 assert_inline_comments(pull_request, visible=0, outdated=1)
666
666
667 @pytest.mark.parametrize("change, content", [
667 @pytest.mark.parametrize("change, content", [
668 ('changed', 'changed\n'),
668 ('changed', 'changed\n'),
669 ('removed', ''),
669 ('removed', ''),
670 ], ids=['changed', 'removed'])
670 ], ids=['changed', 'removed'])
671 def test_comment_flagged_on_change(self, pr_util, change, content):
671 def test_comment_flagged_on_change(self, pr_util, change, content):
672 commits = [
672 commits = [
673 {'message': 'a'},
673 {'message': 'a'},
674 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
674 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
675 {'message': 'c', change: [FileNode('file_b', content)]},
675 {'message': 'c', change: [FileNode('file_b', content)]},
676 ]
676 ]
677 pull_request = pr_util.create_pull_request(
677 pull_request = pr_util.create_pull_request(
678 commits=commits, target_head='a', source_head='b', revisions=['b'])
678 commits=commits, target_head='a', source_head='b', revisions=['b'])
679 pr_util.create_inline_comment(file_path='file_b')
679 pr_util.create_inline_comment(file_path='file_b')
680
680
681 with outdated_comments_patcher():
681 with outdated_comments_patcher():
682 pr_util.add_one_commit(head='c')
682 pr_util.add_one_commit(head='c')
683 assert_inline_comments(pull_request, visible=0, outdated=1)
683 assert_inline_comments(pull_request, visible=0, outdated=1)
684
684
685
685
686 @pytest.mark.usefixtures('config_stub')
686 @pytest.mark.usefixtures('config_stub')
687 class TestUpdateChangedFiles(object):
687 class TestUpdateChangedFiles(object):
688
688
689 def test_no_changes_on_unchanged_diff(self, pr_util):
689 def test_no_changes_on_unchanged_diff(self, pr_util):
690 commits = [
690 commits = [
691 {'message': 'a'},
691 {'message': 'a'},
692 {'message': 'b',
692 {'message': 'b',
693 'added': [FileNode('file_b', 'test_content b\n')]},
693 'added': [FileNode('file_b', 'test_content b\n')]},
694 {'message': 'c',
694 {'message': 'c',
695 'added': [FileNode('file_c', 'test_content c\n')]},
695 'added': [FileNode('file_c', 'test_content c\n')]},
696 ]
696 ]
697 # open a PR from a to b, adding file_b
697 # open a PR from a to b, adding file_b
698 pull_request = pr_util.create_pull_request(
698 pull_request = pr_util.create_pull_request(
699 commits=commits, target_head='a', source_head='b', revisions=['b'],
699 commits=commits, target_head='a', source_head='b', revisions=['b'],
700 name_suffix='per-file-review')
700 name_suffix='per-file-review')
701
701
702 # modify PR adding new file file_c
702 # modify PR adding new file file_c
703 pr_util.add_one_commit(head='c')
703 pr_util.add_one_commit(head='c')
704
704
705 assert_pr_file_changes(
705 assert_pr_file_changes(
706 pull_request,
706 pull_request,
707 added=['file_c'],
707 added=['file_c'],
708 modified=[],
708 modified=[],
709 removed=[])
709 removed=[])
710
710
711 def test_modify_and_undo_modification_diff(self, pr_util):
711 def test_modify_and_undo_modification_diff(self, pr_util):
712 commits = [
712 commits = [
713 {'message': 'a'},
713 {'message': 'a'},
714 {'message': 'b',
714 {'message': 'b',
715 'added': [FileNode('file_b', 'test_content b\n')]},
715 'added': [FileNode('file_b', 'test_content b\n')]},
716 {'message': 'c',
716 {'message': 'c',
717 'changed': [FileNode('file_b', 'test_content b modified\n')]},
717 'changed': [FileNode('file_b', 'test_content b modified\n')]},
718 {'message': 'd',
718 {'message': 'd',
719 'changed': [FileNode('file_b', 'test_content b\n')]},
719 'changed': [FileNode('file_b', 'test_content b\n')]},
720 ]
720 ]
721 # open a PR from a to b, adding file_b
721 # open a PR from a to b, adding file_b
722 pull_request = pr_util.create_pull_request(
722 pull_request = pr_util.create_pull_request(
723 commits=commits, target_head='a', source_head='b', revisions=['b'],
723 commits=commits, target_head='a', source_head='b', revisions=['b'],
724 name_suffix='per-file-review')
724 name_suffix='per-file-review')
725
725
726 # modify PR modifying file file_b
726 # modify PR modifying file file_b
727 pr_util.add_one_commit(head='c')
727 pr_util.add_one_commit(head='c')
728
728
729 assert_pr_file_changes(
729 assert_pr_file_changes(
730 pull_request,
730 pull_request,
731 added=[],
731 added=[],
732 modified=['file_b'],
732 modified=['file_b'],
733 removed=[])
733 removed=[])
734
734
735 # move the head again to d, which rollbacks change,
735 # move the head again to d, which rollbacks change,
736 # meaning we should indicate no changes
736 # meaning we should indicate no changes
737 pr_util.add_one_commit(head='d')
737 pr_util.add_one_commit(head='d')
738
738
739 assert_pr_file_changes(
739 assert_pr_file_changes(
740 pull_request,
740 pull_request,
741 added=[],
741 added=[],
742 modified=[],
742 modified=[],
743 removed=[])
743 removed=[])
744
744
745 def test_updated_all_files_in_pr(self, pr_util):
745 def test_updated_all_files_in_pr(self, pr_util):
746 commits = [
746 commits = [
747 {'message': 'a'},
747 {'message': 'a'},
748 {'message': 'b', 'added': [
748 {'message': 'b', 'added': [
749 FileNode('file_a', 'test_content a\n'),
749 FileNode('file_a', 'test_content a\n'),
750 FileNode('file_b', 'test_content b\n'),
750 FileNode('file_b', 'test_content b\n'),
751 FileNode('file_c', 'test_content c\n')]},
751 FileNode('file_c', 'test_content c\n')]},
752 {'message': 'c', 'changed': [
752 {'message': 'c', 'changed': [
753 FileNode('file_a', 'test_content a changed\n'),
753 FileNode('file_a', 'test_content a changed\n'),
754 FileNode('file_b', 'test_content b changed\n'),
754 FileNode('file_b', 'test_content b changed\n'),
755 FileNode('file_c', 'test_content c changed\n')]},
755 FileNode('file_c', 'test_content c changed\n')]},
756 ]
756 ]
757 # open a PR from a to b, changing 3 files
757 # open a PR from a to b, changing 3 files
758 pull_request = pr_util.create_pull_request(
758 pull_request = pr_util.create_pull_request(
759 commits=commits, target_head='a', source_head='b', revisions=['b'],
759 commits=commits, target_head='a', source_head='b', revisions=['b'],
760 name_suffix='per-file-review')
760 name_suffix='per-file-review')
761
761
762 pr_util.add_one_commit(head='c')
762 pr_util.add_one_commit(head='c')
763
763
764 assert_pr_file_changes(
764 assert_pr_file_changes(
765 pull_request,
765 pull_request,
766 added=[],
766 added=[],
767 modified=['file_a', 'file_b', 'file_c'],
767 modified=['file_a', 'file_b', 'file_c'],
768 removed=[])
768 removed=[])
769
769
770 def test_updated_and_removed_all_files_in_pr(self, pr_util):
770 def test_updated_and_removed_all_files_in_pr(self, pr_util):
771 commits = [
771 commits = [
772 {'message': 'a'},
772 {'message': 'a'},
773 {'message': 'b', 'added': [
773 {'message': 'b', 'added': [
774 FileNode('file_a', 'test_content a\n'),
774 FileNode('file_a', 'test_content a\n'),
775 FileNode('file_b', 'test_content b\n'),
775 FileNode('file_b', 'test_content b\n'),
776 FileNode('file_c', 'test_content c\n')]},
776 FileNode('file_c', 'test_content c\n')]},
777 {'message': 'c', 'removed': [
777 {'message': 'c', 'removed': [
778 FileNode('file_a', 'test_content a changed\n'),
778 FileNode('file_a', 'test_content a changed\n'),
779 FileNode('file_b', 'test_content b changed\n'),
779 FileNode('file_b', 'test_content b changed\n'),
780 FileNode('file_c', 'test_content c changed\n')]},
780 FileNode('file_c', 'test_content c changed\n')]},
781 ]
781 ]
782 # open a PR from a to b, removing 3 files
782 # open a PR from a to b, removing 3 files
783 pull_request = pr_util.create_pull_request(
783 pull_request = pr_util.create_pull_request(
784 commits=commits, target_head='a', source_head='b', revisions=['b'],
784 commits=commits, target_head='a', source_head='b', revisions=['b'],
785 name_suffix='per-file-review')
785 name_suffix='per-file-review')
786
786
787 pr_util.add_one_commit(head='c')
787 pr_util.add_one_commit(head='c')
788
788
789 assert_pr_file_changes(
789 assert_pr_file_changes(
790 pull_request,
790 pull_request,
791 added=[],
791 added=[],
792 modified=[],
792 modified=[],
793 removed=['file_a', 'file_b', 'file_c'])
793 removed=['file_a', 'file_b', 'file_c'])
794
794
795
795
796 def test_update_writes_snapshot_into_pull_request_version(pr_util, config_stub):
796 def test_update_writes_snapshot_into_pull_request_version(pr_util, config_stub):
797 model = PullRequestModel()
797 model = PullRequestModel()
798 pull_request = pr_util.create_pull_request()
798 pull_request = pr_util.create_pull_request()
799 pr_util.update_source_repository()
799 pr_util.update_source_repository()
800
800
801 model.update_commits(pull_request)
801 model.update_commits(pull_request)
802
802
803 # Expect that it has a version entry now
803 # Expect that it has a version entry now
804 assert len(model.get_versions(pull_request)) == 1
804 assert len(model.get_versions(pull_request)) == 1
805
805
806
806
807 def test_update_skips_new_version_if_unchanged(pr_util, config_stub):
807 def test_update_skips_new_version_if_unchanged(pr_util, config_stub):
808 pull_request = pr_util.create_pull_request()
808 pull_request = pr_util.create_pull_request()
809 model = PullRequestModel()
809 model = PullRequestModel()
810 model.update_commits(pull_request)
810 model.update_commits(pull_request)
811
811
812 # Expect that it still has no versions
812 # Expect that it still has no versions
813 assert len(model.get_versions(pull_request)) == 0
813 assert len(model.get_versions(pull_request)) == 0
814
814
815
815
816 def test_update_assigns_comments_to_the_new_version(pr_util, config_stub):
816 def test_update_assigns_comments_to_the_new_version(pr_util, config_stub):
817 model = PullRequestModel()
817 model = PullRequestModel()
818 pull_request = pr_util.create_pull_request()
818 pull_request = pr_util.create_pull_request()
819 comment = pr_util.create_comment()
819 comment = pr_util.create_comment()
820 pr_util.update_source_repository()
820 pr_util.update_source_repository()
821
821
822 model.update_commits(pull_request)
822 model.update_commits(pull_request)
823
823
824 # Expect that the comment is linked to the pr version now
824 # Expect that the comment is linked to the pr version now
825 assert comment.pull_request_version == model.get_versions(pull_request)[0]
825 assert comment.pull_request_version == model.get_versions(pull_request)[0]
826
826
827
827
828 def test_update_adds_a_comment_to_the_pull_request_about_the_change(pr_util, config_stub):
828 def test_update_adds_a_comment_to_the_pull_request_about_the_change(pr_util, config_stub):
829 model = PullRequestModel()
829 model = PullRequestModel()
830 pull_request = pr_util.create_pull_request()
830 pull_request = pr_util.create_pull_request()
831 pr_util.update_source_repository()
831 pr_util.update_source_repository()
832 pr_util.update_source_repository()
832 pr_util.update_source_repository()
833
833
834 model.update_commits(pull_request)
834 model.update_commits(pull_request)
835
835
836 # Expect to find a new comment about the change
836 # Expect to find a new comment about the change
837 expected_message = textwrap.dedent(
837 expected_message = textwrap.dedent(
838 """\
838 """\
839 Pull request updated. Auto status change to |under_review|
839 Pull request updated. Auto status change to |under_review|
840
840
841 .. role:: added
841 .. role:: added
842 .. role:: removed
842 .. role:: removed
843 .. parsed-literal::
843 .. parsed-literal::
844
844
845 Changed commits:
845 Changed commits:
846 * :added:`1 added`
846 * :added:`1 added`
847 * :removed:`0 removed`
847 * :removed:`0 removed`
848
848
849 Changed files:
849 Changed files:
850 * `A file_2 <#a_c--92ed3b5f07b4>`_
850 * `A file_2 <#a_c--92ed3b5f07b4>`_
851
851
852 .. |under_review| replace:: *"Under Review"*"""
852 .. |under_review| replace:: *"Under Review"*"""
853 )
853 )
854 pull_request_comments = sorted(
854 pull_request_comments = sorted(
855 pull_request.comments, key=lambda c: c.modified_at)
855 pull_request.comments, key=lambda c: c.modified_at)
856 update_comment = pull_request_comments[-1]
856 update_comment = pull_request_comments[-1]
857 assert update_comment.text == expected_message
857 assert update_comment.text == expected_message
858
858
859
859
860 def test_create_version_from_snapshot_updates_attributes(pr_util, config_stub):
860 def test_create_version_from_snapshot_updates_attributes(pr_util, config_stub):
861 pull_request = pr_util.create_pull_request()
861 pull_request = pr_util.create_pull_request()
862
862
863 # Avoiding default values
863 # Avoiding default values
864 pull_request.status = PullRequest.STATUS_CLOSED
864 pull_request.status = PullRequest.STATUS_CLOSED
865 pull_request._last_merge_source_rev = "0" * 40
865 pull_request._last_merge_source_rev = "0" * 40
866 pull_request._last_merge_target_rev = "1" * 40
866 pull_request._last_merge_target_rev = "1" * 40
867 pull_request.last_merge_status = 1
867 pull_request.last_merge_status = 1
868 pull_request.merge_rev = "2" * 40
868 pull_request.merge_rev = "2" * 40
869
869
870 # Remember automatic values
870 # Remember automatic values
871 created_on = pull_request.created_on
871 created_on = pull_request.created_on
872 updated_on = pull_request.updated_on
872 updated_on = pull_request.updated_on
873
873
874 # Create a new version of the pull request
874 # Create a new version of the pull request
875 version = PullRequestModel()._create_version_from_snapshot(pull_request)
875 version = PullRequestModel()._create_version_from_snapshot(pull_request)
876
876
877 # Check attributes
877 # Check attributes
878 assert version.title == pr_util.create_parameters['title']
878 assert version.title == pr_util.create_parameters['title']
879 assert version.description == pr_util.create_parameters['description']
879 assert version.description == pr_util.create_parameters['description']
880 assert version.status == PullRequest.STATUS_CLOSED
880 assert version.status == PullRequest.STATUS_CLOSED
881
881
882 # versions get updated created_on
882 # versions get updated created_on
883 assert version.created_on != created_on
883 assert version.created_on != created_on
884
884
885 assert version.updated_on == updated_on
885 assert version.updated_on == updated_on
886 assert version.user_id == pull_request.user_id
886 assert version.user_id == pull_request.user_id
887 assert version.revisions == pr_util.create_parameters['revisions']
887 assert version.revisions == pr_util.create_parameters['revisions']
888 assert version.source_repo == pr_util.source_repository
888 assert version.source_repo == pr_util.source_repository
889 assert version.source_ref == pr_util.create_parameters['source_ref']
889 assert version.source_ref == pr_util.create_parameters['source_ref']
890 assert version.target_repo == pr_util.target_repository
890 assert version.target_repo == pr_util.target_repository
891 assert version.target_ref == pr_util.create_parameters['target_ref']
891 assert version.target_ref == pr_util.create_parameters['target_ref']
892 assert version._last_merge_source_rev == pull_request._last_merge_source_rev
892 assert version._last_merge_source_rev == pull_request._last_merge_source_rev
893 assert version._last_merge_target_rev == pull_request._last_merge_target_rev
893 assert version._last_merge_target_rev == pull_request._last_merge_target_rev
894 assert version.last_merge_status == pull_request.last_merge_status
894 assert version.last_merge_status == pull_request.last_merge_status
895 assert version.merge_rev == pull_request.merge_rev
895 assert version.merge_rev == pull_request.merge_rev
896 assert version.pull_request == pull_request
896 assert version.pull_request == pull_request
897
897
898
898
899 def test_link_comments_to_version_only_updates_unlinked_comments(pr_util, config_stub):
899 def test_link_comments_to_version_only_updates_unlinked_comments(pr_util, config_stub):
900 version1 = pr_util.create_version_of_pull_request()
900 version1 = pr_util.create_version_of_pull_request()
901 comment_linked = pr_util.create_comment(linked_to=version1)
901 comment_linked = pr_util.create_comment(linked_to=version1)
902 comment_unlinked = pr_util.create_comment()
902 comment_unlinked = pr_util.create_comment()
903 version2 = pr_util.create_version_of_pull_request()
903 version2 = pr_util.create_version_of_pull_request()
904
904
905 PullRequestModel()._link_comments_to_version(version2)
905 PullRequestModel()._link_comments_to_version(version2)
906 Session().commit()
906 Session().commit()
907
907
908 # Expect that only the new comment is linked to version2
908 # Expect that only the new comment is linked to version2
909 assert (
909 assert (
910 comment_unlinked.pull_request_version_id ==
910 comment_unlinked.pull_request_version_id ==
911 version2.pull_request_version_id)
911 version2.pull_request_version_id)
912 assert (
912 assert (
913 comment_linked.pull_request_version_id ==
913 comment_linked.pull_request_version_id ==
914 version1.pull_request_version_id)
914 version1.pull_request_version_id)
915 assert (
915 assert (
916 comment_unlinked.pull_request_version_id !=
916 comment_unlinked.pull_request_version_id !=
917 comment_linked.pull_request_version_id)
917 comment_linked.pull_request_version_id)
918
918
919
919
920 def test_calculate_commits():
920 def test_calculate_commits():
921 old_ids = [1, 2, 3]
921 old_ids = [1, 2, 3]
922 new_ids = [1, 3, 4, 5]
922 new_ids = [1, 3, 4, 5]
923 change = PullRequestModel()._calculate_commit_id_changes(old_ids, new_ids)
923 change = PullRequestModel()._calculate_commit_id_changes(old_ids, new_ids)
924 assert change.added == [4, 5]
924 assert change.added == [4, 5]
925 assert change.common == [1, 3]
925 assert change.common == [1, 3]
926 assert change.removed == [2]
926 assert change.removed == [2]
927 assert change.total == [1, 3, 4, 5]
927 assert change.total == [1, 3, 4, 5]
928
928
929
929
930 def assert_inline_comments(pull_request, visible=None, outdated=None):
930 def assert_inline_comments(pull_request, visible=None, outdated=None):
931 if visible is not None:
931 if visible is not None:
932 inline_comments = CommentsModel().get_inline_comments(
932 inline_comments = CommentsModel().get_inline_comments(
933 pull_request.target_repo.repo_id, pull_request=pull_request)
933 pull_request.target_repo.repo_id, pull_request=pull_request)
934 inline_cnt = CommentsModel().get_inline_comments_count(
934 inline_cnt = CommentsModel().get_inline_comments_count(
935 inline_comments)
935 inline_comments)
936 assert inline_cnt == visible
936 assert inline_cnt == visible
937 if outdated is not None:
937 if outdated is not None:
938 outdated_comments = CommentsModel().get_outdated_comments(
938 outdated_comments = CommentsModel().get_outdated_comments(
939 pull_request.target_repo.repo_id, pull_request)
939 pull_request.target_repo.repo_id, pull_request)
940 assert len(outdated_comments) == outdated
940 assert len(outdated_comments) == outdated
941
941
942
942
943 def assert_pr_file_changes(
943 def assert_pr_file_changes(
944 pull_request, added=None, modified=None, removed=None):
944 pull_request, added=None, modified=None, removed=None):
945 pr_versions = PullRequestModel().get_versions(pull_request)
945 pr_versions = PullRequestModel().get_versions(pull_request)
946 # always use first version, ie original PR to calculate changes
946 # always use first version, ie original PR to calculate changes
947 pull_request_version = pr_versions[0]
947 pull_request_version = pr_versions[0]
948 old_diff_data, new_diff_data = PullRequestModel()._generate_update_diffs(
948 old_diff_data, new_diff_data = PullRequestModel()._generate_update_diffs(
949 pull_request, pull_request_version)
949 pull_request, pull_request_version)
950 file_changes = PullRequestModel()._calculate_file_changes(
950 file_changes = PullRequestModel()._calculate_file_changes(
951 old_diff_data, new_diff_data)
951 old_diff_data, new_diff_data)
952
952
953 assert added == file_changes.added, \
953 assert added == file_changes.added, \
954 'expected added:%s vs value:%s' % (added, file_changes.added)
954 'expected added:%s vs value:%s' % (added, file_changes.added)
955 assert modified == file_changes.modified, \
955 assert modified == file_changes.modified, \
956 'expected modified:%s vs value:%s' % (modified, file_changes.modified)
956 'expected modified:%s vs value:%s' % (modified, file_changes.modified)
957 assert removed == file_changes.removed, \
957 assert removed == file_changes.removed, \
958 'expected removed:%s vs value:%s' % (removed, file_changes.removed)
958 'expected removed:%s vs value:%s' % (removed, file_changes.removed)
959
959
960
960
961 def outdated_comments_patcher(use_outdated=True):
961 def outdated_comments_patcher(use_outdated=True):
962 return mock.patch.object(
962 return mock.patch.object(
963 CommentsModel, 'use_outdated_comments',
963 CommentsModel, 'use_outdated_comments',
964 return_value=use_outdated)
964 return_value=use_outdated)
General Comments 0
You need to be logged in to leave comments. Login now