##// END OF EJS Templates
compare: migrated code from pylons to pyramid views.
marcink -
r1957:00f3a509 default
parent child Browse files
Show More
@@ -0,0 +1,323 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2012-2017 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21
22 import logging
23
24 from pyramid.httpexceptions import HTTPBadRequest, HTTPNotFound, HTTPFound
25 from pyramid.view import view_config
26 from pyramid.renderers import render
27 from pyramid.response import Response
28
29
30 from rhodecode.apps._base import RepoAppView
31 from rhodecode.controllers.utils import parse_path_ref, get_commit_from_ref_name
32 from rhodecode.lib import helpers as h
33 from rhodecode.lib import diffs, codeblocks
34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
35 from rhodecode.lib.utils import safe_str
36 from rhodecode.lib.utils2 import safe_unicode, str2bool
37 from rhodecode.lib.vcs.exceptions import (
38 EmptyRepositoryError, RepositoryError, RepositoryRequirementError,
39 NodeDoesNotExistError)
40 from rhodecode.model.db import Repository, ChangesetStatus
41
42 log = logging.getLogger(__name__)
43
44
45 class RepoCompareView(RepoAppView):
46 def load_default_context(self):
47 c = self._get_local_tmpl_context(include_app_defaults=True)
48
49 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
50 c.repo_info = self.db_repo
51 c.rhodecode_repo = self.rhodecode_vcs_repo
52
53 self._register_global_c(c)
54 return c
55
56 def _get_commit_or_redirect(
57 self, ref, ref_type, repo, redirect_after=True, partial=False):
58 """
59 This is a safe way to get a commit. If an error occurs it
60 redirects to a commit with a proper message. If partial is set
61 then it does not do redirect raise and throws an exception instead.
62 """
63 _ = self.request.translate
64 try:
65 return get_commit_from_ref_name(repo, safe_str(ref), ref_type)
66 except EmptyRepositoryError:
67 if not redirect_after:
68 return repo.scm_instance().EMPTY_COMMIT
69 h.flash(h.literal(_('There are no commits yet')),
70 category='warning')
71 raise HTTPFound(
72 h.route_path('repo_summary', repo_name=repo.repo_name))
73
74 except RepositoryError as e:
75 log.exception(safe_str(e))
76 h.flash(safe_str(h.escape(e)), category='warning')
77 if not partial:
78 raise HTTPFound(
79 h.route_path('repo_summary', repo_name=repo.repo_name))
80 raise HTTPBadRequest()
81
82 @LoginRequired()
83 @HasRepoPermissionAnyDecorator(
84 'repository.read', 'repository.write', 'repository.admin')
85 @view_config(
86 route_name='repo_compare_select', request_method='GET',
87 renderer='rhodecode:templates/compare/compare_diff.mako')
88 def compare_select(self):
89 _ = self.request.translate
90 c = self.load_default_context()
91
92 source_repo = self.db_repo_name
93 target_repo = self.request.GET.get('target_repo', source_repo)
94 c.source_repo = Repository.get_by_repo_name(source_repo)
95 c.target_repo = Repository.get_by_repo_name(target_repo)
96
97 if c.source_repo is None or c.target_repo is None:
98 raise HTTPNotFound()
99
100 c.compare_home = True
101 c.commit_ranges = []
102 c.collapse_all_commits = False
103 c.diffset = None
104 c.limited_diff = False
105 c.source_ref = c.target_ref = _('Select commit')
106 c.source_ref_type = ""
107 c.target_ref_type = ""
108 c.commit_statuses = ChangesetStatus.STATUSES
109 c.preview_mode = False
110 c.file_path = None
111
112 return self._get_template_context(c)
113
114 @LoginRequired()
115 @HasRepoPermissionAnyDecorator(
116 'repository.read', 'repository.write', 'repository.admin')
117 @view_config(
118 route_name='repo_compare', request_method='GET',
119 renderer=None)
120 def compare(self):
121 _ = self.request.translate
122 c = self.load_default_context()
123
124 source_ref_type = self.request.matchdict['source_ref_type']
125 source_ref = self.request.matchdict['source_ref']
126 target_ref_type = self.request.matchdict['target_ref_type']
127 target_ref = self.request.matchdict['target_ref']
128
129 # source_ref will be evaluated in source_repo
130 source_repo_name = self.db_repo_name
131 source_path, source_id = parse_path_ref(source_ref)
132
133 # target_ref will be evaluated in target_repo
134 target_repo_name = self.request.GET.get('target_repo', source_repo_name)
135 target_path, target_id = parse_path_ref(
136 target_ref, default_path=self.request.GET.get('f_path', ''))
137
138 # if merge is True
139 # Show what changes since the shared ancestor commit of target/source
140 # the source would get if it was merged with target. Only commits
141 # which are in target but not in source will be shown.
142 merge = str2bool(self.request.GET.get('merge'))
143 # if merge is False
144 # Show a raw diff of source/target refs even if no ancestor exists
145
146 # c.fulldiff disables cut_off_limit
147 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
148
149 c.file_path = target_path
150 c.commit_statuses = ChangesetStatus.STATUSES
151
152 # if partial, returns just compare_commits.html (commits log)
153 partial = self.request.is_xhr
154
155 # swap url for compare_diff page
156 c.swap_url = h.route_path(
157 'repo_compare',
158 repo_name=target_repo_name,
159 source_ref_type=target_ref_type,
160 source_ref=target_ref,
161 target_repo=source_repo_name,
162 target_ref_type=source_ref_type,
163 target_ref=source_ref,
164 _query=dict(merge=merge and '1' or '', f_path=target_path))
165
166 source_repo = Repository.get_by_repo_name(source_repo_name)
167 target_repo = Repository.get_by_repo_name(target_repo_name)
168
169 if source_repo is None:
170 log.error('Could not find the source repo: {}'
171 .format(source_repo_name))
172 h.flash(_('Could not find the source repo: `{}`')
173 .format(h.escape(source_repo_name)), category='error')
174 raise HTTPFound(
175 h.route_path('repo_compare_select', repo_name=self.db_repo_name))
176
177 if target_repo is None:
178 log.error('Could not find the target repo: {}'
179 .format(source_repo_name))
180 h.flash(_('Could not find the target repo: `{}`')
181 .format(h.escape(target_repo_name)), category='error')
182 raise HTTPFound(
183 h.route_path('repo_compare_select', repo_name=self.db_repo_name))
184
185 source_scm = source_repo.scm_instance()
186 target_scm = target_repo.scm_instance()
187
188 source_alias = source_scm.alias
189 target_alias = target_scm.alias
190 if source_alias != target_alias:
191 msg = _('The comparison of two different kinds of remote repos '
192 'is not available')
193 log.error(msg)
194 h.flash(msg, category='error')
195 raise HTTPFound(
196 h.route_path('repo_compare_select', repo_name=self.db_repo_name))
197
198 source_commit = self._get_commit_or_redirect(
199 ref=source_id, ref_type=source_ref_type, repo=source_repo,
200 partial=partial)
201 target_commit = self._get_commit_or_redirect(
202 ref=target_id, ref_type=target_ref_type, repo=target_repo,
203 partial=partial)
204
205 c.compare_home = False
206 c.source_repo = source_repo
207 c.target_repo = target_repo
208 c.source_ref = source_ref
209 c.target_ref = target_ref
210 c.source_ref_type = source_ref_type
211 c.target_ref_type = target_ref_type
212
213 pre_load = ["author", "branch", "date", "message"]
214 c.ancestor = None
215
216 if c.file_path:
217 if source_commit == target_commit:
218 c.commit_ranges = []
219 else:
220 c.commit_ranges = [target_commit]
221 else:
222 try:
223 c.commit_ranges = source_scm.compare(
224 source_commit.raw_id, target_commit.raw_id,
225 target_scm, merge, pre_load=pre_load)
226 if merge:
227 c.ancestor = source_scm.get_common_ancestor(
228 source_commit.raw_id, target_commit.raw_id, target_scm)
229 except RepositoryRequirementError:
230 msg = _('Could not compare repos with different '
231 'large file settings')
232 log.error(msg)
233 if partial:
234 return Response(msg)
235 h.flash(msg, category='error')
236 raise HTTPFound(
237 h.route_path('repo_compare_select',
238 repo_name=self.db_repo_name))
239
240 c.statuses = self.db_repo.statuses(
241 [x.raw_id for x in c.commit_ranges])
242
243 # auto collapse if we have more than limit
244 collapse_limit = diffs.DiffProcessor._collapse_commits_over
245 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
246
247 if partial: # for PR ajax commits loader
248 if not c.ancestor:
249 return Response('') # cannot merge if there is no ancestor
250
251 html = render(
252 'rhodecode:templates/compare/compare_commits.mako',
253 self._get_template_context(c), self.request)
254 return Response(html)
255
256 if c.ancestor:
257 # case we want a simple diff without incoming commits,
258 # previewing what will be merged.
259 # Make the diff on target repo (which is known to have target_ref)
260 log.debug('Using ancestor %s as source_ref instead of %s'
261 % (c.ancestor, source_ref))
262 source_repo = target_repo
263 source_commit = target_repo.get_commit(commit_id=c.ancestor)
264
265 # diff_limit will cut off the whole diff if the limit is applied
266 # otherwise it will just hide the big files from the front-end
267 diff_limit = c.visual.cut_off_limit_diff
268 file_limit = c.visual.cut_off_limit_file
269
270 log.debug('calculating diff between '
271 'source_ref:%s and target_ref:%s for repo `%s`',
272 source_commit, target_commit,
273 safe_unicode(source_repo.scm_instance().path))
274
275 if source_commit.repository != target_commit.repository:
276 msg = _(
277 "Repositories unrelated. "
278 "Cannot compare commit %(commit1)s from repository %(repo1)s "
279 "with commit %(commit2)s from repository %(repo2)s.") % {
280 'commit1': h.show_id(source_commit),
281 'repo1': source_repo.repo_name,
282 'commit2': h.show_id(target_commit),
283 'repo2': target_repo.repo_name,
284 }
285 h.flash(msg, category='error')
286 raise HTTPFound(
287 h.route_path('repo_compare_select',
288 repo_name=self.db_repo_name))
289
290 txt_diff = source_repo.scm_instance().get_diff(
291 commit1=source_commit, commit2=target_commit,
292 path=target_path, path1=source_path)
293
294 diff_processor = diffs.DiffProcessor(
295 txt_diff, format='newdiff', diff_limit=diff_limit,
296 file_limit=file_limit, show_full_diff=c.fulldiff)
297 _parsed = diff_processor.prepare()
298
299 def _node_getter(commit):
300 """ Returns a function that returns a node for a commit or None """
301 def get_node(fname):
302 try:
303 return commit.get_node(fname)
304 except NodeDoesNotExistError:
305 return None
306 return get_node
307
308 diffset = codeblocks.DiffSet(
309 repo_name=source_repo.repo_name,
310 source_node_getter=_node_getter(source_commit),
311 target_node_getter=_node_getter(target_commit),
312 )
313 c.diffset = diffset.render_patchset(
314 _parsed, source_ref, target_ref)
315
316 c.preview_mode = merge
317 c.source_commit = source_commit
318 c.target_commit = target_commit
319
320 html = render(
321 'rhodecode:templates/compare/compare_diff.mako',
322 self._get_template_context(c), self.request)
323 return Response(html) No newline at end of file
@@ -1,304 +1,312 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 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 from rhodecode.apps._base import add_route_with_slash
20 from rhodecode.apps._base import add_route_with_slash
21
21
22
22
23 def includeme(config):
23 def includeme(config):
24
24
25 # Summary
25 # Summary
26 # NOTE(marcink): one additional route is defined in very bottom, catch
26 # NOTE(marcink): one additional route is defined in very bottom, catch
27 # all pattern
27 # all pattern
28 config.add_route(
28 config.add_route(
29 name='repo_summary_explicit',
29 name='repo_summary_explicit',
30 pattern='/{repo_name:.*?[^/]}/summary', repo_route=True)
30 pattern='/{repo_name:.*?[^/]}/summary', repo_route=True)
31 config.add_route(
31 config.add_route(
32 name='repo_summary_commits',
32 name='repo_summary_commits',
33 pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True)
33 pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True)
34
34
35 # repo commits
35 # Commits
36
37 config.add_route(
36 config.add_route(
38 name='repo_commit',
37 name='repo_commit',
39 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True)
38 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True)
40
39
41 config.add_route(
40 config.add_route(
42 name='repo_commit_children',
41 name='repo_commit_children',
43 pattern='/{repo_name:.*?[^/]}/changeset_children/{commit_id}', repo_route=True)
42 pattern='/{repo_name:.*?[^/]}/changeset_children/{commit_id}', repo_route=True)
44
43
45 config.add_route(
44 config.add_route(
46 name='repo_commit_parents',
45 name='repo_commit_parents',
47 pattern='/{repo_name:.*?[^/]}/changeset_parents/{commit_id}', repo_route=True)
46 pattern='/{repo_name:.*?[^/]}/changeset_parents/{commit_id}', repo_route=True)
48
47
49 # still working url for backward compat.
50 config.add_route(
51 name='repo_commit_raw_deprecated',
52 pattern='/{repo_name:.*?[^/]}/raw-changeset/{commit_id}', repo_route=True)
53
54 config.add_route(
48 config.add_route(
55 name='repo_commit_raw',
49 name='repo_commit_raw',
56 pattern='/{repo_name:.*?[^/]}/changeset-diff/{commit_id}', repo_route=True)
50 pattern='/{repo_name:.*?[^/]}/changeset-diff/{commit_id}', repo_route=True)
57
51
58 config.add_route(
52 config.add_route(
59 name='repo_commit_patch',
53 name='repo_commit_patch',
60 pattern='/{repo_name:.*?[^/]}/changeset-patch/{commit_id}', repo_route=True)
54 pattern='/{repo_name:.*?[^/]}/changeset-patch/{commit_id}', repo_route=True)
61
55
62 config.add_route(
56 config.add_route(
63 name='repo_commit_download',
57 name='repo_commit_download',
64 pattern='/{repo_name:.*?[^/]}/changeset-download/{commit_id}', repo_route=True)
58 pattern='/{repo_name:.*?[^/]}/changeset-download/{commit_id}', repo_route=True)
65
59
66 config.add_route(
60 config.add_route(
67 name='repo_commit_data',
61 name='repo_commit_data',
68 pattern='/{repo_name:.*?[^/]}/changeset-data/{commit_id}', repo_route=True)
62 pattern='/{repo_name:.*?[^/]}/changeset-data/{commit_id}', repo_route=True)
69
63
70 config.add_route(
64 config.add_route(
71 name='repo_commit_comment_create',
65 name='repo_commit_comment_create',
72 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/create', repo_route=True)
66 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/create', repo_route=True)
73
67
74 config.add_route(
68 config.add_route(
75 name='repo_commit_comment_preview',
69 name='repo_commit_comment_preview',
76 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/preview', repo_route=True)
70 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/preview', repo_route=True)
77
71
78 config.add_route(
72 config.add_route(
79 name='repo_commit_comment_delete',
73 name='repo_commit_comment_delete',
80 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/delete', repo_route=True)
74 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/delete', repo_route=True)
81
75
82 # repo files
76 # still working url for backward compat.
77 config.add_route(
78 name='repo_commit_raw_deprecated',
79 pattern='/{repo_name:.*?[^/]}/raw-changeset/{commit_id}', repo_route=True)
80
81 # Files
83 config.add_route(
82 config.add_route(
84 name='repo_archivefile',
83 name='repo_archivefile',
85 pattern='/{repo_name:.*?[^/]}/archive/{fname}', repo_route=True)
84 pattern='/{repo_name:.*?[^/]}/archive/{fname}', repo_route=True)
86
85
87 config.add_route(
86 config.add_route(
88 name='repo_files_diff',
87 name='repo_files_diff',
89 pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True)
88 pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True)
90 config.add_route( # legacy route to make old links work
89 config.add_route( # legacy route to make old links work
91 name='repo_files_diff_2way_redirect',
90 name='repo_files_diff_2way_redirect',
92 pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True)
91 pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True)
93
92
94 config.add_route(
93 config.add_route(
95 name='repo_files',
94 name='repo_files',
96 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True)
95 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True)
97 config.add_route(
96 config.add_route(
98 name='repo_files:default_path',
97 name='repo_files:default_path',
99 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True)
98 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True)
100 config.add_route(
99 config.add_route(
101 name='repo_files:default_commit',
100 name='repo_files:default_commit',
102 pattern='/{repo_name:.*?[^/]}/files', repo_route=True)
101 pattern='/{repo_name:.*?[^/]}/files', repo_route=True)
103
102
104 config.add_route(
103 config.add_route(
105 name='repo_files:rendered',
104 name='repo_files:rendered',
106 pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True)
105 pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True)
107
106
108 config.add_route(
107 config.add_route(
109 name='repo_files:annotated',
108 name='repo_files:annotated',
110 pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True)
109 pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True)
111 config.add_route(
110 config.add_route(
112 name='repo_files:annotated_previous',
111 name='repo_files:annotated_previous',
113 pattern='/{repo_name:.*?[^/]}/annotate-previous/{commit_id}/{f_path:.*}', repo_route=True)
112 pattern='/{repo_name:.*?[^/]}/annotate-previous/{commit_id}/{f_path:.*}', repo_route=True)
114
113
115 config.add_route(
114 config.add_route(
116 name='repo_nodetree_full',
115 name='repo_nodetree_full',
117 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/{f_path:.*}', repo_route=True)
116 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/{f_path:.*}', repo_route=True)
118 config.add_route(
117 config.add_route(
119 name='repo_nodetree_full:default_path',
118 name='repo_nodetree_full:default_path',
120 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True)
119 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True)
121
120
122 config.add_route(
121 config.add_route(
123 name='repo_files_nodelist',
122 name='repo_files_nodelist',
124 pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True)
123 pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True)
125
124
126 config.add_route(
125 config.add_route(
127 name='repo_file_raw',
126 name='repo_file_raw',
128 pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True)
127 pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True)
129
128
130 config.add_route(
129 config.add_route(
131 name='repo_file_download',
130 name='repo_file_download',
132 pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True)
131 pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True)
133 config.add_route( # backward compat to keep old links working
132 config.add_route( # backward compat to keep old links working
134 name='repo_file_download:legacy',
133 name='repo_file_download:legacy',
135 pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}',
134 pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}',
136 repo_route=True)
135 repo_route=True)
137
136
138 config.add_route(
137 config.add_route(
139 name='repo_file_history',
138 name='repo_file_history',
140 pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True)
139 pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True)
141
140
142 config.add_route(
141 config.add_route(
143 name='repo_file_authors',
142 name='repo_file_authors',
144 pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True)
143 pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True)
145
144
146 config.add_route(
145 config.add_route(
147 name='repo_files_remove_file',
146 name='repo_files_remove_file',
148 pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}',
147 pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}',
149 repo_route=True)
148 repo_route=True)
150 config.add_route(
149 config.add_route(
151 name='repo_files_delete_file',
150 name='repo_files_delete_file',
152 pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}',
151 pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}',
153 repo_route=True)
152 repo_route=True)
154 config.add_route(
153 config.add_route(
155 name='repo_files_edit_file',
154 name='repo_files_edit_file',
156 pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}',
155 pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}',
157 repo_route=True)
156 repo_route=True)
158 config.add_route(
157 config.add_route(
159 name='repo_files_update_file',
158 name='repo_files_update_file',
160 pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}',
159 pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}',
161 repo_route=True)
160 repo_route=True)
162 config.add_route(
161 config.add_route(
163 name='repo_files_add_file',
162 name='repo_files_add_file',
164 pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}',
163 pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}',
165 repo_route=True)
164 repo_route=True)
166 config.add_route(
165 config.add_route(
167 name='repo_files_create_file',
166 name='repo_files_create_file',
168 pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}',
167 pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}',
169 repo_route=True)
168 repo_route=True)
170
169
171 # refs data
170 # Refs data
172 config.add_route(
171 config.add_route(
173 name='repo_refs_data',
172 name='repo_refs_data',
174 pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True)
173 pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True)
175
174
176 config.add_route(
175 config.add_route(
177 name='repo_refs_changelog_data',
176 name='repo_refs_changelog_data',
178 pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True)
177 pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True)
179
178
180 config.add_route(
179 config.add_route(
181 name='repo_stats',
180 name='repo_stats',
182 pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True)
181 pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True)
183
182
184 # Changelog
183 # Changelog
185 config.add_route(
184 config.add_route(
186 name='repo_changelog',
185 name='repo_changelog',
187 pattern='/{repo_name:.*?[^/]}/changelog', repo_route=True)
186 pattern='/{repo_name:.*?[^/]}/changelog', repo_route=True)
188 config.add_route(
187 config.add_route(
189 name='repo_changelog_file',
188 name='repo_changelog_file',
190 pattern='/{repo_name:.*?[^/]}/changelog/{commit_id}/{f_path:.*}', repo_route=True)
189 pattern='/{repo_name:.*?[^/]}/changelog/{commit_id}/{f_path:.*}', repo_route=True)
191 config.add_route(
190 config.add_route(
192 name='repo_changelog_elements',
191 name='repo_changelog_elements',
193 pattern='/{repo_name:.*?[^/]}/changelog_elements', repo_route=True)
192 pattern='/{repo_name:.*?[^/]}/changelog_elements', repo_route=True)
194
193
194 # Compare
195 config.add_route(
196 name='repo_compare_select',
197 pattern='/{repo_name:.*?[^/]}/compare', repo_route=True)
198
199 config.add_route(
200 name='repo_compare',
201 pattern='/{repo_name:.*?[^/]}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}', repo_route=True)
202
195 # Tags
203 # Tags
196 config.add_route(
204 config.add_route(
197 name='tags_home',
205 name='tags_home',
198 pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
206 pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
199
207
200 # Branches
208 # Branches
201 config.add_route(
209 config.add_route(
202 name='branches_home',
210 name='branches_home',
203 pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
211 pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
204
212
205 config.add_route(
213 config.add_route(
206 name='bookmarks_home',
214 name='bookmarks_home',
207 pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
215 pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
208
216
209 # Pull Requests
217 # Pull Requests
210 config.add_route(
218 config.add_route(
211 name='pullrequest_show',
219 name='pullrequest_show',
212 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id}',
220 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id}',
213 repo_route=True)
221 repo_route=True)
214
222
215 config.add_route(
223 config.add_route(
216 name='pullrequest_show_all',
224 name='pullrequest_show_all',
217 pattern='/{repo_name:.*?[^/]}/pull-request',
225 pattern='/{repo_name:.*?[^/]}/pull-request',
218 repo_route=True, repo_accepted_types=['hg', 'git'])
226 repo_route=True, repo_accepted_types=['hg', 'git'])
219
227
220 config.add_route(
228 config.add_route(
221 name='pullrequest_show_all_data',
229 name='pullrequest_show_all_data',
222 pattern='/{repo_name:.*?[^/]}/pull-request-data',
230 pattern='/{repo_name:.*?[^/]}/pull-request-data',
223 repo_route=True, repo_accepted_types=['hg', 'git'])
231 repo_route=True, repo_accepted_types=['hg', 'git'])
224
232
225 # Settings
233 # Settings
226 config.add_route(
234 config.add_route(
227 name='edit_repo',
235 name='edit_repo',
228 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
236 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
229
237
230 # Settings advanced
238 # Settings advanced
231 config.add_route(
239 config.add_route(
232 name='edit_repo_advanced',
240 name='edit_repo_advanced',
233 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
241 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
234 config.add_route(
242 config.add_route(
235 name='edit_repo_advanced_delete',
243 name='edit_repo_advanced_delete',
236 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
244 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
237 config.add_route(
245 config.add_route(
238 name='edit_repo_advanced_locking',
246 name='edit_repo_advanced_locking',
239 pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True)
247 pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True)
240 config.add_route(
248 config.add_route(
241 name='edit_repo_advanced_journal',
249 name='edit_repo_advanced_journal',
242 pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True)
250 pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True)
243 config.add_route(
251 config.add_route(
244 name='edit_repo_advanced_fork',
252 name='edit_repo_advanced_fork',
245 pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True)
253 pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True)
246
254
247 # Caches
255 # Caches
248 config.add_route(
256 config.add_route(
249 name='edit_repo_caches',
257 name='edit_repo_caches',
250 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
258 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
251
259
252 # Permissions
260 # Permissions
253 config.add_route(
261 config.add_route(
254 name='edit_repo_perms',
262 name='edit_repo_perms',
255 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
263 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
256
264
257 # Repo Review Rules
265 # Repo Review Rules
258 config.add_route(
266 config.add_route(
259 name='repo_reviewers',
267 name='repo_reviewers',
260 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
268 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
261
269
262 config.add_route(
270 config.add_route(
263 name='repo_default_reviewers_data',
271 name='repo_default_reviewers_data',
264 pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True)
272 pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True)
265
273
266 # Maintenance
274 # Maintenance
267 config.add_route(
275 config.add_route(
268 name='repo_maintenance',
276 name='repo_maintenance',
269 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
277 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
270
278
271 config.add_route(
279 config.add_route(
272 name='repo_maintenance_execute',
280 name='repo_maintenance_execute',
273 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
281 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
274
282
275 # Strip
283 # Strip
276 config.add_route(
284 config.add_route(
277 name='strip',
285 name='strip',
278 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
286 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
279
287
280 config.add_route(
288 config.add_route(
281 name='strip_check',
289 name='strip_check',
282 pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True)
290 pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True)
283
291
284 config.add_route(
292 config.add_route(
285 name='strip_execute',
293 name='strip_execute',
286 pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True)
294 pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True)
287
295
288 # ATOM/RSS Feed
296 # ATOM/RSS Feed
289 config.add_route(
297 config.add_route(
290 name='rss_feed_home',
298 name='rss_feed_home',
291 pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True)
299 pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True)
292
300
293 config.add_route(
301 config.add_route(
294 name='atom_feed_home',
302 name='atom_feed_home',
295 pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True)
303 pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True)
296
304
297 # NOTE(marcink): needs to be at the end for catch-all
305 # NOTE(marcink): needs to be at the end for catch-all
298 add_route_with_slash(
306 add_route_with_slash(
299 config,
307 config,
300 name='repo_summary',
308 name='repo_summary',
301 pattern='/{repo_name:.*?[^/]}', repo_route=True)
309 pattern='/{repo_name:.*?[^/]}', repo_route=True)
302
310
303 # Scan module for configuration decorators.
311 # Scan module for configuration decorators.
304 config.scan()
312 config.scan()
@@ -1,675 +1,697 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 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 lxml.html
23 import lxml.html
24
24
25 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
25 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
26 from rhodecode.tests import url, assert_session_flash
26 from rhodecode.tests import assert_session_flash
27 from rhodecode.tests.utils import AssertResponse, commit_change
27 from rhodecode.tests.utils import AssertResponse, commit_change
28
28
29
29
30 def route_path(name, params=None, **kwargs):
31 import urllib
32
33 base_url = {
34 'repo_compare_select': '/{repo_name}/compare',
35 'repo_compare': '/{repo_name}/compare/{source_ref_type}@{source_ref}...{target_ref_type}@{target_ref}',
36 }[name].format(**kwargs)
37
38 if params:
39 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
40 return base_url
41
42
30 @pytest.mark.usefixtures("autologin_user", "app")
43 @pytest.mark.usefixtures("autologin_user", "app")
31 class TestCompareController(object):
44 class TestCompareView(object):
45
46 def test_compare_index_is_reached_at_least_once(self, backend):
47 repo = backend.repo
48 self.app.get(
49 route_path('repo_compare_select', repo_name=repo.repo_name))
32
50
33 @pytest.mark.xfail_backends("svn", reason="Requires pull")
51 @pytest.mark.xfail_backends("svn", reason="Requires pull")
34 def test_compare_remote_with_different_commit_indexes(self, backend):
52 def test_compare_remote_with_different_commit_indexes(self, backend):
35 # Preparing the following repository structure:
53 # Preparing the following repository structure:
36 #
54 #
37 # Origin repository has two commits:
55 # Origin repository has two commits:
38 #
56 #
39 # 0 1
57 # 0 1
40 # A -- D
58 # A -- D
41 #
59 #
42 # The fork of it has a few more commits and "D" has a commit index
60 # The fork of it has a few more commits and "D" has a commit index
43 # which does not exist in origin.
61 # which does not exist in origin.
44 #
62 #
45 # 0 1 2 3 4
63 # 0 1 2 3 4
46 # A -- -- -- D -- E
64 # A -- -- -- D -- E
47 # \- B -- C
65 # \- B -- C
48 #
66 #
49
67
50 fork = backend.create_repo()
68 fork = backend.create_repo()
51
69
52 # prepare fork
70 # prepare fork
53 commit0 = commit_change(
71 commit0 = commit_change(
54 fork.repo_name, filename='file1', content='A',
72 fork.repo_name, filename='file1', content='A',
55 message='A', vcs_type=backend.alias, parent=None, newfile=True)
73 message='A', vcs_type=backend.alias, parent=None, newfile=True)
56
74
57 commit1 = commit_change(
75 commit1 = commit_change(
58 fork.repo_name, filename='file1', content='B',
76 fork.repo_name, filename='file1', content='B',
59 message='B, child of A', vcs_type=backend.alias, parent=commit0)
77 message='B, child of A', vcs_type=backend.alias, parent=commit0)
60
78
61 commit_change( # commit 2
79 commit_change( # commit 2
62 fork.repo_name, filename='file1', content='C',
80 fork.repo_name, filename='file1', content='C',
63 message='C, child of B', vcs_type=backend.alias, parent=commit1)
81 message='C, child of B', vcs_type=backend.alias, parent=commit1)
64
82
65 commit3 = commit_change(
83 commit3 = commit_change(
66 fork.repo_name, filename='file1', content='D',
84 fork.repo_name, filename='file1', content='D',
67 message='D, child of A', vcs_type=backend.alias, parent=commit0)
85 message='D, child of A', vcs_type=backend.alias, parent=commit0)
68
86
69 commit4 = commit_change(
87 commit4 = commit_change(
70 fork.repo_name, filename='file1', content='E',
88 fork.repo_name, filename='file1', content='E',
71 message='E, child of D', vcs_type=backend.alias, parent=commit3)
89 message='E, child of D', vcs_type=backend.alias, parent=commit3)
72
90
73 # prepare origin repository, taking just the history up to D
91 # prepare origin repository, taking just the history up to D
74 origin = backend.create_repo()
92 origin = backend.create_repo()
75
93
76 origin_repo = origin.scm_instance(cache=False)
94 origin_repo = origin.scm_instance(cache=False)
77 origin_repo.config.clear_section('hooks')
95 origin_repo.config.clear_section('hooks')
78 origin_repo.pull(fork.repo_full_path, commit_ids=[commit3.raw_id])
96 origin_repo.pull(fork.repo_full_path, commit_ids=[commit3.raw_id])
79
97
80 # Verify test fixture setup
98 # Verify test fixture setup
81 # This does not work for git
99 # This does not work for git
82 if backend.alias != 'git':
100 if backend.alias != 'git':
83 assert 5 == len(fork.scm_instance().commit_ids)
101 assert 5 == len(fork.scm_instance().commit_ids)
84 assert 2 == len(origin_repo.commit_ids)
102 assert 2 == len(origin_repo.commit_ids)
85
103
86 # Comparing the revisions
104 # Comparing the revisions
87 response = self.app.get(
105 response = self.app.get(
88 url('compare_url',
106 route_path('repo_compare',
89 repo_name=origin.repo_name,
107 repo_name=origin.repo_name,
90 source_ref_type="rev",
108 source_ref_type="rev",
91 source_ref=commit3.raw_id,
109 source_ref=commit3.raw_id,
92 target_repo=fork.repo_name,
93 target_ref_type="rev",
110 target_ref_type="rev",
94 target_ref=commit4.raw_id,
111 target_ref=commit4.raw_id,
95 merge='1',))
112 params=dict(merge='1', target_repo=fork.repo_name)
113 ))
96
114
97 compare_page = ComparePage(response)
115 compare_page = ComparePage(response)
98 compare_page.contains_commits([commit4])
116 compare_page.contains_commits([commit4])
99
117
100 @pytest.mark.xfail_backends("svn", reason="Depends on branch support")
118 @pytest.mark.xfail_backends("svn", reason="Depends on branch support")
101 def test_compare_forks_on_branch_extra_commits(self, backend):
119 def test_compare_forks_on_branch_extra_commits(self, backend):
102 repo1 = backend.create_repo()
120 repo1 = backend.create_repo()
103
121
104 # commit something !
122 # commit something !
105 commit0 = commit_change(
123 commit0 = commit_change(
106 repo1.repo_name, filename='file1', content='line1\n',
124 repo1.repo_name, filename='file1', content='line1\n',
107 message='commit1', vcs_type=backend.alias, parent=None,
125 message='commit1', vcs_type=backend.alias, parent=None,
108 newfile=True)
126 newfile=True)
109
127
110 # fork this repo
128 # fork this repo
111 repo2 = backend.create_fork()
129 repo2 = backend.create_fork()
112
130
113 # add two extra commit into fork
131 # add two extra commit into fork
114 commit1 = commit_change(
132 commit1 = commit_change(
115 repo2.repo_name, filename='file1', content='line1\nline2\n',
133 repo2.repo_name, filename='file1', content='line1\nline2\n',
116 message='commit2', vcs_type=backend.alias, parent=commit0)
134 message='commit2', vcs_type=backend.alias, parent=commit0)
117
135
118 commit2 = commit_change(
136 commit2 = commit_change(
119 repo2.repo_name, filename='file1', content='line1\nline2\nline3\n',
137 repo2.repo_name, filename='file1', content='line1\nline2\nline3\n',
120 message='commit3', vcs_type=backend.alias, parent=commit1)
138 message='commit3', vcs_type=backend.alias, parent=commit1)
121
139
122 commit_id1 = repo1.scm_instance().DEFAULT_BRANCH_NAME
140 commit_id1 = repo1.scm_instance().DEFAULT_BRANCH_NAME
123 commit_id2 = repo2.scm_instance().DEFAULT_BRANCH_NAME
141 commit_id2 = repo2.scm_instance().DEFAULT_BRANCH_NAME
124
142
125 response = self.app.get(
143 response = self.app.get(
126 url('compare_url',
144 route_path('repo_compare',
127 repo_name=repo1.repo_name,
145 repo_name=repo1.repo_name,
128 source_ref_type="branch",
146 source_ref_type="branch",
129 source_ref=commit_id2,
147 source_ref=commit_id2,
130 target_repo=repo2.repo_name,
131 target_ref_type="branch",
148 target_ref_type="branch",
132 target_ref=commit_id1,
149 target_ref=commit_id1,
133 merge='1',))
150 params=dict(merge='1', target_repo=repo2.repo_name)
151 ))
134
152
135 response.mustcontain('%s@%s' % (repo1.repo_name, commit_id2))
153 response.mustcontain('%s@%s' % (repo1.repo_name, commit_id2))
136 response.mustcontain('%s@%s' % (repo2.repo_name, commit_id1))
154 response.mustcontain('%s@%s' % (repo2.repo_name, commit_id1))
137
155
138 compare_page = ComparePage(response)
156 compare_page = ComparePage(response)
139 compare_page.contains_change_summary(1, 2, 0)
157 compare_page.contains_change_summary(1, 2, 0)
140 compare_page.contains_commits([commit1, commit2])
158 compare_page.contains_commits([commit1, commit2])
141 compare_page.contains_file_links_and_anchors([
159 compare_page.contains_file_links_and_anchors([
142 ('file1', 'a_c--826e8142e6ba'),
160 ('file1', 'a_c--826e8142e6ba'),
143 ])
161 ])
144
162
145 # Swap is removed when comparing branches since it's a PR feature and
163 # Swap is removed when comparing branches since it's a PR feature and
146 # it is then a preview mode
164 # it is then a preview mode
147 compare_page.swap_is_hidden()
165 compare_page.swap_is_hidden()
148 compare_page.target_source_are_disabled()
166 compare_page.target_source_are_disabled()
149
167
150 @pytest.mark.xfail_backends("svn", reason="Depends on branch support")
168 @pytest.mark.xfail_backends("svn", reason="Depends on branch support")
151 def test_compare_forks_on_branch_extra_commits_origin_has_incomming(
169 def test_compare_forks_on_branch_extra_commits_origin_has_incomming(
152 self, backend):
170 self, backend):
153 repo1 = backend.create_repo()
171 repo1 = backend.create_repo()
154
172
155 # commit something !
173 # commit something !
156 commit0 = commit_change(
174 commit0 = commit_change(
157 repo1.repo_name, filename='file1', content='line1\n',
175 repo1.repo_name, filename='file1', content='line1\n',
158 message='commit1', vcs_type=backend.alias, parent=None,
176 message='commit1', vcs_type=backend.alias, parent=None,
159 newfile=True)
177 newfile=True)
160
178
161 # fork this repo
179 # fork this repo
162 repo2 = backend.create_fork()
180 repo2 = backend.create_fork()
163
181
164 # now commit something to origin repo
182 # now commit something to origin repo
165 commit_change(
183 commit_change(
166 repo1.repo_name, filename='file2', content='line1file2\n',
184 repo1.repo_name, filename='file2', content='line1file2\n',
167 message='commit2', vcs_type=backend.alias, parent=commit0,
185 message='commit2', vcs_type=backend.alias, parent=commit0,
168 newfile=True)
186 newfile=True)
169
187
170 # add two extra commit into fork
188 # add two extra commit into fork
171 commit1 = commit_change(
189 commit1 = commit_change(
172 repo2.repo_name, filename='file1', content='line1\nline2\n',
190 repo2.repo_name, filename='file1', content='line1\nline2\n',
173 message='commit2', vcs_type=backend.alias, parent=commit0)
191 message='commit2', vcs_type=backend.alias, parent=commit0)
174
192
175 commit2 = commit_change(
193 commit2 = commit_change(
176 repo2.repo_name, filename='file1', content='line1\nline2\nline3\n',
194 repo2.repo_name, filename='file1', content='line1\nline2\nline3\n',
177 message='commit3', vcs_type=backend.alias, parent=commit1)
195 message='commit3', vcs_type=backend.alias, parent=commit1)
178
196
179 commit_id1 = repo1.scm_instance().DEFAULT_BRANCH_NAME
197 commit_id1 = repo1.scm_instance().DEFAULT_BRANCH_NAME
180 commit_id2 = repo2.scm_instance().DEFAULT_BRANCH_NAME
198 commit_id2 = repo2.scm_instance().DEFAULT_BRANCH_NAME
181
199
182 response = self.app.get(
200 response = self.app.get(
183 url('compare_url',
201 route_path('repo_compare',
184 repo_name=repo1.repo_name,
202 repo_name=repo1.repo_name,
185 source_ref_type="branch",
203 source_ref_type="branch",
186 source_ref=commit_id2,
204 source_ref=commit_id2,
187 target_repo=repo2.repo_name,
188 target_ref_type="branch",
205 target_ref_type="branch",
189 target_ref=commit_id1,
206 target_ref=commit_id1,
190 merge='1'))
207 params=dict(merge='1', target_repo=repo2.repo_name),
208 ))
191
209
192 response.mustcontain('%s@%s' % (repo1.repo_name, commit_id2))
210 response.mustcontain('%s@%s' % (repo1.repo_name, commit_id2))
193 response.mustcontain('%s@%s' % (repo2.repo_name, commit_id1))
211 response.mustcontain('%s@%s' % (repo2.repo_name, commit_id1))
194
212
195 compare_page = ComparePage(response)
213 compare_page = ComparePage(response)
196 compare_page.contains_change_summary(1, 2, 0)
214 compare_page.contains_change_summary(1, 2, 0)
197 compare_page.contains_commits([commit1, commit2])
215 compare_page.contains_commits([commit1, commit2])
198 compare_page.contains_file_links_and_anchors([
216 compare_page.contains_file_links_and_anchors([
199 ('file1', 'a_c--826e8142e6ba'),
217 ('file1', 'a_c--826e8142e6ba'),
200 ])
218 ])
201
219
202 # Swap is removed when comparing branches since it's a PR feature and
220 # Swap is removed when comparing branches since it's a PR feature and
203 # it is then a preview mode
221 # it is then a preview mode
204 compare_page.swap_is_hidden()
222 compare_page.swap_is_hidden()
205 compare_page.target_source_are_disabled()
223 compare_page.target_source_are_disabled()
206
224
207 @pytest.mark.xfail_backends("svn")
225 @pytest.mark.xfail_backends("svn")
208 # TODO(marcink): no svn support for compare two seperate repos
226 # TODO(marcink): no svn support for compare two seperate repos
209 def test_compare_of_unrelated_forks(self, backend):
227 def test_compare_of_unrelated_forks(self, backend):
210 orig = backend.create_repo(number_of_commits=1)
228 orig = backend.create_repo(number_of_commits=1)
211 fork = backend.create_repo(number_of_commits=1)
229 fork = backend.create_repo(number_of_commits=1)
212
230
213 response = self.app.get(
231 response = self.app.get(
214 url('compare_url',
232 route_path('repo_compare',
215 repo_name=orig.repo_name,
233 repo_name=orig.repo_name,
216 action="compare",
217 source_ref_type="rev",
234 source_ref_type="rev",
218 source_ref="tip",
235 source_ref="tip",
219 target_ref_type="rev",
236 target_ref_type="rev",
220 target_ref="tip",
237 target_ref="tip",
221 merge='1',
238 params=dict(merge='1', target_repo=fork.repo_name),
222 target_repo=fork.repo_name),
239 ),
223 status=400)
240 status=302)
224
241 response = response.follow()
225 response.mustcontain("Repositories unrelated.")
242 response.mustcontain("Repositories unrelated.")
226
243
227 @pytest.mark.xfail_backends("svn")
244 @pytest.mark.xfail_backends("svn")
228 def test_compare_cherry_pick_commits_from_bottom(self, backend):
245 def test_compare_cherry_pick_commits_from_bottom(self, backend):
229
246
230 # repo1:
247 # repo1:
231 # commit0:
248 # commit0:
232 # commit1:
249 # commit1:
233 # repo1-fork- in which we will cherry pick bottom commits
250 # repo1-fork- in which we will cherry pick bottom commits
234 # commit0:
251 # commit0:
235 # commit1:
252 # commit1:
236 # commit2: x
253 # commit2: x
237 # commit3: x
254 # commit3: x
238 # commit4: x
255 # commit4: x
239 # commit5:
256 # commit5:
240 # make repo1, and commit1+commit2
257 # make repo1, and commit1+commit2
241
258
242 repo1 = backend.create_repo()
259 repo1 = backend.create_repo()
243
260
244 # commit something !
261 # commit something !
245 commit0 = commit_change(
262 commit0 = commit_change(
246 repo1.repo_name, filename='file1', content='line1\n',
263 repo1.repo_name, filename='file1', content='line1\n',
247 message='commit1', vcs_type=backend.alias, parent=None,
264 message='commit1', vcs_type=backend.alias, parent=None,
248 newfile=True)
265 newfile=True)
249 commit1 = commit_change(
266 commit1 = commit_change(
250 repo1.repo_name, filename='file1', content='line1\nline2\n',
267 repo1.repo_name, filename='file1', content='line1\nline2\n',
251 message='commit2', vcs_type=backend.alias, parent=commit0)
268 message='commit2', vcs_type=backend.alias, parent=commit0)
252
269
253 # fork this repo
270 # fork this repo
254 repo2 = backend.create_fork()
271 repo2 = backend.create_fork()
255
272
256 # now make commit3-6
273 # now make commit3-6
257 commit2 = commit_change(
274 commit2 = commit_change(
258 repo1.repo_name, filename='file1', content='line1\nline2\nline3\n',
275 repo1.repo_name, filename='file1', content='line1\nline2\nline3\n',
259 message='commit3', vcs_type=backend.alias, parent=commit1)
276 message='commit3', vcs_type=backend.alias, parent=commit1)
260 commit3 = commit_change(
277 commit3 = commit_change(
261 repo1.repo_name, filename='file1',
278 repo1.repo_name, filename='file1',
262 content='line1\nline2\nline3\nline4\n', message='commit4',
279 content='line1\nline2\nline3\nline4\n', message='commit4',
263 vcs_type=backend.alias, parent=commit2)
280 vcs_type=backend.alias, parent=commit2)
264 commit4 = commit_change(
281 commit4 = commit_change(
265 repo1.repo_name, filename='file1',
282 repo1.repo_name, filename='file1',
266 content='line1\nline2\nline3\nline4\nline5\n', message='commit5',
283 content='line1\nline2\nline3\nline4\nline5\n', message='commit5',
267 vcs_type=backend.alias, parent=commit3)
284 vcs_type=backend.alias, parent=commit3)
268 commit_change( # commit 5
285 commit_change( # commit 5
269 repo1.repo_name, filename='file1',
286 repo1.repo_name, filename='file1',
270 content='line1\nline2\nline3\nline4\nline5\nline6\n',
287 content='line1\nline2\nline3\nline4\nline5\nline6\n',
271 message='commit6', vcs_type=backend.alias, parent=commit4)
288 message='commit6', vcs_type=backend.alias, parent=commit4)
272
289
273 response = self.app.get(
290 response = self.app.get(
274 url('compare_url',
291 route_path('repo_compare',
275 repo_name=repo2.repo_name,
292 repo_name=repo2.repo_name,
276 source_ref_type="rev",
293 source_ref_type="rev",
277 # parent of commit2, in target repo2
294 # parent of commit2, in target repo2
278 source_ref=commit1.raw_id,
295 source_ref=commit1.raw_id,
279 target_repo=repo1.repo_name,
280 target_ref_type="rev",
296 target_ref_type="rev",
281 target_ref=commit4.raw_id,
297 target_ref=commit4.raw_id,
282 merge='1',))
298 params=dict(merge='1', target_repo=repo1.repo_name),
299 ))
283 response.mustcontain('%s@%s' % (repo2.repo_name, commit1.short_id))
300 response.mustcontain('%s@%s' % (repo2.repo_name, commit1.short_id))
284 response.mustcontain('%s@%s' % (repo1.repo_name, commit4.short_id))
301 response.mustcontain('%s@%s' % (repo1.repo_name, commit4.short_id))
285
302
286 # files
303 # files
287 compare_page = ComparePage(response)
304 compare_page = ComparePage(response)
288 compare_page.contains_change_summary(1, 3, 0)
305 compare_page.contains_change_summary(1, 3, 0)
289 compare_page.contains_commits([commit2, commit3, commit4])
306 compare_page.contains_commits([commit2, commit3, commit4])
290 compare_page.contains_file_links_and_anchors([
307 compare_page.contains_file_links_and_anchors([
291 ('file1', 'a_c--826e8142e6ba'),
308 ('file1', 'a_c--826e8142e6ba'),
292 ])
309 ])
293
310
294 @pytest.mark.xfail_backends("svn")
311 @pytest.mark.xfail_backends("svn")
295 def test_compare_cherry_pick_commits_from_top(self, backend):
312 def test_compare_cherry_pick_commits_from_top(self, backend):
296 # repo1:
313 # repo1:
297 # commit0:
314 # commit0:
298 # commit1:
315 # commit1:
299 # repo1-fork- in which we will cherry pick bottom commits
316 # repo1-fork- in which we will cherry pick bottom commits
300 # commit0:
317 # commit0:
301 # commit1:
318 # commit1:
302 # commit2:
319 # commit2:
303 # commit3: x
320 # commit3: x
304 # commit4: x
321 # commit4: x
305 # commit5: x
322 # commit5: x
306
323
307 # make repo1, and commit1+commit2
324 # make repo1, and commit1+commit2
308 repo1 = backend.create_repo()
325 repo1 = backend.create_repo()
309
326
310 # commit something !
327 # commit something !
311 commit0 = commit_change(
328 commit0 = commit_change(
312 repo1.repo_name, filename='file1', content='line1\n',
329 repo1.repo_name, filename='file1', content='line1\n',
313 message='commit1', vcs_type=backend.alias, parent=None,
330 message='commit1', vcs_type=backend.alias, parent=None,
314 newfile=True)
331 newfile=True)
315 commit1 = commit_change(
332 commit1 = commit_change(
316 repo1.repo_name, filename='file1', content='line1\nline2\n',
333 repo1.repo_name, filename='file1', content='line1\nline2\n',
317 message='commit2', vcs_type=backend.alias, parent=commit0)
334 message='commit2', vcs_type=backend.alias, parent=commit0)
318
335
319 # fork this repo
336 # fork this repo
320 backend.create_fork()
337 backend.create_fork()
321
338
322 # now make commit3-6
339 # now make commit3-6
323 commit2 = commit_change(
340 commit2 = commit_change(
324 repo1.repo_name, filename='file1', content='line1\nline2\nline3\n',
341 repo1.repo_name, filename='file1', content='line1\nline2\nline3\n',
325 message='commit3', vcs_type=backend.alias, parent=commit1)
342 message='commit3', vcs_type=backend.alias, parent=commit1)
326 commit3 = commit_change(
343 commit3 = commit_change(
327 repo1.repo_name, filename='file1',
344 repo1.repo_name, filename='file1',
328 content='line1\nline2\nline3\nline4\n', message='commit4',
345 content='line1\nline2\nline3\nline4\n', message='commit4',
329 vcs_type=backend.alias, parent=commit2)
346 vcs_type=backend.alias, parent=commit2)
330 commit4 = commit_change(
347 commit4 = commit_change(
331 repo1.repo_name, filename='file1',
348 repo1.repo_name, filename='file1',
332 content='line1\nline2\nline3\nline4\nline5\n', message='commit5',
349 content='line1\nline2\nline3\nline4\nline5\n', message='commit5',
333 vcs_type=backend.alias, parent=commit3)
350 vcs_type=backend.alias, parent=commit3)
334 commit5 = commit_change(
351 commit5 = commit_change(
335 repo1.repo_name, filename='file1',
352 repo1.repo_name, filename='file1',
336 content='line1\nline2\nline3\nline4\nline5\nline6\n',
353 content='line1\nline2\nline3\nline4\nline5\nline6\n',
337 message='commit6', vcs_type=backend.alias, parent=commit4)
354 message='commit6', vcs_type=backend.alias, parent=commit4)
338
355
339 response = self.app.get(
356 response = self.app.get(
340 url('compare_url',
357 route_path('repo_compare',
341 repo_name=repo1.repo_name,
358 repo_name=repo1.repo_name,
342 source_ref_type="rev",
359 source_ref_type="rev",
343 # parent of commit3, not in source repo2
360 # parent of commit3, not in source repo2
344 source_ref=commit2.raw_id,
361 source_ref=commit2.raw_id,
345 target_ref_type="rev",
362 target_ref_type="rev",
346 target_ref=commit5.raw_id,
363 target_ref=commit5.raw_id,
347 merge='1',))
364 params=dict(merge='1'),
365 ))
348
366
349 response.mustcontain('%s@%s' % (repo1.repo_name, commit2.short_id))
367 response.mustcontain('%s@%s' % (repo1.repo_name, commit2.short_id))
350 response.mustcontain('%s@%s' % (repo1.repo_name, commit5.short_id))
368 response.mustcontain('%s@%s' % (repo1.repo_name, commit5.short_id))
351
369
352 compare_page = ComparePage(response)
370 compare_page = ComparePage(response)
353 compare_page.contains_change_summary(1, 3, 0)
371 compare_page.contains_change_summary(1, 3, 0)
354 compare_page.contains_commits([commit3, commit4, commit5])
372 compare_page.contains_commits([commit3, commit4, commit5])
355
373
356 # files
374 # files
357 compare_page.contains_file_links_and_anchors([
375 compare_page.contains_file_links_and_anchors([
358 ('file1', 'a_c--826e8142e6ba'),
376 ('file1', 'a_c--826e8142e6ba'),
359 ])
377 ])
360
378
361 @pytest.mark.xfail_backends("svn")
379 @pytest.mark.xfail_backends("svn")
362 def test_compare_remote_branches(self, backend):
380 def test_compare_remote_branches(self, backend):
363 repo1 = backend.repo
381 repo1 = backend.repo
364 repo2 = backend.create_fork()
382 repo2 = backend.create_fork()
365
383
366 commit_id1 = repo1.get_commit(commit_idx=3).raw_id
384 commit_id1 = repo1.get_commit(commit_idx=3).raw_id
367 commit_id2 = repo1.get_commit(commit_idx=6).raw_id
385 commit_id2 = repo1.get_commit(commit_idx=6).raw_id
368
386
369 response = self.app.get(
387 response = self.app.get(
370 url('compare_url',
388 route_path('repo_compare',
371 repo_name=repo1.repo_name,
389 repo_name=repo1.repo_name,
372 source_ref_type="rev",
390 source_ref_type="rev",
373 source_ref=commit_id1,
391 source_ref=commit_id1,
374 target_ref_type="rev",
392 target_ref_type="rev",
375 target_ref=commit_id2,
393 target_ref=commit_id2,
376 target_repo=repo2.repo_name,
394 params=dict(merge='1', target_repo=repo2.repo_name),
377 merge='1',))
395 ))
378
396
379 response.mustcontain('%s@%s' % (repo1.repo_name, commit_id1))
397 response.mustcontain('%s@%s' % (repo1.repo_name, commit_id1))
380 response.mustcontain('%s@%s' % (repo2.repo_name, commit_id2))
398 response.mustcontain('%s@%s' % (repo2.repo_name, commit_id2))
381
399
382 compare_page = ComparePage(response)
400 compare_page = ComparePage(response)
383
401
384 # outgoing commits between those commits
402 # outgoing commits between those commits
385 compare_page.contains_commits(
403 compare_page.contains_commits(
386 [repo2.get_commit(commit_idx=x) for x in [4, 5, 6]])
404 [repo2.get_commit(commit_idx=x) for x in [4, 5, 6]])
387
405
388 # files
406 # files
389 compare_page.contains_file_links_and_anchors([
407 compare_page.contains_file_links_and_anchors([
390 ('vcs/backends/hg.py', 'a_c--9c390eb52cd6'),
408 ('vcs/backends/hg.py', 'a_c--9c390eb52cd6'),
391 ('vcs/backends/__init__.py', 'a_c--41b41c1f2796'),
409 ('vcs/backends/__init__.py', 'a_c--41b41c1f2796'),
392 ('vcs/backends/base.py', 'a_c--2f574d260608'),
410 ('vcs/backends/base.py', 'a_c--2f574d260608'),
393 ])
411 ])
394
412
395 @pytest.mark.xfail_backends("svn")
413 @pytest.mark.xfail_backends("svn")
396 def test_source_repo_new_commits_after_forking_simple_diff(self, backend):
414 def test_source_repo_new_commits_after_forking_simple_diff(self, backend):
397 repo1 = backend.create_repo()
415 repo1 = backend.create_repo()
398 r1_name = repo1.repo_name
416 r1_name = repo1.repo_name
399
417
400 commit0 = commit_change(
418 commit0 = commit_change(
401 repo=r1_name, filename='file1',
419 repo=r1_name, filename='file1',
402 content='line1', message='commit1', vcs_type=backend.alias,
420 content='line1', message='commit1', vcs_type=backend.alias,
403 newfile=True)
421 newfile=True)
404 assert repo1.scm_instance().commit_ids == [commit0.raw_id]
422 assert repo1.scm_instance().commit_ids == [commit0.raw_id]
405
423
406 # fork the repo1
424 # fork the repo1
407 repo2 = backend.create_fork()
425 repo2 = backend.create_fork()
408 assert repo2.scm_instance().commit_ids == [commit0.raw_id]
426 assert repo2.scm_instance().commit_ids == [commit0.raw_id]
409
427
410 self.r2_id = repo2.repo_id
428 self.r2_id = repo2.repo_id
411 r2_name = repo2.repo_name
429 r2_name = repo2.repo_name
412
430
413 commit1 = commit_change(
431 commit1 = commit_change(
414 repo=r2_name, filename='file1-fork',
432 repo=r2_name, filename='file1-fork',
415 content='file1-line1-from-fork', message='commit1-fork',
433 content='file1-line1-from-fork', message='commit1-fork',
416 vcs_type=backend.alias, parent=repo2.scm_instance()[-1],
434 vcs_type=backend.alias, parent=repo2.scm_instance()[-1],
417 newfile=True)
435 newfile=True)
418
436
419 commit2 = commit_change(
437 commit2 = commit_change(
420 repo=r2_name, filename='file2-fork',
438 repo=r2_name, filename='file2-fork',
421 content='file2-line1-from-fork', message='commit2-fork',
439 content='file2-line1-from-fork', message='commit2-fork',
422 vcs_type=backend.alias, parent=commit1,
440 vcs_type=backend.alias, parent=commit1,
423 newfile=True)
441 newfile=True)
424
442
425 commit_change( # commit 3
443 commit_change( # commit 3
426 repo=r2_name, filename='file3-fork',
444 repo=r2_name, filename='file3-fork',
427 content='file3-line1-from-fork', message='commit3-fork',
445 content='file3-line1-from-fork', message='commit3-fork',
428 vcs_type=backend.alias, parent=commit2, newfile=True)
446 vcs_type=backend.alias, parent=commit2, newfile=True)
429
447
430 # compare !
448 # compare !
431 commit_id1 = repo1.scm_instance().DEFAULT_BRANCH_NAME
449 commit_id1 = repo1.scm_instance().DEFAULT_BRANCH_NAME
432 commit_id2 = repo2.scm_instance().DEFAULT_BRANCH_NAME
450 commit_id2 = repo2.scm_instance().DEFAULT_BRANCH_NAME
433
451
434 response = self.app.get(
452 response = self.app.get(
435 url('compare_url',
453 route_path('repo_compare',
436 repo_name=r2_name,
454 repo_name=r2_name,
437 source_ref_type="branch",
455 source_ref_type="branch",
438 source_ref=commit_id1,
456 source_ref=commit_id1,
439 target_ref_type="branch",
457 target_ref_type="branch",
440 target_ref=commit_id2,
458 target_ref=commit_id2,
441 target_repo=r1_name,
459 params=dict(merge='1', target_repo=r1_name),
442 merge='1',))
460 ))
443
461
444 response.mustcontain('%s@%s' % (r2_name, commit_id1))
462 response.mustcontain('%s@%s' % (r2_name, commit_id1))
445 response.mustcontain('%s@%s' % (r1_name, commit_id2))
463 response.mustcontain('%s@%s' % (r1_name, commit_id2))
446 response.mustcontain('No files')
464 response.mustcontain('No files')
447 response.mustcontain('No commits in this compare')
465 response.mustcontain('No commits in this compare')
448
466
449 commit0 = commit_change(
467 commit0 = commit_change(
450 repo=r1_name, filename='file2',
468 repo=r1_name, filename='file2',
451 content='line1-added-after-fork', message='commit2-parent',
469 content='line1-added-after-fork', message='commit2-parent',
452 vcs_type=backend.alias, parent=None, newfile=True)
470 vcs_type=backend.alias, parent=None, newfile=True)
453
471
454 # compare !
472 # compare !
455 response = self.app.get(
473 response = self.app.get(
456 url('compare_url',
474 route_path('repo_compare',
457 repo_name=r2_name,
475 repo_name=r2_name,
458 source_ref_type="branch",
476 source_ref_type="branch",
459 source_ref=commit_id1,
477 source_ref=commit_id1,
460 target_ref_type="branch",
478 target_ref_type="branch",
461 target_ref=commit_id2,
479 target_ref=commit_id2,
462 target_repo=r1_name,
480 params=dict(merge='1', target_repo=r1_name),
463 merge='1',))
481 ))
464
482
465 response.mustcontain('%s@%s' % (r2_name, commit_id1))
483 response.mustcontain('%s@%s' % (r2_name, commit_id1))
466 response.mustcontain('%s@%s' % (r1_name, commit_id2))
484 response.mustcontain('%s@%s' % (r1_name, commit_id2))
467
485
468 response.mustcontain("""commit2-parent""")
486 response.mustcontain("""commit2-parent""")
469 response.mustcontain("""line1-added-after-fork""")
487 response.mustcontain("""line1-added-after-fork""")
470 compare_page = ComparePage(response)
488 compare_page = ComparePage(response)
471 compare_page.contains_change_summary(1, 1, 0)
489 compare_page.contains_change_summary(1, 1, 0)
472
490
473 @pytest.mark.xfail_backends("svn")
491 @pytest.mark.xfail_backends("svn")
474 def test_compare_commits(self, backend, xhr_header):
492 def test_compare_commits(self, backend, xhr_header):
475 commit0 = backend.repo.get_commit(commit_idx=0)
493 commit0 = backend.repo.get_commit(commit_idx=0)
476 commit1 = backend.repo.get_commit(commit_idx=1)
494 commit1 = backend.repo.get_commit(commit_idx=1)
477
495
478 response = self.app.get(
496 response = self.app.get(
479 url('compare_url',
497 route_path('repo_compare',
480 repo_name=backend.repo_name,
498 repo_name=backend.repo_name,
481 source_ref_type="rev",
499 source_ref_type="rev",
482 source_ref=commit0.raw_id,
500 source_ref=commit0.raw_id,
483 target_ref_type="rev",
501 target_ref_type="rev",
484 target_ref=commit1.raw_id,
502 target_ref=commit1.raw_id,
485 merge='1',),
503 params=dict(merge='1')
504 ),
486 extra_environ=xhr_header,)
505 extra_environ=xhr_header,)
487
506
488 # outgoing commits between those commits
507 # outgoing commits between those commits
489 compare_page = ComparePage(response)
508 compare_page = ComparePage(response)
490 compare_page.contains_commits(commits=[commit1], ancestors=[commit0])
509 compare_page.contains_commits(commits=[commit1], ancestors=[commit0])
491
510
492 def test_errors_when_comparing_unknown_source_repo(self, backend):
511 def test_errors_when_comparing_unknown_source_repo(self, backend):
493 repo = backend.repo
512 repo = backend.repo
494 badrepo = 'badrepo'
513 badrepo = 'badrepo'
495
514
496 response = self.app.get(
515 response = self.app.get(
497 url('compare_url',
516 route_path('repo_compare',
498 repo_name=badrepo,
517 repo_name=badrepo,
499 source_ref_type="rev",
518 source_ref_type="rev",
500 source_ref='tip',
519 source_ref='tip',
501 target_ref_type="rev",
520 target_ref_type="rev",
502 target_ref='tip',
521 target_ref='tip',
503 target_repo=repo.repo_name,
522 params=dict(merge='1', target_repo=repo.repo_name)
504 merge='1',),
523 ),
505 status=404)
524 status=404)
506
525
507 def test_errors_when_comparing_unknown_target_repo(self, backend):
526 def test_errors_when_comparing_unknown_target_repo(self, backend):
508 repo = backend.repo
527 repo = backend.repo
509 badrepo = 'badrepo'
528 badrepo = 'badrepo'
510
529
511 response = self.app.get(
530 response = self.app.get(
512 url('compare_url',
531 route_path('repo_compare',
513 repo_name=repo.repo_name,
532 repo_name=repo.repo_name,
514 source_ref_type="rev",
533 source_ref_type="rev",
515 source_ref='tip',
534 source_ref='tip',
516 target_ref_type="rev",
535 target_ref_type="rev",
517 target_ref='tip',
536 target_ref='tip',
518 target_repo=badrepo,
537 params=dict(merge='1', target_repo=badrepo),
519 merge='1',),
538 ),
520 status=302)
539 status=302)
521 redirected = response.follow()
540 redirected = response.follow()
522 redirected.mustcontain(
541 redirected.mustcontain(
523 'Could not find the target repo: `{}`'.format(badrepo))
542 'Could not find the target repo: `{}`'.format(badrepo))
524
543
525 def test_compare_not_in_preview_mode(self, backend_stub):
544 def test_compare_not_in_preview_mode(self, backend_stub):
526 commit0 = backend_stub.repo.get_commit(commit_idx=0)
545 commit0 = backend_stub.repo.get_commit(commit_idx=0)
527 commit1 = backend_stub.repo.get_commit(commit_idx=1)
546 commit1 = backend_stub.repo.get_commit(commit_idx=1)
528
547
529 response = self.app.get(url('compare_url',
548 response = self.app.get(
549 route_path('repo_compare',
530 repo_name=backend_stub.repo_name,
550 repo_name=backend_stub.repo_name,
531 source_ref_type="rev",
551 source_ref_type="rev",
532 source_ref=commit0.raw_id,
552 source_ref=commit0.raw_id,
533 target_ref_type="rev",
553 target_ref_type="rev",
534 target_ref=commit1.raw_id,
554 target_ref=commit1.raw_id,
535 ),)
555 ))
536
556
537 # outgoing commits between those commits
557 # outgoing commits between those commits
538 compare_page = ComparePage(response)
558 compare_page = ComparePage(response)
539 compare_page.swap_is_visible()
559 compare_page.swap_is_visible()
540 compare_page.target_source_are_enabled()
560 compare_page.target_source_are_enabled()
541
561
542 def test_compare_of_fork_with_largefiles(self, backend_hg, settings_util):
562 def test_compare_of_fork_with_largefiles(self, backend_hg, settings_util):
543 orig = backend_hg.create_repo(number_of_commits=1)
563 orig = backend_hg.create_repo(number_of_commits=1)
544 fork = backend_hg.create_fork()
564 fork = backend_hg.create_fork()
545
565
546 settings_util.create_repo_rhodecode_ui(
566 settings_util.create_repo_rhodecode_ui(
547 orig, 'extensions', value='', key='largefiles', active=False)
567 orig, 'extensions', value='', key='largefiles', active=False)
548 settings_util.create_repo_rhodecode_ui(
568 settings_util.create_repo_rhodecode_ui(
549 fork, 'extensions', value='', key='largefiles', active=True)
569 fork, 'extensions', value='', key='largefiles', active=True)
550
570
551 compare_module = ('rhodecode.lib.vcs.backends.hg.repository.'
571 compare_module = ('rhodecode.lib.vcs.backends.hg.repository.'
552 'MercurialRepository.compare')
572 'MercurialRepository.compare')
553 with mock.patch(compare_module) as compare_mock:
573 with mock.patch(compare_module) as compare_mock:
554 compare_mock.side_effect = RepositoryRequirementError()
574 compare_mock.side_effect = RepositoryRequirementError()
555
575
556 response = self.app.get(
576 response = self.app.get(
557 url('compare_url',
577 route_path('repo_compare',
558 repo_name=orig.repo_name,
578 repo_name=orig.repo_name,
559 action="compare",
560 source_ref_type="rev",
579 source_ref_type="rev",
561 source_ref="tip",
580 source_ref="tip",
562 target_ref_type="rev",
581 target_ref_type="rev",
563 target_ref="tip",
582 target_ref="tip",
564 merge='1',
583 params=dict(merge='1', target_repo=fork.repo_name),
565 target_repo=fork.repo_name),
584 ),
566 status=302)
585 status=302)
567
586
568 assert_session_flash(
587 assert_session_flash(
569 response,
588 response,
570 'Could not compare repos with different large file settings')
589 'Could not compare repos with different large file settings')
571
590
572
591
573 @pytest.mark.usefixtures("autologin_user")
592 @pytest.mark.usefixtures("autologin_user")
574 class TestCompareControllerSvn(object):
593 class TestCompareControllerSvn(object):
575
594
576 def test_supports_references_with_path(self, app, backend_svn):
595 def test_supports_references_with_path(self, app, backend_svn):
577 repo = backend_svn['svn-simple-layout']
596 repo = backend_svn['svn-simple-layout']
578 commit_id = repo.get_commit(commit_idx=-1).raw_id
597 commit_id = repo.get_commit(commit_idx=-1).raw_id
579 response = app.get(
598 response = app.get(
580 url('compare_url',
599 route_path('repo_compare',
581 repo_name=repo.repo_name,
600 repo_name=repo.repo_name,
582 source_ref_type="tag",
601 source_ref_type="tag",
583 source_ref="%s@%s" % ('tags/v0.1', commit_id),
602 source_ref="%s@%s" % ('tags/v0.1', commit_id),
584 target_ref_type="tag",
603 target_ref_type="tag",
585 target_ref="%s@%s" % ('tags/v0.2', commit_id),
604 target_ref="%s@%s" % ('tags/v0.2', commit_id),
586 merge='1',),
605 params=dict(merge='1'),
606 ),
587 status=200)
607 status=200)
588
608
589 # Expecting no commits, since both paths are at the same revision
609 # Expecting no commits, since both paths are at the same revision
590 response.mustcontain('No commits in this compare')
610 response.mustcontain('No commits in this compare')
591
611
592 # Should find only one file changed when comparing those two tags
612 # Should find only one file changed when comparing those two tags
593 response.mustcontain('example.py')
613 response.mustcontain('example.py')
594 compare_page = ComparePage(response)
614 compare_page = ComparePage(response)
595 compare_page.contains_change_summary(1, 5, 1)
615 compare_page.contains_change_summary(1, 5, 1)
596
616
597 def test_shows_commits_if_different_ids(self, app, backend_svn):
617 def test_shows_commits_if_different_ids(self, app, backend_svn):
598 repo = backend_svn['svn-simple-layout']
618 repo = backend_svn['svn-simple-layout']
599 source_id = repo.get_commit(commit_idx=-6).raw_id
619 source_id = repo.get_commit(commit_idx=-6).raw_id
600 target_id = repo.get_commit(commit_idx=-1).raw_id
620 target_id = repo.get_commit(commit_idx=-1).raw_id
601 response = app.get(
621 response = app.get(
602 url('compare_url',
622 route_path('repo_compare',
603 repo_name=repo.repo_name,
623 repo_name=repo.repo_name,
604 source_ref_type="tag",
624 source_ref_type="tag",
605 source_ref="%s@%s" % ('tags/v0.1', source_id),
625 source_ref="%s@%s" % ('tags/v0.1', source_id),
606 target_ref_type="tag",
626 target_ref_type="tag",
607 target_ref="%s@%s" % ('tags/v0.2', target_id),
627 target_ref="%s@%s" % ('tags/v0.2', target_id),
608 merge='1',),
628 params=dict(merge='1')
629 ),
609 status=200)
630 status=200)
610
631
611 # It should show commits
632 # It should show commits
612 assert 'No commits in this compare' not in response.body
633 assert 'No commits in this compare' not in response.body
613
634
614 # Should find only one file changed when comparing those two tags
635 # Should find only one file changed when comparing those two tags
615 response.mustcontain('example.py')
636 response.mustcontain('example.py')
616 compare_page = ComparePage(response)
637 compare_page = ComparePage(response)
617 compare_page.contains_change_summary(1, 5, 1)
638 compare_page.contains_change_summary(1, 5, 1)
618
639
619
640
620 class ComparePage(AssertResponse):
641 class ComparePage(AssertResponse):
621 """
642 """
622 Abstracts the page template from the tests
643 Abstracts the page template from the tests
623 """
644 """
624
645
625 def contains_file_links_and_anchors(self, files):
646 def contains_file_links_and_anchors(self, files):
626 doc = lxml.html.fromstring(self.response.body)
647 doc = lxml.html.fromstring(self.response.body)
627 for filename, file_id in files:
648 for filename, file_id in files:
628 self.contains_one_anchor(file_id)
649 self.contains_one_anchor(file_id)
629 diffblock = doc.cssselect('[data-f-path="%s"]' % filename)
650 diffblock = doc.cssselect('[data-f-path="%s"]' % filename)
630 assert len(diffblock) == 1
651 assert len(diffblock) == 1
631 assert len(diffblock[0].cssselect('a[href="#%s"]' % file_id)) == 1
652 assert len(diffblock[0].cssselect('a[href="#%s"]' % file_id)) == 1
632
653
633 def contains_change_summary(self, files_changed, inserted, deleted):
654 def contains_change_summary(self, files_changed, inserted, deleted):
634 template = (
655 template = (
635 "{files_changed} file{plural} changed: "
656 "{files_changed} file{plural} changed: "
636 "{inserted} inserted, {deleted} deleted")
657 "{inserted} inserted, {deleted} deleted")
637 self.response.mustcontain(template.format(
658 self.response.mustcontain(template.format(
638 files_changed=files_changed,
659 files_changed=files_changed,
639 plural="s" if files_changed > 1 else "",
660 plural="s" if files_changed > 1 else "",
640 inserted=inserted,
661 inserted=inserted,
641 deleted=deleted))
662 deleted=deleted))
642
663
643 def contains_commits(self, commits, ancestors=None):
664 def contains_commits(self, commits, ancestors=None):
644 response = self.response
665 response = self.response
645
666
646 for commit in commits:
667 for commit in commits:
647 # Expecting to see the commit message in an element which
668 # Expecting to see the commit message in an element which
648 # has the ID "c-{commit.raw_id}"
669 # has the ID "c-{commit.raw_id}"
649 self.element_contains('#c-' + commit.raw_id, commit.message)
670 self.element_contains('#c-' + commit.raw_id, commit.message)
650 self.contains_one_link(
671 self.contains_one_link(
651 'r%s:%s' % (commit.idx, commit.short_id),
672 'r%s:%s' % (commit.idx, commit.short_id),
652 self._commit_url(commit))
673 self._commit_url(commit))
653 if ancestors:
674 if ancestors:
654 response.mustcontain('Ancestor')
675 response.mustcontain('Ancestor')
655 for ancestor in ancestors:
676 for ancestor in ancestors:
656 self.contains_one_link(
677 self.contains_one_link(
657 ancestor.short_id, self._commit_url(ancestor))
678 ancestor.short_id, self._commit_url(ancestor))
658
679
659 def _commit_url(self, commit):
680 def _commit_url(self, commit):
660 return '/%s/changeset/%s' % (commit.repository.name, commit.raw_id)
681 return '/%s/changeset/%s' % (commit.repository.name, commit.raw_id)
661
682
662 def swap_is_hidden(self):
683 def swap_is_hidden(self):
663 assert '<a id="btn-swap"' not in self.response.text
684 assert '<a id="btn-swap"' not in self.response.text
664
685
665 def swap_is_visible(self):
686 def swap_is_visible(self):
666 assert '<a id="btn-swap"' in self.response.text
687 assert '<a id="btn-swap"' in self.response.text
667
688
668 def target_source_are_disabled(self):
689 def target_source_are_disabled(self):
669 response = self.response
690 response = self.response
670 response.mustcontain("var enable_fields = false;")
691 response.mustcontain("var enable_fields = false;")
671 response.mustcontain('.select2("enable", enable_fields)')
692 response.mustcontain('.select2("enable", enable_fields)')
672
693
673 def target_source_are_enabled(self):
694 def target_source_are_enabled(self):
674 response = self.response
695 response = self.response
675 response.mustcontain("var enable_fields = true;")
696 response.mustcontain("var enable_fields = true;")
697
@@ -1,148 +1,163 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 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 pytest
21 import pytest
22
22
23 from rhodecode.tests import url
23 from .test_repo_compare import ComparePage
24 from rhodecode.tests.functional.test_compare import ComparePage
24
25
26 def route_path(name, params=None, **kwargs):
27 import urllib
28
29 base_url = {
30 'repo_compare_select': '/{repo_name}/compare',
31 'repo_compare': '/{repo_name}/compare/{source_ref_type}@{source_ref}...{target_ref_type}@{target_ref}',
32 }[name].format(**kwargs)
33
34 if params:
35 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
36 return base_url
25
37
26
38
27 @pytest.mark.usefixtures("autologin_user", "app")
39 @pytest.mark.usefixtures("autologin_user", "app")
28 class TestCompareController:
40 class TestCompareView(object):
29
41
30 @pytest.mark.xfail_backends("svn", msg="Depends on branch and tag support")
42 @pytest.mark.xfail_backends("svn", msg="Depends on branch and tag support")
31 def test_compare_tag(self, backend):
43 def test_compare_tag(self, backend):
32 tag1 = 'v0.1.2'
44 tag1 = 'v0.1.2'
33 tag2 = 'v0.1.3'
45 tag2 = 'v0.1.3'
34 response = self.app.get(
46 response = self.app.get(
35 url(
47 route_path(
36 'compare_url',
48 'repo_compare',
37 repo_name=backend.repo_name,
49 repo_name=backend.repo_name,
38 source_ref_type="tag",
50 source_ref_type="tag",
39 source_ref=tag1,
51 source_ref=tag1,
40 target_ref_type="tag",
52 target_ref_type="tag",
41 target_ref=tag2),
53 target_ref=tag2),
42 status=200)
54 status=200)
43
55
44 response.mustcontain('%s@%s' % (backend.repo_name, tag1))
56 response.mustcontain('%s@%s' % (backend.repo_name, tag1))
45 response.mustcontain('%s@%s' % (backend.repo_name, tag2))
57 response.mustcontain('%s@%s' % (backend.repo_name, tag2))
46
58
47 # outgoing commits between tags
59 # outgoing commits between tags
48 commit_indexes = {
60 commit_indexes = {
49 'git': [113] + range(115, 121),
61 'git': [113] + range(115, 121),
50 'hg': [112] + range(115, 121),
62 'hg': [112] + range(115, 121),
51 }
63 }
52 repo = backend.repo
64 repo = backend.repo
53 commits = (repo.get_commit(commit_idx=idx)
65 commits = (repo.get_commit(commit_idx=idx)
54 for idx in commit_indexes[backend.alias])
66 for idx in commit_indexes[backend.alias])
55 compare_page = ComparePage(response)
67 compare_page = ComparePage(response)
56 compare_page.contains_change_summary(11, 94, 64)
68 compare_page.contains_change_summary(11, 94, 64)
57 compare_page.contains_commits(commits)
69 compare_page.contains_commits(commits)
58
70
59 # files diff
71 # files diff
60 compare_page.contains_file_links_and_anchors([
72 compare_page.contains_file_links_and_anchors([
61 ('docs/api/utils/index.rst', 'a_c--1c5cf9e91c12'),
73 ('docs/api/utils/index.rst', 'a_c--1c5cf9e91c12'),
62 ('test_and_report.sh', 'a_c--e3305437df55'),
74 ('test_and_report.sh', 'a_c--e3305437df55'),
63 ('.hgignore', 'a_c--c8e92ef85cd1'),
75 ('.hgignore', 'a_c--c8e92ef85cd1'),
64 ('.hgtags', 'a_c--6e08b694d687'),
76 ('.hgtags', 'a_c--6e08b694d687'),
65 ('docs/api/index.rst', 'a_c--2c14b00f3393'),
77 ('docs/api/index.rst', 'a_c--2c14b00f3393'),
66 ('vcs/__init__.py', 'a_c--430ccbc82bdf'),
78 ('vcs/__init__.py', 'a_c--430ccbc82bdf'),
67 ('vcs/backends/hg.py', 'a_c--9c390eb52cd6'),
79 ('vcs/backends/hg.py', 'a_c--9c390eb52cd6'),
68 ('vcs/utils/__init__.py', 'a_c--ebb592c595c0'),
80 ('vcs/utils/__init__.py', 'a_c--ebb592c595c0'),
69 ('vcs/utils/annotate.py', 'a_c--7abc741b5052'),
81 ('vcs/utils/annotate.py', 'a_c--7abc741b5052'),
70 ('vcs/utils/diffs.py', 'a_c--2ef0ef106c56'),
82 ('vcs/utils/diffs.py', 'a_c--2ef0ef106c56'),
71 ('vcs/utils/lazy.py', 'a_c--3150cb87d4b7'),
83 ('vcs/utils/lazy.py', 'a_c--3150cb87d4b7'),
72 ])
84 ])
73
85
74 @pytest.mark.xfail_backends("svn", msg="Depends on branch and tag support")
86 @pytest.mark.xfail_backends("svn", msg="Depends on branch and tag support")
75 def test_compare_tag_branch(self, backend):
87 def test_compare_tag_branch(self, backend):
76 revisions = {
88 revisions = {
77 'hg': {
89 'hg': {
78 'tag': 'v0.2.0',
90 'tag': 'v0.2.0',
79 'branch': 'default',
91 'branch': 'default',
80 'response': (147, 5701, 10177)
92 'response': (147, 5701, 10177)
81 },
93 },
82 'git': {
94 'git': {
83 'tag': 'v0.2.2',
95 'tag': 'v0.2.2',
84 'branch': 'master',
96 'branch': 'master',
85 'response': (71, 2269, 3416)
97 'response': (71, 2269, 3416)
86 },
98 },
87 }
99 }
88
100
89 # Backend specific data, depends on the test repository for
101 # Backend specific data, depends on the test repository for
90 # functional tests.
102 # functional tests.
91 data = revisions[backend.alias]
103 data = revisions[backend.alias]
92
104
93 response = self.app.get(url(
105 response = self.app.get(
94 'compare_url',
106 route_path(
107 'repo_compare',
95 repo_name=backend.repo_name,
108 repo_name=backend.repo_name,
96 source_ref_type='branch',
109 source_ref_type='branch',
97 source_ref=data['branch'],
110 source_ref=data['branch'],
98 target_ref_type="tag",
111 target_ref_type="tag",
99 target_ref=data['tag'],
112 target_ref=data['tag'],
100 ))
113 ))
101
114
102 response.mustcontain('%s@%s' % (backend.repo_name, data['branch']))
115 response.mustcontain('%s@%s' % (backend.repo_name, data['branch']))
103 response.mustcontain('%s@%s' % (backend.repo_name, data['tag']))
116 response.mustcontain('%s@%s' % (backend.repo_name, data['tag']))
104 compare_page = ComparePage(response)
117 compare_page = ComparePage(response)
105 compare_page.contains_change_summary(*data['response'])
118 compare_page.contains_change_summary(*data['response'])
106
119
107 def test_index_branch(self, backend):
120 def test_index_branch(self, backend):
108 head_id = backend.default_head_id
121 head_id = backend.default_head_id
109 response = self.app.get(url(
122 response = self.app.get(
110 'compare_url',
123 route_path(
124 'repo_compare',
111 repo_name=backend.repo_name,
125 repo_name=backend.repo_name,
112 source_ref_type="branch",
126 source_ref_type="branch",
113 source_ref=head_id,
127 source_ref=head_id,
114 target_ref_type="branch",
128 target_ref_type="branch",
115 target_ref=head_id,
129 target_ref=head_id,
116 ))
130 ))
117
131
118 response.mustcontain('%s@%s' % (backend.repo_name, head_id))
132 response.mustcontain('%s@%s' % (backend.repo_name, head_id))
119
133
120 # branches are equal
134 # branches are equal
121 response.mustcontain('No files')
135 response.mustcontain('No files')
122 response.mustcontain('No commits in this compare')
136 response.mustcontain('No commits in this compare')
123
137
124 def test_compare_commits(self, backend):
138 def test_compare_commits(self, backend):
125 repo = backend.repo
139 repo = backend.repo
126 commit1 = repo.get_commit(commit_idx=0)
140 commit1 = repo.get_commit(commit_idx=0)
127 commit2 = repo.get_commit(commit_idx=1)
141 commit2 = repo.get_commit(commit_idx=1)
128
142
129 response = self.app.get(url(
143 response = self.app.get(
130 'compare_url',
144 route_path(
145 'repo_compare',
131 repo_name=backend.repo_name,
146 repo_name=backend.repo_name,
132 source_ref_type="rev",
147 source_ref_type="rev",
133 source_ref=commit1.raw_id,
148 source_ref=commit1.raw_id,
134 target_ref_type="rev",
149 target_ref_type="rev",
135 target_ref=commit2.raw_id,
150 target_ref=commit2.raw_id,
136 ))
151 ))
137 response.mustcontain('%s@%s' % (backend.repo_name, commit1.raw_id))
152 response.mustcontain('%s@%s' % (backend.repo_name, commit1.raw_id))
138 response.mustcontain('%s@%s' % (backend.repo_name, commit2.raw_id))
153 response.mustcontain('%s@%s' % (backend.repo_name, commit2.raw_id))
139 compare_page = ComparePage(response)
154 compare_page = ComparePage(response)
140
155
141 # files
156 # files
142 compare_page.contains_change_summary(1, 7, 0)
157 compare_page.contains_change_summary(1, 7, 0)
143
158
144 # outgoing commits between those commits
159 # outgoing commits between those commits
145 compare_page.contains_commits([commit2])
160 compare_page.contains_commits([commit2])
146 compare_page.contains_file_links_and_anchors([
161 compare_page.contains_file_links_and_anchors([
147 ('.hgignore', 'a_c--c8e92ef85cd1'),
162 ('.hgignore', 'a_c--c8e92ef85cd1'),
148 ])
163 ])
@@ -1,179 +1,183 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 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 pytest
21 import pytest
22
22
23 from rhodecode.lib.vcs import nodes
23 from rhodecode.lib.vcs import nodes
24 from rhodecode.tests import url
25 from rhodecode.tests.fixture import Fixture
24 from rhodecode.tests.fixture import Fixture
26 from rhodecode.tests.utils import commit_change
25 from rhodecode.tests.utils import commit_change
27
26
28 fixture = Fixture()
27 fixture = Fixture()
29
28
30
29
30 def route_path(name, params=None, **kwargs):
31 import urllib
32
33 base_url = {
34 'repo_compare_select': '/{repo_name}/compare',
35 'repo_compare': '/{repo_name}/compare/{source_ref_type}@{source_ref}...{target_ref_type}@{target_ref}',
36 }[name].format(**kwargs)
37
38 if params:
39 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
40 return base_url
41
42
31 @pytest.mark.usefixtures("autologin_user", "app")
43 @pytest.mark.usefixtures("autologin_user", "app")
32 class TestSideBySideDiff(object):
44 class TestSideBySideDiff(object):
33
45
34 def test_diff_side_by_side(self, app, backend, backend_stub):
46 def test_diff_side_by_side(self, app, backend, backend_stub):
35 f_path = 'test_sidebyside_file.py'
47 f_path = 'test_sidebyside_file.py'
36 commit1_content = 'content-25d7e49c18b159446c\n'
48 commit1_content = 'content-25d7e49c18b159446c\n'
37 commit2_content = 'content-603d6c72c46d953420\n'
49 commit2_content = 'content-603d6c72c46d953420\n'
38 repo = backend.create_repo()
50 repo = backend.create_repo()
39
51
40 commit1 = commit_change(
52 commit1 = commit_change(
41 repo.repo_name, filename=f_path, content=commit1_content,
53 repo.repo_name, filename=f_path, content=commit1_content,
42 message='A', vcs_type=backend.alias, parent=None, newfile=True)
54 message='A', vcs_type=backend.alias, parent=None, newfile=True)
43
55
44 commit2 = commit_change(
56 commit2 = commit_change(
45 repo.repo_name, filename=f_path, content=commit2_content,
57 repo.repo_name, filename=f_path, content=commit2_content,
46 message='B, child of A', vcs_type=backend.alias, parent=commit1)
58 message='B, child of A', vcs_type=backend.alias, parent=commit1)
47
59
48 compare_url = url(
60 response = self.app.get(route_path(
49 'compare_url',
61 'repo_compare',
50 repo_name=repo.repo_name,
62 repo_name=repo.repo_name,
51 source_ref_type='rev',
63 source_ref_type='rev',
52 source_ref=commit1.raw_id,
64 source_ref=commit1.raw_id,
53 target_repo=repo.repo_name,
54 target_ref_type='rev',
65 target_ref_type='rev',
55 target_ref=commit2.raw_id,
66 target_ref=commit2.raw_id,
56 f_path=f_path,
67 params=dict(f_path=f_path, target_repo=repo.repo_name, diffmode='sidebyside')
57 diffmode='sidebyside')
68 ))
58
59 response = self.app.get(compare_url)
60
69
61 response.mustcontain('Expand 1 commit')
70 response.mustcontain('Expand 1 commit')
62 response.mustcontain('1 file changed')
71 response.mustcontain('1 file changed')
63
72
64 response.mustcontain(
73 response.mustcontain(
65 'r%s:%s...r%s:%s' % (
74 'r%s:%s...r%s:%s' % (
66 commit1.idx, commit1.short_id, commit2.idx, commit2.short_id))
75 commit1.idx, commit1.short_id, commit2.idx, commit2.short_id))
67
76
68 response.mustcontain('<strong>{}</strong>'.format(f_path))
77 response.mustcontain('<strong>{}</strong>'.format(f_path))
69
78
70 def test_diff_side_by_side_with_empty_file(self, app, backend, backend_stub):
79 def test_diff_side_by_side_with_empty_file(self, app, backend, backend_stub):
71 commits = [
80 commits = [
72 {'message': 'First commit'},
81 {'message': 'First commit'},
73 {'message': 'Commit with binary',
82 {'message': 'Commit with binary',
74 'added': [nodes.FileNode('file.empty', content='')]},
83 'added': [nodes.FileNode('file.empty', content='')]},
75 ]
84 ]
76 f_path = 'file.empty'
85 f_path = 'file.empty'
77 repo = backend.create_repo(commits=commits)
86 repo = backend.create_repo(commits=commits)
78 commit1 = repo.get_commit(commit_idx=0)
87 commit1 = repo.get_commit(commit_idx=0)
79 commit2 = repo.get_commit(commit_idx=1)
88 commit2 = repo.get_commit(commit_idx=1)
80
89
81 compare_url = url(
90 response = self.app.get(route_path(
82 'compare_url',
91 'repo_compare',
83 repo_name=repo.repo_name,
92 repo_name=repo.repo_name,
84 source_ref_type='rev',
93 source_ref_type='rev',
85 source_ref=commit1.raw_id,
94 source_ref=commit1.raw_id,
86 target_repo=repo.repo_name,
87 target_ref_type='rev',
95 target_ref_type='rev',
88 target_ref=commit2.raw_id,
96 target_ref=commit2.raw_id,
89 f_path=f_path,
97 params=dict(f_path=f_path, target_repo=repo.repo_name, diffmode='sidebyside')
90 diffmode='sidebyside')
98 ))
91
92 response = self.app.get(compare_url)
93
99
94 response.mustcontain('Expand 1 commit')
100 response.mustcontain('Expand 1 commit')
95 response.mustcontain('1 file changed')
101 response.mustcontain('1 file changed')
96
102
97 response.mustcontain(
103 response.mustcontain(
98 'r%s:%s...r%s:%s' % (
104 'r%s:%s...r%s:%s' % (
99 commit1.idx, commit1.short_id, commit2.idx, commit2.short_id))
105 commit1.idx, commit1.short_id, commit2.idx, commit2.short_id))
100
106
101 response.mustcontain('<strong>{}</strong>'.format(f_path))
107 response.mustcontain('<strong>{}</strong>'.format(f_path))
102
108
103 def test_diff_sidebyside_two_commits(self, app, backend):
109 def test_diff_sidebyside_two_commits(self, app, backend):
104 commit_id_range = {
110 commit_id_range = {
105 'hg': {
111 'hg': {
106 'commits': ['25d7e49c18b159446cadfa506a5cf8ad1cb04067',
112 'commits': ['25d7e49c18b159446cadfa506a5cf8ad1cb04067',
107 '603d6c72c46d953420c89d36372f08d9f305f5dd'],
113 '603d6c72c46d953420c89d36372f08d9f305f5dd'],
108 'changes': '21 files changed: 943 inserted, 288 deleted'
114 'changes': '21 files changed: 943 inserted, 288 deleted'
109 },
115 },
110 'git': {
116 'git': {
111 'commits': ['6fc9270775aaf5544c1deb014f4ddd60c952fcbb',
117 'commits': ['6fc9270775aaf5544c1deb014f4ddd60c952fcbb',
112 '03fa803d7e9fb14daa9a3089e0d1494eda75d986'],
118 '03fa803d7e9fb14daa9a3089e0d1494eda75d986'],
113 'changes': '21 files changed: 943 inserted, 288 deleted'
119 'changes': '21 files changed: 943 inserted, 288 deleted'
114 },
120 },
115
121
116 'svn': {
122 'svn': {
117 'commits': ['336',
123 'commits': ['336',
118 '337'],
124 '337'],
119 'changes': '21 files changed: 943 inserted, 288 deleted'
125 'changes': '21 files changed: 943 inserted, 288 deleted'
120 },
126 },
121 }
127 }
122
128
123 commit_info = commit_id_range[backend.alias]
129 commit_info = commit_id_range[backend.alias]
124 commit2, commit1 = commit_info['commits']
130 commit2, commit1 = commit_info['commits']
125 file_changes = commit_info['changes']
131 file_changes = commit_info['changes']
126
132
127 compare_url = url(
133 response = self.app.get(route_path(
128 'compare_url',
134 'repo_compare',
129 repo_name=backend.repo_name,
135 repo_name=backend.repo_name,
130 source_ref_type='rev',
136 source_ref_type='rev',
131 source_ref=commit2,
137 source_ref=commit2,
132 target_repo=backend.repo_name,
138 target_repo=backend.repo_name,
133 target_ref_type='rev',
139 target_ref_type='rev',
134 target_ref=commit1,
140 target_ref=commit1,
135 diffmode='sidebyside')
141 params=dict(target_repo=backend.repo_name, diffmode='sidebyside')
136 response = self.app.get(compare_url)
142 ))
137
143
138 response.mustcontain('Expand 1 commit')
144 response.mustcontain('Expand 1 commit')
139 response.mustcontain(file_changes)
145 response.mustcontain(file_changes)
140
146
141 def test_diff_sidebyside_two_commits_single_file(self, app, backend):
147 def test_diff_sidebyside_two_commits_single_file(self, app, backend):
142 commit_id_range = {
148 commit_id_range = {
143 'hg': {
149 'hg': {
144 'commits': ['25d7e49c18b159446cadfa506a5cf8ad1cb04067',
150 'commits': ['25d7e49c18b159446cadfa506a5cf8ad1cb04067',
145 '603d6c72c46d953420c89d36372f08d9f305f5dd'],
151 '603d6c72c46d953420c89d36372f08d9f305f5dd'],
146 'changes': '1 file changed: 1 inserted, 1 deleted'
152 'changes': '1 file changed: 1 inserted, 1 deleted'
147 },
153 },
148 'git': {
154 'git': {
149 'commits': ['6fc9270775aaf5544c1deb014f4ddd60c952fcbb',
155 'commits': ['6fc9270775aaf5544c1deb014f4ddd60c952fcbb',
150 '03fa803d7e9fb14daa9a3089e0d1494eda75d986'],
156 '03fa803d7e9fb14daa9a3089e0d1494eda75d986'],
151 'changes': '1 file changed: 1 inserted, 1 deleted'
157 'changes': '1 file changed: 1 inserted, 1 deleted'
152 },
158 },
153
159
154 'svn': {
160 'svn': {
155 'commits': ['336',
161 'commits': ['336',
156 '337'],
162 '337'],
157 'changes': '1 file changed: 1 inserted, 1 deleted'
163 'changes': '1 file changed: 1 inserted, 1 deleted'
158 },
164 },
159 }
165 }
160 f_path = 'docs/conf.py'
166 f_path = 'docs/conf.py'
161
167
162 commit_info = commit_id_range[backend.alias]
168 commit_info = commit_id_range[backend.alias]
163 commit2, commit1 = commit_info['commits']
169 commit2, commit1 = commit_info['commits']
164 file_changes = commit_info['changes']
170 file_changes = commit_info['changes']
165
171
166 compare_url = url(
172 response = self.app.get(route_path(
167 'compare_url',
173 'repo_compare',
168 repo_name=backend.repo_name,
174 repo_name=backend.repo_name,
169 source_ref_type='rev',
175 source_ref_type='rev',
170 source_ref=commit2,
176 source_ref=commit2,
171 target_repo=backend.repo_name,
172 target_ref_type='rev',
177 target_ref_type='rev',
173 target_ref=commit1,
178 target_ref=commit1,
174 f_path=f_path,
179 params=dict(f_path=f_path, target_repo=backend.repo_name, diffmode='sidebyside')
175 diffmode='sidebyside')
180 ))
176 response = self.app.get(compare_url)
177
181
178 response.mustcontain('Expand 1 commit')
182 response.mustcontain('Expand 1 commit')
179 response.mustcontain(file_changes)
183 response.mustcontain(file_changes)
@@ -1,1278 +1,1279 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 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 itertools
21 import itertools
22 import logging
22 import logging
23 import os
23 import os
24 import shutil
24 import shutil
25 import tempfile
25 import tempfile
26 import collections
26 import collections
27
27
28 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
28 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
29 from pyramid.view import view_config
29 from pyramid.view import view_config
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31 from pyramid.response import Response
31 from pyramid.response import Response
32
32
33 from rhodecode.apps._base import RepoAppView
33 from rhodecode.apps._base import RepoAppView
34
34
35 from rhodecode.controllers.utils import parse_path_ref
35 from rhodecode.controllers.utils import parse_path_ref
36 from rhodecode.lib import diffs, helpers as h, caches
36 from rhodecode.lib import diffs, helpers as h, caches
37 from rhodecode.lib import audit_logger
37 from rhodecode.lib import audit_logger
38 from rhodecode.lib.exceptions import NonRelativePathError
38 from rhodecode.lib.exceptions import NonRelativePathError
39 from rhodecode.lib.codeblocks import (
39 from rhodecode.lib.codeblocks import (
40 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
40 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
41 from rhodecode.lib.utils2 import (
41 from rhodecode.lib.utils2 import (
42 convert_line_endings, detect_mode, safe_str, str2bool)
42 convert_line_endings, detect_mode, safe_str, str2bool)
43 from rhodecode.lib.auth import (
43 from rhodecode.lib.auth import (
44 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
44 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
45 from rhodecode.lib.vcs import path as vcspath
45 from rhodecode.lib.vcs import path as vcspath
46 from rhodecode.lib.vcs.backends.base import EmptyCommit
46 from rhodecode.lib.vcs.backends.base import EmptyCommit
47 from rhodecode.lib.vcs.conf import settings
47 from rhodecode.lib.vcs.conf import settings
48 from rhodecode.lib.vcs.nodes import FileNode
48 from rhodecode.lib.vcs.nodes import FileNode
49 from rhodecode.lib.vcs.exceptions import (
49 from rhodecode.lib.vcs.exceptions import (
50 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
50 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
51 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
51 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
52 NodeDoesNotExistError, CommitError, NodeError)
52 NodeDoesNotExistError, CommitError, NodeError)
53
53
54 from rhodecode.model.scm import ScmModel
54 from rhodecode.model.scm import ScmModel
55 from rhodecode.model.db import Repository
55 from rhodecode.model.db import Repository
56
56
57 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
58
58
59
59
60 class RepoFilesView(RepoAppView):
60 class RepoFilesView(RepoAppView):
61
61
62 @staticmethod
62 @staticmethod
63 def adjust_file_path_for_svn(f_path, repo):
63 def adjust_file_path_for_svn(f_path, repo):
64 """
64 """
65 Computes the relative path of `f_path`.
65 Computes the relative path of `f_path`.
66
66
67 This is mainly based on prefix matching of the recognized tags and
67 This is mainly based on prefix matching of the recognized tags and
68 branches in the underlying repository.
68 branches in the underlying repository.
69 """
69 """
70 tags_and_branches = itertools.chain(
70 tags_and_branches = itertools.chain(
71 repo.branches.iterkeys(),
71 repo.branches.iterkeys(),
72 repo.tags.iterkeys())
72 repo.tags.iterkeys())
73 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
73 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
74
74
75 for name in tags_and_branches:
75 for name in tags_and_branches:
76 if f_path.startswith('{}/'.format(name)):
76 if f_path.startswith('{}/'.format(name)):
77 f_path = vcspath.relpath(f_path, name)
77 f_path = vcspath.relpath(f_path, name)
78 break
78 break
79 return f_path
79 return f_path
80
80
81 def load_default_context(self):
81 def load_default_context(self):
82 c = self._get_local_tmpl_context(include_app_defaults=True)
82 c = self._get_local_tmpl_context(include_app_defaults=True)
83
83
84 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
84 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
85 c.repo_info = self.db_repo
85 c.repo_info = self.db_repo
86 c.rhodecode_repo = self.rhodecode_vcs_repo
86 c.rhodecode_repo = self.rhodecode_vcs_repo
87
87
88 self._register_global_c(c)
88 self._register_global_c(c)
89 return c
89 return c
90
90
91 def _ensure_not_locked(self):
91 def _ensure_not_locked(self):
92 _ = self.request.translate
92 _ = self.request.translate
93
93
94 repo = self.db_repo
94 repo = self.db_repo
95 if repo.enable_locking and repo.locked[0]:
95 if repo.enable_locking and repo.locked[0]:
96 h.flash(_('This repository has been locked by %s on %s')
96 h.flash(_('This repository has been locked by %s on %s')
97 % (h.person_by_id(repo.locked[0]),
97 % (h.person_by_id(repo.locked[0]),
98 h.format_date(h.time_to_datetime(repo.locked[1]))),
98 h.format_date(h.time_to_datetime(repo.locked[1]))),
99 'warning')
99 'warning')
100 files_url = h.route_path(
100 files_url = h.route_path(
101 'repo_files:default_path',
101 'repo_files:default_path',
102 repo_name=self.db_repo_name, commit_id='tip')
102 repo_name=self.db_repo_name, commit_id='tip')
103 raise HTTPFound(files_url)
103 raise HTTPFound(files_url)
104
104
105 def _get_commit_and_path(self):
105 def _get_commit_and_path(self):
106 default_commit_id = self.db_repo.landing_rev[1]
106 default_commit_id = self.db_repo.landing_rev[1]
107 default_f_path = '/'
107 default_f_path = '/'
108
108
109 commit_id = self.request.matchdict.get(
109 commit_id = self.request.matchdict.get(
110 'commit_id', default_commit_id)
110 'commit_id', default_commit_id)
111 f_path = self._get_f_path(self.request.matchdict, default_f_path)
111 f_path = self._get_f_path(self.request.matchdict, default_f_path)
112 return commit_id, f_path
112 return commit_id, f_path
113
113
114 def _get_default_encoding(self, c):
114 def _get_default_encoding(self, c):
115 enc_list = getattr(c, 'default_encodings', [])
115 enc_list = getattr(c, 'default_encodings', [])
116 return enc_list[0] if enc_list else 'UTF-8'
116 return enc_list[0] if enc_list else 'UTF-8'
117
117
118 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
118 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
119 """
119 """
120 This is a safe way to get commit. If an error occurs it redirects to
120 This is a safe way to get commit. If an error occurs it redirects to
121 tip with proper message
121 tip with proper message
122
122
123 :param commit_id: id of commit to fetch
123 :param commit_id: id of commit to fetch
124 :param redirect_after: toggle redirection
124 :param redirect_after: toggle redirection
125 """
125 """
126 _ = self.request.translate
126 _ = self.request.translate
127
127
128 try:
128 try:
129 return self.rhodecode_vcs_repo.get_commit(commit_id)
129 return self.rhodecode_vcs_repo.get_commit(commit_id)
130 except EmptyRepositoryError:
130 except EmptyRepositoryError:
131 if not redirect_after:
131 if not redirect_after:
132 return None
132 return None
133
133
134 _url = h.route_path(
134 _url = h.route_path(
135 'repo_files_add_file',
135 'repo_files_add_file',
136 repo_name=self.db_repo_name, commit_id=0, f_path='',
136 repo_name=self.db_repo_name, commit_id=0, f_path='',
137 _anchor='edit')
137 _anchor='edit')
138
138
139 if h.HasRepoPermissionAny(
139 if h.HasRepoPermissionAny(
140 'repository.write', 'repository.admin')(self.db_repo_name):
140 'repository.write', 'repository.admin')(self.db_repo_name):
141 add_new = h.link_to(
141 add_new = h.link_to(
142 _('Click here to add a new file.'), _url, class_="alert-link")
142 _('Click here to add a new file.'), _url, class_="alert-link")
143 else:
143 else:
144 add_new = ""
144 add_new = ""
145
145
146 h.flash(h.literal(
146 h.flash(h.literal(
147 _('There are no files yet. %s') % add_new), category='warning')
147 _('There are no files yet. %s') % add_new), category='warning')
148 raise HTTPFound(
148 raise HTTPFound(
149 h.route_path('repo_summary', repo_name=self.db_repo_name))
149 h.route_path('repo_summary', repo_name=self.db_repo_name))
150
150
151 except (CommitDoesNotExistError, LookupError):
151 except (CommitDoesNotExistError, LookupError):
152 msg = _('No such commit exists for this repository')
152 msg = _('No such commit exists for this repository')
153 h.flash(msg, category='error')
153 h.flash(msg, category='error')
154 raise HTTPNotFound()
154 raise HTTPNotFound()
155 except RepositoryError as e:
155 except RepositoryError as e:
156 h.flash(safe_str(h.escape(e)), category='error')
156 h.flash(safe_str(h.escape(e)), category='error')
157 raise HTTPNotFound()
157 raise HTTPNotFound()
158
158
159 def _get_filenode_or_redirect(self, commit_obj, path):
159 def _get_filenode_or_redirect(self, commit_obj, path):
160 """
160 """
161 Returns file_node, if error occurs or given path is directory,
161 Returns file_node, if error occurs or given path is directory,
162 it'll redirect to top level path
162 it'll redirect to top level path
163 """
163 """
164 _ = self.request.translate
164 _ = self.request.translate
165
165
166 try:
166 try:
167 file_node = commit_obj.get_node(path)
167 file_node = commit_obj.get_node(path)
168 if file_node.is_dir():
168 if file_node.is_dir():
169 raise RepositoryError('The given path is a directory')
169 raise RepositoryError('The given path is a directory')
170 except CommitDoesNotExistError:
170 except CommitDoesNotExistError:
171 log.exception('No such commit exists for this repository')
171 log.exception('No such commit exists for this repository')
172 h.flash(_('No such commit exists for this repository'), category='error')
172 h.flash(_('No such commit exists for this repository'), category='error')
173 raise HTTPNotFound()
173 raise HTTPNotFound()
174 except RepositoryError as e:
174 except RepositoryError as e:
175 log.warning('Repository error while fetching '
175 log.warning('Repository error while fetching '
176 'filenode `%s`. Err:%s', path, e)
176 'filenode `%s`. Err:%s', path, e)
177 h.flash(safe_str(h.escape(e)), category='error')
177 h.flash(safe_str(h.escape(e)), category='error')
178 raise HTTPNotFound()
178 raise HTTPNotFound()
179
179
180 return file_node
180 return file_node
181
181
182 def _is_valid_head(self, commit_id, repo):
182 def _is_valid_head(self, commit_id, repo):
183 # check if commit is a branch identifier- basically we cannot
183 # check if commit is a branch identifier- basically we cannot
184 # create multiple heads via file editing
184 # create multiple heads via file editing
185 valid_heads = repo.branches.keys() + repo.branches.values()
185 valid_heads = repo.branches.keys() + repo.branches.values()
186
186
187 if h.is_svn(repo) and not repo.is_empty():
187 if h.is_svn(repo) and not repo.is_empty():
188 # Note: Subversion only has one head, we add it here in case there
188 # Note: Subversion only has one head, we add it here in case there
189 # is no branch matched.
189 # is no branch matched.
190 valid_heads.append(repo.get_commit(commit_idx=-1).raw_id)
190 valid_heads.append(repo.get_commit(commit_idx=-1).raw_id)
191
191
192 # check if commit is a branch name or branch hash
192 # check if commit is a branch name or branch hash
193 return commit_id in valid_heads
193 return commit_id in valid_heads
194
194
195 def _get_tree_cache_manager(self, namespace_type):
195 def _get_tree_cache_manager(self, namespace_type):
196 _namespace = caches.get_repo_namespace_key(
196 _namespace = caches.get_repo_namespace_key(
197 namespace_type, self.db_repo_name)
197 namespace_type, self.db_repo_name)
198 return caches.get_cache_manager('repo_cache_long', _namespace)
198 return caches.get_cache_manager('repo_cache_long', _namespace)
199
199
200 def _get_tree_at_commit(
200 def _get_tree_at_commit(
201 self, c, commit_id, f_path, full_load=False, force=False):
201 self, c, commit_id, f_path, full_load=False, force=False):
202 def _cached_tree():
202 def _cached_tree():
203 log.debug('Generating cached file tree for %s, %s, %s',
203 log.debug('Generating cached file tree for %s, %s, %s',
204 self.db_repo_name, commit_id, f_path)
204 self.db_repo_name, commit_id, f_path)
205
205
206 c.full_load = full_load
206 c.full_load = full_load
207 return render(
207 return render(
208 'rhodecode:templates/files/files_browser_tree.mako',
208 'rhodecode:templates/files/files_browser_tree.mako',
209 self._get_template_context(c), self.request)
209 self._get_template_context(c), self.request)
210
210
211 cache_manager = self._get_tree_cache_manager(caches.FILE_TREE)
211 cache_manager = self._get_tree_cache_manager(caches.FILE_TREE)
212
212
213 cache_key = caches.compute_key_from_params(
213 cache_key = caches.compute_key_from_params(
214 self.db_repo_name, commit_id, f_path)
214 self.db_repo_name, commit_id, f_path)
215
215
216 if force:
216 if force:
217 # we want to force recompute of caches
217 # we want to force recompute of caches
218 cache_manager.remove_value(cache_key)
218 cache_manager.remove_value(cache_key)
219
219
220 return cache_manager.get(cache_key, createfunc=_cached_tree)
220 return cache_manager.get(cache_key, createfunc=_cached_tree)
221
221
222 def _get_archive_spec(self, fname):
222 def _get_archive_spec(self, fname):
223 log.debug('Detecting archive spec for: `%s`', fname)
223 log.debug('Detecting archive spec for: `%s`', fname)
224
224
225 fileformat = None
225 fileformat = None
226 ext = None
226 ext = None
227 content_type = None
227 content_type = None
228 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
228 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
229 content_type, extension = ext_data
229 content_type, extension = ext_data
230
230
231 if fname.endswith(extension):
231 if fname.endswith(extension):
232 fileformat = a_type
232 fileformat = a_type
233 log.debug('archive is of type: %s', fileformat)
233 log.debug('archive is of type: %s', fileformat)
234 ext = extension
234 ext = extension
235 break
235 break
236
236
237 if not fileformat:
237 if not fileformat:
238 raise ValueError()
238 raise ValueError()
239
239
240 # left over part of whole fname is the commit
240 # left over part of whole fname is the commit
241 commit_id = fname[:-len(ext)]
241 commit_id = fname[:-len(ext)]
242
242
243 return commit_id, ext, fileformat, content_type
243 return commit_id, ext, fileformat, content_type
244
244
245 @LoginRequired()
245 @LoginRequired()
246 @HasRepoPermissionAnyDecorator(
246 @HasRepoPermissionAnyDecorator(
247 'repository.read', 'repository.write', 'repository.admin')
247 'repository.read', 'repository.write', 'repository.admin')
248 @view_config(
248 @view_config(
249 route_name='repo_archivefile', request_method='GET',
249 route_name='repo_archivefile', request_method='GET',
250 renderer=None)
250 renderer=None)
251 def repo_archivefile(self):
251 def repo_archivefile(self):
252 # archive cache config
252 # archive cache config
253 from rhodecode import CONFIG
253 from rhodecode import CONFIG
254 _ = self.request.translate
254 _ = self.request.translate
255 self.load_default_context()
255 self.load_default_context()
256
256
257 fname = self.request.matchdict['fname']
257 fname = self.request.matchdict['fname']
258 subrepos = self.request.GET.get('subrepos') == 'true'
258 subrepos = self.request.GET.get('subrepos') == 'true'
259
259
260 if not self.db_repo.enable_downloads:
260 if not self.db_repo.enable_downloads:
261 return Response(_('Downloads disabled'))
261 return Response(_('Downloads disabled'))
262
262
263 try:
263 try:
264 commit_id, ext, fileformat, content_type = \
264 commit_id, ext, fileformat, content_type = \
265 self._get_archive_spec(fname)
265 self._get_archive_spec(fname)
266 except ValueError:
266 except ValueError:
267 return Response(_('Unknown archive type for: `{}`').format(fname))
267 return Response(_('Unknown archive type for: `{}`').format(fname))
268
268
269 try:
269 try:
270 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
270 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
271 except CommitDoesNotExistError:
271 except CommitDoesNotExistError:
272 return Response(_('Unknown commit_id %s') % commit_id)
272 return Response(_('Unknown commit_id %s') % commit_id)
273 except EmptyRepositoryError:
273 except EmptyRepositoryError:
274 return Response(_('Empty repository'))
274 return Response(_('Empty repository'))
275
275
276 archive_name = '%s-%s%s%s' % (
276 archive_name = '%s-%s%s%s' % (
277 safe_str(self.db_repo_name.replace('/', '_')),
277 safe_str(self.db_repo_name.replace('/', '_')),
278 '-sub' if subrepos else '',
278 '-sub' if subrepos else '',
279 safe_str(commit.short_id), ext)
279 safe_str(commit.short_id), ext)
280
280
281 use_cached_archive = False
281 use_cached_archive = False
282 archive_cache_enabled = CONFIG.get(
282 archive_cache_enabled = CONFIG.get(
283 'archive_cache_dir') and not self.request.GET.get('no_cache')
283 'archive_cache_dir') and not self.request.GET.get('no_cache')
284
284
285 if archive_cache_enabled:
285 if archive_cache_enabled:
286 # check if we it's ok to write
286 # check if we it's ok to write
287 if not os.path.isdir(CONFIG['archive_cache_dir']):
287 if not os.path.isdir(CONFIG['archive_cache_dir']):
288 os.makedirs(CONFIG['archive_cache_dir'])
288 os.makedirs(CONFIG['archive_cache_dir'])
289 cached_archive_path = os.path.join(
289 cached_archive_path = os.path.join(
290 CONFIG['archive_cache_dir'], archive_name)
290 CONFIG['archive_cache_dir'], archive_name)
291 if os.path.isfile(cached_archive_path):
291 if os.path.isfile(cached_archive_path):
292 log.debug('Found cached archive in %s', cached_archive_path)
292 log.debug('Found cached archive in %s', cached_archive_path)
293 fd, archive = None, cached_archive_path
293 fd, archive = None, cached_archive_path
294 use_cached_archive = True
294 use_cached_archive = True
295 else:
295 else:
296 log.debug('Archive %s is not yet cached', archive_name)
296 log.debug('Archive %s is not yet cached', archive_name)
297
297
298 if not use_cached_archive:
298 if not use_cached_archive:
299 # generate new archive
299 # generate new archive
300 fd, archive = tempfile.mkstemp()
300 fd, archive = tempfile.mkstemp()
301 log.debug('Creating new temp archive in %s', archive)
301 log.debug('Creating new temp archive in %s', archive)
302 try:
302 try:
303 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
303 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
304 except ImproperArchiveTypeError:
304 except ImproperArchiveTypeError:
305 return _('Unknown archive type')
305 return _('Unknown archive type')
306 if archive_cache_enabled:
306 if archive_cache_enabled:
307 # if we generated the archive and we have cache enabled
307 # if we generated the archive and we have cache enabled
308 # let's use this for future
308 # let's use this for future
309 log.debug('Storing new archive in %s', cached_archive_path)
309 log.debug('Storing new archive in %s', cached_archive_path)
310 shutil.move(archive, cached_archive_path)
310 shutil.move(archive, cached_archive_path)
311 archive = cached_archive_path
311 archive = cached_archive_path
312
312
313 # store download action
313 # store download action
314 audit_logger.store_web(
314 audit_logger.store_web(
315 'repo.archive.download', action_data={
315 'repo.archive.download', action_data={
316 'user_agent': self.request.user_agent,
316 'user_agent': self.request.user_agent,
317 'archive_name': archive_name,
317 'archive_name': archive_name,
318 'archive_spec': fname,
318 'archive_spec': fname,
319 'archive_cached': use_cached_archive},
319 'archive_cached': use_cached_archive},
320 user=self._rhodecode_user,
320 user=self._rhodecode_user,
321 repo=self.db_repo,
321 repo=self.db_repo,
322 commit=True
322 commit=True
323 )
323 )
324
324
325 def get_chunked_archive(archive):
325 def get_chunked_archive(archive):
326 with open(archive, 'rb') as stream:
326 with open(archive, 'rb') as stream:
327 while True:
327 while True:
328 data = stream.read(16 * 1024)
328 data = stream.read(16 * 1024)
329 if not data:
329 if not data:
330 if fd: # fd means we used temporary file
330 if fd: # fd means we used temporary file
331 os.close(fd)
331 os.close(fd)
332 if not archive_cache_enabled:
332 if not archive_cache_enabled:
333 log.debug('Destroying temp archive %s', archive)
333 log.debug('Destroying temp archive %s', archive)
334 os.remove(archive)
334 os.remove(archive)
335 break
335 break
336 yield data
336 yield data
337
337
338 response = Response(app_iter=get_chunked_archive(archive))
338 response = Response(app_iter=get_chunked_archive(archive))
339 response.content_disposition = str(
339 response.content_disposition = str(
340 'attachment; filename=%s' % archive_name)
340 'attachment; filename=%s' % archive_name)
341 response.content_type = str(content_type)
341 response.content_type = str(content_type)
342
342
343 return response
343 return response
344
344
345 def _get_file_node(self, commit_id, f_path):
345 def _get_file_node(self, commit_id, f_path):
346 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
346 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
347 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
347 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
348 try:
348 try:
349 node = commit.get_node(f_path)
349 node = commit.get_node(f_path)
350 if node.is_dir():
350 if node.is_dir():
351 raise NodeError('%s path is a %s not a file'
351 raise NodeError('%s path is a %s not a file'
352 % (node, type(node)))
352 % (node, type(node)))
353 except NodeDoesNotExistError:
353 except NodeDoesNotExistError:
354 commit = EmptyCommit(
354 commit = EmptyCommit(
355 commit_id=commit_id,
355 commit_id=commit_id,
356 idx=commit.idx,
356 idx=commit.idx,
357 repo=commit.repository,
357 repo=commit.repository,
358 alias=commit.repository.alias,
358 alias=commit.repository.alias,
359 message=commit.message,
359 message=commit.message,
360 author=commit.author,
360 author=commit.author,
361 date=commit.date)
361 date=commit.date)
362 node = FileNode(f_path, '', commit=commit)
362 node = FileNode(f_path, '', commit=commit)
363 else:
363 else:
364 commit = EmptyCommit(
364 commit = EmptyCommit(
365 repo=self.rhodecode_vcs_repo,
365 repo=self.rhodecode_vcs_repo,
366 alias=self.rhodecode_vcs_repo.alias)
366 alias=self.rhodecode_vcs_repo.alias)
367 node = FileNode(f_path, '', commit=commit)
367 node = FileNode(f_path, '', commit=commit)
368 return node
368 return node
369
369
370 @LoginRequired()
370 @LoginRequired()
371 @HasRepoPermissionAnyDecorator(
371 @HasRepoPermissionAnyDecorator(
372 'repository.read', 'repository.write', 'repository.admin')
372 'repository.read', 'repository.write', 'repository.admin')
373 @view_config(
373 @view_config(
374 route_name='repo_files_diff', request_method='GET',
374 route_name='repo_files_diff', request_method='GET',
375 renderer=None)
375 renderer=None)
376 def repo_files_diff(self):
376 def repo_files_diff(self):
377 c = self.load_default_context()
377 c = self.load_default_context()
378 f_path = self._get_f_path(self.request.matchdict)
378 f_path = self._get_f_path(self.request.matchdict)
379 diff1 = self.request.GET.get('diff1', '')
379 diff1 = self.request.GET.get('diff1', '')
380 diff2 = self.request.GET.get('diff2', '')
380 diff2 = self.request.GET.get('diff2', '')
381
381
382 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
382 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
383
383
384 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
384 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
385 line_context = self.request.GET.get('context', 3)
385 line_context = self.request.GET.get('context', 3)
386
386
387 if not any((diff1, diff2)):
387 if not any((diff1, diff2)):
388 h.flash(
388 h.flash(
389 'Need query parameter "diff1" or "diff2" to generate a diff.',
389 'Need query parameter "diff1" or "diff2" to generate a diff.',
390 category='error')
390 category='error')
391 raise HTTPBadRequest()
391 raise HTTPBadRequest()
392
392
393 c.action = self.request.GET.get('diff')
393 c.action = self.request.GET.get('diff')
394 if c.action not in ['download', 'raw']:
394 if c.action not in ['download', 'raw']:
395 compare_url = h.url(
395 compare_url = h.route_path(
396 'compare_url', repo_name=self.db_repo_name,
396 'repo_compare',
397 repo_name=self.db_repo_name,
397 source_ref_type='rev',
398 source_ref_type='rev',
398 source_ref=diff1,
399 source_ref=diff1,
399 target_repo=self.db_repo_name,
400 target_repo=self.db_repo_name,
400 target_ref_type='rev',
401 target_ref_type='rev',
401 target_ref=diff2,
402 target_ref=diff2,
402 f_path=f_path)
403 _query=dict(f_path=f_path))
403 # redirect to new view if we render diff
404 # redirect to new view if we render diff
404 raise HTTPFound(compare_url)
405 raise HTTPFound(compare_url)
405
406
406 try:
407 try:
407 node1 = self._get_file_node(diff1, path1)
408 node1 = self._get_file_node(diff1, path1)
408 node2 = self._get_file_node(diff2, f_path)
409 node2 = self._get_file_node(diff2, f_path)
409 except (RepositoryError, NodeError):
410 except (RepositoryError, NodeError):
410 log.exception("Exception while trying to get node from repository")
411 log.exception("Exception while trying to get node from repository")
411 raise HTTPFound(
412 raise HTTPFound(
412 h.route_path('repo_files', repo_name=self.db_repo_name,
413 h.route_path('repo_files', repo_name=self.db_repo_name,
413 commit_id='tip', f_path=f_path))
414 commit_id='tip', f_path=f_path))
414
415
415 if all(isinstance(node.commit, EmptyCommit)
416 if all(isinstance(node.commit, EmptyCommit)
416 for node in (node1, node2)):
417 for node in (node1, node2)):
417 raise HTTPNotFound()
418 raise HTTPNotFound()
418
419
419 c.commit_1 = node1.commit
420 c.commit_1 = node1.commit
420 c.commit_2 = node2.commit
421 c.commit_2 = node2.commit
421
422
422 if c.action == 'download':
423 if c.action == 'download':
423 _diff = diffs.get_gitdiff(node1, node2,
424 _diff = diffs.get_gitdiff(node1, node2,
424 ignore_whitespace=ignore_whitespace,
425 ignore_whitespace=ignore_whitespace,
425 context=line_context)
426 context=line_context)
426 diff = diffs.DiffProcessor(_diff, format='gitdiff')
427 diff = diffs.DiffProcessor(_diff, format='gitdiff')
427
428
428 response = Response(diff.as_raw())
429 response = Response(diff.as_raw())
429 response.content_type = 'text/plain'
430 response.content_type = 'text/plain'
430 response.content_disposition = (
431 response.content_disposition = (
431 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
432 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
432 )
433 )
433 charset = self._get_default_encoding(c)
434 charset = self._get_default_encoding(c)
434 if charset:
435 if charset:
435 response.charset = charset
436 response.charset = charset
436 return response
437 return response
437
438
438 elif c.action == 'raw':
439 elif c.action == 'raw':
439 _diff = diffs.get_gitdiff(node1, node2,
440 _diff = diffs.get_gitdiff(node1, node2,
440 ignore_whitespace=ignore_whitespace,
441 ignore_whitespace=ignore_whitespace,
441 context=line_context)
442 context=line_context)
442 diff = diffs.DiffProcessor(_diff, format='gitdiff')
443 diff = diffs.DiffProcessor(_diff, format='gitdiff')
443
444
444 response = Response(diff.as_raw())
445 response = Response(diff.as_raw())
445 response.content_type = 'text/plain'
446 response.content_type = 'text/plain'
446 charset = self._get_default_encoding(c)
447 charset = self._get_default_encoding(c)
447 if charset:
448 if charset:
448 response.charset = charset
449 response.charset = charset
449 return response
450 return response
450
451
451 # in case we ever end up here
452 # in case we ever end up here
452 raise HTTPNotFound()
453 raise HTTPNotFound()
453
454
454 @LoginRequired()
455 @LoginRequired()
455 @HasRepoPermissionAnyDecorator(
456 @HasRepoPermissionAnyDecorator(
456 'repository.read', 'repository.write', 'repository.admin')
457 'repository.read', 'repository.write', 'repository.admin')
457 @view_config(
458 @view_config(
458 route_name='repo_files_diff_2way_redirect', request_method='GET',
459 route_name='repo_files_diff_2way_redirect', request_method='GET',
459 renderer=None)
460 renderer=None)
460 def repo_files_diff_2way_redirect(self):
461 def repo_files_diff_2way_redirect(self):
461 """
462 """
462 Kept only to make OLD links work
463 Kept only to make OLD links work
463 """
464 """
464 f_path = self._get_f_path(self.request.matchdict)
465 f_path = self._get_f_path(self.request.matchdict)
465 diff1 = self.request.GET.get('diff1', '')
466 diff1 = self.request.GET.get('diff1', '')
466 diff2 = self.request.GET.get('diff2', '')
467 diff2 = self.request.GET.get('diff2', '')
467
468
468 if not any((diff1, diff2)):
469 if not any((diff1, diff2)):
469 h.flash(
470 h.flash(
470 'Need query parameter "diff1" or "diff2" to generate a diff.',
471 'Need query parameter "diff1" or "diff2" to generate a diff.',
471 category='error')
472 category='error')
472 raise HTTPBadRequest()
473 raise HTTPBadRequest()
473
474
474 compare_url = h.url(
475 compare_url = h.route_path(
475 'compare_url', repo_name=self.db_repo_name,
476 'repo_compare',
477 repo_name=self.db_repo_name,
476 source_ref_type='rev',
478 source_ref_type='rev',
477 source_ref=diff1,
479 source_ref=diff1,
478 target_repo=self.db_repo_name,
479 target_ref_type='rev',
480 target_ref_type='rev',
480 target_ref=diff2,
481 target_ref=diff2,
481 f_path=f_path,
482 _query=dict(f_path=f_path, diffmode='sideside',
482 diffmode='sideside')
483 target_repo=self.db_repo_name,))
483 raise HTTPFound(compare_url)
484 raise HTTPFound(compare_url)
484
485
485 @LoginRequired()
486 @LoginRequired()
486 @HasRepoPermissionAnyDecorator(
487 @HasRepoPermissionAnyDecorator(
487 'repository.read', 'repository.write', 'repository.admin')
488 'repository.read', 'repository.write', 'repository.admin')
488 @view_config(
489 @view_config(
489 route_name='repo_files', request_method='GET',
490 route_name='repo_files', request_method='GET',
490 renderer=None)
491 renderer=None)
491 @view_config(
492 @view_config(
492 route_name='repo_files:default_path', request_method='GET',
493 route_name='repo_files:default_path', request_method='GET',
493 renderer=None)
494 renderer=None)
494 @view_config(
495 @view_config(
495 route_name='repo_files:default_commit', request_method='GET',
496 route_name='repo_files:default_commit', request_method='GET',
496 renderer=None)
497 renderer=None)
497 @view_config(
498 @view_config(
498 route_name='repo_files:rendered', request_method='GET',
499 route_name='repo_files:rendered', request_method='GET',
499 renderer=None)
500 renderer=None)
500 @view_config(
501 @view_config(
501 route_name='repo_files:annotated', request_method='GET',
502 route_name='repo_files:annotated', request_method='GET',
502 renderer=None)
503 renderer=None)
503 def repo_files(self):
504 def repo_files(self):
504 c = self.load_default_context()
505 c = self.load_default_context()
505
506
506 view_name = getattr(self.request.matched_route, 'name', None)
507 view_name = getattr(self.request.matched_route, 'name', None)
507
508
508 c.annotate = view_name == 'repo_files:annotated'
509 c.annotate = view_name == 'repo_files:annotated'
509 # default is false, but .rst/.md files later are auto rendered, we can
510 # default is false, but .rst/.md files later are auto rendered, we can
510 # overwrite auto rendering by setting this GET flag
511 # overwrite auto rendering by setting this GET flag
511 c.renderer = view_name == 'repo_files:rendered' or \
512 c.renderer = view_name == 'repo_files:rendered' or \
512 not self.request.GET.get('no-render', False)
513 not self.request.GET.get('no-render', False)
513
514
514 # redirect to given commit_id from form if given
515 # redirect to given commit_id from form if given
515 get_commit_id = self.request.GET.get('at_rev', None)
516 get_commit_id = self.request.GET.get('at_rev', None)
516 if get_commit_id:
517 if get_commit_id:
517 self._get_commit_or_redirect(get_commit_id)
518 self._get_commit_or_redirect(get_commit_id)
518
519
519 commit_id, f_path = self._get_commit_and_path()
520 commit_id, f_path = self._get_commit_and_path()
520 c.commit = self._get_commit_or_redirect(commit_id)
521 c.commit = self._get_commit_or_redirect(commit_id)
521 c.branch = self.request.GET.get('branch', None)
522 c.branch = self.request.GET.get('branch', None)
522 c.f_path = f_path
523 c.f_path = f_path
523
524
524 # prev link
525 # prev link
525 try:
526 try:
526 prev_commit = c.commit.prev(c.branch)
527 prev_commit = c.commit.prev(c.branch)
527 c.prev_commit = prev_commit
528 c.prev_commit = prev_commit
528 c.url_prev = h.route_path(
529 c.url_prev = h.route_path(
529 'repo_files', repo_name=self.db_repo_name,
530 'repo_files', repo_name=self.db_repo_name,
530 commit_id=prev_commit.raw_id, f_path=f_path)
531 commit_id=prev_commit.raw_id, f_path=f_path)
531 if c.branch:
532 if c.branch:
532 c.url_prev += '?branch=%s' % c.branch
533 c.url_prev += '?branch=%s' % c.branch
533 except (CommitDoesNotExistError, VCSError):
534 except (CommitDoesNotExistError, VCSError):
534 c.url_prev = '#'
535 c.url_prev = '#'
535 c.prev_commit = EmptyCommit()
536 c.prev_commit = EmptyCommit()
536
537
537 # next link
538 # next link
538 try:
539 try:
539 next_commit = c.commit.next(c.branch)
540 next_commit = c.commit.next(c.branch)
540 c.next_commit = next_commit
541 c.next_commit = next_commit
541 c.url_next = h.route_path(
542 c.url_next = h.route_path(
542 'repo_files', repo_name=self.db_repo_name,
543 'repo_files', repo_name=self.db_repo_name,
543 commit_id=next_commit.raw_id, f_path=f_path)
544 commit_id=next_commit.raw_id, f_path=f_path)
544 if c.branch:
545 if c.branch:
545 c.url_next += '?branch=%s' % c.branch
546 c.url_next += '?branch=%s' % c.branch
546 except (CommitDoesNotExistError, VCSError):
547 except (CommitDoesNotExistError, VCSError):
547 c.url_next = '#'
548 c.url_next = '#'
548 c.next_commit = EmptyCommit()
549 c.next_commit = EmptyCommit()
549
550
550 # files or dirs
551 # files or dirs
551 try:
552 try:
552 c.file = c.commit.get_node(f_path)
553 c.file = c.commit.get_node(f_path)
553 c.file_author = True
554 c.file_author = True
554 c.file_tree = ''
555 c.file_tree = ''
555
556
556 # load file content
557 # load file content
557 if c.file.is_file():
558 if c.file.is_file():
558 c.lf_node = c.file.get_largefile_node()
559 c.lf_node = c.file.get_largefile_node()
559
560
560 c.file_source_page = 'true'
561 c.file_source_page = 'true'
561 c.file_last_commit = c.file.last_commit
562 c.file_last_commit = c.file.last_commit
562 if c.file.size < c.visual.cut_off_limit_diff:
563 if c.file.size < c.visual.cut_off_limit_diff:
563 if c.annotate: # annotation has precedence over renderer
564 if c.annotate: # annotation has precedence over renderer
564 c.annotated_lines = filenode_as_annotated_lines_tokens(
565 c.annotated_lines = filenode_as_annotated_lines_tokens(
565 c.file
566 c.file
566 )
567 )
567 else:
568 else:
568 c.renderer = (
569 c.renderer = (
569 c.renderer and h.renderer_from_filename(c.file.path)
570 c.renderer and h.renderer_from_filename(c.file.path)
570 )
571 )
571 if not c.renderer:
572 if not c.renderer:
572 c.lines = filenode_as_lines_tokens(c.file)
573 c.lines = filenode_as_lines_tokens(c.file)
573
574
574 c.on_branch_head = self._is_valid_head(
575 c.on_branch_head = self._is_valid_head(
575 commit_id, self.rhodecode_vcs_repo)
576 commit_id, self.rhodecode_vcs_repo)
576
577
577 branch = c.commit.branch if (
578 branch = c.commit.branch if (
578 c.commit.branch and '/' not in c.commit.branch) else None
579 c.commit.branch and '/' not in c.commit.branch) else None
579 c.branch_or_raw_id = branch or c.commit.raw_id
580 c.branch_or_raw_id = branch or c.commit.raw_id
580 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
581 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
581
582
582 author = c.file_last_commit.author
583 author = c.file_last_commit.author
583 c.authors = [[
584 c.authors = [[
584 h.email(author),
585 h.email(author),
585 h.person(author, 'username_or_name_or_email'),
586 h.person(author, 'username_or_name_or_email'),
586 1
587 1
587 ]]
588 ]]
588
589
589 else: # load tree content at path
590 else: # load tree content at path
590 c.file_source_page = 'false'
591 c.file_source_page = 'false'
591 c.authors = []
592 c.authors = []
592 # this loads a simple tree without metadata to speed things up
593 # this loads a simple tree without metadata to speed things up
593 # later via ajax we call repo_nodetree_full and fetch whole
594 # later via ajax we call repo_nodetree_full and fetch whole
594 c.file_tree = self._get_tree_at_commit(
595 c.file_tree = self._get_tree_at_commit(
595 c, c.commit.raw_id, f_path)
596 c, c.commit.raw_id, f_path)
596
597
597 except RepositoryError as e:
598 except RepositoryError as e:
598 h.flash(safe_str(h.escape(e)), category='error')
599 h.flash(safe_str(h.escape(e)), category='error')
599 raise HTTPNotFound()
600 raise HTTPNotFound()
600
601
601 if self.request.environ.get('HTTP_X_PJAX'):
602 if self.request.environ.get('HTTP_X_PJAX'):
602 html = render('rhodecode:templates/files/files_pjax.mako',
603 html = render('rhodecode:templates/files/files_pjax.mako',
603 self._get_template_context(c), self.request)
604 self._get_template_context(c), self.request)
604 else:
605 else:
605 html = render('rhodecode:templates/files/files.mako',
606 html = render('rhodecode:templates/files/files.mako',
606 self._get_template_context(c), self.request)
607 self._get_template_context(c), self.request)
607 return Response(html)
608 return Response(html)
608
609
609 @HasRepoPermissionAnyDecorator(
610 @HasRepoPermissionAnyDecorator(
610 'repository.read', 'repository.write', 'repository.admin')
611 'repository.read', 'repository.write', 'repository.admin')
611 @view_config(
612 @view_config(
612 route_name='repo_files:annotated_previous', request_method='GET',
613 route_name='repo_files:annotated_previous', request_method='GET',
613 renderer=None)
614 renderer=None)
614 def repo_files_annotated_previous(self):
615 def repo_files_annotated_previous(self):
615 self.load_default_context()
616 self.load_default_context()
616
617
617 commit_id, f_path = self._get_commit_and_path()
618 commit_id, f_path = self._get_commit_and_path()
618 commit = self._get_commit_or_redirect(commit_id)
619 commit = self._get_commit_or_redirect(commit_id)
619 prev_commit_id = commit.raw_id
620 prev_commit_id = commit.raw_id
620 line_anchor = self.request.GET.get('line_anchor')
621 line_anchor = self.request.GET.get('line_anchor')
621 is_file = False
622 is_file = False
622 try:
623 try:
623 _file = commit.get_node(f_path)
624 _file = commit.get_node(f_path)
624 is_file = _file.is_file()
625 is_file = _file.is_file()
625 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
626 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
626 pass
627 pass
627
628
628 if is_file:
629 if is_file:
629 history = commit.get_file_history(f_path)
630 history = commit.get_file_history(f_path)
630 prev_commit_id = history[1].raw_id \
631 prev_commit_id = history[1].raw_id \
631 if len(history) > 1 else prev_commit_id
632 if len(history) > 1 else prev_commit_id
632 prev_url = h.route_path(
633 prev_url = h.route_path(
633 'repo_files:annotated', repo_name=self.db_repo_name,
634 'repo_files:annotated', repo_name=self.db_repo_name,
634 commit_id=prev_commit_id, f_path=f_path,
635 commit_id=prev_commit_id, f_path=f_path,
635 _anchor='L{}'.format(line_anchor))
636 _anchor='L{}'.format(line_anchor))
636
637
637 raise HTTPFound(prev_url)
638 raise HTTPFound(prev_url)
638
639
639 @LoginRequired()
640 @LoginRequired()
640 @HasRepoPermissionAnyDecorator(
641 @HasRepoPermissionAnyDecorator(
641 'repository.read', 'repository.write', 'repository.admin')
642 'repository.read', 'repository.write', 'repository.admin')
642 @view_config(
643 @view_config(
643 route_name='repo_nodetree_full', request_method='GET',
644 route_name='repo_nodetree_full', request_method='GET',
644 renderer=None, xhr=True)
645 renderer=None, xhr=True)
645 @view_config(
646 @view_config(
646 route_name='repo_nodetree_full:default_path', request_method='GET',
647 route_name='repo_nodetree_full:default_path', request_method='GET',
647 renderer=None, xhr=True)
648 renderer=None, xhr=True)
648 def repo_nodetree_full(self):
649 def repo_nodetree_full(self):
649 """
650 """
650 Returns rendered html of file tree that contains commit date,
651 Returns rendered html of file tree that contains commit date,
651 author, commit_id for the specified combination of
652 author, commit_id for the specified combination of
652 repo, commit_id and file path
653 repo, commit_id and file path
653 """
654 """
654 c = self.load_default_context()
655 c = self.load_default_context()
655
656
656 commit_id, f_path = self._get_commit_and_path()
657 commit_id, f_path = self._get_commit_and_path()
657 commit = self._get_commit_or_redirect(commit_id)
658 commit = self._get_commit_or_redirect(commit_id)
658 try:
659 try:
659 dir_node = commit.get_node(f_path)
660 dir_node = commit.get_node(f_path)
660 except RepositoryError as e:
661 except RepositoryError as e:
661 return Response('error: {}'.format(safe_str(e)))
662 return Response('error: {}'.format(safe_str(e)))
662
663
663 if dir_node.is_file():
664 if dir_node.is_file():
664 return Response('')
665 return Response('')
665
666
666 c.file = dir_node
667 c.file = dir_node
667 c.commit = commit
668 c.commit = commit
668
669
669 # using force=True here, make a little trick. We flush the cache and
670 # using force=True here, make a little trick. We flush the cache and
670 # compute it using the same key as without previous full_load, so now
671 # compute it using the same key as without previous full_load, so now
671 # the fully loaded tree is now returned instead of partial,
672 # the fully loaded tree is now returned instead of partial,
672 # and we store this in caches
673 # and we store this in caches
673 html = self._get_tree_at_commit(
674 html = self._get_tree_at_commit(
674 c, commit.raw_id, dir_node.path, full_load=True, force=True)
675 c, commit.raw_id, dir_node.path, full_load=True, force=True)
675
676
676 return Response(html)
677 return Response(html)
677
678
678 def _get_attachement_disposition(self, f_path):
679 def _get_attachement_disposition(self, f_path):
679 return 'attachment; filename=%s' % \
680 return 'attachment; filename=%s' % \
680 safe_str(f_path.split(Repository.NAME_SEP)[-1])
681 safe_str(f_path.split(Repository.NAME_SEP)[-1])
681
682
682 @LoginRequired()
683 @LoginRequired()
683 @HasRepoPermissionAnyDecorator(
684 @HasRepoPermissionAnyDecorator(
684 'repository.read', 'repository.write', 'repository.admin')
685 'repository.read', 'repository.write', 'repository.admin')
685 @view_config(
686 @view_config(
686 route_name='repo_file_raw', request_method='GET',
687 route_name='repo_file_raw', request_method='GET',
687 renderer=None)
688 renderer=None)
688 def repo_file_raw(self):
689 def repo_file_raw(self):
689 """
690 """
690 Action for show as raw, some mimetypes are "rendered",
691 Action for show as raw, some mimetypes are "rendered",
691 those include images, icons.
692 those include images, icons.
692 """
693 """
693 c = self.load_default_context()
694 c = self.load_default_context()
694
695
695 commit_id, f_path = self._get_commit_and_path()
696 commit_id, f_path = self._get_commit_and_path()
696 commit = self._get_commit_or_redirect(commit_id)
697 commit = self._get_commit_or_redirect(commit_id)
697 file_node = self._get_filenode_or_redirect(commit, f_path)
698 file_node = self._get_filenode_or_redirect(commit, f_path)
698
699
699 raw_mimetype_mapping = {
700 raw_mimetype_mapping = {
700 # map original mimetype to a mimetype used for "show as raw"
701 # map original mimetype to a mimetype used for "show as raw"
701 # you can also provide a content-disposition to override the
702 # you can also provide a content-disposition to override the
702 # default "attachment" disposition.
703 # default "attachment" disposition.
703 # orig_type: (new_type, new_dispo)
704 # orig_type: (new_type, new_dispo)
704
705
705 # show images inline:
706 # show images inline:
706 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
707 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
707 # for example render an SVG with javascript inside or even render
708 # for example render an SVG with javascript inside or even render
708 # HTML.
709 # HTML.
709 'image/x-icon': ('image/x-icon', 'inline'),
710 'image/x-icon': ('image/x-icon', 'inline'),
710 'image/png': ('image/png', 'inline'),
711 'image/png': ('image/png', 'inline'),
711 'image/gif': ('image/gif', 'inline'),
712 'image/gif': ('image/gif', 'inline'),
712 'image/jpeg': ('image/jpeg', 'inline'),
713 'image/jpeg': ('image/jpeg', 'inline'),
713 'application/pdf': ('application/pdf', 'inline'),
714 'application/pdf': ('application/pdf', 'inline'),
714 }
715 }
715
716
716 mimetype = file_node.mimetype
717 mimetype = file_node.mimetype
717 try:
718 try:
718 mimetype, disposition = raw_mimetype_mapping[mimetype]
719 mimetype, disposition = raw_mimetype_mapping[mimetype]
719 except KeyError:
720 except KeyError:
720 # we don't know anything special about this, handle it safely
721 # we don't know anything special about this, handle it safely
721 if file_node.is_binary:
722 if file_node.is_binary:
722 # do same as download raw for binary files
723 # do same as download raw for binary files
723 mimetype, disposition = 'application/octet-stream', 'attachment'
724 mimetype, disposition = 'application/octet-stream', 'attachment'
724 else:
725 else:
725 # do not just use the original mimetype, but force text/plain,
726 # do not just use the original mimetype, but force text/plain,
726 # otherwise it would serve text/html and that might be unsafe.
727 # otherwise it would serve text/html and that might be unsafe.
727 # Note: underlying vcs library fakes text/plain mimetype if the
728 # Note: underlying vcs library fakes text/plain mimetype if the
728 # mimetype can not be determined and it thinks it is not
729 # mimetype can not be determined and it thinks it is not
729 # binary.This might lead to erroneous text display in some
730 # binary.This might lead to erroneous text display in some
730 # cases, but helps in other cases, like with text files
731 # cases, but helps in other cases, like with text files
731 # without extension.
732 # without extension.
732 mimetype, disposition = 'text/plain', 'inline'
733 mimetype, disposition = 'text/plain', 'inline'
733
734
734 if disposition == 'attachment':
735 if disposition == 'attachment':
735 disposition = self._get_attachement_disposition(f_path)
736 disposition = self._get_attachement_disposition(f_path)
736
737
737 def stream_node():
738 def stream_node():
738 yield file_node.raw_bytes
739 yield file_node.raw_bytes
739
740
740 response = Response(app_iter=stream_node())
741 response = Response(app_iter=stream_node())
741 response.content_disposition = disposition
742 response.content_disposition = disposition
742 response.content_type = mimetype
743 response.content_type = mimetype
743
744
744 charset = self._get_default_encoding(c)
745 charset = self._get_default_encoding(c)
745 if charset:
746 if charset:
746 response.charset = charset
747 response.charset = charset
747
748
748 return response
749 return response
749
750
750 @LoginRequired()
751 @LoginRequired()
751 @HasRepoPermissionAnyDecorator(
752 @HasRepoPermissionAnyDecorator(
752 'repository.read', 'repository.write', 'repository.admin')
753 'repository.read', 'repository.write', 'repository.admin')
753 @view_config(
754 @view_config(
754 route_name='repo_file_download', request_method='GET',
755 route_name='repo_file_download', request_method='GET',
755 renderer=None)
756 renderer=None)
756 @view_config(
757 @view_config(
757 route_name='repo_file_download:legacy', request_method='GET',
758 route_name='repo_file_download:legacy', request_method='GET',
758 renderer=None)
759 renderer=None)
759 def repo_file_download(self):
760 def repo_file_download(self):
760 c = self.load_default_context()
761 c = self.load_default_context()
761
762
762 commit_id, f_path = self._get_commit_and_path()
763 commit_id, f_path = self._get_commit_and_path()
763 commit = self._get_commit_or_redirect(commit_id)
764 commit = self._get_commit_or_redirect(commit_id)
764 file_node = self._get_filenode_or_redirect(commit, f_path)
765 file_node = self._get_filenode_or_redirect(commit, f_path)
765
766
766 if self.request.GET.get('lf'):
767 if self.request.GET.get('lf'):
767 # only if lf get flag is passed, we download this file
768 # only if lf get flag is passed, we download this file
768 # as LFS/Largefile
769 # as LFS/Largefile
769 lf_node = file_node.get_largefile_node()
770 lf_node = file_node.get_largefile_node()
770 if lf_node:
771 if lf_node:
771 # overwrite our pointer with the REAL large-file
772 # overwrite our pointer with the REAL large-file
772 file_node = lf_node
773 file_node = lf_node
773
774
774 disposition = self._get_attachement_disposition(f_path)
775 disposition = self._get_attachement_disposition(f_path)
775
776
776 def stream_node():
777 def stream_node():
777 yield file_node.raw_bytes
778 yield file_node.raw_bytes
778
779
779 response = Response(app_iter=stream_node())
780 response = Response(app_iter=stream_node())
780 response.content_disposition = disposition
781 response.content_disposition = disposition
781 response.content_type = file_node.mimetype
782 response.content_type = file_node.mimetype
782
783
783 charset = self._get_default_encoding(c)
784 charset = self._get_default_encoding(c)
784 if charset:
785 if charset:
785 response.charset = charset
786 response.charset = charset
786
787
787 return response
788 return response
788
789
789 def _get_nodelist_at_commit(self, repo_name, commit_id, f_path):
790 def _get_nodelist_at_commit(self, repo_name, commit_id, f_path):
790 def _cached_nodes():
791 def _cached_nodes():
791 log.debug('Generating cached nodelist for %s, %s, %s',
792 log.debug('Generating cached nodelist for %s, %s, %s',
792 repo_name, commit_id, f_path)
793 repo_name, commit_id, f_path)
793 _d, _f = ScmModel().get_nodes(
794 _d, _f = ScmModel().get_nodes(
794 repo_name, commit_id, f_path, flat=False)
795 repo_name, commit_id, f_path, flat=False)
795 return _d + _f
796 return _d + _f
796
797
797 cache_manager = self._get_tree_cache_manager(caches.FILE_SEARCH_TREE_META)
798 cache_manager = self._get_tree_cache_manager(caches.FILE_SEARCH_TREE_META)
798
799
799 cache_key = caches.compute_key_from_params(
800 cache_key = caches.compute_key_from_params(
800 repo_name, commit_id, f_path)
801 repo_name, commit_id, f_path)
801 return cache_manager.get(cache_key, createfunc=_cached_nodes)
802 return cache_manager.get(cache_key, createfunc=_cached_nodes)
802
803
803 @LoginRequired()
804 @LoginRequired()
804 @HasRepoPermissionAnyDecorator(
805 @HasRepoPermissionAnyDecorator(
805 'repository.read', 'repository.write', 'repository.admin')
806 'repository.read', 'repository.write', 'repository.admin')
806 @view_config(
807 @view_config(
807 route_name='repo_files_nodelist', request_method='GET',
808 route_name='repo_files_nodelist', request_method='GET',
808 renderer='json_ext', xhr=True)
809 renderer='json_ext', xhr=True)
809 def repo_nodelist(self):
810 def repo_nodelist(self):
810 self.load_default_context()
811 self.load_default_context()
811
812
812 commit_id, f_path = self._get_commit_and_path()
813 commit_id, f_path = self._get_commit_and_path()
813 commit = self._get_commit_or_redirect(commit_id)
814 commit = self._get_commit_or_redirect(commit_id)
814
815
815 metadata = self._get_nodelist_at_commit(
816 metadata = self._get_nodelist_at_commit(
816 self.db_repo_name, commit.raw_id, f_path)
817 self.db_repo_name, commit.raw_id, f_path)
817 return {'nodes': metadata}
818 return {'nodes': metadata}
818
819
819 def _create_references(
820 def _create_references(
820 self, branches_or_tags, symbolic_reference, f_path):
821 self, branches_or_tags, symbolic_reference, f_path):
821 items = []
822 items = []
822 for name, commit_id in branches_or_tags.items():
823 for name, commit_id in branches_or_tags.items():
823 sym_ref = symbolic_reference(commit_id, name, f_path)
824 sym_ref = symbolic_reference(commit_id, name, f_path)
824 items.append((sym_ref, name))
825 items.append((sym_ref, name))
825 return items
826 return items
826
827
827 def _symbolic_reference(self, commit_id, name, f_path):
828 def _symbolic_reference(self, commit_id, name, f_path):
828 return commit_id
829 return commit_id
829
830
830 def _symbolic_reference_svn(self, commit_id, name, f_path):
831 def _symbolic_reference_svn(self, commit_id, name, f_path):
831 new_f_path = vcspath.join(name, f_path)
832 new_f_path = vcspath.join(name, f_path)
832 return u'%s@%s' % (new_f_path, commit_id)
833 return u'%s@%s' % (new_f_path, commit_id)
833
834
834 def _get_node_history(self, commit_obj, f_path, commits=None):
835 def _get_node_history(self, commit_obj, f_path, commits=None):
835 """
836 """
836 get commit history for given node
837 get commit history for given node
837
838
838 :param commit_obj: commit to calculate history
839 :param commit_obj: commit to calculate history
839 :param f_path: path for node to calculate history for
840 :param f_path: path for node to calculate history for
840 :param commits: if passed don't calculate history and take
841 :param commits: if passed don't calculate history and take
841 commits defined in this list
842 commits defined in this list
842 """
843 """
843 _ = self.request.translate
844 _ = self.request.translate
844
845
845 # calculate history based on tip
846 # calculate history based on tip
846 tip = self.rhodecode_vcs_repo.get_commit()
847 tip = self.rhodecode_vcs_repo.get_commit()
847 if commits is None:
848 if commits is None:
848 pre_load = ["author", "branch"]
849 pre_load = ["author", "branch"]
849 try:
850 try:
850 commits = tip.get_file_history(f_path, pre_load=pre_load)
851 commits = tip.get_file_history(f_path, pre_load=pre_load)
851 except (NodeDoesNotExistError, CommitError):
852 except (NodeDoesNotExistError, CommitError):
852 # this node is not present at tip!
853 # this node is not present at tip!
853 commits = commit_obj.get_file_history(f_path, pre_load=pre_load)
854 commits = commit_obj.get_file_history(f_path, pre_load=pre_load)
854
855
855 history = []
856 history = []
856 commits_group = ([], _("Changesets"))
857 commits_group = ([], _("Changesets"))
857 for commit in commits:
858 for commit in commits:
858 branch = ' (%s)' % commit.branch if commit.branch else ''
859 branch = ' (%s)' % commit.branch if commit.branch else ''
859 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
860 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
860 commits_group[0].append((commit.raw_id, n_desc,))
861 commits_group[0].append((commit.raw_id, n_desc,))
861 history.append(commits_group)
862 history.append(commits_group)
862
863
863 symbolic_reference = self._symbolic_reference
864 symbolic_reference = self._symbolic_reference
864
865
865 if self.rhodecode_vcs_repo.alias == 'svn':
866 if self.rhodecode_vcs_repo.alias == 'svn':
866 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
867 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
867 f_path, self.rhodecode_vcs_repo)
868 f_path, self.rhodecode_vcs_repo)
868 if adjusted_f_path != f_path:
869 if adjusted_f_path != f_path:
869 log.debug(
870 log.debug(
870 'Recognized svn tag or branch in file "%s", using svn '
871 'Recognized svn tag or branch in file "%s", using svn '
871 'specific symbolic references', f_path)
872 'specific symbolic references', f_path)
872 f_path = adjusted_f_path
873 f_path = adjusted_f_path
873 symbolic_reference = self._symbolic_reference_svn
874 symbolic_reference = self._symbolic_reference_svn
874
875
875 branches = self._create_references(
876 branches = self._create_references(
876 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path)
877 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path)
877 branches_group = (branches, _("Branches"))
878 branches_group = (branches, _("Branches"))
878
879
879 tags = self._create_references(
880 tags = self._create_references(
880 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path)
881 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path)
881 tags_group = (tags, _("Tags"))
882 tags_group = (tags, _("Tags"))
882
883
883 history.append(branches_group)
884 history.append(branches_group)
884 history.append(tags_group)
885 history.append(tags_group)
885
886
886 return history, commits
887 return history, commits
887
888
888 @LoginRequired()
889 @LoginRequired()
889 @HasRepoPermissionAnyDecorator(
890 @HasRepoPermissionAnyDecorator(
890 'repository.read', 'repository.write', 'repository.admin')
891 'repository.read', 'repository.write', 'repository.admin')
891 @view_config(
892 @view_config(
892 route_name='repo_file_history', request_method='GET',
893 route_name='repo_file_history', request_method='GET',
893 renderer='json_ext')
894 renderer='json_ext')
894 def repo_file_history(self):
895 def repo_file_history(self):
895 self.load_default_context()
896 self.load_default_context()
896
897
897 commit_id, f_path = self._get_commit_and_path()
898 commit_id, f_path = self._get_commit_and_path()
898 commit = self._get_commit_or_redirect(commit_id)
899 commit = self._get_commit_or_redirect(commit_id)
899 file_node = self._get_filenode_or_redirect(commit, f_path)
900 file_node = self._get_filenode_or_redirect(commit, f_path)
900
901
901 if file_node.is_file():
902 if file_node.is_file():
902 file_history, _hist = self._get_node_history(commit, f_path)
903 file_history, _hist = self._get_node_history(commit, f_path)
903
904
904 res = []
905 res = []
905 for obj in file_history:
906 for obj in file_history:
906 res.append({
907 res.append({
907 'text': obj[1],
908 'text': obj[1],
908 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
909 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
909 })
910 })
910
911
911 data = {
912 data = {
912 'more': False,
913 'more': False,
913 'results': res
914 'results': res
914 }
915 }
915 return data
916 return data
916
917
917 log.warning('Cannot fetch history for directory')
918 log.warning('Cannot fetch history for directory')
918 raise HTTPBadRequest()
919 raise HTTPBadRequest()
919
920
920 @LoginRequired()
921 @LoginRequired()
921 @HasRepoPermissionAnyDecorator(
922 @HasRepoPermissionAnyDecorator(
922 'repository.read', 'repository.write', 'repository.admin')
923 'repository.read', 'repository.write', 'repository.admin')
923 @view_config(
924 @view_config(
924 route_name='repo_file_authors', request_method='GET',
925 route_name='repo_file_authors', request_method='GET',
925 renderer='rhodecode:templates/files/file_authors_box.mako')
926 renderer='rhodecode:templates/files/file_authors_box.mako')
926 def repo_file_authors(self):
927 def repo_file_authors(self):
927 c = self.load_default_context()
928 c = self.load_default_context()
928
929
929 commit_id, f_path = self._get_commit_and_path()
930 commit_id, f_path = self._get_commit_and_path()
930 commit = self._get_commit_or_redirect(commit_id)
931 commit = self._get_commit_or_redirect(commit_id)
931 file_node = self._get_filenode_or_redirect(commit, f_path)
932 file_node = self._get_filenode_or_redirect(commit, f_path)
932
933
933 if not file_node.is_file():
934 if not file_node.is_file():
934 raise HTTPBadRequest()
935 raise HTTPBadRequest()
935
936
936 c.file_last_commit = file_node.last_commit
937 c.file_last_commit = file_node.last_commit
937 if self.request.GET.get('annotate') == '1':
938 if self.request.GET.get('annotate') == '1':
938 # use _hist from annotation if annotation mode is on
939 # use _hist from annotation if annotation mode is on
939 commit_ids = set(x[1] for x in file_node.annotate)
940 commit_ids = set(x[1] for x in file_node.annotate)
940 _hist = (
941 _hist = (
941 self.rhodecode_vcs_repo.get_commit(commit_id)
942 self.rhodecode_vcs_repo.get_commit(commit_id)
942 for commit_id in commit_ids)
943 for commit_id in commit_ids)
943 else:
944 else:
944 _f_history, _hist = self._get_node_history(commit, f_path)
945 _f_history, _hist = self._get_node_history(commit, f_path)
945 c.file_author = False
946 c.file_author = False
946
947
947 unique = collections.OrderedDict()
948 unique = collections.OrderedDict()
948 for commit in _hist:
949 for commit in _hist:
949 author = commit.author
950 author = commit.author
950 if author not in unique:
951 if author not in unique:
951 unique[commit.author] = [
952 unique[commit.author] = [
952 h.email(author),
953 h.email(author),
953 h.person(author, 'username_or_name_or_email'),
954 h.person(author, 'username_or_name_or_email'),
954 1 # counter
955 1 # counter
955 ]
956 ]
956
957
957 else:
958 else:
958 # increase counter
959 # increase counter
959 unique[commit.author][2] += 1
960 unique[commit.author][2] += 1
960
961
961 c.authors = [val for val in unique.values()]
962 c.authors = [val for val in unique.values()]
962
963
963 return self._get_template_context(c)
964 return self._get_template_context(c)
964
965
965 @LoginRequired()
966 @LoginRequired()
966 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
967 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
967 @view_config(
968 @view_config(
968 route_name='repo_files_remove_file', request_method='GET',
969 route_name='repo_files_remove_file', request_method='GET',
969 renderer='rhodecode:templates/files/files_delete.mako')
970 renderer='rhodecode:templates/files/files_delete.mako')
970 def repo_files_remove_file(self):
971 def repo_files_remove_file(self):
971 _ = self.request.translate
972 _ = self.request.translate
972 c = self.load_default_context()
973 c = self.load_default_context()
973 commit_id, f_path = self._get_commit_and_path()
974 commit_id, f_path = self._get_commit_and_path()
974
975
975 self._ensure_not_locked()
976 self._ensure_not_locked()
976
977
977 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
978 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
978 h.flash(_('You can only delete files with commit '
979 h.flash(_('You can only delete files with commit '
979 'being a valid branch '), category='warning')
980 'being a valid branch '), category='warning')
980 raise HTTPFound(
981 raise HTTPFound(
981 h.route_path('repo_files',
982 h.route_path('repo_files',
982 repo_name=self.db_repo_name, commit_id='tip',
983 repo_name=self.db_repo_name, commit_id='tip',
983 f_path=f_path))
984 f_path=f_path))
984
985
985 c.commit = self._get_commit_or_redirect(commit_id)
986 c.commit = self._get_commit_or_redirect(commit_id)
986 c.file = self._get_filenode_or_redirect(c.commit, f_path)
987 c.file = self._get_filenode_or_redirect(c.commit, f_path)
987
988
988 c.default_message = _(
989 c.default_message = _(
989 'Deleted file {} via RhodeCode Enterprise').format(f_path)
990 'Deleted file {} via RhodeCode Enterprise').format(f_path)
990 c.f_path = f_path
991 c.f_path = f_path
991
992
992 return self._get_template_context(c)
993 return self._get_template_context(c)
993
994
994 @LoginRequired()
995 @LoginRequired()
995 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
996 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
996 @CSRFRequired()
997 @CSRFRequired()
997 @view_config(
998 @view_config(
998 route_name='repo_files_delete_file', request_method='POST',
999 route_name='repo_files_delete_file', request_method='POST',
999 renderer=None)
1000 renderer=None)
1000 def repo_files_delete_file(self):
1001 def repo_files_delete_file(self):
1001 _ = self.request.translate
1002 _ = self.request.translate
1002
1003
1003 c = self.load_default_context()
1004 c = self.load_default_context()
1004 commit_id, f_path = self._get_commit_and_path()
1005 commit_id, f_path = self._get_commit_and_path()
1005
1006
1006 self._ensure_not_locked()
1007 self._ensure_not_locked()
1007
1008
1008 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1009 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1009 h.flash(_('You can only delete files with commit '
1010 h.flash(_('You can only delete files with commit '
1010 'being a valid branch '), category='warning')
1011 'being a valid branch '), category='warning')
1011 raise HTTPFound(
1012 raise HTTPFound(
1012 h.route_path('repo_files',
1013 h.route_path('repo_files',
1013 repo_name=self.db_repo_name, commit_id='tip',
1014 repo_name=self.db_repo_name, commit_id='tip',
1014 f_path=f_path))
1015 f_path=f_path))
1015
1016
1016 c.commit = self._get_commit_or_redirect(commit_id)
1017 c.commit = self._get_commit_or_redirect(commit_id)
1017 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1018 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1018
1019
1019 c.default_message = _(
1020 c.default_message = _(
1020 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1021 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1021 c.f_path = f_path
1022 c.f_path = f_path
1022 node_path = f_path
1023 node_path = f_path
1023 author = self._rhodecode_db_user.full_contact
1024 author = self._rhodecode_db_user.full_contact
1024 message = self.request.POST.get('message') or c.default_message
1025 message = self.request.POST.get('message') or c.default_message
1025 try:
1026 try:
1026 nodes = {
1027 nodes = {
1027 node_path: {
1028 node_path: {
1028 'content': ''
1029 'content': ''
1029 }
1030 }
1030 }
1031 }
1031 ScmModel().delete_nodes(
1032 ScmModel().delete_nodes(
1032 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1033 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1033 message=message,
1034 message=message,
1034 nodes=nodes,
1035 nodes=nodes,
1035 parent_commit=c.commit,
1036 parent_commit=c.commit,
1036 author=author,
1037 author=author,
1037 )
1038 )
1038
1039
1039 h.flash(
1040 h.flash(
1040 _('Successfully deleted file `{}`').format(
1041 _('Successfully deleted file `{}`').format(
1041 h.escape(f_path)), category='success')
1042 h.escape(f_path)), category='success')
1042 except Exception:
1043 except Exception:
1043 log.exception('Error during commit operation')
1044 log.exception('Error during commit operation')
1044 h.flash(_('Error occurred during commit'), category='error')
1045 h.flash(_('Error occurred during commit'), category='error')
1045 raise HTTPFound(
1046 raise HTTPFound(
1046 h.route_path('repo_commit', repo_name=self.db_repo_name,
1047 h.route_path('repo_commit', repo_name=self.db_repo_name,
1047 commit_id='tip'))
1048 commit_id='tip'))
1048
1049
1049 @LoginRequired()
1050 @LoginRequired()
1050 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1051 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1051 @view_config(
1052 @view_config(
1052 route_name='repo_files_edit_file', request_method='GET',
1053 route_name='repo_files_edit_file', request_method='GET',
1053 renderer='rhodecode:templates/files/files_edit.mako')
1054 renderer='rhodecode:templates/files/files_edit.mako')
1054 def repo_files_edit_file(self):
1055 def repo_files_edit_file(self):
1055 _ = self.request.translate
1056 _ = self.request.translate
1056 c = self.load_default_context()
1057 c = self.load_default_context()
1057 commit_id, f_path = self._get_commit_and_path()
1058 commit_id, f_path = self._get_commit_and_path()
1058
1059
1059 self._ensure_not_locked()
1060 self._ensure_not_locked()
1060
1061
1061 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1062 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1062 h.flash(_('You can only edit files with commit '
1063 h.flash(_('You can only edit files with commit '
1063 'being a valid branch '), category='warning')
1064 'being a valid branch '), category='warning')
1064 raise HTTPFound(
1065 raise HTTPFound(
1065 h.route_path('repo_files',
1066 h.route_path('repo_files',
1066 repo_name=self.db_repo_name, commit_id='tip',
1067 repo_name=self.db_repo_name, commit_id='tip',
1067 f_path=f_path))
1068 f_path=f_path))
1068
1069
1069 c.commit = self._get_commit_or_redirect(commit_id)
1070 c.commit = self._get_commit_or_redirect(commit_id)
1070 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1071 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1071
1072
1072 if c.file.is_binary:
1073 if c.file.is_binary:
1073 files_url = h.route_path(
1074 files_url = h.route_path(
1074 'repo_files',
1075 'repo_files',
1075 repo_name=self.db_repo_name,
1076 repo_name=self.db_repo_name,
1076 commit_id=c.commit.raw_id, f_path=f_path)
1077 commit_id=c.commit.raw_id, f_path=f_path)
1077 raise HTTPFound(files_url)
1078 raise HTTPFound(files_url)
1078
1079
1079 c.default_message = _(
1080 c.default_message = _(
1080 'Edited file {} via RhodeCode Enterprise').format(f_path)
1081 'Edited file {} via RhodeCode Enterprise').format(f_path)
1081 c.f_path = f_path
1082 c.f_path = f_path
1082
1083
1083 return self._get_template_context(c)
1084 return self._get_template_context(c)
1084
1085
1085 @LoginRequired()
1086 @LoginRequired()
1086 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1087 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1087 @CSRFRequired()
1088 @CSRFRequired()
1088 @view_config(
1089 @view_config(
1089 route_name='repo_files_update_file', request_method='POST',
1090 route_name='repo_files_update_file', request_method='POST',
1090 renderer=None)
1091 renderer=None)
1091 def repo_files_update_file(self):
1092 def repo_files_update_file(self):
1092 _ = self.request.translate
1093 _ = self.request.translate
1093 c = self.load_default_context()
1094 c = self.load_default_context()
1094 commit_id, f_path = self._get_commit_and_path()
1095 commit_id, f_path = self._get_commit_and_path()
1095
1096
1096 self._ensure_not_locked()
1097 self._ensure_not_locked()
1097
1098
1098 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1099 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1099 h.flash(_('You can only edit files with commit '
1100 h.flash(_('You can only edit files with commit '
1100 'being a valid branch '), category='warning')
1101 'being a valid branch '), category='warning')
1101 raise HTTPFound(
1102 raise HTTPFound(
1102 h.route_path('repo_files',
1103 h.route_path('repo_files',
1103 repo_name=self.db_repo_name, commit_id='tip',
1104 repo_name=self.db_repo_name, commit_id='tip',
1104 f_path=f_path))
1105 f_path=f_path))
1105
1106
1106 c.commit = self._get_commit_or_redirect(commit_id)
1107 c.commit = self._get_commit_or_redirect(commit_id)
1107 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1108 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1108
1109
1109 if c.file.is_binary:
1110 if c.file.is_binary:
1110 raise HTTPFound(
1111 raise HTTPFound(
1111 h.route_path('repo_files',
1112 h.route_path('repo_files',
1112 repo_name=self.db_repo_name,
1113 repo_name=self.db_repo_name,
1113 commit_id=c.commit.raw_id,
1114 commit_id=c.commit.raw_id,
1114 f_path=f_path))
1115 f_path=f_path))
1115
1116
1116 c.default_message = _(
1117 c.default_message = _(
1117 'Edited file {} via RhodeCode Enterprise').format(f_path)
1118 'Edited file {} via RhodeCode Enterprise').format(f_path)
1118 c.f_path = f_path
1119 c.f_path = f_path
1119 old_content = c.file.content
1120 old_content = c.file.content
1120 sl = old_content.splitlines(1)
1121 sl = old_content.splitlines(1)
1121 first_line = sl[0] if sl else ''
1122 first_line = sl[0] if sl else ''
1122
1123
1123 r_post = self.request.POST
1124 r_post = self.request.POST
1124 # modes: 0 - Unix, 1 - Mac, 2 - DOS
1125 # modes: 0 - Unix, 1 - Mac, 2 - DOS
1125 mode = detect_mode(first_line, 0)
1126 mode = detect_mode(first_line, 0)
1126 content = convert_line_endings(r_post.get('content', ''), mode)
1127 content = convert_line_endings(r_post.get('content', ''), mode)
1127
1128
1128 message = r_post.get('message') or c.default_message
1129 message = r_post.get('message') or c.default_message
1129 org_f_path = c.file.unicode_path
1130 org_f_path = c.file.unicode_path
1130 filename = r_post['filename']
1131 filename = r_post['filename']
1131 org_filename = c.file.name
1132 org_filename = c.file.name
1132
1133
1133 if content == old_content and filename == org_filename:
1134 if content == old_content and filename == org_filename:
1134 h.flash(_('No changes'), category='warning')
1135 h.flash(_('No changes'), category='warning')
1135 raise HTTPFound(
1136 raise HTTPFound(
1136 h.route_path('repo_commit', repo_name=self.db_repo_name,
1137 h.route_path('repo_commit', repo_name=self.db_repo_name,
1137 commit_id='tip'))
1138 commit_id='tip'))
1138 try:
1139 try:
1139 mapping = {
1140 mapping = {
1140 org_f_path: {
1141 org_f_path: {
1141 'org_filename': org_f_path,
1142 'org_filename': org_f_path,
1142 'filename': os.path.join(c.file.dir_path, filename),
1143 'filename': os.path.join(c.file.dir_path, filename),
1143 'content': content,
1144 'content': content,
1144 'lexer': '',
1145 'lexer': '',
1145 'op': 'mod',
1146 'op': 'mod',
1146 }
1147 }
1147 }
1148 }
1148
1149
1149 ScmModel().update_nodes(
1150 ScmModel().update_nodes(
1150 user=self._rhodecode_db_user.user_id,
1151 user=self._rhodecode_db_user.user_id,
1151 repo=self.db_repo,
1152 repo=self.db_repo,
1152 message=message,
1153 message=message,
1153 nodes=mapping,
1154 nodes=mapping,
1154 parent_commit=c.commit,
1155 parent_commit=c.commit,
1155 )
1156 )
1156
1157
1157 h.flash(
1158 h.flash(
1158 _('Successfully committed changes to file `{}`').format(
1159 _('Successfully committed changes to file `{}`').format(
1159 h.escape(f_path)), category='success')
1160 h.escape(f_path)), category='success')
1160 except Exception:
1161 except Exception:
1161 log.exception('Error occurred during commit')
1162 log.exception('Error occurred during commit')
1162 h.flash(_('Error occurred during commit'), category='error')
1163 h.flash(_('Error occurred during commit'), category='error')
1163 raise HTTPFound(
1164 raise HTTPFound(
1164 h.route_path('repo_commit', repo_name=self.db_repo_name,
1165 h.route_path('repo_commit', repo_name=self.db_repo_name,
1165 commit_id='tip'))
1166 commit_id='tip'))
1166
1167
1167 @LoginRequired()
1168 @LoginRequired()
1168 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1169 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1169 @view_config(
1170 @view_config(
1170 route_name='repo_files_add_file', request_method='GET',
1171 route_name='repo_files_add_file', request_method='GET',
1171 renderer='rhodecode:templates/files/files_add.mako')
1172 renderer='rhodecode:templates/files/files_add.mako')
1172 def repo_files_add_file(self):
1173 def repo_files_add_file(self):
1173 _ = self.request.translate
1174 _ = self.request.translate
1174 c = self.load_default_context()
1175 c = self.load_default_context()
1175 commit_id, f_path = self._get_commit_and_path()
1176 commit_id, f_path = self._get_commit_and_path()
1176
1177
1177 self._ensure_not_locked()
1178 self._ensure_not_locked()
1178
1179
1179 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1180 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1180 if c.commit is None:
1181 if c.commit is None:
1181 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1182 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1182 c.default_message = (_('Added file via RhodeCode Enterprise'))
1183 c.default_message = (_('Added file via RhodeCode Enterprise'))
1183 c.f_path = f_path
1184 c.f_path = f_path
1184
1185
1185 return self._get_template_context(c)
1186 return self._get_template_context(c)
1186
1187
1187 @LoginRequired()
1188 @LoginRequired()
1188 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1189 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1189 @CSRFRequired()
1190 @CSRFRequired()
1190 @view_config(
1191 @view_config(
1191 route_name='repo_files_create_file', request_method='POST',
1192 route_name='repo_files_create_file', request_method='POST',
1192 renderer=None)
1193 renderer=None)
1193 def repo_files_create_file(self):
1194 def repo_files_create_file(self):
1194 _ = self.request.translate
1195 _ = self.request.translate
1195 c = self.load_default_context()
1196 c = self.load_default_context()
1196 commit_id, f_path = self._get_commit_and_path()
1197 commit_id, f_path = self._get_commit_and_path()
1197
1198
1198 self._ensure_not_locked()
1199 self._ensure_not_locked()
1199
1200
1200 r_post = self.request.POST
1201 r_post = self.request.POST
1201
1202
1202 c.commit = self._get_commit_or_redirect(
1203 c.commit = self._get_commit_or_redirect(
1203 commit_id, redirect_after=False)
1204 commit_id, redirect_after=False)
1204 if c.commit is None:
1205 if c.commit is None:
1205 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1206 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1206 c.default_message = (_('Added file via RhodeCode Enterprise'))
1207 c.default_message = (_('Added file via RhodeCode Enterprise'))
1207 c.f_path = f_path
1208 c.f_path = f_path
1208 unix_mode = 0
1209 unix_mode = 0
1209 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1210 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1210
1211
1211 message = r_post.get('message') or c.default_message
1212 message = r_post.get('message') or c.default_message
1212 filename = r_post.get('filename')
1213 filename = r_post.get('filename')
1213 location = r_post.get('location', '') # dir location
1214 location = r_post.get('location', '') # dir location
1214 file_obj = r_post.get('upload_file', None)
1215 file_obj = r_post.get('upload_file', None)
1215
1216
1216 if file_obj is not None and hasattr(file_obj, 'filename'):
1217 if file_obj is not None and hasattr(file_obj, 'filename'):
1217 filename = r_post.get('filename_upload')
1218 filename = r_post.get('filename_upload')
1218 content = file_obj.file
1219 content = file_obj.file
1219
1220
1220 if hasattr(content, 'file'):
1221 if hasattr(content, 'file'):
1221 # non posix systems store real file under file attr
1222 # non posix systems store real file under file attr
1222 content = content.file
1223 content = content.file
1223
1224
1224 default_redirect_url = h.route_path(
1225 default_redirect_url = h.route_path(
1225 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1226 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1226
1227
1227 # If there's no commit, redirect to repo summary
1228 # If there's no commit, redirect to repo summary
1228 if type(c.commit) is EmptyCommit:
1229 if type(c.commit) is EmptyCommit:
1229 redirect_url = h.route_path(
1230 redirect_url = h.route_path(
1230 'repo_summary', repo_name=self.db_repo_name)
1231 'repo_summary', repo_name=self.db_repo_name)
1231 else:
1232 else:
1232 redirect_url = default_redirect_url
1233 redirect_url = default_redirect_url
1233
1234
1234 if not filename:
1235 if not filename:
1235 h.flash(_('No filename'), category='warning')
1236 h.flash(_('No filename'), category='warning')
1236 raise HTTPFound(redirect_url)
1237 raise HTTPFound(redirect_url)
1237
1238
1238 # extract the location from filename,
1239 # extract the location from filename,
1239 # allows using foo/bar.txt syntax to create subdirectories
1240 # allows using foo/bar.txt syntax to create subdirectories
1240 subdir_loc = filename.rsplit('/', 1)
1241 subdir_loc = filename.rsplit('/', 1)
1241 if len(subdir_loc) == 2:
1242 if len(subdir_loc) == 2:
1242 location = os.path.join(location, subdir_loc[0])
1243 location = os.path.join(location, subdir_loc[0])
1243
1244
1244 # strip all crap out of file, just leave the basename
1245 # strip all crap out of file, just leave the basename
1245 filename = os.path.basename(filename)
1246 filename = os.path.basename(filename)
1246 node_path = os.path.join(location, filename)
1247 node_path = os.path.join(location, filename)
1247 author = self._rhodecode_db_user.full_contact
1248 author = self._rhodecode_db_user.full_contact
1248
1249
1249 try:
1250 try:
1250 nodes = {
1251 nodes = {
1251 node_path: {
1252 node_path: {
1252 'content': content
1253 'content': content
1253 }
1254 }
1254 }
1255 }
1255 ScmModel().create_nodes(
1256 ScmModel().create_nodes(
1256 user=self._rhodecode_db_user.user_id,
1257 user=self._rhodecode_db_user.user_id,
1257 repo=self.db_repo,
1258 repo=self.db_repo,
1258 message=message,
1259 message=message,
1259 nodes=nodes,
1260 nodes=nodes,
1260 parent_commit=c.commit,
1261 parent_commit=c.commit,
1261 author=author,
1262 author=author,
1262 )
1263 )
1263
1264
1264 h.flash(
1265 h.flash(
1265 _('Successfully committed new file `{}`').format(
1266 _('Successfully committed new file `{}`').format(
1266 h.escape(node_path)), category='success')
1267 h.escape(node_path)), category='success')
1267 except NonRelativePathError:
1268 except NonRelativePathError:
1268 h.flash(_(
1269 h.flash(_(
1269 'The location specified must be a relative path and must not '
1270 'The location specified must be a relative path and must not '
1270 'contain .. in the path'), category='warning')
1271 'contain .. in the path'), category='warning')
1271 raise HTTPFound(default_redirect_url)
1272 raise HTTPFound(default_redirect_url)
1272 except (NodeError, NodeAlreadyExistsError) as e:
1273 except (NodeError, NodeAlreadyExistsError) as e:
1273 h.flash(_(h.escape(e)), category='error')
1274 h.flash(_(h.escape(e)), category='error')
1274 except Exception:
1275 except Exception:
1275 log.exception('Error occurred during commit')
1276 log.exception('Error occurred during commit')
1276 h.flash(_('Error occurred during commit'), category='error')
1277 h.flash(_('Error occurred during commit'), category='error')
1277
1278
1278 raise HTTPFound(default_redirect_url)
1279 raise HTTPFound(default_redirect_url)
@@ -1,599 +1,588 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 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 Routes configuration
22 Routes configuration
23
23
24 The more specific and detailed routes should be defined first so they
24 The more specific and detailed routes should be defined first so they
25 may take precedent over the more generic routes. For more information
25 may take precedent over the more generic routes. For more information
26 refer to the routes manual at http://routes.groovie.org/docs/
26 refer to the routes manual at http://routes.groovie.org/docs/
27
27
28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
29 and _route_name variable which uses some of stored naming here to do redirects.
29 and _route_name variable which uses some of stored naming here to do redirects.
30 """
30 """
31 import os
31 import os
32 import re
32 import re
33 from routes import Mapper
33 from routes import Mapper
34
34
35 # prefix for non repository related links needs to be prefixed with `/`
35 # prefix for non repository related links needs to be prefixed with `/`
36 ADMIN_PREFIX = '/_admin'
36 ADMIN_PREFIX = '/_admin'
37 STATIC_FILE_PREFIX = '/_static'
37 STATIC_FILE_PREFIX = '/_static'
38
38
39 # Default requirements for URL parts
39 # Default requirements for URL parts
40 URL_NAME_REQUIREMENTS = {
40 URL_NAME_REQUIREMENTS = {
41 # group name can have a slash in them, but they must not end with a slash
41 # group name can have a slash in them, but they must not end with a slash
42 'group_name': r'.*?[^/]',
42 'group_name': r'.*?[^/]',
43 'repo_group_name': r'.*?[^/]',
43 'repo_group_name': r'.*?[^/]',
44 # repo names can have a slash in them, but they must not end with a slash
44 # repo names can have a slash in them, but they must not end with a slash
45 'repo_name': r'.*?[^/]',
45 'repo_name': r'.*?[^/]',
46 # file path eats up everything at the end
46 # file path eats up everything at the end
47 'f_path': r'.*',
47 'f_path': r'.*',
48 # reference types
48 # reference types
49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
51 }
51 }
52
52
53
53
54 class JSRoutesMapper(Mapper):
54 class JSRoutesMapper(Mapper):
55 """
55 """
56 Wrapper for routes.Mapper to make pyroutes compatible url definitions
56 Wrapper for routes.Mapper to make pyroutes compatible url definitions
57 """
57 """
58 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
58 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
59 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
59 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
60 def __init__(self, *args, **kw):
60 def __init__(self, *args, **kw):
61 super(JSRoutesMapper, self).__init__(*args, **kw)
61 super(JSRoutesMapper, self).__init__(*args, **kw)
62 self._jsroutes = []
62 self._jsroutes = []
63
63
64 def connect(self, *args, **kw):
64 def connect(self, *args, **kw):
65 """
65 """
66 Wrapper for connect to take an extra argument jsroute=True
66 Wrapper for connect to take an extra argument jsroute=True
67
67
68 :param jsroute: boolean, if True will add the route to the pyroutes list
68 :param jsroute: boolean, if True will add the route to the pyroutes list
69 """
69 """
70 if kw.pop('jsroute', False):
70 if kw.pop('jsroute', False):
71 if not self._named_route_regex.match(args[0]):
71 if not self._named_route_regex.match(args[0]):
72 raise Exception('only named routes can be added to pyroutes')
72 raise Exception('only named routes can be added to pyroutes')
73 self._jsroutes.append(args[0])
73 self._jsroutes.append(args[0])
74
74
75 super(JSRoutesMapper, self).connect(*args, **kw)
75 super(JSRoutesMapper, self).connect(*args, **kw)
76
76
77 def _extract_route_information(self, route):
77 def _extract_route_information(self, route):
78 """
78 """
79 Convert a route into tuple(name, path, args), eg:
79 Convert a route into tuple(name, path, args), eg:
80 ('show_user', '/profile/%(username)s', ['username'])
80 ('show_user', '/profile/%(username)s', ['username'])
81 """
81 """
82 routepath = route.routepath
82 routepath = route.routepath
83 def replace(matchobj):
83 def replace(matchobj):
84 if matchobj.group(1):
84 if matchobj.group(1):
85 return "%%(%s)s" % matchobj.group(1).split(':')[0]
85 return "%%(%s)s" % matchobj.group(1).split(':')[0]
86 else:
86 else:
87 return "%%(%s)s" % matchobj.group(2)
87 return "%%(%s)s" % matchobj.group(2)
88
88
89 routepath = self._argument_prog.sub(replace, routepath)
89 routepath = self._argument_prog.sub(replace, routepath)
90 return (
90 return (
91 route.name,
91 route.name,
92 routepath,
92 routepath,
93 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
93 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
94 for arg in self._argument_prog.findall(route.routepath)]
94 for arg in self._argument_prog.findall(route.routepath)]
95 )
95 )
96
96
97 def jsroutes(self):
97 def jsroutes(self):
98 """
98 """
99 Return a list of pyroutes.js compatible routes
99 Return a list of pyroutes.js compatible routes
100 """
100 """
101 for route_name in self._jsroutes:
101 for route_name in self._jsroutes:
102 yield self._extract_route_information(self._routenames[route_name])
102 yield self._extract_route_information(self._routenames[route_name])
103
103
104
104
105 def make_map(config):
105 def make_map(config):
106 """Create, configure and return the routes Mapper"""
106 """Create, configure and return the routes Mapper"""
107 rmap = JSRoutesMapper(
107 rmap = JSRoutesMapper(
108 directory=config['pylons.paths']['controllers'],
108 directory=config['pylons.paths']['controllers'],
109 always_scan=config['debug'])
109 always_scan=config['debug'])
110 rmap.minimization = False
110 rmap.minimization = False
111 rmap.explicit = False
111 rmap.explicit = False
112
112
113 from rhodecode.lib.utils2 import str2bool
113 from rhodecode.lib.utils2 import str2bool
114 from rhodecode.model import repo, repo_group
114 from rhodecode.model import repo, repo_group
115
115
116 def check_repo(environ, match_dict):
116 def check_repo(environ, match_dict):
117 """
117 """
118 check for valid repository for proper 404 handling
118 check for valid repository for proper 404 handling
119
119
120 :param environ:
120 :param environ:
121 :param match_dict:
121 :param match_dict:
122 """
122 """
123 repo_name = match_dict.get('repo_name')
123 repo_name = match_dict.get('repo_name')
124
124
125 if match_dict.get('f_path'):
125 if match_dict.get('f_path'):
126 # fix for multiple initial slashes that causes errors
126 # fix for multiple initial slashes that causes errors
127 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
127 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
128 repo_model = repo.RepoModel()
128 repo_model = repo.RepoModel()
129 by_name_match = repo_model.get_by_repo_name(repo_name)
129 by_name_match = repo_model.get_by_repo_name(repo_name)
130 # if we match quickly from database, short circuit the operation,
130 # if we match quickly from database, short circuit the operation,
131 # and validate repo based on the type.
131 # and validate repo based on the type.
132 if by_name_match:
132 if by_name_match:
133 return True
133 return True
134
134
135 by_id_match = repo_model.get_repo_by_id(repo_name)
135 by_id_match = repo_model.get_repo_by_id(repo_name)
136 if by_id_match:
136 if by_id_match:
137 repo_name = by_id_match.repo_name
137 repo_name = by_id_match.repo_name
138 match_dict['repo_name'] = repo_name
138 match_dict['repo_name'] = repo_name
139 return True
139 return True
140
140
141 return False
141 return False
142
142
143 def check_group(environ, match_dict):
143 def check_group(environ, match_dict):
144 """
144 """
145 check for valid repository group path for proper 404 handling
145 check for valid repository group path for proper 404 handling
146
146
147 :param environ:
147 :param environ:
148 :param match_dict:
148 :param match_dict:
149 """
149 """
150 repo_group_name = match_dict.get('group_name')
150 repo_group_name = match_dict.get('group_name')
151 repo_group_model = repo_group.RepoGroupModel()
151 repo_group_model = repo_group.RepoGroupModel()
152 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
152 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
153 if by_name_match:
153 if by_name_match:
154 return True
154 return True
155
155
156 return False
156 return False
157
157
158 def check_user_group(environ, match_dict):
158 def check_user_group(environ, match_dict):
159 """
159 """
160 check for valid user group for proper 404 handling
160 check for valid user group for proper 404 handling
161
161
162 :param environ:
162 :param environ:
163 :param match_dict:
163 :param match_dict:
164 """
164 """
165 return True
165 return True
166
166
167 def check_int(environ, match_dict):
167 def check_int(environ, match_dict):
168 return match_dict.get('id').isdigit()
168 return match_dict.get('id').isdigit()
169
169
170
170
171 #==========================================================================
171 #==========================================================================
172 # CUSTOM ROUTES HERE
172 # CUSTOM ROUTES HERE
173 #==========================================================================
173 #==========================================================================
174
174
175 # ping and pylons error test
175 # ping and pylons error test
176 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
176 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
177 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
177 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
178
178
179 # ADMIN REPOSITORY ROUTES
179 # ADMIN REPOSITORY ROUTES
180 with rmap.submapper(path_prefix=ADMIN_PREFIX,
180 with rmap.submapper(path_prefix=ADMIN_PREFIX,
181 controller='admin/repos') as m:
181 controller='admin/repos') as m:
182 m.connect('repos', '/repos',
182 m.connect('repos', '/repos',
183 action='create', conditions={'method': ['POST']})
183 action='create', conditions={'method': ['POST']})
184 m.connect('repos', '/repos',
184 m.connect('repos', '/repos',
185 action='index', conditions={'method': ['GET']})
185 action='index', conditions={'method': ['GET']})
186 m.connect('new_repo', '/create_repository', jsroute=True,
186 m.connect('new_repo', '/create_repository', jsroute=True,
187 action='create_repository', conditions={'method': ['GET']})
187 action='create_repository', conditions={'method': ['GET']})
188 m.connect('delete_repo', '/repos/{repo_name}',
188 m.connect('delete_repo', '/repos/{repo_name}',
189 action='delete', conditions={'method': ['DELETE']},
189 action='delete', conditions={'method': ['DELETE']},
190 requirements=URL_NAME_REQUIREMENTS)
190 requirements=URL_NAME_REQUIREMENTS)
191 m.connect('repo', '/repos/{repo_name}',
191 m.connect('repo', '/repos/{repo_name}',
192 action='show', conditions={'method': ['GET'],
192 action='show', conditions={'method': ['GET'],
193 'function': check_repo},
193 'function': check_repo},
194 requirements=URL_NAME_REQUIREMENTS)
194 requirements=URL_NAME_REQUIREMENTS)
195
195
196 # ADMIN REPOSITORY GROUPS ROUTES
196 # ADMIN REPOSITORY GROUPS ROUTES
197 with rmap.submapper(path_prefix=ADMIN_PREFIX,
197 with rmap.submapper(path_prefix=ADMIN_PREFIX,
198 controller='admin/repo_groups') as m:
198 controller='admin/repo_groups') as m:
199 m.connect('repo_groups', '/repo_groups',
199 m.connect('repo_groups', '/repo_groups',
200 action='create', conditions={'method': ['POST']})
200 action='create', conditions={'method': ['POST']})
201 m.connect('repo_groups', '/repo_groups',
201 m.connect('repo_groups', '/repo_groups',
202 action='index', conditions={'method': ['GET']})
202 action='index', conditions={'method': ['GET']})
203 m.connect('new_repo_group', '/repo_groups/new',
203 m.connect('new_repo_group', '/repo_groups/new',
204 action='new', conditions={'method': ['GET']})
204 action='new', conditions={'method': ['GET']})
205 m.connect('update_repo_group', '/repo_groups/{group_name}',
205 m.connect('update_repo_group', '/repo_groups/{group_name}',
206 action='update', conditions={'method': ['PUT'],
206 action='update', conditions={'method': ['PUT'],
207 'function': check_group},
207 'function': check_group},
208 requirements=URL_NAME_REQUIREMENTS)
208 requirements=URL_NAME_REQUIREMENTS)
209
209
210 # EXTRAS REPO GROUP ROUTES
210 # EXTRAS REPO GROUP ROUTES
211 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
211 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
212 action='edit',
212 action='edit',
213 conditions={'method': ['GET'], 'function': check_group},
213 conditions={'method': ['GET'], 'function': check_group},
214 requirements=URL_NAME_REQUIREMENTS)
214 requirements=URL_NAME_REQUIREMENTS)
215 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
215 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
216 action='edit',
216 action='edit',
217 conditions={'method': ['PUT'], 'function': check_group},
217 conditions={'method': ['PUT'], 'function': check_group},
218 requirements=URL_NAME_REQUIREMENTS)
218 requirements=URL_NAME_REQUIREMENTS)
219
219
220 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
220 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
221 action='edit_repo_group_advanced',
221 action='edit_repo_group_advanced',
222 conditions={'method': ['GET'], 'function': check_group},
222 conditions={'method': ['GET'], 'function': check_group},
223 requirements=URL_NAME_REQUIREMENTS)
223 requirements=URL_NAME_REQUIREMENTS)
224 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
224 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
225 action='edit_repo_group_advanced',
225 action='edit_repo_group_advanced',
226 conditions={'method': ['PUT'], 'function': check_group},
226 conditions={'method': ['PUT'], 'function': check_group},
227 requirements=URL_NAME_REQUIREMENTS)
227 requirements=URL_NAME_REQUIREMENTS)
228
228
229 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
229 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
230 action='edit_repo_group_perms',
230 action='edit_repo_group_perms',
231 conditions={'method': ['GET'], 'function': check_group},
231 conditions={'method': ['GET'], 'function': check_group},
232 requirements=URL_NAME_REQUIREMENTS)
232 requirements=URL_NAME_REQUIREMENTS)
233 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
233 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
234 action='update_perms',
234 action='update_perms',
235 conditions={'method': ['PUT'], 'function': check_group},
235 conditions={'method': ['PUT'], 'function': check_group},
236 requirements=URL_NAME_REQUIREMENTS)
236 requirements=URL_NAME_REQUIREMENTS)
237
237
238 m.connect('delete_repo_group', '/repo_groups/{group_name}',
238 m.connect('delete_repo_group', '/repo_groups/{group_name}',
239 action='delete', conditions={'method': ['DELETE'],
239 action='delete', conditions={'method': ['DELETE'],
240 'function': check_group},
240 'function': check_group},
241 requirements=URL_NAME_REQUIREMENTS)
241 requirements=URL_NAME_REQUIREMENTS)
242
242
243 # ADMIN USER ROUTES
243 # ADMIN USER ROUTES
244 with rmap.submapper(path_prefix=ADMIN_PREFIX,
244 with rmap.submapper(path_prefix=ADMIN_PREFIX,
245 controller='admin/users') as m:
245 controller='admin/users') as m:
246 m.connect('users', '/users',
246 m.connect('users', '/users',
247 action='create', conditions={'method': ['POST']})
247 action='create', conditions={'method': ['POST']})
248 m.connect('new_user', '/users/new',
248 m.connect('new_user', '/users/new',
249 action='new', conditions={'method': ['GET']})
249 action='new', conditions={'method': ['GET']})
250 m.connect('update_user', '/users/{user_id}',
250 m.connect('update_user', '/users/{user_id}',
251 action='update', conditions={'method': ['PUT']})
251 action='update', conditions={'method': ['PUT']})
252 m.connect('delete_user', '/users/{user_id}',
252 m.connect('delete_user', '/users/{user_id}',
253 action='delete', conditions={'method': ['DELETE']})
253 action='delete', conditions={'method': ['DELETE']})
254 m.connect('edit_user', '/users/{user_id}/edit',
254 m.connect('edit_user', '/users/{user_id}/edit',
255 action='edit', conditions={'method': ['GET']}, jsroute=True)
255 action='edit', conditions={'method': ['GET']}, jsroute=True)
256 m.connect('user', '/users/{user_id}',
256 m.connect('user', '/users/{user_id}',
257 action='show', conditions={'method': ['GET']})
257 action='show', conditions={'method': ['GET']})
258 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
258 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
259 action='reset_password', conditions={'method': ['POST']})
259 action='reset_password', conditions={'method': ['POST']})
260 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
260 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
261 action='create_personal_repo_group', conditions={'method': ['POST']})
261 action='create_personal_repo_group', conditions={'method': ['POST']})
262
262
263 # EXTRAS USER ROUTES
263 # EXTRAS USER ROUTES
264 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
264 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
265 action='edit_advanced', conditions={'method': ['GET']})
265 action='edit_advanced', conditions={'method': ['GET']})
266 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
266 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
267 action='update_advanced', conditions={'method': ['PUT']})
267 action='update_advanced', conditions={'method': ['PUT']})
268
268
269 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
269 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
270 action='edit_global_perms', conditions={'method': ['GET']})
270 action='edit_global_perms', conditions={'method': ['GET']})
271 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
271 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
272 action='update_global_perms', conditions={'method': ['PUT']})
272 action='update_global_perms', conditions={'method': ['PUT']})
273
273
274 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
274 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
275 action='edit_perms_summary', conditions={'method': ['GET']})
275 action='edit_perms_summary', conditions={'method': ['GET']})
276
276
277 # ADMIN USER GROUPS REST ROUTES
277 # ADMIN USER GROUPS REST ROUTES
278 with rmap.submapper(path_prefix=ADMIN_PREFIX,
278 with rmap.submapper(path_prefix=ADMIN_PREFIX,
279 controller='admin/user_groups') as m:
279 controller='admin/user_groups') as m:
280 m.connect('users_groups', '/user_groups',
280 m.connect('users_groups', '/user_groups',
281 action='create', conditions={'method': ['POST']})
281 action='create', conditions={'method': ['POST']})
282 m.connect('users_groups', '/user_groups',
282 m.connect('users_groups', '/user_groups',
283 action='index', conditions={'method': ['GET']})
283 action='index', conditions={'method': ['GET']})
284 m.connect('new_users_group', '/user_groups/new',
284 m.connect('new_users_group', '/user_groups/new',
285 action='new', conditions={'method': ['GET']})
285 action='new', conditions={'method': ['GET']})
286 m.connect('update_users_group', '/user_groups/{user_group_id}',
286 m.connect('update_users_group', '/user_groups/{user_group_id}',
287 action='update', conditions={'method': ['PUT']})
287 action='update', conditions={'method': ['PUT']})
288 m.connect('delete_users_group', '/user_groups/{user_group_id}',
288 m.connect('delete_users_group', '/user_groups/{user_group_id}',
289 action='delete', conditions={'method': ['DELETE']})
289 action='delete', conditions={'method': ['DELETE']})
290 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
290 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
291 action='edit', conditions={'method': ['GET']},
291 action='edit', conditions={'method': ['GET']},
292 function=check_user_group)
292 function=check_user_group)
293
293
294 # EXTRAS USER GROUP ROUTES
294 # EXTRAS USER GROUP ROUTES
295 m.connect('edit_user_group_global_perms',
295 m.connect('edit_user_group_global_perms',
296 '/user_groups/{user_group_id}/edit/global_permissions',
296 '/user_groups/{user_group_id}/edit/global_permissions',
297 action='edit_global_perms', conditions={'method': ['GET']})
297 action='edit_global_perms', conditions={'method': ['GET']})
298 m.connect('edit_user_group_global_perms',
298 m.connect('edit_user_group_global_perms',
299 '/user_groups/{user_group_id}/edit/global_permissions',
299 '/user_groups/{user_group_id}/edit/global_permissions',
300 action='update_global_perms', conditions={'method': ['PUT']})
300 action='update_global_perms', conditions={'method': ['PUT']})
301 m.connect('edit_user_group_perms_summary',
301 m.connect('edit_user_group_perms_summary',
302 '/user_groups/{user_group_id}/edit/permissions_summary',
302 '/user_groups/{user_group_id}/edit/permissions_summary',
303 action='edit_perms_summary', conditions={'method': ['GET']})
303 action='edit_perms_summary', conditions={'method': ['GET']})
304
304
305 m.connect('edit_user_group_perms',
305 m.connect('edit_user_group_perms',
306 '/user_groups/{user_group_id}/edit/permissions',
306 '/user_groups/{user_group_id}/edit/permissions',
307 action='edit_perms', conditions={'method': ['GET']})
307 action='edit_perms', conditions={'method': ['GET']})
308 m.connect('edit_user_group_perms',
308 m.connect('edit_user_group_perms',
309 '/user_groups/{user_group_id}/edit/permissions',
309 '/user_groups/{user_group_id}/edit/permissions',
310 action='update_perms', conditions={'method': ['PUT']})
310 action='update_perms', conditions={'method': ['PUT']})
311
311
312 m.connect('edit_user_group_advanced',
312 m.connect('edit_user_group_advanced',
313 '/user_groups/{user_group_id}/edit/advanced',
313 '/user_groups/{user_group_id}/edit/advanced',
314 action='edit_advanced', conditions={'method': ['GET']})
314 action='edit_advanced', conditions={'method': ['GET']})
315
315
316 m.connect('edit_user_group_advanced_sync',
316 m.connect('edit_user_group_advanced_sync',
317 '/user_groups/{user_group_id}/edit/advanced/sync',
317 '/user_groups/{user_group_id}/edit/advanced/sync',
318 action='edit_advanced_set_synchronization', conditions={'method': ['POST']})
318 action='edit_advanced_set_synchronization', conditions={'method': ['POST']})
319
319
320 m.connect('edit_user_group_members',
320 m.connect('edit_user_group_members',
321 '/user_groups/{user_group_id}/edit/members', jsroute=True,
321 '/user_groups/{user_group_id}/edit/members', jsroute=True,
322 action='user_group_members', conditions={'method': ['GET']})
322 action='user_group_members', conditions={'method': ['GET']})
323
323
324 # ADMIN DEFAULTS REST ROUTES
324 # ADMIN DEFAULTS REST ROUTES
325 with rmap.submapper(path_prefix=ADMIN_PREFIX,
325 with rmap.submapper(path_prefix=ADMIN_PREFIX,
326 controller='admin/defaults') as m:
326 controller='admin/defaults') as m:
327 m.connect('admin_defaults_repositories', '/defaults/repositories',
327 m.connect('admin_defaults_repositories', '/defaults/repositories',
328 action='update_repository_defaults', conditions={'method': ['POST']})
328 action='update_repository_defaults', conditions={'method': ['POST']})
329 m.connect('admin_defaults_repositories', '/defaults/repositories',
329 m.connect('admin_defaults_repositories', '/defaults/repositories',
330 action='index', conditions={'method': ['GET']})
330 action='index', conditions={'method': ['GET']})
331
331
332 # ADMIN SETTINGS ROUTES
332 # ADMIN SETTINGS ROUTES
333 with rmap.submapper(path_prefix=ADMIN_PREFIX,
333 with rmap.submapper(path_prefix=ADMIN_PREFIX,
334 controller='admin/settings') as m:
334 controller='admin/settings') as m:
335
335
336 # default
336 # default
337 m.connect('admin_settings', '/settings',
337 m.connect('admin_settings', '/settings',
338 action='settings_global_update',
338 action='settings_global_update',
339 conditions={'method': ['POST']})
339 conditions={'method': ['POST']})
340 m.connect('admin_settings', '/settings',
340 m.connect('admin_settings', '/settings',
341 action='settings_global', conditions={'method': ['GET']})
341 action='settings_global', conditions={'method': ['GET']})
342
342
343 m.connect('admin_settings_vcs', '/settings/vcs',
343 m.connect('admin_settings_vcs', '/settings/vcs',
344 action='settings_vcs_update',
344 action='settings_vcs_update',
345 conditions={'method': ['POST']})
345 conditions={'method': ['POST']})
346 m.connect('admin_settings_vcs', '/settings/vcs',
346 m.connect('admin_settings_vcs', '/settings/vcs',
347 action='settings_vcs',
347 action='settings_vcs',
348 conditions={'method': ['GET']})
348 conditions={'method': ['GET']})
349 m.connect('admin_settings_vcs', '/settings/vcs',
349 m.connect('admin_settings_vcs', '/settings/vcs',
350 action='delete_svn_pattern',
350 action='delete_svn_pattern',
351 conditions={'method': ['DELETE']})
351 conditions={'method': ['DELETE']})
352
352
353 m.connect('admin_settings_mapping', '/settings/mapping',
353 m.connect('admin_settings_mapping', '/settings/mapping',
354 action='settings_mapping_update',
354 action='settings_mapping_update',
355 conditions={'method': ['POST']})
355 conditions={'method': ['POST']})
356 m.connect('admin_settings_mapping', '/settings/mapping',
356 m.connect('admin_settings_mapping', '/settings/mapping',
357 action='settings_mapping', conditions={'method': ['GET']})
357 action='settings_mapping', conditions={'method': ['GET']})
358
358
359 m.connect('admin_settings_global', '/settings/global',
359 m.connect('admin_settings_global', '/settings/global',
360 action='settings_global_update',
360 action='settings_global_update',
361 conditions={'method': ['POST']})
361 conditions={'method': ['POST']})
362 m.connect('admin_settings_global', '/settings/global',
362 m.connect('admin_settings_global', '/settings/global',
363 action='settings_global', conditions={'method': ['GET']})
363 action='settings_global', conditions={'method': ['GET']})
364
364
365 m.connect('admin_settings_visual', '/settings/visual',
365 m.connect('admin_settings_visual', '/settings/visual',
366 action='settings_visual_update',
366 action='settings_visual_update',
367 conditions={'method': ['POST']})
367 conditions={'method': ['POST']})
368 m.connect('admin_settings_visual', '/settings/visual',
368 m.connect('admin_settings_visual', '/settings/visual',
369 action='settings_visual', conditions={'method': ['GET']})
369 action='settings_visual', conditions={'method': ['GET']})
370
370
371 m.connect('admin_settings_issuetracker',
371 m.connect('admin_settings_issuetracker',
372 '/settings/issue-tracker', action='settings_issuetracker',
372 '/settings/issue-tracker', action='settings_issuetracker',
373 conditions={'method': ['GET']})
373 conditions={'method': ['GET']})
374 m.connect('admin_settings_issuetracker_save',
374 m.connect('admin_settings_issuetracker_save',
375 '/settings/issue-tracker/save',
375 '/settings/issue-tracker/save',
376 action='settings_issuetracker_save',
376 action='settings_issuetracker_save',
377 conditions={'method': ['POST']})
377 conditions={'method': ['POST']})
378 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
378 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
379 action='settings_issuetracker_test',
379 action='settings_issuetracker_test',
380 conditions={'method': ['POST']})
380 conditions={'method': ['POST']})
381 m.connect('admin_issuetracker_delete',
381 m.connect('admin_issuetracker_delete',
382 '/settings/issue-tracker/delete',
382 '/settings/issue-tracker/delete',
383 action='settings_issuetracker_delete',
383 action='settings_issuetracker_delete',
384 conditions={'method': ['DELETE']})
384 conditions={'method': ['DELETE']})
385
385
386 m.connect('admin_settings_email', '/settings/email',
386 m.connect('admin_settings_email', '/settings/email',
387 action='settings_email_update',
387 action='settings_email_update',
388 conditions={'method': ['POST']})
388 conditions={'method': ['POST']})
389 m.connect('admin_settings_email', '/settings/email',
389 m.connect('admin_settings_email', '/settings/email',
390 action='settings_email', conditions={'method': ['GET']})
390 action='settings_email', conditions={'method': ['GET']})
391
391
392 m.connect('admin_settings_hooks', '/settings/hooks',
392 m.connect('admin_settings_hooks', '/settings/hooks',
393 action='settings_hooks_update',
393 action='settings_hooks_update',
394 conditions={'method': ['POST', 'DELETE']})
394 conditions={'method': ['POST', 'DELETE']})
395 m.connect('admin_settings_hooks', '/settings/hooks',
395 m.connect('admin_settings_hooks', '/settings/hooks',
396 action='settings_hooks', conditions={'method': ['GET']})
396 action='settings_hooks', conditions={'method': ['GET']})
397
397
398 m.connect('admin_settings_search', '/settings/search',
398 m.connect('admin_settings_search', '/settings/search',
399 action='settings_search', conditions={'method': ['GET']})
399 action='settings_search', conditions={'method': ['GET']})
400
400
401 m.connect('admin_settings_supervisor', '/settings/supervisor',
401 m.connect('admin_settings_supervisor', '/settings/supervisor',
402 action='settings_supervisor', conditions={'method': ['GET']})
402 action='settings_supervisor', conditions={'method': ['GET']})
403 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
403 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
404 action='settings_supervisor_log', conditions={'method': ['GET']})
404 action='settings_supervisor_log', conditions={'method': ['GET']})
405
405
406 m.connect('admin_settings_labs', '/settings/labs',
406 m.connect('admin_settings_labs', '/settings/labs',
407 action='settings_labs_update',
407 action='settings_labs_update',
408 conditions={'method': ['POST']})
408 conditions={'method': ['POST']})
409 m.connect('admin_settings_labs', '/settings/labs',
409 m.connect('admin_settings_labs', '/settings/labs',
410 action='settings_labs', conditions={'method': ['GET']})
410 action='settings_labs', conditions={'method': ['GET']})
411
411
412 # ADMIN MY ACCOUNT
412 # ADMIN MY ACCOUNT
413 with rmap.submapper(path_prefix=ADMIN_PREFIX,
413 with rmap.submapper(path_prefix=ADMIN_PREFIX,
414 controller='admin/my_account') as m:
414 controller='admin/my_account') as m:
415
415
416 # NOTE(marcink): this needs to be kept for password force flag to be
416 # NOTE(marcink): this needs to be kept for password force flag to be
417 # handled in pylons controllers, remove after full migration to pyramid
417 # handled in pylons controllers, remove after full migration to pyramid
418 m.connect('my_account_password', '/my_account/password',
418 m.connect('my_account_password', '/my_account/password',
419 action='my_account_password', conditions={'method': ['GET']})
419 action='my_account_password', conditions={'method': ['GET']})
420
420
421 #==========================================================================
421 #==========================================================================
422 # REPOSITORY ROUTES
422 # REPOSITORY ROUTES
423 #==========================================================================
423 #==========================================================================
424
424
425 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
425 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
426 controller='admin/repos', action='repo_creating',
426 controller='admin/repos', action='repo_creating',
427 requirements=URL_NAME_REQUIREMENTS)
427 requirements=URL_NAME_REQUIREMENTS)
428 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
428 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
429 controller='admin/repos', action='repo_check',
429 controller='admin/repos', action='repo_check',
430 requirements=URL_NAME_REQUIREMENTS)
430 requirements=URL_NAME_REQUIREMENTS)
431
431
432 # repo edit options
432 # repo edit options
433 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
433 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
434 controller='admin/repos', action='edit_fields',
434 controller='admin/repos', action='edit_fields',
435 conditions={'method': ['GET'], 'function': check_repo},
435 conditions={'method': ['GET'], 'function': check_repo},
436 requirements=URL_NAME_REQUIREMENTS)
436 requirements=URL_NAME_REQUIREMENTS)
437 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
437 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
438 controller='admin/repos', action='create_repo_field',
438 controller='admin/repos', action='create_repo_field',
439 conditions={'method': ['PUT'], 'function': check_repo},
439 conditions={'method': ['PUT'], 'function': check_repo},
440 requirements=URL_NAME_REQUIREMENTS)
440 requirements=URL_NAME_REQUIREMENTS)
441 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
441 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
442 controller='admin/repos', action='delete_repo_field',
442 controller='admin/repos', action='delete_repo_field',
443 conditions={'method': ['DELETE'], 'function': check_repo},
443 conditions={'method': ['DELETE'], 'function': check_repo},
444 requirements=URL_NAME_REQUIREMENTS)
444 requirements=URL_NAME_REQUIREMENTS)
445
445
446 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
446 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
447 controller='admin/repos', action='toggle_locking',
447 controller='admin/repos', action='toggle_locking',
448 conditions={'method': ['GET'], 'function': check_repo},
448 conditions={'method': ['GET'], 'function': check_repo},
449 requirements=URL_NAME_REQUIREMENTS)
449 requirements=URL_NAME_REQUIREMENTS)
450
450
451 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
451 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
452 controller='admin/repos', action='edit_remote_form',
452 controller='admin/repos', action='edit_remote_form',
453 conditions={'method': ['GET'], 'function': check_repo},
453 conditions={'method': ['GET'], 'function': check_repo},
454 requirements=URL_NAME_REQUIREMENTS)
454 requirements=URL_NAME_REQUIREMENTS)
455 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
455 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
456 controller='admin/repos', action='edit_remote',
456 controller='admin/repos', action='edit_remote',
457 conditions={'method': ['PUT'], 'function': check_repo},
457 conditions={'method': ['PUT'], 'function': check_repo},
458 requirements=URL_NAME_REQUIREMENTS)
458 requirements=URL_NAME_REQUIREMENTS)
459
459
460 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
460 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
461 controller='admin/repos', action='edit_statistics_form',
461 controller='admin/repos', action='edit_statistics_form',
462 conditions={'method': ['GET'], 'function': check_repo},
462 conditions={'method': ['GET'], 'function': check_repo},
463 requirements=URL_NAME_REQUIREMENTS)
463 requirements=URL_NAME_REQUIREMENTS)
464 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
464 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
465 controller='admin/repos', action='edit_statistics',
465 controller='admin/repos', action='edit_statistics',
466 conditions={'method': ['PUT'], 'function': check_repo},
466 conditions={'method': ['PUT'], 'function': check_repo},
467 requirements=URL_NAME_REQUIREMENTS)
467 requirements=URL_NAME_REQUIREMENTS)
468 rmap.connect('repo_settings_issuetracker',
468 rmap.connect('repo_settings_issuetracker',
469 '/{repo_name}/settings/issue-tracker',
469 '/{repo_name}/settings/issue-tracker',
470 controller='admin/repos', action='repo_issuetracker',
470 controller='admin/repos', action='repo_issuetracker',
471 conditions={'method': ['GET'], 'function': check_repo},
471 conditions={'method': ['GET'], 'function': check_repo},
472 requirements=URL_NAME_REQUIREMENTS)
472 requirements=URL_NAME_REQUIREMENTS)
473 rmap.connect('repo_issuetracker_test',
473 rmap.connect('repo_issuetracker_test',
474 '/{repo_name}/settings/issue-tracker/test',
474 '/{repo_name}/settings/issue-tracker/test',
475 controller='admin/repos', action='repo_issuetracker_test',
475 controller='admin/repos', action='repo_issuetracker_test',
476 conditions={'method': ['POST'], 'function': check_repo},
476 conditions={'method': ['POST'], 'function': check_repo},
477 requirements=URL_NAME_REQUIREMENTS)
477 requirements=URL_NAME_REQUIREMENTS)
478 rmap.connect('repo_issuetracker_delete',
478 rmap.connect('repo_issuetracker_delete',
479 '/{repo_name}/settings/issue-tracker/delete',
479 '/{repo_name}/settings/issue-tracker/delete',
480 controller='admin/repos', action='repo_issuetracker_delete',
480 controller='admin/repos', action='repo_issuetracker_delete',
481 conditions={'method': ['DELETE'], 'function': check_repo},
481 conditions={'method': ['DELETE'], 'function': check_repo},
482 requirements=URL_NAME_REQUIREMENTS)
482 requirements=URL_NAME_REQUIREMENTS)
483 rmap.connect('repo_issuetracker_save',
483 rmap.connect('repo_issuetracker_save',
484 '/{repo_name}/settings/issue-tracker/save',
484 '/{repo_name}/settings/issue-tracker/save',
485 controller='admin/repos', action='repo_issuetracker_save',
485 controller='admin/repos', action='repo_issuetracker_save',
486 conditions={'method': ['POST'], 'function': check_repo},
486 conditions={'method': ['POST'], 'function': check_repo},
487 requirements=URL_NAME_REQUIREMENTS)
487 requirements=URL_NAME_REQUIREMENTS)
488 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
488 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
489 controller='admin/repos', action='repo_settings_vcs_update',
489 controller='admin/repos', action='repo_settings_vcs_update',
490 conditions={'method': ['POST'], 'function': check_repo},
490 conditions={'method': ['POST'], 'function': check_repo},
491 requirements=URL_NAME_REQUIREMENTS)
491 requirements=URL_NAME_REQUIREMENTS)
492 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
492 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
493 controller='admin/repos', action='repo_settings_vcs',
493 controller='admin/repos', action='repo_settings_vcs',
494 conditions={'method': ['GET'], 'function': check_repo},
494 conditions={'method': ['GET'], 'function': check_repo},
495 requirements=URL_NAME_REQUIREMENTS)
495 requirements=URL_NAME_REQUIREMENTS)
496 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
496 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
497 controller='admin/repos', action='repo_delete_svn_pattern',
497 controller='admin/repos', action='repo_delete_svn_pattern',
498 conditions={'method': ['DELETE'], 'function': check_repo},
498 conditions={'method': ['DELETE'], 'function': check_repo},
499 requirements=URL_NAME_REQUIREMENTS)
499 requirements=URL_NAME_REQUIREMENTS)
500 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
500 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
501 controller='admin/repos', action='repo_settings_pullrequest',
501 controller='admin/repos', action='repo_settings_pullrequest',
502 conditions={'method': ['GET', 'POST'], 'function': check_repo},
502 conditions={'method': ['GET', 'POST'], 'function': check_repo},
503 requirements=URL_NAME_REQUIREMENTS)
503 requirements=URL_NAME_REQUIREMENTS)
504
504
505 rmap.connect('compare_home',
506 '/{repo_name}/compare',
507 controller='compare', action='index',
508 conditions={'function': check_repo},
509 requirements=URL_NAME_REQUIREMENTS)
510
511 rmap.connect('compare_url',
512 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
513 controller='compare', action='compare',
514 conditions={'function': check_repo},
515 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
516
505
517 rmap.connect('pullrequest_home',
506 rmap.connect('pullrequest_home',
518 '/{repo_name}/pull-request/new', controller='pullrequests',
507 '/{repo_name}/pull-request/new', controller='pullrequests',
519 action='index', conditions={'function': check_repo,
508 action='index', conditions={'function': check_repo,
520 'method': ['GET']},
509 'method': ['GET']},
521 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
510 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
522
511
523 rmap.connect('pullrequest',
512 rmap.connect('pullrequest',
524 '/{repo_name}/pull-request/new', controller='pullrequests',
513 '/{repo_name}/pull-request/new', controller='pullrequests',
525 action='create', conditions={'function': check_repo,
514 action='create', conditions={'function': check_repo,
526 'method': ['POST']},
515 'method': ['POST']},
527 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
516 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
528
517
529 rmap.connect('pullrequest_repo_refs',
518 rmap.connect('pullrequest_repo_refs',
530 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
519 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
531 controller='pullrequests',
520 controller='pullrequests',
532 action='get_repo_refs',
521 action='get_repo_refs',
533 conditions={'function': check_repo, 'method': ['GET']},
522 conditions={'function': check_repo, 'method': ['GET']},
534 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
523 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
535
524
536 rmap.connect('pullrequest_repo_destinations',
525 rmap.connect('pullrequest_repo_destinations',
537 '/{repo_name}/pull-request/repo-destinations',
526 '/{repo_name}/pull-request/repo-destinations',
538 controller='pullrequests',
527 controller='pullrequests',
539 action='get_repo_destinations',
528 action='get_repo_destinations',
540 conditions={'function': check_repo, 'method': ['GET']},
529 conditions={'function': check_repo, 'method': ['GET']},
541 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
530 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
542
531
543 rmap.connect('pullrequest_show',
532 rmap.connect('pullrequest_show',
544 '/{repo_name}/pull-request/{pull_request_id}',
533 '/{repo_name}/pull-request/{pull_request_id}',
545 controller='pullrequests',
534 controller='pullrequests',
546 action='show', conditions={'function': check_repo,
535 action='show', conditions={'function': check_repo,
547 'method': ['GET']},
536 'method': ['GET']},
548 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
537 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
549
538
550 rmap.connect('pullrequest_update',
539 rmap.connect('pullrequest_update',
551 '/{repo_name}/pull-request/{pull_request_id}',
540 '/{repo_name}/pull-request/{pull_request_id}',
552 controller='pullrequests',
541 controller='pullrequests',
553 action='update', conditions={'function': check_repo,
542 action='update', conditions={'function': check_repo,
554 'method': ['PUT']},
543 'method': ['PUT']},
555 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
544 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
556
545
557 rmap.connect('pullrequest_merge',
546 rmap.connect('pullrequest_merge',
558 '/{repo_name}/pull-request/{pull_request_id}',
547 '/{repo_name}/pull-request/{pull_request_id}',
559 controller='pullrequests',
548 controller='pullrequests',
560 action='merge', conditions={'function': check_repo,
549 action='merge', conditions={'function': check_repo,
561 'method': ['POST']},
550 'method': ['POST']},
562 requirements=URL_NAME_REQUIREMENTS)
551 requirements=URL_NAME_REQUIREMENTS)
563
552
564 rmap.connect('pullrequest_delete',
553 rmap.connect('pullrequest_delete',
565 '/{repo_name}/pull-request/{pull_request_id}',
554 '/{repo_name}/pull-request/{pull_request_id}',
566 controller='pullrequests',
555 controller='pullrequests',
567 action='delete', conditions={'function': check_repo,
556 action='delete', conditions={'function': check_repo,
568 'method': ['DELETE']},
557 'method': ['DELETE']},
569 requirements=URL_NAME_REQUIREMENTS)
558 requirements=URL_NAME_REQUIREMENTS)
570
559
571 rmap.connect('pullrequest_comment',
560 rmap.connect('pullrequest_comment',
572 '/{repo_name}/pull-request-comment/{pull_request_id}',
561 '/{repo_name}/pull-request-comment/{pull_request_id}',
573 controller='pullrequests',
562 controller='pullrequests',
574 action='comment', conditions={'function': check_repo,
563 action='comment', conditions={'function': check_repo,
575 'method': ['POST']},
564 'method': ['POST']},
576 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
565 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
577
566
578 rmap.connect('pullrequest_comment_delete',
567 rmap.connect('pullrequest_comment_delete',
579 '/{repo_name}/pull-request-comment/{comment_id}/delete',
568 '/{repo_name}/pull-request-comment/{comment_id}/delete',
580 controller='pullrequests', action='delete_comment',
569 controller='pullrequests', action='delete_comment',
581 conditions={'function': check_repo, 'method': ['DELETE']},
570 conditions={'function': check_repo, 'method': ['DELETE']},
582 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
571 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
583
572
584 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
573 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
585 controller='forks', action='fork_create',
574 controller='forks', action='fork_create',
586 conditions={'function': check_repo, 'method': ['POST']},
575 conditions={'function': check_repo, 'method': ['POST']},
587 requirements=URL_NAME_REQUIREMENTS)
576 requirements=URL_NAME_REQUIREMENTS)
588
577
589 rmap.connect('repo_fork_home', '/{repo_name}/fork',
578 rmap.connect('repo_fork_home', '/{repo_name}/fork',
590 controller='forks', action='fork',
579 controller='forks', action='fork',
591 conditions={'function': check_repo},
580 conditions={'function': check_repo},
592 requirements=URL_NAME_REQUIREMENTS)
581 requirements=URL_NAME_REQUIREMENTS)
593
582
594 rmap.connect('repo_forks_home', '/{repo_name}/forks',
583 rmap.connect('repo_forks_home', '/{repo_name}/forks',
595 controller='forks', action='forks',
584 controller='forks', action='forks',
596 conditions={'function': check_repo},
585 conditions={'function': check_repo},
597 requirements=URL_NAME_REQUIREMENTS)
586 requirements=URL_NAME_REQUIREMENTS)
598
587
599 return rmap
588 return rmap
@@ -1,216 +1,217 b''
1
1
2 /******************************************************************************
2 /******************************************************************************
3 * *
3 * *
4 * DO NOT CHANGE THIS FILE MANUALLY *
4 * DO NOT CHANGE THIS FILE MANUALLY *
5 * *
5 * *
6 * *
6 * *
7 * This file is automatically generated when the app starts up with *
7 * This file is automatically generated when the app starts up with *
8 * generate_js_files = true *
8 * generate_js_files = true *
9 * *
9 * *
10 * To add a route here pass jsroute=True to the route definition in the app *
10 * To add a route here pass jsroute=True to the route definition in the app *
11 * *
11 * *
12 ******************************************************************************/
12 ******************************************************************************/
13 function registerRCRoutes() {
13 function registerRCRoutes() {
14 // routes registration
14 // routes registration
15 pyroutes.register('new_repo', '/_admin/create_repository', []);
15 pyroutes.register('new_repo', '/_admin/create_repository', []);
16 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
16 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
17 pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
17 pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
18 pyroutes.register('compare_url', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
19 pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']);
18 pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']);
20 pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']);
19 pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']);
21 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
20 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
22 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
21 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
23 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
22 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
24 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
23 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
25 pyroutes.register('pullrequest_comment', '/%(repo_name)s/pull-request-comment/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
24 pyroutes.register('pullrequest_comment', '/%(repo_name)s/pull-request-comment/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
26 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request-comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
25 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request-comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
27 pyroutes.register('favicon', '/favicon.ico', []);
26 pyroutes.register('favicon', '/favicon.ico', []);
28 pyroutes.register('robots', '/robots.txt', []);
27 pyroutes.register('robots', '/robots.txt', []);
29 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
28 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
30 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
29 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
31 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
30 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
32 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
31 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
33 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
32 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
34 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
33 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
35 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/settings/integrations', ['repo_group_name']);
34 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/settings/integrations', ['repo_group_name']);
36 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
35 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
37 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
36 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
38 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
37 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
39 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
38 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
40 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
39 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
41 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
40 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
42 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
41 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
43 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
42 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
44 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
43 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
45 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
44 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
46 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
45 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
47 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
46 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
48 pyroutes.register('admin_home', '/_admin', []);
47 pyroutes.register('admin_home', '/_admin', []);
49 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
48 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
50 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
49 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
51 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
50 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
52 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
51 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
53 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
52 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
54 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
53 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
55 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
54 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
56 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
55 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
57 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
56 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
58 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
57 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
59 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
58 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
60 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
59 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
61 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
60 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
62 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
61 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
63 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
62 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
64 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
63 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
65 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
64 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
66 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
65 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
67 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
66 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
68 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
67 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
69 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
68 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
70 pyroutes.register('users', '/_admin/users', []);
69 pyroutes.register('users', '/_admin/users', []);
71 pyroutes.register('users_data', '/_admin/users_data', []);
70 pyroutes.register('users_data', '/_admin/users_data', []);
72 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
71 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
73 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
72 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
74 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
73 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
75 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
74 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
76 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
75 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
77 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
76 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
78 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
77 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
79 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
78 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
80 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
79 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
81 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
80 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
82 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
81 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
83 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
82 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
84 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
83 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
85 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
84 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
86 pyroutes.register('channelstream_proxy', '/_channelstream', []);
85 pyroutes.register('channelstream_proxy', '/_channelstream', []);
87 pyroutes.register('login', '/_admin/login', []);
86 pyroutes.register('login', '/_admin/login', []);
88 pyroutes.register('logout', '/_admin/logout', []);
87 pyroutes.register('logout', '/_admin/logout', []);
89 pyroutes.register('register', '/_admin/register', []);
88 pyroutes.register('register', '/_admin/register', []);
90 pyroutes.register('reset_password', '/_admin/password_reset', []);
89 pyroutes.register('reset_password', '/_admin/password_reset', []);
91 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
90 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
92 pyroutes.register('home', '/', []);
91 pyroutes.register('home', '/', []);
93 pyroutes.register('user_autocomplete_data', '/_users', []);
92 pyroutes.register('user_autocomplete_data', '/_users', []);
94 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
93 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
95 pyroutes.register('repo_list_data', '/_repos', []);
94 pyroutes.register('repo_list_data', '/_repos', []);
96 pyroutes.register('goto_switcher_data', '/_goto_data', []);
95 pyroutes.register('goto_switcher_data', '/_goto_data', []);
97 pyroutes.register('journal', '/_admin/journal', []);
96 pyroutes.register('journal', '/_admin/journal', []);
98 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
97 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
99 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
98 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
100 pyroutes.register('journal_public', '/_admin/public_journal', []);
99 pyroutes.register('journal_public', '/_admin/public_journal', []);
101 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
100 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
102 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
101 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
103 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
102 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
104 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
103 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
105 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
104 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
106 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
105 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
107 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
106 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
108 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
107 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
109 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
108 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
110 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
109 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
111 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
112 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
110 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
113 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
111 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
114 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
112 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
115 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
113 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
116 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
114 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
117 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
115 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
118 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
116 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
117 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
119 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
118 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
120 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
119 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
121 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
120 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
122 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
121 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
123 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
122 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
124 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
123 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
125 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
124 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
126 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
125 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
127 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
126 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
128 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
127 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
129 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
128 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
130 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
129 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
131 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
130 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
132 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
131 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
133 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
132 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
134 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
133 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
135 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
134 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
136 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
135 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
137 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
136 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
138 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
137 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
139 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
138 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
140 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
139 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
141 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
140 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
142 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
141 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
143 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
142 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
144 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
143 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
145 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
144 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
146 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
145 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
147 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
146 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
147 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
148 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
148 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
149 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
149 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
150 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
150 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
151 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
151 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
152 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
152 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
153 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
153 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
154 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
154 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
155 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
155 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
156 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
156 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
157 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
157 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
158 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
158 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
159 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
159 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
160 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
160 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
161 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
161 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
162 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
162 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
163 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
163 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
164 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
164 pyroutes.register('repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
165 pyroutes.register('repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
165 pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
166 pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
166 pyroutes.register('strip', '/%(repo_name)s/settings/strip', ['repo_name']);
167 pyroutes.register('strip', '/%(repo_name)s/settings/strip', ['repo_name']);
167 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
168 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
168 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
169 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
169 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
170 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
170 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
171 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
171 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
172 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
172 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
173 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
173 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
174 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
174 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
175 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
175 pyroutes.register('search', '/_admin/search', []);
176 pyroutes.register('search', '/_admin/search', []);
176 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
177 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
177 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
178 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
178 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
179 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
179 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
180 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
180 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
181 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
181 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
182 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
182 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
183 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
183 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
184 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
184 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
185 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
185 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
186 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
186 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
187 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
187 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
188 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
188 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
189 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
189 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
190 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
190 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
191 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
191 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
192 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
192 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
193 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
193 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
194 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
194 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
195 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
195 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
196 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
196 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
197 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
197 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
198 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
198 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
199 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
199 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
200 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
200 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
201 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
201 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
202 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
202 pyroutes.register('gists_show', '/_admin/gists', []);
203 pyroutes.register('gists_show', '/_admin/gists', []);
203 pyroutes.register('gists_new', '/_admin/gists/new', []);
204 pyroutes.register('gists_new', '/_admin/gists/new', []);
204 pyroutes.register('gists_create', '/_admin/gists/create', []);
205 pyroutes.register('gists_create', '/_admin/gists/create', []);
205 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
206 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
206 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
207 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
207 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
208 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
208 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
209 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
209 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
210 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
210 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
211 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
211 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
212 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
212 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
213 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
213 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
214 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
214 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
215 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
215 pyroutes.register('apiv2', '/_admin/api', []);
216 pyroutes.register('apiv2', '/_admin/api', []);
216 }
217 }
@@ -1,501 +1,501 b''
1 // # Copyright (C) 2010-2017 RhodeCode GmbH
1 // # Copyright (C) 2010-2017 RhodeCode GmbH
2 // #
2 // #
3 // # This program is free software: you can redistribute it and/or modify
3 // # This program is free software: you can redistribute it and/or modify
4 // # it under the terms of the GNU Affero General Public License, version 3
4 // # it under the terms of the GNU Affero General Public License, version 3
5 // # (only), as published by the Free Software Foundation.
5 // # (only), as published by the Free Software Foundation.
6 // #
6 // #
7 // # This program is distributed in the hope that it will be useful,
7 // # This program is distributed in the hope that it will be useful,
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // # GNU General Public License for more details.
10 // # GNU General Public License for more details.
11 // #
11 // #
12 // # You should have received a copy of the GNU Affero General Public License
12 // # You should have received a copy of the GNU Affero General Public License
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 // #
14 // #
15 // # This program is dual-licensed. If you wish to learn more about the
15 // # This program is dual-licensed. If you wish to learn more about the
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 /**
19 /**
20 RhodeCode JS Files
20 RhodeCode JS Files
21 **/
21 **/
22
22
23 if (typeof console == "undefined" || typeof console.log == "undefined"){
23 if (typeof console == "undefined" || typeof console.log == "undefined"){
24 console = { log: function() {} }
24 console = { log: function() {} }
25 }
25 }
26
26
27 // TODO: move the following function to submodules
27 // TODO: move the following function to submodules
28
28
29 /**
29 /**
30 * show more
30 * show more
31 */
31 */
32 var show_more_event = function(){
32 var show_more_event = function(){
33 $('table .show_more').click(function(e) {
33 $('table .show_more').click(function(e) {
34 var cid = e.target.id.substring(1);
34 var cid = e.target.id.substring(1);
35 var button = $(this);
35 var button = $(this);
36 if (button.hasClass('open')) {
36 if (button.hasClass('open')) {
37 $('#'+cid).hide();
37 $('#'+cid).hide();
38 button.removeClass('open');
38 button.removeClass('open');
39 } else {
39 } else {
40 $('#'+cid).show();
40 $('#'+cid).show();
41 button.addClass('open one');
41 button.addClass('open one');
42 }
42 }
43 });
43 });
44 };
44 };
45
45
46 var compare_radio_buttons = function(repo_name, compare_ref_type){
46 var compare_radio_buttons = function(repo_name, compare_ref_type){
47 $('#compare_action').on('click', function(e){
47 $('#compare_action').on('click', function(e){
48 e.preventDefault();
48 e.preventDefault();
49
49
50 var source = $('input[name=compare_source]:checked').val();
50 var source = $('input[name=compare_source]:checked').val();
51 var target = $('input[name=compare_target]:checked').val();
51 var target = $('input[name=compare_target]:checked').val();
52 if(source && target){
52 if(source && target){
53 var url_data = {
53 var url_data = {
54 repo_name: repo_name,
54 repo_name: repo_name,
55 source_ref: source,
55 source_ref: source,
56 source_ref_type: compare_ref_type,
56 source_ref_type: compare_ref_type,
57 target_ref: target,
57 target_ref: target,
58 target_ref_type: compare_ref_type,
58 target_ref_type: compare_ref_type,
59 merge: 1
59 merge: 1
60 };
60 };
61 window.location = pyroutes.url('compare_url', url_data);
61 window.location = pyroutes.url('repo_compare', url_data);
62 }
62 }
63 });
63 });
64 $('.compare-radio-button').on('click', function(e){
64 $('.compare-radio-button').on('click', function(e){
65 var source = $('input[name=compare_source]:checked').val();
65 var source = $('input[name=compare_source]:checked').val();
66 var target = $('input[name=compare_target]:checked').val();
66 var target = $('input[name=compare_target]:checked').val();
67 if(source && target){
67 if(source && target){
68 $('#compare_action').removeAttr("disabled");
68 $('#compare_action').removeAttr("disabled");
69 $('#compare_action').removeClass("disabled");
69 $('#compare_action').removeClass("disabled");
70 }
70 }
71 })
71 })
72 };
72 };
73
73
74 var showRepoSize = function(target, repo_name, commit_id, callback) {
74 var showRepoSize = function(target, repo_name, commit_id, callback) {
75 var container = $('#' + target);
75 var container = $('#' + target);
76 var url = pyroutes.url('repo_stats',
76 var url = pyroutes.url('repo_stats',
77 {"repo_name": repo_name, "commit_id": commit_id});
77 {"repo_name": repo_name, "commit_id": commit_id});
78
78
79 if (!container.hasClass('loaded')) {
79 if (!container.hasClass('loaded')) {
80 $.ajax({url: url})
80 $.ajax({url: url})
81 .complete(function (data) {
81 .complete(function (data) {
82 var responseJSON = data.responseJSON;
82 var responseJSON = data.responseJSON;
83 container.addClass('loaded');
83 container.addClass('loaded');
84 container.html(responseJSON.size);
84 container.html(responseJSON.size);
85 callback(responseJSON.code_stats)
85 callback(responseJSON.code_stats)
86 })
86 })
87 .fail(function (data) {
87 .fail(function (data) {
88 console.log('failed to load repo stats');
88 console.log('failed to load repo stats');
89 });
89 });
90 }
90 }
91
91
92 };
92 };
93
93
94 var showRepoStats = function(target, data){
94 var showRepoStats = function(target, data){
95 var container = $('#' + target);
95 var container = $('#' + target);
96
96
97 if (container.hasClass('loaded')) {
97 if (container.hasClass('loaded')) {
98 return
98 return
99 }
99 }
100
100
101 var total = 0;
101 var total = 0;
102 var no_data = true;
102 var no_data = true;
103 var tbl = document.createElement('table');
103 var tbl = document.createElement('table');
104 tbl.setAttribute('class', 'trending_language_tbl');
104 tbl.setAttribute('class', 'trending_language_tbl');
105
105
106 $.each(data, function(key, val){
106 $.each(data, function(key, val){
107 total += val.count;
107 total += val.count;
108 });
108 });
109
109
110 var sortedStats = [];
110 var sortedStats = [];
111 for (var obj in data){
111 for (var obj in data){
112 sortedStats.push([obj, data[obj]])
112 sortedStats.push([obj, data[obj]])
113 }
113 }
114 var sortedData = sortedStats.sort(function (a, b) {
114 var sortedData = sortedStats.sort(function (a, b) {
115 return b[1].count - a[1].count
115 return b[1].count - a[1].count
116 });
116 });
117 var cnt = 0;
117 var cnt = 0;
118 $.each(sortedData, function(idx, val){
118 $.each(sortedData, function(idx, val){
119 cnt += 1;
119 cnt += 1;
120 no_data = false;
120 no_data = false;
121
121
122 var hide = cnt > 2;
122 var hide = cnt > 2;
123 var tr = document.createElement('tr');
123 var tr = document.createElement('tr');
124 if (hide) {
124 if (hide) {
125 tr.setAttribute('style', 'display:none');
125 tr.setAttribute('style', 'display:none');
126 tr.setAttribute('class', 'stats_hidden');
126 tr.setAttribute('class', 'stats_hidden');
127 }
127 }
128
128
129 var key = val[0];
129 var key = val[0];
130 var obj = {"desc": val[1].desc, "count": val[1].count};
130 var obj = {"desc": val[1].desc, "count": val[1].count};
131
131
132 var percentage = Math.round((obj.count / total * 100), 2);
132 var percentage = Math.round((obj.count / total * 100), 2);
133
133
134 var td1 = document.createElement('td');
134 var td1 = document.createElement('td');
135 td1.width = 300;
135 td1.width = 300;
136 var trending_language_label = document.createElement('div');
136 var trending_language_label = document.createElement('div');
137 trending_language_label.innerHTML = obj.desc + " (.{0})".format(key);
137 trending_language_label.innerHTML = obj.desc + " (.{0})".format(key);
138 td1.appendChild(trending_language_label);
138 td1.appendChild(trending_language_label);
139
139
140 var td2 = document.createElement('td');
140 var td2 = document.createElement('td');
141 var trending_language = document.createElement('div');
141 var trending_language = document.createElement('div');
142 var nr_files = obj.count +" "+ _ngettext('file', 'files', obj.count);
142 var nr_files = obj.count +" "+ _ngettext('file', 'files', obj.count);
143
143
144 trending_language.title = key + " " + nr_files;
144 trending_language.title = key + " " + nr_files;
145
145
146 trending_language.innerHTML = "<span>" + percentage + "% " + nr_files
146 trending_language.innerHTML = "<span>" + percentage + "% " + nr_files
147 + "</span><b>" + percentage + "% " + nr_files + "</b>";
147 + "</span><b>" + percentage + "% " + nr_files + "</b>";
148
148
149 trending_language.setAttribute("class", 'trending_language');
149 trending_language.setAttribute("class", 'trending_language');
150 $('b', trending_language)[0].style.width = percentage + "%";
150 $('b', trending_language)[0].style.width = percentage + "%";
151 td2.appendChild(trending_language);
151 td2.appendChild(trending_language);
152
152
153 tr.appendChild(td1);
153 tr.appendChild(td1);
154 tr.appendChild(td2);
154 tr.appendChild(td2);
155 tbl.appendChild(tr);
155 tbl.appendChild(tr);
156 if (cnt == 3) {
156 if (cnt == 3) {
157 var show_more = document.createElement('tr');
157 var show_more = document.createElement('tr');
158 var td = document.createElement('td');
158 var td = document.createElement('td');
159 lnk = document.createElement('a');
159 lnk = document.createElement('a');
160
160
161 lnk.href = '#';
161 lnk.href = '#';
162 lnk.innerHTML = _gettext('Show more');
162 lnk.innerHTML = _gettext('Show more');
163 lnk.id = 'code_stats_show_more';
163 lnk.id = 'code_stats_show_more';
164 td.appendChild(lnk);
164 td.appendChild(lnk);
165
165
166 show_more.appendChild(td);
166 show_more.appendChild(td);
167 show_more.appendChild(document.createElement('td'));
167 show_more.appendChild(document.createElement('td'));
168 tbl.appendChild(show_more);
168 tbl.appendChild(show_more);
169 }
169 }
170 });
170 });
171
171
172 $(container).html(tbl);
172 $(container).html(tbl);
173 $(container).addClass('loaded');
173 $(container).addClass('loaded');
174
174
175 $('#code_stats_show_more').on('click', function (e) {
175 $('#code_stats_show_more').on('click', function (e) {
176 e.preventDefault();
176 e.preventDefault();
177 $('.stats_hidden').each(function (idx) {
177 $('.stats_hidden').each(function (idx) {
178 $(this).css("display", "");
178 $(this).css("display", "");
179 });
179 });
180 $('#code_stats_show_more').hide();
180 $('#code_stats_show_more').hide();
181 });
181 });
182
182
183 };
183 };
184
184
185 // returns a node from given html;
185 // returns a node from given html;
186 var fromHTML = function(html){
186 var fromHTML = function(html){
187 var _html = document.createElement('element');
187 var _html = document.createElement('element');
188 _html.innerHTML = html;
188 _html.innerHTML = html;
189 return _html;
189 return _html;
190 };
190 };
191
191
192 // Toggle Collapsable Content
192 // Toggle Collapsable Content
193 function collapsableContent() {
193 function collapsableContent() {
194
194
195 $('.collapsable-content').not('.no-hide').hide();
195 $('.collapsable-content').not('.no-hide').hide();
196
196
197 $('.btn-collapse').unbind(); //in case we've been here before
197 $('.btn-collapse').unbind(); //in case we've been here before
198 $('.btn-collapse').click(function() {
198 $('.btn-collapse').click(function() {
199 var button = $(this);
199 var button = $(this);
200 var togglename = $(this).data("toggle");
200 var togglename = $(this).data("toggle");
201 $('.collapsable-content[data-toggle='+togglename+']').toggle();
201 $('.collapsable-content[data-toggle='+togglename+']').toggle();
202 if ($(this).html()=="Show Less")
202 if ($(this).html()=="Show Less")
203 $(this).html("Show More");
203 $(this).html("Show More");
204 else
204 else
205 $(this).html("Show Less");
205 $(this).html("Show Less");
206 });
206 });
207 };
207 };
208
208
209 var timeagoActivate = function() {
209 var timeagoActivate = function() {
210 $("time.timeago").timeago();
210 $("time.timeago").timeago();
211 };
211 };
212
212
213
213
214 var clipboardActivate = function() {
214 var clipboardActivate = function() {
215 /*
215 /*
216 *
216 *
217 * <i class="tooltip icon-plus clipboard-action" data-clipboard-text="${commit.raw_id}" title="${_('Copy the full commit id')}"></i>
217 * <i class="tooltip icon-plus clipboard-action" data-clipboard-text="${commit.raw_id}" title="${_('Copy the full commit id')}"></i>
218 * */
218 * */
219 var clipboard = new Clipboard('.clipboard-action');
219 var clipboard = new Clipboard('.clipboard-action');
220
220
221 clipboard.on('success', function(e) {
221 clipboard.on('success', function(e) {
222 var callback = function () {
222 var callback = function () {
223 $(e.trigger).animate({'opacity': 1.00}, 200)
223 $(e.trigger).animate({'opacity': 1.00}, 200)
224 };
224 };
225 $(e.trigger).animate({'opacity': 0.15}, 200, callback);
225 $(e.trigger).animate({'opacity': 0.15}, 200, callback);
226 e.clearSelection();
226 e.clearSelection();
227 });
227 });
228 };
228 };
229
229
230
230
231 // Formatting values in a Select2 dropdown of commit references
231 // Formatting values in a Select2 dropdown of commit references
232 var formatSelect2SelectionRefs = function(commit_ref){
232 var formatSelect2SelectionRefs = function(commit_ref){
233 var tmpl = '';
233 var tmpl = '';
234 if (!commit_ref.text || commit_ref.type === 'sha'){
234 if (!commit_ref.text || commit_ref.type === 'sha'){
235 return commit_ref.text;
235 return commit_ref.text;
236 }
236 }
237 if (commit_ref.type === 'branch'){
237 if (commit_ref.type === 'branch'){
238 tmpl = tmpl.concat('<i class="icon-branch"></i> ');
238 tmpl = tmpl.concat('<i class="icon-branch"></i> ');
239 } else if (commit_ref.type === 'tag'){
239 } else if (commit_ref.type === 'tag'){
240 tmpl = tmpl.concat('<i class="icon-tag"></i> ');
240 tmpl = tmpl.concat('<i class="icon-tag"></i> ');
241 } else if (commit_ref.type === 'book'){
241 } else if (commit_ref.type === 'book'){
242 tmpl = tmpl.concat('<i class="icon-bookmark"></i> ');
242 tmpl = tmpl.concat('<i class="icon-bookmark"></i> ');
243 }
243 }
244 return tmpl.concat(commit_ref.text);
244 return tmpl.concat(commit_ref.text);
245 };
245 };
246
246
247 // takes a given html element and scrolls it down offset pixels
247 // takes a given html element and scrolls it down offset pixels
248 function offsetScroll(element, offset) {
248 function offsetScroll(element, offset) {
249 setTimeout(function() {
249 setTimeout(function() {
250 var location = element.offset().top;
250 var location = element.offset().top;
251 // some browsers use body, some use html
251 // some browsers use body, some use html
252 $('html, body').animate({ scrollTop: (location - offset) });
252 $('html, body').animate({ scrollTop: (location - offset) });
253 }, 100);
253 }, 100);
254 }
254 }
255
255
256 // scroll an element `percent`% from the top of page in `time` ms
256 // scroll an element `percent`% from the top of page in `time` ms
257 function scrollToElement(element, percent, time) {
257 function scrollToElement(element, percent, time) {
258 percent = (percent === undefined ? 25 : percent);
258 percent = (percent === undefined ? 25 : percent);
259 time = (time === undefined ? 100 : time);
259 time = (time === undefined ? 100 : time);
260
260
261 var $element = $(element);
261 var $element = $(element);
262 if ($element.length == 0) {
262 if ($element.length == 0) {
263 throw('Cannot scroll to {0}'.format(element))
263 throw('Cannot scroll to {0}'.format(element))
264 }
264 }
265 var elOffset = $element.offset().top;
265 var elOffset = $element.offset().top;
266 var elHeight = $element.height();
266 var elHeight = $element.height();
267 var windowHeight = $(window).height();
267 var windowHeight = $(window).height();
268 var offset = elOffset;
268 var offset = elOffset;
269 if (elHeight < windowHeight) {
269 if (elHeight < windowHeight) {
270 offset = elOffset - ((windowHeight / (100 / percent)) - (elHeight / 2));
270 offset = elOffset - ((windowHeight / (100 / percent)) - (elHeight / 2));
271 }
271 }
272 setTimeout(function() {
272 setTimeout(function() {
273 $('html, body').animate({ scrollTop: offset});
273 $('html, body').animate({ scrollTop: offset});
274 }, time);
274 }, time);
275 }
275 }
276
276
277 /**
277 /**
278 * global hooks after DOM is loaded
278 * global hooks after DOM is loaded
279 */
279 */
280 $(document).ready(function() {
280 $(document).ready(function() {
281 firefoxAnchorFix();
281 firefoxAnchorFix();
282
282
283 $('.navigation a.menulink').on('click', function(e){
283 $('.navigation a.menulink').on('click', function(e){
284 var menuitem = $(this).parent('li');
284 var menuitem = $(this).parent('li');
285 if (menuitem.hasClass('open')) {
285 if (menuitem.hasClass('open')) {
286 menuitem.removeClass('open');
286 menuitem.removeClass('open');
287 } else {
287 } else {
288 menuitem.addClass('open');
288 menuitem.addClass('open');
289 $(document).on('click', function(event) {
289 $(document).on('click', function(event) {
290 if (!$(event.target).closest(menuitem).length) {
290 if (!$(event.target).closest(menuitem).length) {
291 menuitem.removeClass('open');
291 menuitem.removeClass('open');
292 }
292 }
293 });
293 });
294 }
294 }
295 });
295 });
296 $('.compare_view_files').on(
296 $('.compare_view_files').on(
297 'mouseenter mouseleave', 'tr.line .lineno a',function(event) {
297 'mouseenter mouseleave', 'tr.line .lineno a',function(event) {
298 if (event.type === "mouseenter") {
298 if (event.type === "mouseenter") {
299 $(this).parents('tr.line').addClass('hover');
299 $(this).parents('tr.line').addClass('hover');
300 } else {
300 } else {
301 $(this).parents('tr.line').removeClass('hover');
301 $(this).parents('tr.line').removeClass('hover');
302 }
302 }
303 });
303 });
304
304
305 $('.compare_view_files').on(
305 $('.compare_view_files').on(
306 'mouseenter mouseleave', 'tr.line .add-comment-line a',function(event){
306 'mouseenter mouseleave', 'tr.line .add-comment-line a',function(event){
307 if (event.type === "mouseenter") {
307 if (event.type === "mouseenter") {
308 $(this).parents('tr.line').addClass('commenting');
308 $(this).parents('tr.line').addClass('commenting');
309 } else {
309 } else {
310 $(this).parents('tr.line').removeClass('commenting');
310 $(this).parents('tr.line').removeClass('commenting');
311 }
311 }
312 });
312 });
313
313
314 $('body').on( /* TODO: replace the $('.compare_view_files').on('click') below
314 $('body').on( /* TODO: replace the $('.compare_view_files').on('click') below
315 when new diffs are integrated */
315 when new diffs are integrated */
316 'click', '.cb-lineno a', function(event) {
316 'click', '.cb-lineno a', function(event) {
317
317
318 if ($(this).attr('data-line-no') !== ""){
318 if ($(this).attr('data-line-no') !== ""){
319 $('.cb-line-selected').removeClass('cb-line-selected');
319 $('.cb-line-selected').removeClass('cb-line-selected');
320 var td = $(this).parent();
320 var td = $(this).parent();
321 td.addClass('cb-line-selected'); // line number td
321 td.addClass('cb-line-selected'); // line number td
322 td.prev().addClass('cb-line-selected'); // line data td
322 td.prev().addClass('cb-line-selected'); // line data td
323 td.next().addClass('cb-line-selected'); // line content td
323 td.next().addClass('cb-line-selected'); // line content td
324
324
325 // Replace URL without jumping to it if browser supports.
325 // Replace URL without jumping to it if browser supports.
326 // Default otherwise
326 // Default otherwise
327 if (history.pushState) {
327 if (history.pushState) {
328 var new_location = location.href.rstrip('#');
328 var new_location = location.href.rstrip('#');
329 if (location.hash) {
329 if (location.hash) {
330 new_location = new_location.replace(location.hash, "");
330 new_location = new_location.replace(location.hash, "");
331 }
331 }
332
332
333 // Make new anchor url
333 // Make new anchor url
334 new_location = new_location + $(this).attr('href');
334 new_location = new_location + $(this).attr('href');
335 history.pushState(true, document.title, new_location);
335 history.pushState(true, document.title, new_location);
336
336
337 return false;
337 return false;
338 }
338 }
339 }
339 }
340 });
340 });
341
341
342 $('.compare_view_files').on( /* TODO: replace this with .cb function above
342 $('.compare_view_files').on( /* TODO: replace this with .cb function above
343 when new diffs are integrated */
343 when new diffs are integrated */
344 'click', 'tr.line .lineno a',function(event) {
344 'click', 'tr.line .lineno a',function(event) {
345 if ($(this).text() != ""){
345 if ($(this).text() != ""){
346 $('tr.line').removeClass('selected');
346 $('tr.line').removeClass('selected');
347 $(this).parents("tr.line").addClass('selected');
347 $(this).parents("tr.line").addClass('selected');
348
348
349 // Replace URL without jumping to it if browser supports.
349 // Replace URL without jumping to it if browser supports.
350 // Default otherwise
350 // Default otherwise
351 if (history.pushState) {
351 if (history.pushState) {
352 var new_location = location.href;
352 var new_location = location.href;
353 if (location.hash){
353 if (location.hash){
354 new_location = new_location.replace(location.hash, "");
354 new_location = new_location.replace(location.hash, "");
355 }
355 }
356
356
357 // Make new anchor url
357 // Make new anchor url
358 var new_location = new_location+$(this).attr('href');
358 var new_location = new_location+$(this).attr('href');
359 history.pushState(true, document.title, new_location);
359 history.pushState(true, document.title, new_location);
360
360
361 return false;
361 return false;
362 }
362 }
363 }
363 }
364 });
364 });
365
365
366 $('.compare_view_files').on(
366 $('.compare_view_files').on(
367 'click', 'tr.line .add-comment-line a',function(event) {
367 'click', 'tr.line .add-comment-line a',function(event) {
368 var tr = $(event.currentTarget).parents('tr.line')[0];
368 var tr = $(event.currentTarget).parents('tr.line')[0];
369 injectInlineForm(tr);
369 injectInlineForm(tr);
370 return false;
370 return false;
371 });
371 });
372
372
373 $('.collapse_file').on('click', function(e) {
373 $('.collapse_file').on('click', function(e) {
374 e.stopPropagation();
374 e.stopPropagation();
375 if ($(e.target).is('a')) { return; }
375 if ($(e.target).is('a')) { return; }
376 var node = $(e.delegateTarget).first();
376 var node = $(e.delegateTarget).first();
377 var icon = $($(node.children().first()).children().first());
377 var icon = $($(node.children().first()).children().first());
378 var id = node.attr('fid');
378 var id = node.attr('fid');
379 var target = $('#'+id);
379 var target = $('#'+id);
380 var tr = $('#tr_'+id);
380 var tr = $('#tr_'+id);
381 var diff = $('#diff_'+id);
381 var diff = $('#diff_'+id);
382 if(node.hasClass('expand_file')){
382 if(node.hasClass('expand_file')){
383 node.removeClass('expand_file');
383 node.removeClass('expand_file');
384 icon.removeClass('expand_file_icon');
384 icon.removeClass('expand_file_icon');
385 node.addClass('collapse_file');
385 node.addClass('collapse_file');
386 icon.addClass('collapse_file_icon');
386 icon.addClass('collapse_file_icon');
387 diff.show();
387 diff.show();
388 tr.show();
388 tr.show();
389 target.show();
389 target.show();
390 } else {
390 } else {
391 node.removeClass('collapse_file');
391 node.removeClass('collapse_file');
392 icon.removeClass('collapse_file_icon');
392 icon.removeClass('collapse_file_icon');
393 node.addClass('expand_file');
393 node.addClass('expand_file');
394 icon.addClass('expand_file_icon');
394 icon.addClass('expand_file_icon');
395 diff.hide();
395 diff.hide();
396 tr.hide();
396 tr.hide();
397 target.hide();
397 target.hide();
398 }
398 }
399 });
399 });
400
400
401 $('#expand_all_files').click(function() {
401 $('#expand_all_files').click(function() {
402 $('.expand_file').each(function() {
402 $('.expand_file').each(function() {
403 var node = $(this);
403 var node = $(this);
404 var icon = $($(node.children().first()).children().first());
404 var icon = $($(node.children().first()).children().first());
405 var id = $(this).attr('fid');
405 var id = $(this).attr('fid');
406 var target = $('#'+id);
406 var target = $('#'+id);
407 var tr = $('#tr_'+id);
407 var tr = $('#tr_'+id);
408 var diff = $('#diff_'+id);
408 var diff = $('#diff_'+id);
409 node.removeClass('expand_file');
409 node.removeClass('expand_file');
410 icon.removeClass('expand_file_icon');
410 icon.removeClass('expand_file_icon');
411 node.addClass('collapse_file');
411 node.addClass('collapse_file');
412 icon.addClass('collapse_file_icon');
412 icon.addClass('collapse_file_icon');
413 diff.show();
413 diff.show();
414 tr.show();
414 tr.show();
415 target.show();
415 target.show();
416 });
416 });
417 });
417 });
418
418
419 $('#collapse_all_files').click(function() {
419 $('#collapse_all_files').click(function() {
420 $('.collapse_file').each(function() {
420 $('.collapse_file').each(function() {
421 var node = $(this);
421 var node = $(this);
422 var icon = $($(node.children().first()).children().first());
422 var icon = $($(node.children().first()).children().first());
423 var id = $(this).attr('fid');
423 var id = $(this).attr('fid');
424 var target = $('#'+id);
424 var target = $('#'+id);
425 var tr = $('#tr_'+id);
425 var tr = $('#tr_'+id);
426 var diff = $('#diff_'+id);
426 var diff = $('#diff_'+id);
427 node.removeClass('collapse_file');
427 node.removeClass('collapse_file');
428 icon.removeClass('collapse_file_icon');
428 icon.removeClass('collapse_file_icon');
429 node.addClass('expand_file');
429 node.addClass('expand_file');
430 icon.addClass('expand_file_icon');
430 icon.addClass('expand_file_icon');
431 diff.hide();
431 diff.hide();
432 tr.hide();
432 tr.hide();
433 target.hide();
433 target.hide();
434 });
434 });
435 });
435 });
436
436
437 // Mouse over behavior for comments and line selection
437 // Mouse over behavior for comments and line selection
438
438
439 // Select the line that comes from the url anchor
439 // Select the line that comes from the url anchor
440 // At the time of development, Chrome didn't seem to support jquery's :target
440 // At the time of development, Chrome didn't seem to support jquery's :target
441 // element, so I had to scroll manually
441 // element, so I had to scroll manually
442
442
443 if (location.hash) {
443 if (location.hash) {
444 var result = splitDelimitedHash(location.hash);
444 var result = splitDelimitedHash(location.hash);
445 var loc = result.loc;
445 var loc = result.loc;
446 if (loc.length > 1) {
446 if (loc.length > 1) {
447
447
448 var highlightable_line_tds = [];
448 var highlightable_line_tds = [];
449
449
450 // source code line format
450 // source code line format
451 var page_highlights = loc.substring(
451 var page_highlights = loc.substring(
452 loc.indexOf('#') + 1).split('L');
452 loc.indexOf('#') + 1).split('L');
453
453
454 if (page_highlights.length > 1) {
454 if (page_highlights.length > 1) {
455 var highlight_ranges = page_highlights[1].split(",");
455 var highlight_ranges = page_highlights[1].split(",");
456 var h_lines = [];
456 var h_lines = [];
457 for (var pos in highlight_ranges) {
457 for (var pos in highlight_ranges) {
458 var _range = highlight_ranges[pos].split('-');
458 var _range = highlight_ranges[pos].split('-');
459 if (_range.length === 2) {
459 if (_range.length === 2) {
460 var start = parseInt(_range[0]);
460 var start = parseInt(_range[0]);
461 var end = parseInt(_range[1]);
461 var end = parseInt(_range[1]);
462 if (start < end) {
462 if (start < end) {
463 for (var i = start; i <= end; i++) {
463 for (var i = start; i <= end; i++) {
464 h_lines.push(i);
464 h_lines.push(i);
465 }
465 }
466 }
466 }
467 }
467 }
468 else {
468 else {
469 h_lines.push(parseInt(highlight_ranges[pos]));
469 h_lines.push(parseInt(highlight_ranges[pos]));
470 }
470 }
471 }
471 }
472 for (pos in h_lines) {
472 for (pos in h_lines) {
473 var line_td = $('td.cb-lineno#L' + h_lines[pos]);
473 var line_td = $('td.cb-lineno#L' + h_lines[pos]);
474 if (line_td.length) {
474 if (line_td.length) {
475 highlightable_line_tds.push(line_td);
475 highlightable_line_tds.push(line_td);
476 }
476 }
477 }
477 }
478 }
478 }
479
479
480 // now check a direct id reference (diff page)
480 // now check a direct id reference (diff page)
481 if ($(loc).length && $(loc).hasClass('cb-lineno')) {
481 if ($(loc).length && $(loc).hasClass('cb-lineno')) {
482 highlightable_line_tds.push($(loc));
482 highlightable_line_tds.push($(loc));
483 }
483 }
484 $.each(highlightable_line_tds, function (i, $td) {
484 $.each(highlightable_line_tds, function (i, $td) {
485 $td.addClass('cb-line-selected'); // line number td
485 $td.addClass('cb-line-selected'); // line number td
486 $td.prev().addClass('cb-line-selected'); // line data
486 $td.prev().addClass('cb-line-selected'); // line data
487 $td.next().addClass('cb-line-selected'); // line content
487 $td.next().addClass('cb-line-selected'); // line content
488 });
488 });
489
489
490 if (highlightable_line_tds.length) {
490 if (highlightable_line_tds.length) {
491 var $first_line_td = highlightable_line_tds[0];
491 var $first_line_td = highlightable_line_tds[0];
492 scrollToElement($first_line_td);
492 scrollToElement($first_line_td);
493 $.Topic('/ui/plugins/code/anchor_focus').prepareOrPublish({
493 $.Topic('/ui/plugins/code/anchor_focus').prepareOrPublish({
494 td: $first_line_td,
494 td: $first_line_td,
495 remainder: result.remainder
495 remainder: result.remainder
496 });
496 });
497 }
497 }
498 }
498 }
499 }
499 }
500 collapsableContent();
500 collapsableContent();
501 });
501 });
@@ -1,600 +1,609 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="root.mako"/>
2 <%inherit file="root.mako"/>
3
3
4 <div class="outerwrapper">
4 <div class="outerwrapper">
5 <!-- HEADER -->
5 <!-- HEADER -->
6 <div class="header">
6 <div class="header">
7 <div id="header-inner" class="wrapper">
7 <div id="header-inner" class="wrapper">
8 <div id="logo">
8 <div id="logo">
9 <div class="logo-wrapper">
9 <div class="logo-wrapper">
10 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
10 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
11 </div>
11 </div>
12 %if c.rhodecode_name:
12 %if c.rhodecode_name:
13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
14 %endif
14 %endif
15 </div>
15 </div>
16 <!-- MENU BAR NAV -->
16 <!-- MENU BAR NAV -->
17 ${self.menu_bar_nav()}
17 ${self.menu_bar_nav()}
18 <!-- END MENU BAR NAV -->
18 <!-- END MENU BAR NAV -->
19 </div>
19 </div>
20 </div>
20 </div>
21 ${self.menu_bar_subnav()}
21 ${self.menu_bar_subnav()}
22 <!-- END HEADER -->
22 <!-- END HEADER -->
23
23
24 <!-- CONTENT -->
24 <!-- CONTENT -->
25 <div id="content" class="wrapper">
25 <div id="content" class="wrapper">
26
26
27 <rhodecode-toast id="notifications"></rhodecode-toast>
27 <rhodecode-toast id="notifications"></rhodecode-toast>
28
28
29 <div class="main">
29 <div class="main">
30 ${next.main()}
30 ${next.main()}
31 </div>
31 </div>
32 </div>
32 </div>
33 <!-- END CONTENT -->
33 <!-- END CONTENT -->
34
34
35 </div>
35 </div>
36 <!-- FOOTER -->
36 <!-- FOOTER -->
37 <div id="footer">
37 <div id="footer">
38 <div id="footer-inner" class="title wrapper">
38 <div id="footer-inner" class="title wrapper">
39 <div>
39 <div>
40 <p class="footer-link-right">
40 <p class="footer-link-right">
41 % if c.visual.show_version:
41 % if c.visual.show_version:
42 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
42 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
43 % endif
43 % endif
44 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
44 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
45 % if c.visual.rhodecode_support_url:
45 % if c.visual.rhodecode_support_url:
46 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
46 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
47 % endif
47 % endif
48 </p>
48 </p>
49 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
49 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
50 <p class="server-instance" style="display:${sid}">
50 <p class="server-instance" style="display:${sid}">
51 ## display hidden instance ID if specially defined
51 ## display hidden instance ID if specially defined
52 % if c.rhodecode_instanceid:
52 % if c.rhodecode_instanceid:
53 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
53 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
54 % endif
54 % endif
55 </p>
55 </p>
56 </div>
56 </div>
57 </div>
57 </div>
58 </div>
58 </div>
59
59
60 <!-- END FOOTER -->
60 <!-- END FOOTER -->
61
61
62 ### MAKO DEFS ###
62 ### MAKO DEFS ###
63
63
64 <%def name="menu_bar_subnav()">
64 <%def name="menu_bar_subnav()">
65 </%def>
65 </%def>
66
66
67 <%def name="breadcrumbs(class_='breadcrumbs')">
67 <%def name="breadcrumbs(class_='breadcrumbs')">
68 <div class="${class_}">
68 <div class="${class_}">
69 ${self.breadcrumbs_links()}
69 ${self.breadcrumbs_links()}
70 </div>
70 </div>
71 </%def>
71 </%def>
72
72
73 <%def name="admin_menu()">
73 <%def name="admin_menu()">
74 <ul class="admin_menu submenu">
74 <ul class="admin_menu submenu">
75 <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
75 <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
76 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
76 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
77 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
77 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
78 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
78 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
79 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
79 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
80 <li><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
80 <li><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
81 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
81 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
82 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
82 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
83 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
83 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
84 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
84 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
85 </ul>
85 </ul>
86 </%def>
86 </%def>
87
87
88
88
89 <%def name="dt_info_panel(elements)">
89 <%def name="dt_info_panel(elements)">
90 <dl class="dl-horizontal">
90 <dl class="dl-horizontal">
91 %for dt, dd, title, show_items in elements:
91 %for dt, dd, title, show_items in elements:
92 <dt>${dt}:</dt>
92 <dt>${dt}:</dt>
93 <dd title="${h.tooltip(title)}">
93 <dd title="${h.tooltip(title)}">
94 %if callable(dd):
94 %if callable(dd):
95 ## allow lazy evaluation of elements
95 ## allow lazy evaluation of elements
96 ${dd()}
96 ${dd()}
97 %else:
97 %else:
98 ${dd}
98 ${dd}
99 %endif
99 %endif
100 %if show_items:
100 %if show_items:
101 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
101 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
102 %endif
102 %endif
103 </dd>
103 </dd>
104
104
105 %if show_items:
105 %if show_items:
106 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
106 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
107 %for item in show_items:
107 %for item in show_items:
108 <dt></dt>
108 <dt></dt>
109 <dd>${item}</dd>
109 <dd>${item}</dd>
110 %endfor
110 %endfor
111 </div>
111 </div>
112 %endif
112 %endif
113
113
114 %endfor
114 %endfor
115 </dl>
115 </dl>
116 </%def>
116 </%def>
117
117
118
118
119 <%def name="gravatar(email, size=16)">
119 <%def name="gravatar(email, size=16)">
120 <%
120 <%
121 if (size > 16):
121 if (size > 16):
122 gravatar_class = 'gravatar gravatar-large'
122 gravatar_class = 'gravatar gravatar-large'
123 else:
123 else:
124 gravatar_class = 'gravatar'
124 gravatar_class = 'gravatar'
125 %>
125 %>
126 <%doc>
126 <%doc>
127 TODO: johbo: For now we serve double size images to make it smooth
127 TODO: johbo: For now we serve double size images to make it smooth
128 for retina. This is how it worked until now. Should be replaced
128 for retina. This is how it worked until now. Should be replaced
129 with a better solution at some point.
129 with a better solution at some point.
130 </%doc>
130 </%doc>
131 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
131 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
132 </%def>
132 </%def>
133
133
134
134
135 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
135 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
136 <% email = h.email_or_none(contact) %>
136 <% email = h.email_or_none(contact) %>
137 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
137 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
138 ${self.gravatar(email, size)}
138 ${self.gravatar(email, size)}
139 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
139 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
140 </div>
140 </div>
141 </%def>
141 </%def>
142
142
143
143
144 ## admin menu used for people that have some admin resources
144 ## admin menu used for people that have some admin resources
145 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
145 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
146 <ul class="submenu">
146 <ul class="submenu">
147 %if repositories:
147 %if repositories:
148 <li class="local-admin-repos"><a href="${h.url('repos')}">${_('Repositories')}</a></li>
148 <li class="local-admin-repos"><a href="${h.url('repos')}">${_('Repositories')}</a></li>
149 %endif
149 %endif
150 %if repository_groups:
150 %if repository_groups:
151 <li class="local-admin-repo-groups"><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
151 <li class="local-admin-repo-groups"><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
152 %endif
152 %endif
153 %if user_groups:
153 %if user_groups:
154 <li class="local-admin-user-groups"><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
154 <li class="local-admin-user-groups"><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
155 %endif
155 %endif
156 </ul>
156 </ul>
157 </%def>
157 </%def>
158
158
159 <%def name="repo_page_title(repo_instance)">
159 <%def name="repo_page_title(repo_instance)">
160 <div class="title-content">
160 <div class="title-content">
161 <div class="title-main">
161 <div class="title-main">
162 ## SVN/HG/GIT icons
162 ## SVN/HG/GIT icons
163 %if h.is_hg(repo_instance):
163 %if h.is_hg(repo_instance):
164 <i class="icon-hg"></i>
164 <i class="icon-hg"></i>
165 %endif
165 %endif
166 %if h.is_git(repo_instance):
166 %if h.is_git(repo_instance):
167 <i class="icon-git"></i>
167 <i class="icon-git"></i>
168 %endif
168 %endif
169 %if h.is_svn(repo_instance):
169 %if h.is_svn(repo_instance):
170 <i class="icon-svn"></i>
170 <i class="icon-svn"></i>
171 %endif
171 %endif
172
172
173 ## public/private
173 ## public/private
174 %if repo_instance.private:
174 %if repo_instance.private:
175 <i class="icon-repo-private"></i>
175 <i class="icon-repo-private"></i>
176 %else:
176 %else:
177 <i class="icon-repo-public"></i>
177 <i class="icon-repo-public"></i>
178 %endif
178 %endif
179
179
180 ## repo name with group name
180 ## repo name with group name
181 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
181 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
182
182
183 </div>
183 </div>
184
184
185 ## FORKED
185 ## FORKED
186 %if repo_instance.fork:
186 %if repo_instance.fork:
187 <p>
187 <p>
188 <i class="icon-code-fork"></i> ${_('Fork of')}
188 <i class="icon-code-fork"></i> ${_('Fork of')}
189 <a href="${h.route_path('repo_summary',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
189 <a href="${h.route_path('repo_summary',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
190 </p>
190 </p>
191 %endif
191 %endif
192
192
193 ## IMPORTED FROM REMOTE
193 ## IMPORTED FROM REMOTE
194 %if repo_instance.clone_uri:
194 %if repo_instance.clone_uri:
195 <p>
195 <p>
196 <i class="icon-code-fork"></i> ${_('Clone from')}
196 <i class="icon-code-fork"></i> ${_('Clone from')}
197 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
197 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
198 </p>
198 </p>
199 %endif
199 %endif
200
200
201 ## LOCKING STATUS
201 ## LOCKING STATUS
202 %if repo_instance.locked[0]:
202 %if repo_instance.locked[0]:
203 <p class="locking_locked">
203 <p class="locking_locked">
204 <i class="icon-repo-lock"></i>
204 <i class="icon-repo-lock"></i>
205 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
205 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
206 </p>
206 </p>
207 %elif repo_instance.enable_locking:
207 %elif repo_instance.enable_locking:
208 <p class="locking_unlocked">
208 <p class="locking_unlocked">
209 <i class="icon-repo-unlock"></i>
209 <i class="icon-repo-unlock"></i>
210 ${_('Repository not locked. Pull repository to lock it.')}
210 ${_('Repository not locked. Pull repository to lock it.')}
211 </p>
211 </p>
212 %endif
212 %endif
213
213
214 </div>
214 </div>
215 </%def>
215 </%def>
216
216
217 <%def name="repo_menu(active=None)">
217 <%def name="repo_menu(active=None)">
218 <%
218 <%
219 def is_active(selected):
219 def is_active(selected):
220 if selected == active:
220 if selected == active:
221 return "active"
221 return "active"
222 %>
222 %>
223
223
224 <!--- CONTEXT BAR -->
224 <!--- CONTEXT BAR -->
225 <div id="context-bar">
225 <div id="context-bar">
226 <div class="wrapper">
226 <div class="wrapper">
227 <ul id="context-pages" class="horizontal-list navigation">
227 <ul id="context-pages" class="horizontal-list navigation">
228 <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
228 <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
229 <li class="${is_active('changelog')}"><a class="menulink" href="${h.route_path('repo_changelog', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
229 <li class="${is_active('changelog')}"><a class="menulink" href="${h.route_path('repo_changelog', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
230 <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
230 <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
231 <li class="${is_active('compare')}">
231 <li class="${is_active('compare')}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
232 <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a>
233 </li>
234 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
232 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
235 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
233 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
236 <li class="${is_active('showpullrequest')}">
234 <li class="${is_active('showpullrequest')}">
237 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
235 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
238 %if c.repository_pull_requests:
236 %if c.repository_pull_requests:
239 <span class="pr_notifications">${c.repository_pull_requests}</span>
237 <span class="pr_notifications">${c.repository_pull_requests}</span>
240 %endif
238 %endif
241 <div class="menulabel">${_('Pull Requests')}</div>
239 <div class="menulabel">${_('Pull Requests')}</div>
242 </a>
240 </a>
243 </li>
241 </li>
244 %endif
242 %endif
245 <li class="${is_active('options')}">
243 <li class="${is_active('options')}">
246 <a class="menulink dropdown">
244 <a class="menulink dropdown">
247 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
245 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
248 </a>
246 </a>
249 <ul class="submenu">
247 <ul class="submenu">
250 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
248 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
251 <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
249 <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
252 %endif
250 %endif
253 %if c.rhodecode_db_repo.fork:
251 %if c.rhodecode_db_repo.fork:
254 <li><a href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1], target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], merge=1)}">
252 <li>
255 ${_('Compare fork')}</a></li>
253 <a title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
254 href="${h.route_path('repo_compare',
255 repo_name=c.rhodecode_db_repo.fork.repo_name,
256 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
257 source_ref=c.rhodecode_db_repo.landing_rev[1],
258 target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
259 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
260 _query=dict(merge=1))}"
261 >
262 ${_('Compare fork')}
263 </a>
264 </li>
256 %endif
265 %endif
257
266
258 <li><a href="${h.route_path('search_repo',repo_name=c.repo_name)}">${_('Search')}</a></li>
267 <li><a href="${h.route_path('search_repo',repo_name=c.repo_name)}">${_('Search')}</a></li>
259
268
260 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
269 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
261 %if c.rhodecode_db_repo.locked[0]:
270 %if c.rhodecode_db_repo.locked[0]:
262 <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
271 <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
263 %else:
272 %else:
264 <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
273 <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
265 %endif
274 %endif
266 %endif
275 %endif
267 %if c.rhodecode_user.username != h.DEFAULT_USER:
276 %if c.rhodecode_user.username != h.DEFAULT_USER:
268 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
277 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
269 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li>
278 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li>
270 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
279 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
271 %endif
280 %endif
272 %endif
281 %endif
273 </ul>
282 </ul>
274 </li>
283 </li>
275 </ul>
284 </ul>
276 </div>
285 </div>
277 <div class="clear"></div>
286 <div class="clear"></div>
278 </div>
287 </div>
279 <!--- END CONTEXT BAR -->
288 <!--- END CONTEXT BAR -->
280
289
281 </%def>
290 </%def>
282
291
283 <%def name="usermenu(active=False)">
292 <%def name="usermenu(active=False)">
284 ## USER MENU
293 ## USER MENU
285 <li id="quick_login_li" class="${'active' if active else ''}">
294 <li id="quick_login_li" class="${'active' if active else ''}">
286 <a id="quick_login_link" class="menulink childs">
295 <a id="quick_login_link" class="menulink childs">
287 ${gravatar(c.rhodecode_user.email, 20)}
296 ${gravatar(c.rhodecode_user.email, 20)}
288 <span class="user">
297 <span class="user">
289 %if c.rhodecode_user.username != h.DEFAULT_USER:
298 %if c.rhodecode_user.username != h.DEFAULT_USER:
290 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
299 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
291 %else:
300 %else:
292 <span>${_('Sign in')}</span>
301 <span>${_('Sign in')}</span>
293 %endif
302 %endif
294 </span>
303 </span>
295 </a>
304 </a>
296
305
297 <div class="user-menu submenu">
306 <div class="user-menu submenu">
298 <div id="quick_login">
307 <div id="quick_login">
299 %if c.rhodecode_user.username == h.DEFAULT_USER:
308 %if c.rhodecode_user.username == h.DEFAULT_USER:
300 <h4>${_('Sign in to your account')}</h4>
309 <h4>${_('Sign in to your account')}</h4>
301 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
310 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
302 <div class="form form-vertical">
311 <div class="form form-vertical">
303 <div class="fields">
312 <div class="fields">
304 <div class="field">
313 <div class="field">
305 <div class="label">
314 <div class="label">
306 <label for="username">${_('Username')}:</label>
315 <label for="username">${_('Username')}:</label>
307 </div>
316 </div>
308 <div class="input">
317 <div class="input">
309 ${h.text('username',class_='focus',tabindex=1)}
318 ${h.text('username',class_='focus',tabindex=1)}
310 </div>
319 </div>
311
320
312 </div>
321 </div>
313 <div class="field">
322 <div class="field">
314 <div class="label">
323 <div class="label">
315 <label for="password">${_('Password')}:</label>
324 <label for="password">${_('Password')}:</label>
316 %if h.HasPermissionAny('hg.password_reset.enabled')():
325 %if h.HasPermissionAny('hg.password_reset.enabled')():
317 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span>
326 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span>
318 %endif
327 %endif
319 </div>
328 </div>
320 <div class="input">
329 <div class="input">
321 ${h.password('password',class_='focus',tabindex=2)}
330 ${h.password('password',class_='focus',tabindex=2)}
322 </div>
331 </div>
323 </div>
332 </div>
324 <div class="buttons">
333 <div class="buttons">
325 <div class="register">
334 <div class="register">
326 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
335 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
327 ${h.link_to(_("Don't have an account?"),h.route_path('register'))} <br/>
336 ${h.link_to(_("Don't have an account?"),h.route_path('register'))} <br/>
328 %endif
337 %endif
329 ${h.link_to(_("Using external auth? Sign In here."),h.route_path('login'))}
338 ${h.link_to(_("Using external auth? Sign In here."),h.route_path('login'))}
330 </div>
339 </div>
331 <div class="submit">
340 <div class="submit">
332 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
341 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
333 </div>
342 </div>
334 </div>
343 </div>
335 </div>
344 </div>
336 </div>
345 </div>
337 ${h.end_form()}
346 ${h.end_form()}
338 %else:
347 %else:
339 <div class="">
348 <div class="">
340 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
349 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
341 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
350 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
342 <div class="email">${c.rhodecode_user.email}</div>
351 <div class="email">${c.rhodecode_user.email}</div>
343 </div>
352 </div>
344 <div class="">
353 <div class="">
345 <ol class="links">
354 <ol class="links">
346 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
355 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
347 % if c.rhodecode_user.personal_repo_group:
356 % if c.rhodecode_user.personal_repo_group:
348 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
357 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
349 % endif
358 % endif
350 <li class="logout">
359 <li class="logout">
351 ${h.secure_form(h.route_path('logout'), request=request)}
360 ${h.secure_form(h.route_path('logout'), request=request)}
352 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
361 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
353 ${h.end_form()}
362 ${h.end_form()}
354 </li>
363 </li>
355 </ol>
364 </ol>
356 </div>
365 </div>
357 %endif
366 %endif
358 </div>
367 </div>
359 </div>
368 </div>
360 %if c.rhodecode_user.username != h.DEFAULT_USER:
369 %if c.rhodecode_user.username != h.DEFAULT_USER:
361 <div class="pill_container">
370 <div class="pill_container">
362 <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a>
371 <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a>
363 </div>
372 </div>
364 % endif
373 % endif
365 </li>
374 </li>
366 </%def>
375 </%def>
367
376
368 <%def name="menu_items(active=None)">
377 <%def name="menu_items(active=None)">
369 <%
378 <%
370 def is_active(selected):
379 def is_active(selected):
371 if selected == active:
380 if selected == active:
372 return "active"
381 return "active"
373 return ""
382 return ""
374 %>
383 %>
375 <ul id="quick" class="main_nav navigation horizontal-list">
384 <ul id="quick" class="main_nav navigation horizontal-list">
376 <!-- repo switcher -->
385 <!-- repo switcher -->
377 <li class="${is_active('repositories')} repo_switcher_li has_select2">
386 <li class="${is_active('repositories')} repo_switcher_li has_select2">
378 <input id="repo_switcher" name="repo_switcher" type="hidden">
387 <input id="repo_switcher" name="repo_switcher" type="hidden">
379 </li>
388 </li>
380
389
381 ## ROOT MENU
390 ## ROOT MENU
382 %if c.rhodecode_user.username != h.DEFAULT_USER:
391 %if c.rhodecode_user.username != h.DEFAULT_USER:
383 <li class="${is_active('journal')}">
392 <li class="${is_active('journal')}">
384 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
393 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
385 <div class="menulabel">${_('Journal')}</div>
394 <div class="menulabel">${_('Journal')}</div>
386 </a>
395 </a>
387 </li>
396 </li>
388 %else:
397 %else:
389 <li class="${is_active('journal')}">
398 <li class="${is_active('journal')}">
390 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
399 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
391 <div class="menulabel">${_('Public journal')}</div>
400 <div class="menulabel">${_('Public journal')}</div>
392 </a>
401 </a>
393 </li>
402 </li>
394 %endif
403 %endif
395 <li class="${is_active('gists')}">
404 <li class="${is_active('gists')}">
396 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
405 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
397 <div class="menulabel">${_('Gists')}</div>
406 <div class="menulabel">${_('Gists')}</div>
398 </a>
407 </a>
399 </li>
408 </li>
400 <li class="${is_active('search')}">
409 <li class="${is_active('search')}">
401 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.route_path('search')}">
410 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.route_path('search')}">
402 <div class="menulabel">${_('Search')}</div>
411 <div class="menulabel">${_('Search')}</div>
403 </a>
412 </a>
404 </li>
413 </li>
405 % if h.HasPermissionAll('hg.admin')('access admin main page'):
414 % if h.HasPermissionAll('hg.admin')('access admin main page'):
406 <li class="${is_active('admin')}">
415 <li class="${is_active('admin')}">
407 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
416 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
408 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
417 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
409 </a>
418 </a>
410 ${admin_menu()}
419 ${admin_menu()}
411 </li>
420 </li>
412 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
421 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
413 <li class="${is_active('admin')}">
422 <li class="${is_active('admin')}">
414 <a class="menulink childs" title="${_('Delegated Admin settings')}">
423 <a class="menulink childs" title="${_('Delegated Admin settings')}">
415 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
424 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
416 </a>
425 </a>
417 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
426 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
418 c.rhodecode_user.repository_groups_admin,
427 c.rhodecode_user.repository_groups_admin,
419 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
428 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
420 </li>
429 </li>
421 % endif
430 % endif
422 % if c.debug_style:
431 % if c.debug_style:
423 <li class="${is_active('debug_style')}">
432 <li class="${is_active('debug_style')}">
424 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
433 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
425 <div class="menulabel">${_('Style')}</div>
434 <div class="menulabel">${_('Style')}</div>
426 </a>
435 </a>
427 </li>
436 </li>
428 % endif
437 % endif
429 ## render extra user menu
438 ## render extra user menu
430 ${usermenu(active=(active=='my_account'))}
439 ${usermenu(active=(active=='my_account'))}
431 </ul>
440 </ul>
432
441
433 <script type="text/javascript">
442 <script type="text/javascript">
434 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
443 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
435
444
436 /*format the look of items in the list*/
445 /*format the look of items in the list*/
437 var format = function(state, escapeMarkup){
446 var format = function(state, escapeMarkup){
438 if (!state.id){
447 if (!state.id){
439 return state.text; // optgroup
448 return state.text; // optgroup
440 }
449 }
441 var obj_dict = state.obj;
450 var obj_dict = state.obj;
442 var tmpl = '';
451 var tmpl = '';
443
452
444 if(obj_dict && state.type == 'repo'){
453 if(obj_dict && state.type == 'repo'){
445 if(obj_dict['repo_type'] === 'hg'){
454 if(obj_dict['repo_type'] === 'hg'){
446 tmpl += '<i class="icon-hg"></i> ';
455 tmpl += '<i class="icon-hg"></i> ';
447 }
456 }
448 else if(obj_dict['repo_type'] === 'git'){
457 else if(obj_dict['repo_type'] === 'git'){
449 tmpl += '<i class="icon-git"></i> ';
458 tmpl += '<i class="icon-git"></i> ';
450 }
459 }
451 else if(obj_dict['repo_type'] === 'svn'){
460 else if(obj_dict['repo_type'] === 'svn'){
452 tmpl += '<i class="icon-svn"></i> ';
461 tmpl += '<i class="icon-svn"></i> ';
453 }
462 }
454 if(obj_dict['private']){
463 if(obj_dict['private']){
455 tmpl += '<i class="icon-lock" ></i> ';
464 tmpl += '<i class="icon-lock" ></i> ';
456 }
465 }
457 else if(visual_show_public_icon){
466 else if(visual_show_public_icon){
458 tmpl += '<i class="icon-unlock-alt"></i> ';
467 tmpl += '<i class="icon-unlock-alt"></i> ';
459 }
468 }
460 }
469 }
461 if(obj_dict && state.type == 'commit') {
470 if(obj_dict && state.type == 'commit') {
462 tmpl += '<i class="icon-tag"></i>';
471 tmpl += '<i class="icon-tag"></i>';
463 }
472 }
464 if(obj_dict && state.type == 'group'){
473 if(obj_dict && state.type == 'group'){
465 tmpl += '<i class="icon-folder-close"></i> ';
474 tmpl += '<i class="icon-folder-close"></i> ';
466 }
475 }
467 tmpl += escapeMarkup(state.text);
476 tmpl += escapeMarkup(state.text);
468 return tmpl;
477 return tmpl;
469 };
478 };
470
479
471 var formatResult = function(result, container, query, escapeMarkup) {
480 var formatResult = function(result, container, query, escapeMarkup) {
472 return format(result, escapeMarkup);
481 return format(result, escapeMarkup);
473 };
482 };
474
483
475 var formatSelection = function(data, container, escapeMarkup) {
484 var formatSelection = function(data, container, escapeMarkup) {
476 return format(data, escapeMarkup);
485 return format(data, escapeMarkup);
477 };
486 };
478
487
479 $("#repo_switcher").select2({
488 $("#repo_switcher").select2({
480 cachedDataSource: {},
489 cachedDataSource: {},
481 minimumInputLength: 2,
490 minimumInputLength: 2,
482 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
491 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
483 dropdownAutoWidth: true,
492 dropdownAutoWidth: true,
484 formatResult: formatResult,
493 formatResult: formatResult,
485 formatSelection: formatSelection,
494 formatSelection: formatSelection,
486 containerCssClass: "repo-switcher",
495 containerCssClass: "repo-switcher",
487 dropdownCssClass: "repo-switcher-dropdown",
496 dropdownCssClass: "repo-switcher-dropdown",
488 escapeMarkup: function(m){
497 escapeMarkup: function(m){
489 // don't escape our custom placeholder
498 // don't escape our custom placeholder
490 if(m.substr(0,23) == '<div class="menulabel">'){
499 if(m.substr(0,23) == '<div class="menulabel">'){
491 return m;
500 return m;
492 }
501 }
493
502
494 return Select2.util.escapeMarkup(m);
503 return Select2.util.escapeMarkup(m);
495 },
504 },
496 query: $.debounce(250, function(query){
505 query: $.debounce(250, function(query){
497 self = this;
506 self = this;
498 var cacheKey = query.term;
507 var cacheKey = query.term;
499 var cachedData = self.cachedDataSource[cacheKey];
508 var cachedData = self.cachedDataSource[cacheKey];
500
509
501 if (cachedData) {
510 if (cachedData) {
502 query.callback({results: cachedData.results});
511 query.callback({results: cachedData.results});
503 } else {
512 } else {
504 $.ajax({
513 $.ajax({
505 url: pyroutes.url('goto_switcher_data'),
514 url: pyroutes.url('goto_switcher_data'),
506 data: {'query': query.term},
515 data: {'query': query.term},
507 dataType: 'json',
516 dataType: 'json',
508 type: 'GET',
517 type: 'GET',
509 success: function(data) {
518 success: function(data) {
510 self.cachedDataSource[cacheKey] = data;
519 self.cachedDataSource[cacheKey] = data;
511 query.callback({results: data.results});
520 query.callback({results: data.results});
512 },
521 },
513 error: function(data, textStatus, errorThrown) {
522 error: function(data, textStatus, errorThrown) {
514 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
523 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
515 }
524 }
516 })
525 })
517 }
526 }
518 })
527 })
519 });
528 });
520
529
521 $("#repo_switcher").on('select2-selecting', function(e){
530 $("#repo_switcher").on('select2-selecting', function(e){
522 e.preventDefault();
531 e.preventDefault();
523 window.location = e.choice.url;
532 window.location = e.choice.url;
524 });
533 });
525
534
526 </script>
535 </script>
527 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
536 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
528 </%def>
537 </%def>
529
538
530 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
539 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
531 <div class="modal-dialog">
540 <div class="modal-dialog">
532 <div class="modal-content">
541 <div class="modal-content">
533 <div class="modal-header">
542 <div class="modal-header">
534 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
543 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
535 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
544 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
536 </div>
545 </div>
537 <div class="modal-body">
546 <div class="modal-body">
538 <div class="block-left">
547 <div class="block-left">
539 <table class="keyboard-mappings">
548 <table class="keyboard-mappings">
540 <tbody>
549 <tbody>
541 <tr>
550 <tr>
542 <th></th>
551 <th></th>
543 <th>${_('Site-wide shortcuts')}</th>
552 <th>${_('Site-wide shortcuts')}</th>
544 </tr>
553 </tr>
545 <%
554 <%
546 elems = [
555 elems = [
547 ('/', 'Open quick search box'),
556 ('/', 'Open quick search box'),
548 ('g h', 'Goto home page'),
557 ('g h', 'Goto home page'),
549 ('g g', 'Goto my private gists page'),
558 ('g g', 'Goto my private gists page'),
550 ('g G', 'Goto my public gists page'),
559 ('g G', 'Goto my public gists page'),
551 ('n r', 'New repository page'),
560 ('n r', 'New repository page'),
552 ('n g', 'New gist page'),
561 ('n g', 'New gist page'),
553 ]
562 ]
554 %>
563 %>
555 %for key, desc in elems:
564 %for key, desc in elems:
556 <tr>
565 <tr>
557 <td class="keys">
566 <td class="keys">
558 <span class="key tag">${key}</span>
567 <span class="key tag">${key}</span>
559 </td>
568 </td>
560 <td>${desc}</td>
569 <td>${desc}</td>
561 </tr>
570 </tr>
562 %endfor
571 %endfor
563 </tbody>
572 </tbody>
564 </table>
573 </table>
565 </div>
574 </div>
566 <div class="block-left">
575 <div class="block-left">
567 <table class="keyboard-mappings">
576 <table class="keyboard-mappings">
568 <tbody>
577 <tbody>
569 <tr>
578 <tr>
570 <th></th>
579 <th></th>
571 <th>${_('Repositories')}</th>
580 <th>${_('Repositories')}</th>
572 </tr>
581 </tr>
573 <%
582 <%
574 elems = [
583 elems = [
575 ('g s', 'Goto summary page'),
584 ('g s', 'Goto summary page'),
576 ('g c', 'Goto changelog page'),
585 ('g c', 'Goto changelog page'),
577 ('g f', 'Goto files page'),
586 ('g f', 'Goto files page'),
578 ('g F', 'Goto files page with file search activated'),
587 ('g F', 'Goto files page with file search activated'),
579 ('g p', 'Goto pull requests page'),
588 ('g p', 'Goto pull requests page'),
580 ('g o', 'Goto repository settings'),
589 ('g o', 'Goto repository settings'),
581 ('g O', 'Goto repository permissions settings'),
590 ('g O', 'Goto repository permissions settings'),
582 ]
591 ]
583 %>
592 %>
584 %for key, desc in elems:
593 %for key, desc in elems:
585 <tr>
594 <tr>
586 <td class="keys">
595 <td class="keys">
587 <span class="key tag">${key}</span>
596 <span class="key tag">${key}</span>
588 </td>
597 </td>
589 <td>${desc}</td>
598 <td>${desc}</td>
590 </tr>
599 </tr>
591 %endfor
600 %endfor
592 </tbody>
601 </tbody>
593 </table>
602 </table>
594 </div>
603 </div>
595 </div>
604 </div>
596 <div class="modal-footer">
605 <div class="modal-footer">
597 </div>
606 </div>
598 </div><!-- /.modal-content -->
607 </div><!-- /.modal-content -->
599 </div><!-- /.modal-dialog -->
608 </div><!-- /.modal-dialog -->
600 </div><!-- /.modal -->
609 </div><!-- /.modal -->
@@ -1,299 +1,297 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 <%inherit file="/base/base.mako"/>
3 <%inherit file="/base/base.mako"/>
4
4
5 <%def name="title()">
5 <%def name="title()">
6 ${_('%s Changelog') % c.repo_name}
6 ${_('%s Changelog') % c.repo_name}
7 %if c.changelog_for_path:
7 %if c.changelog_for_path:
8 /${c.changelog_for_path}
8 /${c.changelog_for_path}
9 %endif
9 %endif
10 %if c.rhodecode_name:
10 %if c.rhodecode_name:
11 &middot; ${h.branding(c.rhodecode_name)}
11 &middot; ${h.branding(c.rhodecode_name)}
12 %endif
12 %endif
13 </%def>
13 </%def>
14
14
15 <%def name="breadcrumbs_links()">
15 <%def name="breadcrumbs_links()">
16 %if c.changelog_for_path:
16 %if c.changelog_for_path:
17 /${c.changelog_for_path}
17 /${c.changelog_for_path}
18 %endif
18 %endif
19 </%def>
19 </%def>
20
20
21 <%def name="menu_bar_nav()">
21 <%def name="menu_bar_nav()">
22 ${self.menu_items(active='repositories')}
22 ${self.menu_items(active='repositories')}
23 </%def>
23 </%def>
24
24
25 <%def name="menu_bar_subnav()">
25 <%def name="menu_bar_subnav()">
26 ${self.repo_menu(active='changelog')}
26 ${self.repo_menu(active='changelog')}
27 </%def>
27 </%def>
28
28
29 <%def name="main()">
29 <%def name="main()">
30
30
31 <div class="box">
31 <div class="box">
32 <div class="title">
32 <div class="title">
33 ${self.repo_page_title(c.rhodecode_db_repo)}
33 ${self.repo_page_title(c.rhodecode_db_repo)}
34 <ul class="links">
34 <ul class="links">
35 <li>
35 <li>
36 <a href="#" class="btn btn-small" id="rev_range_container" style="display:none;"></a>
36 <a href="#" class="btn btn-small" id="rev_range_container" style="display:none;"></a>
37 %if c.rhodecode_db_repo.fork:
37 %if c.rhodecode_db_repo.fork:
38 <span>
38 <span>
39 <a id="compare_fork_button"
39 <a id="compare_fork_button"
40 title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
40 title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
41 class="btn btn-small"
41 class="btn btn-small"
42 href="${h.url('compare_url',
42 href="${h.route_path('repo_compare',
43 repo_name=c.rhodecode_db_repo.fork.repo_name,
43 repo_name=c.rhodecode_db_repo.fork.repo_name,
44 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
44 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
45 source_ref=c.rhodecode_db_repo.landing_rev[1],
45 source_ref=c.rhodecode_db_repo.landing_rev[1],
46 target_repo=c.repo_name,
47 target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
46 target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
48 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
47 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
49 merge=1)}"
48 _query=dict(merge=1, target_repo=c.repo_name))}"
50 >
49 >
51 <i class="icon-loop"></i>
52 ${_('Compare fork with Parent (%s)' % c.rhodecode_db_repo.fork.repo_name)}
50 ${_('Compare fork with Parent (%s)' % c.rhodecode_db_repo.fork.repo_name)}
53 </a>
51 </a>
54 </span>
52 </span>
55 %endif
53 %endif
56
54
57 ## pr open link
55 ## pr open link
58 %if h.is_hg(c.rhodecode_repo) or h.is_git(c.rhodecode_repo):
56 %if h.is_hg(c.rhodecode_repo) or h.is_git(c.rhodecode_repo):
59 <span>
57 <span>
60 <a id="open_new_pull_request" class="btn btn-small btn-success" href="${h.url('pullrequest_home',repo_name=c.repo_name)}">
58 <a id="open_new_pull_request" class="btn btn-small btn-success" href="${h.url('pullrequest_home',repo_name=c.repo_name)}">
61 ${_('Open new pull request')}
59 ${_('Open new pull request')}
62 </a>
60 </a>
63 </span>
61 </span>
64 %endif
62 %endif
65
63
66 ## clear selection
64 ## clear selection
67 <div title="${_('Clear selection')}" class="btn" id="rev_range_clear" style="display:none">
65 <div title="${_('Clear selection')}" class="btn" id="rev_range_clear" style="display:none">
68 ${_('Clear selection')}
66 ${_('Clear selection')}
69 </div>
67 </div>
70
68
71 </li>
69 </li>
72 </ul>
70 </ul>
73 </div>
71 </div>
74
72
75 % if c.pagination:
73 % if c.pagination:
76 <script type="text/javascript" src="${h.asset('js/jquery.commits-graph.js')}"></script>
74 <script type="text/javascript" src="${h.asset('js/jquery.commits-graph.js')}"></script>
77
75
78 <div class="graph-header">
76 <div class="graph-header">
79 <div id="filter_changelog">
77 <div id="filter_changelog">
80 ${h.hidden('branch_filter')}
78 ${h.hidden('branch_filter')}
81 %if c.selected_name:
79 %if c.selected_name:
82 <div class="btn btn-default" id="clear_filter" >
80 <div class="btn btn-default" id="clear_filter" >
83 ${_('Clear filter')}
81 ${_('Clear filter')}
84 </div>
82 </div>
85 %endif
83 %endif
86 </div>
84 </div>
87 ${self.breadcrumbs('breadcrumbs_light')}
85 ${self.breadcrumbs('breadcrumbs_light')}
88 <div id="commit-counter" data-total=${c.total_cs} class="pull-right">
86 <div id="commit-counter" data-total=${c.total_cs} class="pull-right">
89 ${_ungettext('showing %d out of %d commit', 'showing %d out of %d commits', c.showing_commits) % (c.showing_commits, c.total_cs)}
87 ${_ungettext('showing %d out of %d commit', 'showing %d out of %d commits', c.showing_commits) % (c.showing_commits, c.total_cs)}
90 </div>
88 </div>
91 </div>
89 </div>
92
90
93 <div id="graph">
91 <div id="graph">
94 <div class="graph-col-wrapper">
92 <div class="graph-col-wrapper">
95 <div id="graph_nodes">
93 <div id="graph_nodes">
96 <div id="graph_canvas"></div>
94 <div id="graph_canvas"></div>
97 </div>
95 </div>
98 <div id="graph_content" class="main-content graph_full_width">
96 <div id="graph_content" class="main-content graph_full_width">
99
97
100 <div class="table">
98 <div class="table">
101 <table id="changesets" class="rctable">
99 <table id="changesets" class="rctable">
102 <tr>
100 <tr>
103 ## checkbox
101 ## checkbox
104 <th></th>
102 <th></th>
105 <th colspan="2"></th>
103 <th colspan="2"></th>
106
104
107 <th>${_('Commit')}</th>
105 <th>${_('Commit')}</th>
108 ## commit message expand arrow
106 ## commit message expand arrow
109 <th></th>
107 <th></th>
110 <th>${_('Commit Message')}</th>
108 <th>${_('Commit Message')}</th>
111
109
112 <th>${_('Age')}</th>
110 <th>${_('Age')}</th>
113 <th>${_('Author')}</th>
111 <th>${_('Author')}</th>
114
112
115 <th>${_('Refs')}</th>
113 <th>${_('Refs')}</th>
116 </tr>
114 </tr>
117
115
118 <tbody class="commits-range">
116 <tbody class="commits-range">
119 <%include file='changelog_elements.mako'/>
117 <%include file='changelog_elements.mako'/>
120 </tbody>
118 </tbody>
121 </table>
119 </table>
122 </div>
120 </div>
123 </div>
121 </div>
124 <div class="pagination-wh pagination-left">
122 <div class="pagination-wh pagination-left">
125 ${c.pagination.pager('$link_previous ~2~ $link_next')}
123 ${c.pagination.pager('$link_previous ~2~ $link_next')}
126 </div>
124 </div>
127 </div>
125 </div>
128
126
129 <script type="text/javascript">
127 <script type="text/javascript">
130 var cache = {};
128 var cache = {};
131 $(function(){
129 $(function(){
132
130
133 // Create links to commit ranges when range checkboxes are selected
131 // Create links to commit ranges when range checkboxes are selected
134 var $commitCheckboxes = $('.commit-range');
132 var $commitCheckboxes = $('.commit-range');
135 // cache elements
133 // cache elements
136 var $commitRangeContainer = $('#rev_range_container');
134 var $commitRangeContainer = $('#rev_range_container');
137 var $commitRangeClear = $('#rev_range_clear');
135 var $commitRangeClear = $('#rev_range_clear');
138
136
139 var checkboxRangeSelector = function(e){
137 var checkboxRangeSelector = function(e){
140 var selectedCheckboxes = [];
138 var selectedCheckboxes = [];
141 for (pos in $commitCheckboxes){
139 for (pos in $commitCheckboxes){
142 if($commitCheckboxes[pos].checked){
140 if($commitCheckboxes[pos].checked){
143 selectedCheckboxes.push($commitCheckboxes[pos]);
141 selectedCheckboxes.push($commitCheckboxes[pos]);
144 }
142 }
145 }
143 }
146 var open_new_pull_request = $('#open_new_pull_request');
144 var open_new_pull_request = $('#open_new_pull_request');
147 if(open_new_pull_request){
145 if(open_new_pull_request){
148 var selected_changes = selectedCheckboxes.length;
146 var selected_changes = selectedCheckboxes.length;
149 if (selected_changes > 1 || selected_changes == 1 && templateContext.repo_type != 'hg') {
147 if (selected_changes > 1 || selected_changes == 1 && templateContext.repo_type != 'hg') {
150 open_new_pull_request.hide();
148 open_new_pull_request.hide();
151 } else {
149 } else {
152 if (selected_changes == 1) {
150 if (selected_changes == 1) {
153 open_new_pull_request.html(_gettext('Open new pull request for selected commit'));
151 open_new_pull_request.html(_gettext('Open new pull request for selected commit'));
154 } else if (selected_changes == 0) {
152 } else if (selected_changes == 0) {
155 open_new_pull_request.html(_gettext('Open new pull request'));
153 open_new_pull_request.html(_gettext('Open new pull request'));
156 }
154 }
157 open_new_pull_request.show();
155 open_new_pull_request.show();
158 }
156 }
159 }
157 }
160
158
161 if (selectedCheckboxes.length>0){
159 if (selectedCheckboxes.length>0){
162 var revEnd = selectedCheckboxes[0].name;
160 var revEnd = selectedCheckboxes[0].name;
163 var revStart = selectedCheckboxes[selectedCheckboxes.length-1].name;
161 var revStart = selectedCheckboxes[selectedCheckboxes.length-1].name;
164 var url = pyroutes.url('repo_commit',
162 var url = pyroutes.url('repo_commit',
165 {'repo_name': '${c.repo_name}',
163 {'repo_name': '${c.repo_name}',
166 'commit_id': revStart+'...'+revEnd});
164 'commit_id': revStart+'...'+revEnd});
167
165
168 var link = (revStart == revEnd)
166 var link = (revStart == revEnd)
169 ? _gettext('Show selected commit __S')
167 ? _gettext('Show selected commit __S')
170 : _gettext('Show selected commits __S ... __E');
168 : _gettext('Show selected commits __S ... __E');
171
169
172 link = link.replace('__S', revStart.substr(0,6));
170 link = link.replace('__S', revStart.substr(0,6));
173 link = link.replace('__E', revEnd.substr(0,6));
171 link = link.replace('__E', revEnd.substr(0,6));
174
172
175 $commitRangeContainer
173 $commitRangeContainer
176 .attr('href',url)
174 .attr('href',url)
177 .html(link)
175 .html(link)
178 .show();
176 .show();
179
177
180 $commitRangeClear.show();
178 $commitRangeClear.show();
181 var _url = pyroutes.url('pullrequest_home',
179 var _url = pyroutes.url('pullrequest_home',
182 {'repo_name': '${c.repo_name}',
180 {'repo_name': '${c.repo_name}',
183 'commit': revEnd});
181 'commit': revEnd});
184 open_new_pull_request.attr('href', _url);
182 open_new_pull_request.attr('href', _url);
185 $('#compare_fork_button').hide();
183 $('#compare_fork_button').hide();
186 } else {
184 } else {
187 $commitRangeContainer.hide();
185 $commitRangeContainer.hide();
188 $commitRangeClear.hide();
186 $commitRangeClear.hide();
189
187
190 %if c.branch_name:
188 %if c.branch_name:
191 var _url = pyroutes.url('pullrequest_home',
189 var _url = pyroutes.url('pullrequest_home',
192 {'repo_name': '${c.repo_name}',
190 {'repo_name': '${c.repo_name}',
193 'branch':'${c.branch_name}'});
191 'branch':'${c.branch_name}'});
194 open_new_pull_request.attr('href', _url);
192 open_new_pull_request.attr('href', _url);
195 %else:
193 %else:
196 var _url = pyroutes.url('pullrequest_home',
194 var _url = pyroutes.url('pullrequest_home',
197 {'repo_name': '${c.repo_name}'});
195 {'repo_name': '${c.repo_name}'});
198 open_new_pull_request.attr('href', _url);
196 open_new_pull_request.attr('href', _url);
199 %endif
197 %endif
200 $('#compare_fork_button').show();
198 $('#compare_fork_button').show();
201 }
199 }
202 };
200 };
203
201
204 $commitCheckboxes.on('click', checkboxRangeSelector);
202 $commitCheckboxes.on('click', checkboxRangeSelector);
205
203
206 $commitRangeClear.on('click',function(e) {
204 $commitRangeClear.on('click',function(e) {
207 $commitCheckboxes.attr('checked', false);
205 $commitCheckboxes.attr('checked', false);
208 checkboxRangeSelector();
206 checkboxRangeSelector();
209 e.preventDefault();
207 e.preventDefault();
210 });
208 });
211
209
212 // make sure the buttons are consistent when navigate back and forth
210 // make sure the buttons are consistent when navigate back and forth
213 checkboxRangeSelector();
211 checkboxRangeSelector();
214
212
215 var msgs = $('.message');
213 var msgs = $('.message');
216 // get first element height
214 // get first element height
217 var el = $('#graph_content .container')[0];
215 var el = $('#graph_content .container')[0];
218 var row_h = el.clientHeight;
216 var row_h = el.clientHeight;
219 for (var i=0; i < msgs.length; i++) {
217 for (var i=0; i < msgs.length; i++) {
220 var m = msgs[i];
218 var m = msgs[i];
221
219
222 var h = m.clientHeight;
220 var h = m.clientHeight;
223 var pad = $(m).css('padding');
221 var pad = $(m).css('padding');
224 if (h > row_h) {
222 if (h > row_h) {
225 var offset = row_h - (h+12);
223 var offset = row_h - (h+12);
226 $(m.nextElementSibling).css('display','block');
224 $(m.nextElementSibling).css('display','block');
227 $(m.nextElementSibling).css('margin-top',offset+'px');
225 $(m.nextElementSibling).css('margin-top',offset+'px');
228 }
226 }
229 }
227 }
230
228
231 $("#clear_filter").on("click", function() {
229 $("#clear_filter").on("click", function() {
232 var filter = {'repo_name': '${c.repo_name}'};
230 var filter = {'repo_name': '${c.repo_name}'};
233 window.location = pyroutes.url('repo_changelog', filter);
231 window.location = pyroutes.url('repo_changelog', filter);
234 });
232 });
235
233
236 $("#branch_filter").select2({
234 $("#branch_filter").select2({
237 'dropdownAutoWidth': true,
235 'dropdownAutoWidth': true,
238 'width': 'resolve',
236 'width': 'resolve',
239 'placeholder': "${c.selected_name or _('Filter changelog')}",
237 'placeholder': "${c.selected_name or _('Filter changelog')}",
240 containerCssClass: "drop-menu",
238 containerCssClass: "drop-menu",
241 dropdownCssClass: "drop-menu-dropdown",
239 dropdownCssClass: "drop-menu-dropdown",
242 query: function(query){
240 query: function(query){
243 var key = 'cache';
241 var key = 'cache';
244 var cached = cache[key] ;
242 var cached = cache[key] ;
245 if(cached) {
243 if(cached) {
246 var data = {results: []};
244 var data = {results: []};
247 //filter results
245 //filter results
248 $.each(cached.results, function(){
246 $.each(cached.results, function(){
249 var section = this.text;
247 var section = this.text;
250 var children = [];
248 var children = [];
251 $.each(this.children, function(){
249 $.each(this.children, function(){
252 if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
250 if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
253 children.push({'id': this.id, 'text': this.text, 'type': this.type})
251 children.push({'id': this.id, 'text': this.text, 'type': this.type})
254 }
252 }
255 });
253 });
256 data.results.push({'text': section, 'children': children});
254 data.results.push({'text': section, 'children': children});
257 query.callback({results: data.results});
255 query.callback({results: data.results});
258 });
256 });
259 }else{
257 }else{
260 $.ajax({
258 $.ajax({
261 url: pyroutes.url('repo_refs_changelog_data', {'repo_name': '${c.repo_name}'}),
259 url: pyroutes.url('repo_refs_changelog_data', {'repo_name': '${c.repo_name}'}),
262 data: {},
260 data: {},
263 dataType: 'json',
261 dataType: 'json',
264 type: 'GET',
262 type: 'GET',
265 success: function(data) {
263 success: function(data) {
266 cache[key] = data;
264 cache[key] = data;
267 query.callback({results: data.results});
265 query.callback({results: data.results});
268 }
266 }
269 })
267 })
270 }
268 }
271 }
269 }
272 });
270 });
273 $('#branch_filter').on('change', function(e){
271 $('#branch_filter').on('change', function(e){
274 var data = $('#branch_filter').select2('data');
272 var data = $('#branch_filter').select2('data');
275 var selected = data.text;
273 var selected = data.text;
276 var filter = {'repo_name': '${c.repo_name}'};
274 var filter = {'repo_name': '${c.repo_name}'};
277 if(data.type == 'branch' || data.type == 'branch_closed'){
275 if(data.type == 'branch' || data.type == 'branch_closed'){
278 filter["branch"] = selected;
276 filter["branch"] = selected;
279 }
277 }
280 else if (data.type == 'book'){
278 else if (data.type == 'book'){
281 filter["bookmark"] = selected;
279 filter["bookmark"] = selected;
282 }
280 }
283 window.location = pyroutes.url('repo_changelog', filter);
281 window.location = pyroutes.url('repo_changelog', filter);
284 });
282 });
285
283
286 commitsController = new CommitsController();
284 commitsController = new CommitsController();
287 % if not c.changelog_for_path:
285 % if not c.changelog_for_path:
288 commitsController.reloadGraph();
286 commitsController.reloadGraph();
289 % endif
287 % endif
290
288
291 });
289 });
292
290
293 </script>
291 </script>
294 </div>
292 </div>
295 % else:
293 % else:
296 ${_('There are no changes yet')}
294 ${_('There are no changes yet')}
297 % endif
295 % endif
298 </div>
296 </div>
299 </%def>
297 </%def>
@@ -1,125 +1,131 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('%s Commits') % c.repo_name} -
5 ${_('%s Commits') % c.repo_name} -
6 r${c.commit_ranges[0].revision}:${h.short_id(c.commit_ranges[0].raw_id)}
6 r${c.commit_ranges[0].revision}:${h.short_id(c.commit_ranges[0].raw_id)}
7 ...
7 ...
8 r${c.commit_ranges[-1].revision}:${h.short_id(c.commit_ranges[-1].raw_id)}
8 r${c.commit_ranges[-1].revision}:${h.short_id(c.commit_ranges[-1].raw_id)}
9 ${_ungettext('(%s commit)','(%s commits)', len(c.commit_ranges)) % len(c.commit_ranges)}
9 ${_ungettext('(%s commit)','(%s commits)', len(c.commit_ranges)) % len(c.commit_ranges)}
10 %if c.rhodecode_name:
10 %if c.rhodecode_name:
11 &middot; ${h.branding(c.rhodecode_name)}
11 &middot; ${h.branding(c.rhodecode_name)}
12 %endif
12 %endif
13 </%def>
13 </%def>
14
14
15 <%def name="breadcrumbs_links()">
15 <%def name="breadcrumbs_links()">
16 ${_('Commits')} -
16 ${_('Commits')} -
17 r${c.commit_ranges[0].revision}:${h.short_id(c.commit_ranges[0].raw_id)}
17 r${c.commit_ranges[0].revision}:${h.short_id(c.commit_ranges[0].raw_id)}
18 ...
18 ...
19 r${c.commit_ranges[-1].revision}:${h.short_id(c.commit_ranges[-1].raw_id)}
19 r${c.commit_ranges[-1].revision}:${h.short_id(c.commit_ranges[-1].raw_id)}
20 ${_ungettext('(%s commit)','(%s commits)', len(c.commit_ranges)) % len(c.commit_ranges)}
20 ${_ungettext('(%s commit)','(%s commits)', len(c.commit_ranges)) % len(c.commit_ranges)}
21 </%def>
21 </%def>
22
22
23 <%def name="menu_bar_nav()">
23 <%def name="menu_bar_nav()">
24 ${self.menu_items(active='repositories')}
24 ${self.menu_items(active='repositories')}
25 </%def>
25 </%def>
26
26
27 <%def name="menu_bar_subnav()">
27 <%def name="menu_bar_subnav()">
28 ${self.repo_menu(active='changelog')}
28 ${self.repo_menu(active='changelog')}
29 </%def>
29 </%def>
30
30
31 <%def name="main()">
31 <%def name="main()">
32 <div class="summary-header">
32 <div class="summary-header">
33 <div class="title">
33 <div class="title">
34 ${self.repo_page_title(c.rhodecode_db_repo)}
34 ${self.repo_page_title(c.rhodecode_db_repo)}
35 </div>
35 </div>
36 </div>
36 </div>
37
37
38
38
39 <div class="summary changeset">
39 <div class="summary changeset">
40 <div class="summary-detail">
40 <div class="summary-detail">
41 <div class="summary-detail-header">
41 <div class="summary-detail-header">
42 <span class="breadcrumbs files_location">
42 <span class="breadcrumbs files_location">
43 <h4>
43 <h4>
44 ${_('Commit Range')}
44 ${_('Commit Range')}
45 <code>
45 <code>
46 r${c.commit_ranges[0].revision}:${h.short_id(c.commit_ranges[0].raw_id)}...r${c.commit_ranges[-1].revision}:${h.short_id(c.commit_ranges[-1].raw_id)}
46 r${c.commit_ranges[0].revision}:${h.short_id(c.commit_ranges[0].raw_id)}...r${c.commit_ranges[-1].revision}:${h.short_id(c.commit_ranges[-1].raw_id)}
47 </code>
47 </code>
48 </h4>
48 </h4>
49 </span>
49 </span>
50 </div>
50 </div>
51
51
52 <div class="fieldset">
52 <div class="fieldset">
53 <div class="left-label">
53 <div class="left-label">
54 ${_('Diff option')}:
54 ${_('Diff option')}:
55 </div>
55 </div>
56 <div class="right-content">
56 <div class="right-content">
57 <div class="header-buttons">
57 <div class="header-buttons">
58 <a href="${h.url('compare_url', repo_name=c.repo_name, source_ref_type='rev', source_ref=getattr(c.commit_ranges[0].parents[0] if c.commit_ranges[0].parents else h.EmptyCommit(), 'raw_id'), target_ref_type='rev', target_ref=c.commit_ranges[-1].raw_id)}">
58 <a href="${h.route_path('repo_compare',
59 repo_name=c.repo_name,
60 source_ref_type='rev',
61 source_ref=getattr(c.commit_ranges[0].parents[0] if c.commit_ranges[0].parents else h.EmptyCommit(), 'raw_id'),
62 target_ref_type='rev',
63 target_ref=c.commit_ranges[-1].raw_id)}"
64 >
59 ${_('Show combined compare')}
65 ${_('Show combined compare')}
60 </a>
66 </a>
61 </div>
67 </div>
62 </div>
68 </div>
63 </div>
69 </div>
64
70
65 <%doc>
71 <%doc>
66 ##TODO(marcink): implement this and diff menus
72 ##TODO(marcink): implement this and diff menus
67 <div class="fieldset">
73 <div class="fieldset">
68 <div class="left-label">
74 <div class="left-label">
69 ${_('Diff options')}:
75 ${_('Diff options')}:
70 </div>
76 </div>
71 <div class="right-content">
77 <div class="right-content">
72 <div class="diff-actions">
78 <div class="diff-actions">
73 <a href="${h.route_path('repo_commit_raw',repo_name=c.repo_name,commit_id='?')}" class="tooltip" title="${h.tooltip(_('Raw diff'))}">
79 <a href="${h.route_path('repo_commit_raw',repo_name=c.repo_name,commit_id='?')}" class="tooltip" title="${h.tooltip(_('Raw diff'))}">
74 ${_('Raw Diff')}
80 ${_('Raw Diff')}
75 </a>
81 </a>
76 |
82 |
77 <a href="${h.route_path('repo_commit_patch',repo_name=c.repo_name,commit_id='?')}" class="tooltip" title="${h.tooltip(_('Patch diff'))}">
83 <a href="${h.route_path('repo_commit_patch',repo_name=c.repo_name,commit_id='?')}" class="tooltip" title="${h.tooltip(_('Patch diff'))}">
78 ${_('Patch Diff')}
84 ${_('Patch Diff')}
79 </a>
85 </a>
80 |
86 |
81 <a href="${h.route_path('repo_commit_download',repo_name=c.repo_name,commit_id='?',_query=dict(diff='download'))}" class="tooltip" title="${h.tooltip(_('Download diff'))}">
87 <a href="${h.route_path('repo_commit_download',repo_name=c.repo_name,commit_id='?',_query=dict(diff='download'))}" class="tooltip" title="${h.tooltip(_('Download diff'))}">
82 ${_('Download Diff')}
88 ${_('Download Diff')}
83 </a>
89 </a>
84 </div>
90 </div>
85 </div>
91 </div>
86 </div>
92 </div>
87 </%doc>
93 </%doc>
88 </div> <!-- end summary-detail -->
94 </div> <!-- end summary-detail -->
89
95
90 </div> <!-- end summary -->
96 </div> <!-- end summary -->
91
97
92 <div id="changeset_compare_view_content">
98 <div id="changeset_compare_view_content">
93 <div class="pull-left">
99 <div class="pull-left">
94 <div class="btn-group">
100 <div class="btn-group">
95 <a
101 <a
96 class="btn"
102 class="btn"
97 href="#"
103 href="#"
98 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
104 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
99 ${_ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
105 ${_ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
100 </a>
106 </a>
101 <a
107 <a
102 class="btn"
108 class="btn"
103 href="#"
109 href="#"
104 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
110 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
105 ${_ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
111 ${_ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
106 </a>
112 </a>
107 </div>
113 </div>
108 </div>
114 </div>
109 ## Commit range generated below
115 ## Commit range generated below
110 <%include file="../compare/compare_commits.mako"/>
116 <%include file="../compare/compare_commits.mako"/>
111 <div class="cs_files">
117 <div class="cs_files">
112 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
118 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
113 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
119 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
114 <%namespace name="diff_block" file="/changeset/diff_block.mako"/>
120 <%namespace name="diff_block" file="/changeset/diff_block.mako"/>
115 ${cbdiffs.render_diffset_menu()}
121 ${cbdiffs.render_diffset_menu()}
116 %for commit in c.commit_ranges:
122 %for commit in c.commit_ranges:
117 ${cbdiffs.render_diffset(
123 ${cbdiffs.render_diffset(
118 diffset=c.changes[commit.raw_id],
124 diffset=c.changes[commit.raw_id],
119 collapse_when_files_over=5,
125 collapse_when_files_over=5,
120 commit=commit,
126 commit=commit,
121 )}
127 )}
122 %endfor
128 %endfor
123 </div>
129 </div>
124 </div>
130 </div>
125 </%def>
131 </%def>
@@ -1,333 +1,333 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
3 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
4
4
5 <%def name="title()">
5 <%def name="title()">
6 %if c.compare_home:
6 %if c.compare_home:
7 ${_('%s Compare') % c.repo_name}
7 ${_('%s Compare') % c.repo_name}
8 %else:
8 %else:
9 ${_('%s Compare') % c.repo_name} - ${'%s@%s' % (c.source_repo.repo_name, c.source_ref)} &gt; ${'%s@%s' % (c.target_repo.repo_name, c.target_ref)}
9 ${_('%s Compare') % c.repo_name} - ${'%s@%s' % (c.source_repo.repo_name, c.source_ref)} &gt; ${'%s@%s' % (c.target_repo.repo_name, c.target_ref)}
10 %endif
10 %endif
11 %if c.rhodecode_name:
11 %if c.rhodecode_name:
12 &middot; ${h.branding(c.rhodecode_name)}
12 &middot; ${h.branding(c.rhodecode_name)}
13 %endif
13 %endif
14 </%def>
14 </%def>
15
15
16 <%def name="breadcrumbs_links()">
16 <%def name="breadcrumbs_links()">
17 ${_ungettext('%s commit','%s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
17 ${_ungettext('%s commit','%s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
18 </%def>
18 </%def>
19
19
20 <%def name="menu_bar_nav()">
20 <%def name="menu_bar_nav()">
21 ${self.menu_items(active='repositories')}
21 ${self.menu_items(active='repositories')}
22 </%def>
22 </%def>
23
23
24 <%def name="menu_bar_subnav()">
24 <%def name="menu_bar_subnav()">
25 ${self.repo_menu(active='compare')}
25 ${self.repo_menu(active='compare')}
26 </%def>
26 </%def>
27
27
28 <%def name="main()">
28 <%def name="main()">
29 <script type="text/javascript">
29 <script type="text/javascript">
30 // set fake commitId on this commit-range page
30 // set fake commitId on this commit-range page
31 templateContext.commit_data.commit_id = "${h.EmptyCommit().raw_id}";
31 templateContext.commit_data.commit_id = "${h.EmptyCommit().raw_id}";
32 </script>
32 </script>
33
33
34 <div class="box">
34 <div class="box">
35 <div class="title">
35 <div class="title">
36 ${self.repo_page_title(c.rhodecode_db_repo)}
36 ${self.repo_page_title(c.rhodecode_db_repo)}
37 </div>
37 </div>
38
38
39 <div class="summary changeset">
39 <div class="summary changeset">
40 <div class="summary-detail">
40 <div class="summary-detail">
41 <div class="summary-detail-header">
41 <div class="summary-detail-header">
42 <span class="breadcrumbs files_location">
42 <span class="breadcrumbs files_location">
43 <h4>
43 <h4>
44 ${_('Compare Commits')}
44 ${_('Compare Commits')}
45 % if c.file_path:
45 % if c.file_path:
46 ${_('for file')} <a href="#${'a_' + h.FID('',c.file_path)}">${c.file_path}</a>
46 ${_('for file')} <a href="#${'a_' + h.FID('',c.file_path)}">${c.file_path}</a>
47 % endif
47 % endif
48
48
49 % if c.commit_ranges:
49 % if c.commit_ranges:
50 <code>
50 <code>
51 r${c.source_commit.revision}:${h.short_id(c.source_commit.raw_id)}...r${c.target_commit.revision}:${h.short_id(c.target_commit.raw_id)}
51 r${c.source_commit.revision}:${h.short_id(c.source_commit.raw_id)}...r${c.target_commit.revision}:${h.short_id(c.target_commit.raw_id)}
52 </code>
52 </code>
53 % endif
53 % endif
54 </h4>
54 </h4>
55 </span>
55 </span>
56 </div>
56 </div>
57
57
58 <div class="fieldset">
58 <div class="fieldset">
59 <div class="left-label">
59 <div class="left-label">
60 ${_('Target')}:
60 ${_('Target')}:
61 </div>
61 </div>
62 <div class="right-content">
62 <div class="right-content">
63 <div>
63 <div>
64 <div class="code-header" >
64 <div class="code-header" >
65 <div class="compare_header">
65 <div class="compare_header">
66 ## The hidden elements are replaced with a select2 widget
66 ## The hidden elements are replaced with a select2 widget
67 ${h.hidden('compare_source')}
67 ${h.hidden('compare_source')}
68 </div>
68 </div>
69 </div>
69 </div>
70 </div>
70 </div>
71 </div>
71 </div>
72 </div>
72 </div>
73
73
74 <div class="fieldset">
74 <div class="fieldset">
75 <div class="left-label">
75 <div class="left-label">
76 ${_('Source')}:
76 ${_('Source')}:
77 </div>
77 </div>
78 <div class="right-content">
78 <div class="right-content">
79 <div>
79 <div>
80 <div class="code-header" >
80 <div class="code-header" >
81 <div class="compare_header">
81 <div class="compare_header">
82 ## The hidden elements are replaced with a select2 widget
82 ## The hidden elements are replaced with a select2 widget
83 ${h.hidden('compare_target')}
83 ${h.hidden('compare_target')}
84 </div>
84 </div>
85 </div>
85 </div>
86 </div>
86 </div>
87 </div>
87 </div>
88 </div>
88 </div>
89
89
90 <div class="fieldset">
90 <div class="fieldset">
91 <div class="left-label">
91 <div class="left-label">
92 ${_('Actions')}:
92 ${_('Actions')}:
93 </div>
93 </div>
94 <div class="right-content">
94 <div class="right-content">
95 <div>
95 <div>
96 <div class="code-header" >
96 <div class="code-header" >
97 <div class="compare_header">
97 <div class="compare_header">
98
98
99 <div class="compare-buttons">
99 <div class="compare-buttons">
100 % if c.compare_home:
100 % if c.compare_home:
101 <a id="compare_revs" class="btn btn-primary"> ${_('Compare Commits')}</a>
101 <a id="compare_revs" class="btn btn-primary"> ${_('Compare Commits')}</a>
102
102
103 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Swap')}</a>
103 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Swap')}</a>
104 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Comment')}</a>
104 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Comment')}</a>
105 <div id="changeset_compare_view_content">
105 <div id="changeset_compare_view_content">
106 <div class="help-block">${_('Compare commits, branches, bookmarks or tags.')}</div>
106 <div class="help-block">${_('Compare commits, branches, bookmarks or tags.')}</div>
107 </div>
107 </div>
108
108
109 % elif c.preview_mode:
109 % elif c.preview_mode:
110 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Compare Commits')}</a>
110 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Compare Commits')}</a>
111 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Swap')}</a>
111 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Swap')}</a>
112 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Comment')}</a>
112 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Comment')}</a>
113
113
114 % else:
114 % else:
115 <a id="compare_revs" class="btn btn-primary"> ${_('Compare Commits')}</a>
115 <a id="compare_revs" class="btn btn-primary"> ${_('Compare Commits')}</a>
116 <a id="btn-swap" class="btn btn-primary" href="${c.swap_url}">${_('Swap')}</a>
116 <a id="btn-swap" class="btn btn-primary" href="${c.swap_url}">${_('Swap')}</a>
117
117
118 ## allow comment only if there are commits to comment on
118 ## allow comment only if there are commits to comment on
119 % if c.diffset and c.diffset.files and c.commit_ranges:
119 % if c.diffset and c.diffset.files and c.commit_ranges:
120 <a id="compare_changeset_status_toggle" class="btn btn-primary">${_('Comment')}</a>
120 <a id="compare_changeset_status_toggle" class="btn btn-primary">${_('Comment')}</a>
121 % else:
121 % else:
122 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Comment')}</a>
122 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Comment')}</a>
123 % endif
123 % endif
124 % endif
124 % endif
125 </div>
125 </div>
126 </div>
126 </div>
127 </div>
127 </div>
128 </div>
128 </div>
129 </div>
129 </div>
130 </div>
130 </div>
131
131
132 <%doc>
132 <%doc>
133 ##TODO(marcink): implement this and diff menus
133 ##TODO(marcink): implement this and diff menus
134 <div class="fieldset">
134 <div class="fieldset">
135 <div class="left-label">
135 <div class="left-label">
136 ${_('Diff options')}:
136 ${_('Diff options')}:
137 </div>
137 </div>
138 <div class="right-content">
138 <div class="right-content">
139 <div class="diff-actions">
139 <div class="diff-actions">
140 <a href="${h.route_path('repo_commit_raw',repo_name=c.repo_name,commit_id='?')}" class="tooltip" title="${h.tooltip(_('Raw diff'))}">
140 <a href="${h.route_path('repo_commit_raw',repo_name=c.repo_name,commit_id='?')}" class="tooltip" title="${h.tooltip(_('Raw diff'))}">
141 ${_('Raw Diff')}
141 ${_('Raw Diff')}
142 </a>
142 </a>
143 |
143 |
144 <a href="${h.route_path('repo_commit_patch',repo_name=c.repo_name,commit_id='?')}" class="tooltip" title="${h.tooltip(_('Patch diff'))}">
144 <a href="${h.route_path('repo_commit_patch',repo_name=c.repo_name,commit_id='?')}" class="tooltip" title="${h.tooltip(_('Patch diff'))}">
145 ${_('Patch Diff')}
145 ${_('Patch Diff')}
146 </a>
146 </a>
147 |
147 |
148 <a href="${h.route_path('repo_commit_download',repo_name=c.repo_name,commit_id='?',_query=dict(diff='download'))}" class="tooltip" title="${h.tooltip(_('Download diff'))}">
148 <a href="${h.route_path('repo_commit_download',repo_name=c.repo_name,commit_id='?',_query=dict(diff='download'))}" class="tooltip" title="${h.tooltip(_('Download diff'))}">
149 ${_('Download Diff')}
149 ${_('Download Diff')}
150 </a>
150 </a>
151 </div>
151 </div>
152 </div>
152 </div>
153 </div>
153 </div>
154 </%doc>
154 </%doc>
155
155
156 ## commit status form
156 ## commit status form
157 <div class="fieldset" id="compare_changeset_status" style="display: none; margin-bottom: -80px;">
157 <div class="fieldset" id="compare_changeset_status" style="display: none; margin-bottom: -80px;">
158 <div class="left-label">
158 <div class="left-label">
159 ${_('Commit status')}:
159 ${_('Commit status')}:
160 </div>
160 </div>
161 <div class="right-content">
161 <div class="right-content">
162 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
162 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
163 ## main comment form and it status
163 ## main comment form and it status
164 <%
164 <%
165 def revs(_revs):
165 def revs(_revs):
166 form_inputs = []
166 form_inputs = []
167 for cs in _revs:
167 for cs in _revs:
168 tmpl = '<input type="hidden" data-commit-id="%(cid)s" name="commit_ids" value="%(cid)s">' % {'cid': cs.raw_id}
168 tmpl = '<input type="hidden" data-commit-id="%(cid)s" name="commit_ids" value="%(cid)s">' % {'cid': cs.raw_id}
169 form_inputs.append(tmpl)
169 form_inputs.append(tmpl)
170 return form_inputs
170 return form_inputs
171 %>
171 %>
172 <div>
172 <div>
173 ${comment.comments(h.route_path('repo_commit_comment_create', repo_name=c.repo_name, commit_id='0'*16), None, is_compare=True, form_extras=revs(c.commit_ranges))}
173 ${comment.comments(h.route_path('repo_commit_comment_create', repo_name=c.repo_name, commit_id='0'*16), None, is_compare=True, form_extras=revs(c.commit_ranges))}
174 </div>
174 </div>
175 </div>
175 </div>
176 </div>
176 </div>
177
177
178 </div> <!-- end summary-detail -->
178 </div> <!-- end summary-detail -->
179 </div> <!-- end summary -->
179 </div> <!-- end summary -->
180
180
181 ## use JS script to load it quickly before potentially large diffs render long time
181 ## use JS script to load it quickly before potentially large diffs render long time
182 ## this prevents from situation when large diffs block rendering of select2 fields
182 ## this prevents from situation when large diffs block rendering of select2 fields
183 <script type="text/javascript">
183 <script type="text/javascript">
184
184
185 var cache = {};
185 var cache = {};
186
186
187 var formatSelection = function(repoName){
187 var formatSelection = function(repoName){
188 return function(data, container, escapeMarkup) {
188 return function(data, container, escapeMarkup) {
189 var selection = data ? this.text(data) : "";
189 var selection = data ? this.text(data) : "";
190 return escapeMarkup('{0}@{1}'.format(repoName, selection));
190 return escapeMarkup('{0}@{1}'.format(repoName, selection));
191 }
191 }
192 };
192 };
193
193
194 var feedCompareData = function(query, cachedValue){
194 var feedCompareData = function(query, cachedValue){
195 var data = {results: []};
195 var data = {results: []};
196 //filter results
196 //filter results
197 $.each(cachedValue.results, function() {
197 $.each(cachedValue.results, function() {
198 var section = this.text;
198 var section = this.text;
199 var children = [];
199 var children = [];
200 $.each(this.children, function() {
200 $.each(this.children, function() {
201 if (query.term.length === 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
201 if (query.term.length === 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
202 children.push({
202 children.push({
203 'id': this.id,
203 'id': this.id,
204 'text': this.text,
204 'text': this.text,
205 'type': this.type
205 'type': this.type
206 })
206 })
207 }
207 }
208 });
208 });
209 data.results.push({
209 data.results.push({
210 'text': section,
210 'text': section,
211 'children': children
211 'children': children
212 })
212 })
213 });
213 });
214 //push the typed in changeset
214 //push the typed in changeset
215 data.results.push({
215 data.results.push({
216 'text': _gettext('specify commit'),
216 'text': _gettext('specify commit'),
217 'children': [{
217 'children': [{
218 'id': query.term,
218 'id': query.term,
219 'text': query.term,
219 'text': query.term,
220 'type': 'rev'
220 'type': 'rev'
221 }]
221 }]
222 });
222 });
223 query.callback(data);
223 query.callback(data);
224 };
224 };
225
225
226 var loadCompareData = function(repoName, query, cache){
226 var loadCompareData = function(repoName, query, cache){
227 $.ajax({
227 $.ajax({
228 url: pyroutes.url('repo_refs_data', {'repo_name': repoName}),
228 url: pyroutes.url('repo_refs_data', {'repo_name': repoName}),
229 data: {},
229 data: {},
230 dataType: 'json',
230 dataType: 'json',
231 type: 'GET',
231 type: 'GET',
232 success: function(data) {
232 success: function(data) {
233 cache[repoName] = data;
233 cache[repoName] = data;
234 query.callback({results: data.results});
234 query.callback({results: data.results});
235 }
235 }
236 })
236 })
237 };
237 };
238
238
239 var enable_fields = ${"false" if c.preview_mode else "true"};
239 var enable_fields = ${"false" if c.preview_mode else "true"};
240 $("#compare_source").select2({
240 $("#compare_source").select2({
241 placeholder: "${'%s@%s' % (c.source_repo.repo_name, c.source_ref)}",
241 placeholder: "${'%s@%s' % (c.source_repo.repo_name, c.source_ref)}",
242 containerCssClass: "drop-menu",
242 containerCssClass: "drop-menu",
243 dropdownCssClass: "drop-menu-dropdown",
243 dropdownCssClass: "drop-menu-dropdown",
244 formatSelection: formatSelection("${c.source_repo.repo_name}"),
244 formatSelection: formatSelection("${c.source_repo.repo_name}"),
245 dropdownAutoWidth: true,
245 dropdownAutoWidth: true,
246 query: function(query) {
246 query: function(query) {
247 var repoName = '${c.source_repo.repo_name}';
247 var repoName = '${c.source_repo.repo_name}';
248 var cachedValue = cache[repoName];
248 var cachedValue = cache[repoName];
249
249
250 if (cachedValue){
250 if (cachedValue){
251 feedCompareData(query, cachedValue);
251 feedCompareData(query, cachedValue);
252 }
252 }
253 else {
253 else {
254 loadCompareData(repoName, query, cache);
254 loadCompareData(repoName, query, cache);
255 }
255 }
256 }
256 }
257 }).select2("enable", enable_fields);
257 }).select2("enable", enable_fields);
258
258
259 $("#compare_target").select2({
259 $("#compare_target").select2({
260 placeholder: "${'%s@%s' % (c.target_repo.repo_name, c.target_ref)}",
260 placeholder: "${'%s@%s' % (c.target_repo.repo_name, c.target_ref)}",
261 dropdownAutoWidth: true,
261 dropdownAutoWidth: true,
262 containerCssClass: "drop-menu",
262 containerCssClass: "drop-menu",
263 dropdownCssClass: "drop-menu-dropdown",
263 dropdownCssClass: "drop-menu-dropdown",
264 formatSelection: formatSelection("${c.target_repo.repo_name}"),
264 formatSelection: formatSelection("${c.target_repo.repo_name}"),
265 query: function(query) {
265 query: function(query) {
266 var repoName = '${c.target_repo.repo_name}';
266 var repoName = '${c.target_repo.repo_name}';
267 var cachedValue = cache[repoName];
267 var cachedValue = cache[repoName];
268
268
269 if (cachedValue){
269 if (cachedValue){
270 feedCompareData(query, cachedValue);
270 feedCompareData(query, cachedValue);
271 }
271 }
272 else {
272 else {
273 loadCompareData(repoName, query, cache);
273 loadCompareData(repoName, query, cache);
274 }
274 }
275 }
275 }
276 }).select2("enable", enable_fields);
276 }).select2("enable", enable_fields);
277 var initial_compare_source = {id: "${c.source_ref}", type:"${c.source_ref_type}"};
277 var initial_compare_source = {id: "${c.source_ref}", type:"${c.source_ref_type}"};
278 var initial_compare_target = {id: "${c.target_ref}", type:"${c.target_ref_type}"};
278 var initial_compare_target = {id: "${c.target_ref}", type:"${c.target_ref_type}"};
279
279
280 $('#compare_revs').on('click', function(e) {
280 $('#compare_revs').on('click', function(e) {
281 var source = $('#compare_source').select2('data') || initial_compare_source;
281 var source = $('#compare_source').select2('data') || initial_compare_source;
282 var target = $('#compare_target').select2('data') || initial_compare_target;
282 var target = $('#compare_target').select2('data') || initial_compare_target;
283 if (source && target) {
283 if (source && target) {
284 var url_data = {
284 var url_data = {
285 repo_name: "${c.repo_name}",
285 repo_name: "${c.repo_name}",
286 source_ref: source.id,
286 source_ref: source.id,
287 source_ref_type: source.type,
287 source_ref_type: source.type,
288 target_ref: target.id,
288 target_ref: target.id,
289 target_ref_type: target.type
289 target_ref_type: target.type
290 };
290 };
291 window.location = pyroutes.url('compare_url', url_data);
291 window.location = pyroutes.url('repo_compare', url_data);
292 }
292 }
293 });
293 });
294 $('#compare_changeset_status_toggle').on('click', function(e) {
294 $('#compare_changeset_status_toggle').on('click', function(e) {
295 $('#compare_changeset_status').toggle();
295 $('#compare_changeset_status').toggle();
296 });
296 });
297
297
298 </script>
298 </script>
299
299
300 ## table diff data
300 ## table diff data
301 <div class="table">
301 <div class="table">
302
302
303
303
304 % if not c.compare_home:
304 % if not c.compare_home:
305 <div id="changeset_compare_view_content">
305 <div id="changeset_compare_view_content">
306 <div class="pull-left">
306 <div class="pull-left">
307 <div class="btn-group">
307 <div class="btn-group">
308 <a
308 <a
309 class="btn"
309 class="btn"
310 href="#"
310 href="#"
311 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
311 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
312 ${_ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
312 ${_ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
313 </a>
313 </a>
314 <a
314 <a
315 class="btn"
315 class="btn"
316 href="#"
316 href="#"
317 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
317 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
318 ${_ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
318 ${_ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
319 </a>
319 </a>
320 </div>
320 </div>
321 </div>
321 </div>
322 <div style="padding:0 10px 10px 0px" class="pull-left"></div>
322 <div style="padding:0 10px 10px 0px" class="pull-left"></div>
323 ## commit compare generated below
323 ## commit compare generated below
324 <%include file="compare_commits.mako"/>
324 <%include file="compare_commits.mako"/>
325 ${cbdiffs.render_diffset_menu()}
325 ${cbdiffs.render_diffset_menu()}
326 ${cbdiffs.render_diffset(c.diffset)}
326 ${cbdiffs.render_diffset(c.diffset)}
327 </div>
327 </div>
328 % endif
328 % endif
329
329
330 </div>
330 </div>
331 </div>
331 </div>
332
332
333 </%def> No newline at end of file
333 </%def>
@@ -1,324 +1,324 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title(*args)">
3 <%def name="title(*args)">
4 ${_('%s Files') % c.repo_name}
4 ${_('%s Files') % c.repo_name}
5 %if hasattr(c,'file'):
5 %if hasattr(c,'file'):
6 &middot; ${h.safe_unicode(c.file.path) or '\\'}
6 &middot; ${h.safe_unicode(c.file.path) or '\\'}
7 %endif
7 %endif
8
8
9 %if c.rhodecode_name:
9 %if c.rhodecode_name:
10 &middot; ${h.branding(c.rhodecode_name)}
10 &middot; ${h.branding(c.rhodecode_name)}
11 %endif
11 %endif
12 </%def>
12 </%def>
13
13
14 <%def name="breadcrumbs_links()">
14 <%def name="breadcrumbs_links()">
15 ${_('Files')}
15 ${_('Files')}
16 %if c.file:
16 %if c.file:
17 @ ${h.show_id(c.commit)}
17 @ ${h.show_id(c.commit)}
18 %endif
18 %endif
19 </%def>
19 </%def>
20
20
21 <%def name="menu_bar_nav()">
21 <%def name="menu_bar_nav()">
22 ${self.menu_items(active='repositories')}
22 ${self.menu_items(active='repositories')}
23 </%def>
23 </%def>
24
24
25 <%def name="menu_bar_subnav()">
25 <%def name="menu_bar_subnav()">
26 ${self.repo_menu(active='files')}
26 ${self.repo_menu(active='files')}
27 </%def>
27 </%def>
28
28
29 <%def name="main()">
29 <%def name="main()">
30 <div class="title">
30 <div class="title">
31 ${self.repo_page_title(c.rhodecode_db_repo)}
31 ${self.repo_page_title(c.rhodecode_db_repo)}
32 </div>
32 </div>
33
33
34 <div id="pjax-container" class="summary">
34 <div id="pjax-container" class="summary">
35 <div id="files_data">
35 <div id="files_data">
36 <%include file='files_pjax.mako'/>
36 <%include file='files_pjax.mako'/>
37 </div>
37 </div>
38 </div>
38 </div>
39 <script>
39 <script>
40 var curState = {
40 var curState = {
41 commit_id: "${c.commit.raw_id}"
41 commit_id: "${c.commit.raw_id}"
42 };
42 };
43
43
44 var getState = function(context) {
44 var getState = function(context) {
45 var url = $(location).attr('href');
45 var url = $(location).attr('href');
46 var _base_url = '${h.route_path("repo_files",repo_name=c.repo_name,commit_id='',f_path='')}';
46 var _base_url = '${h.route_path("repo_files",repo_name=c.repo_name,commit_id='',f_path='')}';
47 var _annotate_url = '${h.route_path("repo_files:annotated",repo_name=c.repo_name,commit_id='',f_path='')}';
47 var _annotate_url = '${h.route_path("repo_files:annotated",repo_name=c.repo_name,commit_id='',f_path='')}';
48 _base_url = _base_url.replace('//', '/');
48 _base_url = _base_url.replace('//', '/');
49 _annotate_url = _annotate_url.replace('//', '/');
49 _annotate_url = _annotate_url.replace('//', '/');
50
50
51 //extract f_path from url.
51 //extract f_path from url.
52 var parts = url.split(_base_url);
52 var parts = url.split(_base_url);
53 if (parts.length != 2) {
53 if (parts.length != 2) {
54 parts = url.split(_annotate_url);
54 parts = url.split(_annotate_url);
55 if (parts.length != 2) {
55 if (parts.length != 2) {
56 var rev = "tip";
56 var rev = "tip";
57 var f_path = "";
57 var f_path = "";
58 } else {
58 } else {
59 var parts2 = parts[1].split('/');
59 var parts2 = parts[1].split('/');
60 var rev = parts2.shift(); // pop the first element which is the revision
60 var rev = parts2.shift(); // pop the first element which is the revision
61 var f_path = parts2.join('/');
61 var f_path = parts2.join('/');
62 }
62 }
63
63
64 } else {
64 } else {
65 var parts2 = parts[1].split('/');
65 var parts2 = parts[1].split('/');
66 var rev = parts2.shift(); // pop the first element which is the revision
66 var rev = parts2.shift(); // pop the first element which is the revision
67 var f_path = parts2.join('/');
67 var f_path = parts2.join('/');
68 }
68 }
69
69
70 var _node_list_url = pyroutes.url('repo_files_nodelist',
70 var _node_list_url = pyroutes.url('repo_files_nodelist',
71 {repo_name: templateContext.repo_name,
71 {repo_name: templateContext.repo_name,
72 commit_id: rev, f_path: f_path});
72 commit_id: rev, f_path: f_path});
73 var _url_base = pyroutes.url('repo_files',
73 var _url_base = pyroutes.url('repo_files',
74 {repo_name: templateContext.repo_name,
74 {repo_name: templateContext.repo_name,
75 commit_id: rev, f_path:'__FPATH__'});
75 commit_id: rev, f_path:'__FPATH__'});
76 return {
76 return {
77 url: url,
77 url: url,
78 f_path: f_path,
78 f_path: f_path,
79 rev: rev,
79 rev: rev,
80 commit_id: curState.commit_id,
80 commit_id: curState.commit_id,
81 node_list_url: _node_list_url,
81 node_list_url: _node_list_url,
82 url_base: _url_base
82 url_base: _url_base
83 };
83 };
84 };
84 };
85
85
86 var metadataRequest = null;
86 var metadataRequest = null;
87 var getFilesMetadata = function() {
87 var getFilesMetadata = function() {
88 if (metadataRequest && metadataRequest.readyState != 4) {
88 if (metadataRequest && metadataRequest.readyState != 4) {
89 metadataRequest.abort();
89 metadataRequest.abort();
90 }
90 }
91 if (fileSourcePage) {
91 if (fileSourcePage) {
92 return false;
92 return false;
93 }
93 }
94
94
95 if ($('#file-tree-wrapper').hasClass('full-load')) {
95 if ($('#file-tree-wrapper').hasClass('full-load')) {
96 // in case our HTML wrapper has full-load class we don't
96 // in case our HTML wrapper has full-load class we don't
97 // trigger the async load of metadata
97 // trigger the async load of metadata
98 return false;
98 return false;
99 }
99 }
100
100
101 var state = getState('metadata');
101 var state = getState('metadata');
102 var url_data = {
102 var url_data = {
103 'repo_name': templateContext.repo_name,
103 'repo_name': templateContext.repo_name,
104 'commit_id': state.commit_id,
104 'commit_id': state.commit_id,
105 'f_path': state.f_path
105 'f_path': state.f_path
106 };
106 };
107
107
108 var url = pyroutes.url('repo_nodetree_full', url_data);
108 var url = pyroutes.url('repo_nodetree_full', url_data);
109
109
110 metadataRequest = $.ajax({url: url});
110 metadataRequest = $.ajax({url: url});
111
111
112 metadataRequest.done(function(data) {
112 metadataRequest.done(function(data) {
113 $('#file-tree').html(data);
113 $('#file-tree').html(data);
114 timeagoActivate();
114 timeagoActivate();
115 });
115 });
116 metadataRequest.fail(function (data, textStatus, errorThrown) {
116 metadataRequest.fail(function (data, textStatus, errorThrown) {
117 console.log(data);
117 console.log(data);
118 if (data.status != 0) {
118 if (data.status != 0) {
119 alert("Error while fetching metadata.\nError code {0} ({1}).Please consider reloading the page".format(data.status,data.statusText));
119 alert("Error while fetching metadata.\nError code {0} ({1}).Please consider reloading the page".format(data.status,data.statusText));
120 }
120 }
121 });
121 });
122 };
122 };
123
123
124 var callbacks = function() {
124 var callbacks = function() {
125 var state = getState('callbacks');
125 var state = getState('callbacks');
126 timeagoActivate();
126 timeagoActivate();
127
127
128 // used for history, and switch to
128 // used for history, and switch to
129 var initialCommitData = {
129 var initialCommitData = {
130 id: null,
130 id: null,
131 text: '${_("Pick Commit")}',
131 text: '${_("Pick Commit")}',
132 type: 'sha',
132 type: 'sha',
133 raw_id: null,
133 raw_id: null,
134 files_url: null
134 files_url: null
135 };
135 };
136
136
137 if ($('#trimmed_message_box').height() < 50) {
137 if ($('#trimmed_message_box').height() < 50) {
138 $('#message_expand').hide();
138 $('#message_expand').hide();
139 }
139 }
140
140
141 $('#message_expand').on('click', function(e) {
141 $('#message_expand').on('click', function(e) {
142 $('#trimmed_message_box').css('max-height', 'none');
142 $('#trimmed_message_box').css('max-height', 'none');
143 $(this).hide();
143 $(this).hide();
144 });
144 });
145
145
146 if (fileSourcePage) {
146 if (fileSourcePage) {
147 // variants for with source code, not tree view
147 // variants for with source code, not tree view
148
148
149 // select code link event
149 // select code link event
150 $("#hlcode").mouseup(getSelectionLink);
150 $("#hlcode").mouseup(getSelectionLink);
151
151
152 // file history select2
152 // file history select2
153 select2FileHistorySwitcher('#diff1', initialCommitData, state);
153 select2FileHistorySwitcher('#diff1', initialCommitData, state);
154
154
155 // show at, diff to actions handlers
155 // show at, diff to actions handlers
156 $('#diff1').on('change', function(e) {
156 $('#diff1').on('change', function(e) {
157 $('#diff_to_commit').removeClass('disabled').removeAttr("disabled");
157 $('#diff_to_commit').removeClass('disabled').removeAttr("disabled");
158 $('#diff_to_commit').val(_gettext('Diff to Commit ') + e.val.truncateAfter(8, '...'));
158 $('#diff_to_commit').val(_gettext('Diff to Commit ') + e.val.truncateAfter(8, '...'));
159
159
160 $('#show_at_commit').removeClass('disabled').removeAttr("disabled");
160 $('#show_at_commit').removeClass('disabled').removeAttr("disabled");
161 $('#show_at_commit').val(_gettext('Show at Commit ') + e.val.truncateAfter(8, '...'));
161 $('#show_at_commit').val(_gettext('Show at Commit ') + e.val.truncateAfter(8, '...'));
162 });
162 });
163
163
164 $('#diff_to_commit').on('click', function(e) {
164 $('#diff_to_commit').on('click', function(e) {
165 var diff1 = $('#diff1').val();
165 var diff1 = $('#diff1').val();
166 var diff2 = $('#diff2').val();
166 var diff2 = $('#diff2').val();
167
167
168 var url_data = {
168 var url_data = {
169 repo_name: templateContext.repo_name,
169 repo_name: templateContext.repo_name,
170 source_ref: diff1,
170 source_ref: diff1,
171 source_ref_type: 'rev',
171 source_ref_type: 'rev',
172 target_ref: diff2,
172 target_ref: diff2,
173 target_ref_type: 'rev',
173 target_ref_type: 'rev',
174 merge: 1,
174 merge: 1,
175 f_path: state.f_path
175 f_path: state.f_path
176 };
176 };
177 window.location = pyroutes.url('compare_url', url_data);
177 window.location = pyroutes.url('repo_compare', url_data);
178 });
178 });
179
179
180 $('#show_at_commit').on('click', function(e) {
180 $('#show_at_commit').on('click', function(e) {
181 var diff1 = $('#diff1').val();
181 var diff1 = $('#diff1').val();
182
182
183 var annotate = $('#annotate').val();
183 var annotate = $('#annotate').val();
184 if (annotate === "True") {
184 if (annotate === "True") {
185 var url = pyroutes.url('repo_files:annotated',
185 var url = pyroutes.url('repo_files:annotated',
186 {'repo_name': templateContext.repo_name,
186 {'repo_name': templateContext.repo_name,
187 'commit_id': diff1, 'f_path': state.f_path});
187 'commit_id': diff1, 'f_path': state.f_path});
188 } else {
188 } else {
189 var url = pyroutes.url('repo_files',
189 var url = pyroutes.url('repo_files',
190 {'repo_name': templateContext.repo_name,
190 {'repo_name': templateContext.repo_name,
191 'commit_id': diff1, 'f_path': state.f_path});
191 'commit_id': diff1, 'f_path': state.f_path});
192 }
192 }
193 window.location = url;
193 window.location = url;
194
194
195 });
195 });
196
196
197 // show more authors
197 // show more authors
198 $('#show_authors').on('click', function(e) {
198 $('#show_authors').on('click', function(e) {
199 e.preventDefault();
199 e.preventDefault();
200 var url = pyroutes.url('repo_file_authors',
200 var url = pyroutes.url('repo_file_authors',
201 {'repo_name': templateContext.repo_name,
201 {'repo_name': templateContext.repo_name,
202 'commit_id': state.rev, 'f_path': state.f_path});
202 'commit_id': state.rev, 'f_path': state.f_path});
203
203
204 $.pjax({
204 $.pjax({
205 url: url,
205 url: url,
206 data: 'annotate=${"1" if c.annotate else "0"}',
206 data: 'annotate=${"1" if c.annotate else "0"}',
207 container: '#file_authors',
207 container: '#file_authors',
208 push: false,
208 push: false,
209 timeout: pjaxTimeout
209 timeout: pjaxTimeout
210 }).complete(function(){
210 }).complete(function(){
211 $('#show_authors').hide();
211 $('#show_authors').hide();
212 })
212 })
213 });
213 });
214
214
215 // load file short history
215 // load file short history
216 $('#file_history_overview').on('click', function(e) {
216 $('#file_history_overview').on('click', function(e) {
217 e.preventDefault();
217 e.preventDefault();
218 path = state.f_path;
218 path = state.f_path;
219 if (path.indexOf("#") >= 0) {
219 if (path.indexOf("#") >= 0) {
220 path = path.slice(0, path.indexOf("#"));
220 path = path.slice(0, path.indexOf("#"));
221 }
221 }
222 var url = pyroutes.url('repo_changelog_file',
222 var url = pyroutes.url('repo_changelog_file',
223 {'repo_name': templateContext.repo_name,
223 {'repo_name': templateContext.repo_name,
224 'commit_id': state.rev, 'f_path': path, 'limit': 6});
224 'commit_id': state.rev, 'f_path': path, 'limit': 6});
225 $('#file_history_container').show();
225 $('#file_history_container').show();
226 $('#file_history_container').html('<div class="file-history-inner">{0}</div>'.format(_gettext('Loading ...')));
226 $('#file_history_container').html('<div class="file-history-inner">{0}</div>'.format(_gettext('Loading ...')));
227
227
228 $.pjax({
228 $.pjax({
229 url: url,
229 url: url,
230 container: '#file_history_container',
230 container: '#file_history_container',
231 push: false,
231 push: false,
232 timeout: pjaxTimeout
232 timeout: pjaxTimeout
233 })
233 })
234 });
234 });
235
235
236 }
236 }
237 else {
237 else {
238 getFilesMetadata();
238 getFilesMetadata();
239
239
240 // fuzzy file filter
240 // fuzzy file filter
241 fileBrowserListeners(state.node_list_url, state.url_base);
241 fileBrowserListeners(state.node_list_url, state.url_base);
242
242
243 // switch to widget
243 // switch to widget
244 select2RefSwitcher('#refs_filter', initialCommitData);
244 select2RefSwitcher('#refs_filter', initialCommitData);
245 $('#refs_filter').on('change', function(e) {
245 $('#refs_filter').on('change', function(e) {
246 var data = $('#refs_filter').select2('data');
246 var data = $('#refs_filter').select2('data');
247 curState.commit_id = data.raw_id;
247 curState.commit_id = data.raw_id;
248 $.pjax({url: data.files_url, container: '#pjax-container', timeout: pjaxTimeout});
248 $.pjax({url: data.files_url, container: '#pjax-container', timeout: pjaxTimeout});
249 });
249 });
250
250
251 $("#prev_commit_link").on('click', function(e) {
251 $("#prev_commit_link").on('click', function(e) {
252 var data = $(this).data();
252 var data = $(this).data();
253 curState.commit_id = data.commitId;
253 curState.commit_id = data.commitId;
254 });
254 });
255
255
256 $("#next_commit_link").on('click', function(e) {
256 $("#next_commit_link").on('click', function(e) {
257 var data = $(this).data();
257 var data = $(this).data();
258 curState.commit_id = data.commitId;
258 curState.commit_id = data.commitId;
259 });
259 });
260
260
261 $('#at_rev').on("keypress", function(e) {
261 $('#at_rev').on("keypress", function(e) {
262 /* ENTER PRESSED */
262 /* ENTER PRESSED */
263 if (e.keyCode === 13) {
263 if (e.keyCode === 13) {
264 var rev = $('#at_rev').val();
264 var rev = $('#at_rev').val();
265 // explicit reload page here. with pjax entering bad input
265 // explicit reload page here. with pjax entering bad input
266 // produces not so nice results
266 // produces not so nice results
267 window.location = pyroutes.url('repo_files',
267 window.location = pyroutes.url('repo_files',
268 {'repo_name': templateContext.repo_name,
268 {'repo_name': templateContext.repo_name,
269 'commit_id': rev, 'f_path': state.f_path});
269 'commit_id': rev, 'f_path': state.f_path});
270 }
270 }
271 });
271 });
272 }
272 }
273 };
273 };
274
274
275 var pjaxTimeout = 5000;
275 var pjaxTimeout = 5000;
276
276
277 $(document).pjax(".pjax-link", "#pjax-container", {
277 $(document).pjax(".pjax-link", "#pjax-container", {
278 "fragment": "#pjax-content",
278 "fragment": "#pjax-content",
279 "maxCacheLength": 1000,
279 "maxCacheLength": 1000,
280 "timeout": pjaxTimeout
280 "timeout": pjaxTimeout
281 });
281 });
282
282
283 // define global back/forward states
283 // define global back/forward states
284 var isPjaxPopState = false;
284 var isPjaxPopState = false;
285 $(document).on('pjax:popstate', function() {
285 $(document).on('pjax:popstate', function() {
286 isPjaxPopState = true;
286 isPjaxPopState = true;
287 });
287 });
288
288
289 $(document).on('pjax:end', function(xhr, options) {
289 $(document).on('pjax:end', function(xhr, options) {
290 if (isPjaxPopState) {
290 if (isPjaxPopState) {
291 isPjaxPopState = false;
291 isPjaxPopState = false;
292 callbacks();
292 callbacks();
293 _NODEFILTER.resetFilter();
293 _NODEFILTER.resetFilter();
294 }
294 }
295
295
296 // run callback for tracking if defined for google analytics etc.
296 // run callback for tracking if defined for google analytics etc.
297 // this is used to trigger tracking on pjax
297 // this is used to trigger tracking on pjax
298 if (typeof window.rhodecode_statechange_callback !== 'undefined') {
298 if (typeof window.rhodecode_statechange_callback !== 'undefined') {
299 var state = getState('statechange');
299 var state = getState('statechange');
300 rhodecode_statechange_callback(state.url, null)
300 rhodecode_statechange_callback(state.url, null)
301 }
301 }
302 });
302 });
303
303
304 $(document).on('pjax:success', function(event, xhr, options) {
304 $(document).on('pjax:success', function(event, xhr, options) {
305 if (event.target.id == "file_history_container") {
305 if (event.target.id == "file_history_container") {
306 $('#file_history_overview').hide();
306 $('#file_history_overview').hide();
307 $('#file_history_overview_full').show();
307 $('#file_history_overview_full').show();
308 timeagoActivate();
308 timeagoActivate();
309 } else {
309 } else {
310 callbacks();
310 callbacks();
311 }
311 }
312 });
312 });
313
313
314 $(document).ready(function() {
314 $(document).ready(function() {
315 callbacks();
315 callbacks();
316 var search_GET = "${request.GET.get('search','')}";
316 var search_GET = "${request.GET.get('search','')}";
317 if (search_GET == "1") {
317 if (search_GET == "1") {
318 _NODEFILTER.initFilter();
318 _NODEFILTER.initFilter();
319 }
319 }
320 });
320 });
321
321
322 </script>
322 </script>
323
323
324 </%def>
324 </%def>
@@ -1,47 +1,56 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
3
3
4 % if c.forks_pager:
4 % if c.forks_pager:
5 <table class="rctable fork_summary">
5 <table class="rctable fork_summary">
6 <tr>
6 <tr>
7 <th>${_('Owner')}</th>
7 <th>${_('Owner')}</th>
8 <th>${_('Fork')}</th>
8 <th>${_('Fork')}</th>
9 <th>${_('Description')}</th>
9 <th>${_('Description')}</th>
10 <th>${_('Forked')}</th>
10 <th>${_('Forked')}</th>
11 <th></th>
11 <th></th>
12 </tr>
12 </tr>
13 % for f in c.forks_pager:
13 % for f in c.forks_pager:
14 <tr>
14 <tr>
15 <td class="td-user fork_user">
15 <td class="td-user fork_user">
16 ${base.gravatar_with_user(f.user.email, 16)}
16 ${base.gravatar_with_user(f.user.email, 16)}
17 </td>
17 </td>
18 <td class="td-componentname">
18 <td class="td-componentname">
19 ${h.link_to(f.repo_name,h.route_path('repo_summary',repo_name=f.repo_name))}
19 ${h.link_to(f.repo_name,h.route_path('repo_summary',repo_name=f.repo_name))}
20 </td>
20 </td>
21 <td class="td-description">
21 <td class="td-description">
22 <div class="truncate">${f.description}</div>
22 <div class="truncate">${f.description}</div>
23 </td>
23 </td>
24 <td class="td-time follower_date">
24 <td class="td-time follower_date">
25 ${h.age_component(f.created_on, time_is_local=True)}
25 ${h.age_component(f.created_on, time_is_local=True)}
26 </td>
26 </td>
27 <td class="td-compare">
27 <td class="td-compare">
28 <a title="${h.tooltip(_('Compare fork with %s' % c.repo_name))}"
28 <a title="${h.tooltip(_('Compare fork with %s' % c.repo_name))}"
29 href="${h.url('compare_url',repo_name=c.repo_name, source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1],target_repo=f.repo_name,target_ref_type=c.rhodecode_db_repo.landing_rev[0],target_ref=c.rhodecode_db_repo.landing_rev[1], merge=1)}"
29 class="btn-link"
30 class="btn-link"><i class="icon-loop"></i> ${_('Compare fork')}</a>
30 href="${h.route_path('repo_compare',
31 repo_name=c.repo_name,
32 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
33 source_ref=c.rhodecode_db_repo.landing_rev[1],
34 target_ref_type=c.rhodecode_db_repo.landing_rev[0],
35 target_ref=c.rhodecode_db_repo.landing_rev[1],
36 _query=dict(merge=1, target_repo=f.repo_name))}"
37 >
38 ${_('Compare fork')}
39 </a>
31 </td>
40 </td>
32 </tr>
41 </tr>
33 % endfor
42 % endfor
34 </table>
43 </table>
35 <div class="pagination-wh pagination-left">
44 <div class="pagination-wh pagination-left">
36 <script type="text/javascript">
45 <script type="text/javascript">
37 $(document).pjax('#forks .pager_link','#forks');
46 $(document).pjax('#forks .pager_link','#forks');
38 $(document).on('pjax:success',function(){
47 $(document).on('pjax:success',function(){
39 show_more_event();
48 show_more_event();
40 timeagoActivate();
49 timeagoActivate();
41 });
50 });
42 </script>
51 </script>
43 ${c.forks_pager.pager('$link_previous ~2~ $link_next')}
52 ${c.forks_pager.pager('$link_previous ~2~ $link_next')}
44 </div>
53 </div>
45 % else:
54 % else:
46 ${_('There are no forks yet')}
55 ${_('There are no forks yet')}
47 % endif
56 % endif
@@ -1,526 +1,526 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${c.repo_name} ${_('New pull request')}
4 ${c.repo_name} ${_('New pull request')}
5 </%def>
5 </%def>
6
6
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${_('New pull request')}
8 ${_('New pull request')}
9 </%def>
9 </%def>
10
10
11 <%def name="menu_bar_nav()">
11 <%def name="menu_bar_nav()">
12 ${self.menu_items(active='repositories')}
12 ${self.menu_items(active='repositories')}
13 </%def>
13 </%def>
14
14
15 <%def name="menu_bar_subnav()">
15 <%def name="menu_bar_subnav()">
16 ${self.repo_menu(active='showpullrequest')}
16 ${self.repo_menu(active='showpullrequest')}
17 </%def>
17 </%def>
18
18
19 <%def name="main()">
19 <%def name="main()">
20 <div class="box">
20 <div class="box">
21 <div class="title">
21 <div class="title">
22 ${self.repo_page_title(c.rhodecode_db_repo)}
22 ${self.repo_page_title(c.rhodecode_db_repo)}
23 </div>
23 </div>
24
24
25 ${h.secure_form(h.url('pullrequest', repo_name=c.repo_name), method='post', id='pull_request_form')}
25 ${h.secure_form(h.url('pullrequest', repo_name=c.repo_name), method='post', id='pull_request_form')}
26
26
27 ${self.breadcrumbs()}
27 ${self.breadcrumbs()}
28
28
29 <div class="box pr-summary">
29 <div class="box pr-summary">
30
30
31 <div class="summary-details block-left">
31 <div class="summary-details block-left">
32
32
33
33
34 <div class="pr-details-title">
34 <div class="pr-details-title">
35 ${_('Pull request summary')}
35 ${_('Pull request summary')}
36 </div>
36 </div>
37
37
38 <div class="form" style="padding-top: 10px">
38 <div class="form" style="padding-top: 10px">
39 <!-- fields -->
39 <!-- fields -->
40
40
41 <div class="fields" >
41 <div class="fields" >
42
42
43 <div class="field">
43 <div class="field">
44 <div class="label">
44 <div class="label">
45 <label for="pullrequest_title">${_('Title')}:</label>
45 <label for="pullrequest_title">${_('Title')}:</label>
46 </div>
46 </div>
47 <div class="input">
47 <div class="input">
48 ${h.text('pullrequest_title', c.default_title, class_="medium autogenerated-title")}
48 ${h.text('pullrequest_title', c.default_title, class_="medium autogenerated-title")}
49 </div>
49 </div>
50 </div>
50 </div>
51
51
52 <div class="field">
52 <div class="field">
53 <div class="label label-textarea">
53 <div class="label label-textarea">
54 <label for="pullrequest_desc">${_('Description')}:</label>
54 <label for="pullrequest_desc">${_('Description')}:</label>
55 </div>
55 </div>
56 <div class="textarea text-area editor">
56 <div class="textarea text-area editor">
57 ${h.textarea('pullrequest_desc',size=30, )}
57 ${h.textarea('pullrequest_desc',size=30, )}
58 <span class="help-block">${_('Write a short description on this pull request')}</span>
58 <span class="help-block">${_('Write a short description on this pull request')}</span>
59 </div>
59 </div>
60 </div>
60 </div>
61
61
62 <div class="field">
62 <div class="field">
63 <div class="label label-textarea">
63 <div class="label label-textarea">
64 <label for="pullrequest_desc">${_('Commit flow')}:</label>
64 <label for="pullrequest_desc">${_('Commit flow')}:</label>
65 </div>
65 </div>
66
66
67 ## TODO: johbo: Abusing the "content" class here to get the
67 ## TODO: johbo: Abusing the "content" class here to get the
68 ## desired effect. Should be replaced by a proper solution.
68 ## desired effect. Should be replaced by a proper solution.
69
69
70 ##ORG
70 ##ORG
71 <div class="content">
71 <div class="content">
72 <strong>${_('Source repository')}:</strong>
72 <strong>${_('Source repository')}:</strong>
73 ${c.rhodecode_db_repo.description}
73 ${c.rhodecode_db_repo.description}
74 </div>
74 </div>
75 <div class="content">
75 <div class="content">
76 ${h.hidden('source_repo')}
76 ${h.hidden('source_repo')}
77 ${h.hidden('source_ref')}
77 ${h.hidden('source_ref')}
78 </div>
78 </div>
79
79
80 ##OTHER, most Probably the PARENT OF THIS FORK
80 ##OTHER, most Probably the PARENT OF THIS FORK
81 <div class="content">
81 <div class="content">
82 ## filled with JS
82 ## filled with JS
83 <div id="target_repo_desc"></div>
83 <div id="target_repo_desc"></div>
84 </div>
84 </div>
85
85
86 <div class="content">
86 <div class="content">
87 ${h.hidden('target_repo')}
87 ${h.hidden('target_repo')}
88 ${h.hidden('target_ref')}
88 ${h.hidden('target_ref')}
89 <span id="target_ref_loading" style="display: none">
89 <span id="target_ref_loading" style="display: none">
90 ${_('Loading refs...')}
90 ${_('Loading refs...')}
91 </span>
91 </span>
92 </div>
92 </div>
93 </div>
93 </div>
94
94
95 <div class="field">
95 <div class="field">
96 <div class="label label-textarea">
96 <div class="label label-textarea">
97 <label for="pullrequest_submit"></label>
97 <label for="pullrequest_submit"></label>
98 </div>
98 </div>
99 <div class="input">
99 <div class="input">
100 <div class="pr-submit-button">
100 <div class="pr-submit-button">
101 ${h.submit('save',_('Submit Pull Request'),class_="btn")}
101 ${h.submit('save',_('Submit Pull Request'),class_="btn")}
102 </div>
102 </div>
103 <div id="pr_open_message"></div>
103 <div id="pr_open_message"></div>
104 </div>
104 </div>
105 </div>
105 </div>
106
106
107 <div class="pr-spacing-container"></div>
107 <div class="pr-spacing-container"></div>
108 </div>
108 </div>
109 </div>
109 </div>
110 </div>
110 </div>
111 <div>
111 <div>
112 ## AUTHOR
112 ## AUTHOR
113 <div class="reviewers-title block-right">
113 <div class="reviewers-title block-right">
114 <div class="pr-details-title">
114 <div class="pr-details-title">
115 ${_('Author of this pull request')}
115 ${_('Author of this pull request')}
116 </div>
116 </div>
117 </div>
117 </div>
118 <div class="block-right pr-details-content reviewers">
118 <div class="block-right pr-details-content reviewers">
119 <ul class="group_members">
119 <ul class="group_members">
120 <li>
120 <li>
121 ${self.gravatar_with_user(c.rhodecode_user.email, 16)}
121 ${self.gravatar_with_user(c.rhodecode_user.email, 16)}
122 </li>
122 </li>
123 </ul>
123 </ul>
124 </div>
124 </div>
125
125
126 ## REVIEW RULES
126 ## REVIEW RULES
127 <div id="review_rules" style="display: none" class="reviewers-title block-right">
127 <div id="review_rules" style="display: none" class="reviewers-title block-right">
128 <div class="pr-details-title">
128 <div class="pr-details-title">
129 ${_('Reviewer rules')}
129 ${_('Reviewer rules')}
130 </div>
130 </div>
131 <div class="pr-reviewer-rules">
131 <div class="pr-reviewer-rules">
132 ## review rules will be appended here, by default reviewers logic
132 ## review rules will be appended here, by default reviewers logic
133 </div>
133 </div>
134 </div>
134 </div>
135
135
136 ## REVIEWERS
136 ## REVIEWERS
137 <div class="reviewers-title block-right">
137 <div class="reviewers-title block-right">
138 <div class="pr-details-title">
138 <div class="pr-details-title">
139 ${_('Pull request reviewers')}
139 ${_('Pull request reviewers')}
140 <span class="calculate-reviewers"> - ${_('loading...')}</span>
140 <span class="calculate-reviewers"> - ${_('loading...')}</span>
141 </div>
141 </div>
142 </div>
142 </div>
143 <div id="reviewers" class="block-right pr-details-content reviewers">
143 <div id="reviewers" class="block-right pr-details-content reviewers">
144 ## members goes here, filled via JS based on initial selection !
144 ## members goes here, filled via JS based on initial selection !
145 <input type="hidden" name="__start__" value="review_members:sequence">
145 <input type="hidden" name="__start__" value="review_members:sequence">
146 <ul id="review_members" class="group_members"></ul>
146 <ul id="review_members" class="group_members"></ul>
147 <input type="hidden" name="__end__" value="review_members:sequence">
147 <input type="hidden" name="__end__" value="review_members:sequence">
148 <div id="add_reviewer_input" class='ac'>
148 <div id="add_reviewer_input" class='ac'>
149 <div class="reviewer_ac">
149 <div class="reviewer_ac">
150 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
150 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
151 <div id="reviewers_container"></div>
151 <div id="reviewers_container"></div>
152 </div>
152 </div>
153 </div>
153 </div>
154 </div>
154 </div>
155 </div>
155 </div>
156 </div>
156 </div>
157 <div class="box">
157 <div class="box">
158 <div>
158 <div>
159 ## overview pulled by ajax
159 ## overview pulled by ajax
160 <div id="pull_request_overview"></div>
160 <div id="pull_request_overview"></div>
161 </div>
161 </div>
162 </div>
162 </div>
163 ${h.end_form()}
163 ${h.end_form()}
164 </div>
164 </div>
165
165
166 <script type="text/javascript">
166 <script type="text/javascript">
167 $(function(){
167 $(function(){
168 var defaultSourceRepo = '${c.default_repo_data['source_repo_name']}';
168 var defaultSourceRepo = '${c.default_repo_data['source_repo_name']}';
169 var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n};
169 var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n};
170 var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}';
170 var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}';
171 var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n};
171 var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n};
172
172
173 var $pullRequestForm = $('#pull_request_form');
173 var $pullRequestForm = $('#pull_request_form');
174 var $sourceRepo = $('#source_repo', $pullRequestForm);
174 var $sourceRepo = $('#source_repo', $pullRequestForm);
175 var $targetRepo = $('#target_repo', $pullRequestForm);
175 var $targetRepo = $('#target_repo', $pullRequestForm);
176 var $sourceRef = $('#source_ref', $pullRequestForm);
176 var $sourceRef = $('#source_ref', $pullRequestForm);
177 var $targetRef = $('#target_ref', $pullRequestForm);
177 var $targetRef = $('#target_ref', $pullRequestForm);
178
178
179 var sourceRepo = function() { return $sourceRepo.eq(0).val() };
179 var sourceRepo = function() { return $sourceRepo.eq(0).val() };
180 var sourceRef = function() { return $sourceRef.eq(0).val().split(':') };
180 var sourceRef = function() { return $sourceRef.eq(0).val().split(':') };
181
181
182 var targetRepo = function() { return $targetRepo.eq(0).val() };
182 var targetRepo = function() { return $targetRepo.eq(0).val() };
183 var targetRef = function() { return $targetRef.eq(0).val().split(':') };
183 var targetRef = function() { return $targetRef.eq(0).val().split(':') };
184
184
185 var calculateContainerWidth = function() {
185 var calculateContainerWidth = function() {
186 var maxWidth = 0;
186 var maxWidth = 0;
187 var repoSelect2Containers = ['#source_repo', '#target_repo'];
187 var repoSelect2Containers = ['#source_repo', '#target_repo'];
188 $.each(repoSelect2Containers, function(idx, value) {
188 $.each(repoSelect2Containers, function(idx, value) {
189 $(value).select2('container').width('auto');
189 $(value).select2('container').width('auto');
190 var curWidth = $(value).select2('container').width();
190 var curWidth = $(value).select2('container').width();
191 if (maxWidth <= curWidth) {
191 if (maxWidth <= curWidth) {
192 maxWidth = curWidth;
192 maxWidth = curWidth;
193 }
193 }
194 $.each(repoSelect2Containers, function(idx, value) {
194 $.each(repoSelect2Containers, function(idx, value) {
195 $(value).select2('container').width(maxWidth + 10);
195 $(value).select2('container').width(maxWidth + 10);
196 });
196 });
197 });
197 });
198 };
198 };
199
199
200 var initRefSelection = function(selectedRef) {
200 var initRefSelection = function(selectedRef) {
201 return function(element, callback) {
201 return function(element, callback) {
202 // translate our select2 id into a text, it's a mapping to show
202 // translate our select2 id into a text, it's a mapping to show
203 // simple label when selecting by internal ID.
203 // simple label when selecting by internal ID.
204 var id, refData;
204 var id, refData;
205 if (selectedRef === undefined) {
205 if (selectedRef === undefined) {
206 id = element.val();
206 id = element.val();
207 refData = element.val().split(':');
207 refData = element.val().split(':');
208 } else {
208 } else {
209 id = selectedRef;
209 id = selectedRef;
210 refData = selectedRef.split(':');
210 refData = selectedRef.split(':');
211 }
211 }
212
212
213 var text = refData[1];
213 var text = refData[1];
214 if (refData[0] === 'rev') {
214 if (refData[0] === 'rev') {
215 text = text.substring(0, 12);
215 text = text.substring(0, 12);
216 }
216 }
217
217
218 var data = {id: id, text: text};
218 var data = {id: id, text: text};
219
219
220 callback(data);
220 callback(data);
221 };
221 };
222 };
222 };
223
223
224 var formatRefSelection = function(item) {
224 var formatRefSelection = function(item) {
225 var prefix = '';
225 var prefix = '';
226 var refData = item.id.split(':');
226 var refData = item.id.split(':');
227 if (refData[0] === 'branch') {
227 if (refData[0] === 'branch') {
228 prefix = '<i class="icon-branch"></i>';
228 prefix = '<i class="icon-branch"></i>';
229 }
229 }
230 else if (refData[0] === 'book') {
230 else if (refData[0] === 'book') {
231 prefix = '<i class="icon-bookmark"></i>';
231 prefix = '<i class="icon-bookmark"></i>';
232 }
232 }
233 else if (refData[0] === 'tag') {
233 else if (refData[0] === 'tag') {
234 prefix = '<i class="icon-tag"></i>';
234 prefix = '<i class="icon-tag"></i>';
235 }
235 }
236
236
237 var originalOption = item.element;
237 var originalOption = item.element;
238 return prefix + item.text;
238 return prefix + item.text;
239 };
239 };
240
240
241 // custom code mirror
241 // custom code mirror
242 var codeMirrorInstance = initPullRequestsCodeMirror('#pullrequest_desc');
242 var codeMirrorInstance = initPullRequestsCodeMirror('#pullrequest_desc');
243
243
244 reviewersController = new ReviewersController();
244 reviewersController = new ReviewersController();
245
245
246 var queryTargetRepo = function(self, query) {
246 var queryTargetRepo = function(self, query) {
247 // cache ALL results if query is empty
247 // cache ALL results if query is empty
248 var cacheKey = query.term || '__';
248 var cacheKey = query.term || '__';
249 var cachedData = self.cachedDataSource[cacheKey];
249 var cachedData = self.cachedDataSource[cacheKey];
250
250
251 if (cachedData) {
251 if (cachedData) {
252 query.callback({results: cachedData.results});
252 query.callback({results: cachedData.results});
253 } else {
253 } else {
254 $.ajax({
254 $.ajax({
255 url: pyroutes.url('pullrequest_repo_destinations', {'repo_name': templateContext.repo_name}),
255 url: pyroutes.url('pullrequest_repo_destinations', {'repo_name': templateContext.repo_name}),
256 data: {query: query.term},
256 data: {query: query.term},
257 dataType: 'json',
257 dataType: 'json',
258 type: 'GET',
258 type: 'GET',
259 success: function(data) {
259 success: function(data) {
260 self.cachedDataSource[cacheKey] = data;
260 self.cachedDataSource[cacheKey] = data;
261 query.callback({results: data.results});
261 query.callback({results: data.results});
262 },
262 },
263 error: function(data, textStatus, errorThrown) {
263 error: function(data, textStatus, errorThrown) {
264 alert(
264 alert(
265 "Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
265 "Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
266 }
266 }
267 });
267 });
268 }
268 }
269 };
269 };
270
270
271 var queryTargetRefs = function(initialData, query) {
271 var queryTargetRefs = function(initialData, query) {
272 var data = {results: []};
272 var data = {results: []};
273 // filter initialData
273 // filter initialData
274 $.each(initialData, function() {
274 $.each(initialData, function() {
275 var section = this.text;
275 var section = this.text;
276 var children = [];
276 var children = [];
277 $.each(this.children, function() {
277 $.each(this.children, function() {
278 if (query.term.length === 0 ||
278 if (query.term.length === 0 ||
279 this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ) {
279 this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ) {
280 children.push({'id': this.id, 'text': this.text})
280 children.push({'id': this.id, 'text': this.text})
281 }
281 }
282 });
282 });
283 data.results.push({'text': section, 'children': children})
283 data.results.push({'text': section, 'children': children})
284 });
284 });
285 query.callback({results: data.results});
285 query.callback({results: data.results});
286 };
286 };
287
287
288 var loadRepoRefDiffPreview = function() {
288 var loadRepoRefDiffPreview = function() {
289
289
290 var url_data = {
290 var url_data = {
291 'repo_name': targetRepo(),
291 'repo_name': targetRepo(),
292 'target_repo': sourceRepo(),
292 'target_repo': sourceRepo(),
293 'source_ref': targetRef()[2],
293 'source_ref': targetRef()[2],
294 'source_ref_type': 'rev',
294 'source_ref_type': 'rev',
295 'target_ref': sourceRef()[2],
295 'target_ref': sourceRef()[2],
296 'target_ref_type': 'rev',
296 'target_ref_type': 'rev',
297 'merge': true,
297 'merge': true,
298 '_': Date.now() // bypass browser caching
298 '_': Date.now() // bypass browser caching
299 }; // gather the source/target ref and repo here
299 }; // gather the source/target ref and repo here
300
300
301 if (sourceRef().length !== 3 || targetRef().length !== 3) {
301 if (sourceRef().length !== 3 || targetRef().length !== 3) {
302 prButtonLock(true, "${_('Please select source and target')}");
302 prButtonLock(true, "${_('Please select source and target')}");
303 return;
303 return;
304 }
304 }
305 var url = pyroutes.url('compare_url', url_data);
305 var url = pyroutes.url('repo_compare', url_data);
306
306
307 // lock PR button, so we cannot send PR before it's calculated
307 // lock PR button, so we cannot send PR before it's calculated
308 prButtonLock(true, "${_('Loading compare ...')}", 'compare');
308 prButtonLock(true, "${_('Loading compare ...')}", 'compare');
309
309
310 if (loadRepoRefDiffPreview._currentRequest) {
310 if (loadRepoRefDiffPreview._currentRequest) {
311 loadRepoRefDiffPreview._currentRequest.abort();
311 loadRepoRefDiffPreview._currentRequest.abort();
312 }
312 }
313
313
314 loadRepoRefDiffPreview._currentRequest = $.get(url)
314 loadRepoRefDiffPreview._currentRequest = $.get(url)
315 .error(function(data, textStatus, errorThrown) {
315 .error(function(data, textStatus, errorThrown) {
316 alert(
316 alert(
317 "Error while processing request.\nError code {0} ({1}).".format(
317 "Error while processing request.\nError code {0} ({1}).".format(
318 data.status, data.statusText));
318 data.status, data.statusText));
319 })
319 })
320 .done(function(data) {
320 .done(function(data) {
321 loadRepoRefDiffPreview._currentRequest = null;
321 loadRepoRefDiffPreview._currentRequest = null;
322 $('#pull_request_overview').html(data);
322 $('#pull_request_overview').html(data);
323
323
324 var commitElements = $(data).find('tr[commit_id]');
324 var commitElements = $(data).find('tr[commit_id]');
325
325
326 var prTitleAndDesc = getTitleAndDescription(
326 var prTitleAndDesc = getTitleAndDescription(
327 sourceRef()[1], commitElements, 5);
327 sourceRef()[1], commitElements, 5);
328
328
329 var title = prTitleAndDesc[0];
329 var title = prTitleAndDesc[0];
330 var proposedDescription = prTitleAndDesc[1];
330 var proposedDescription = prTitleAndDesc[1];
331
331
332 var useGeneratedTitle = (
332 var useGeneratedTitle = (
333 $('#pullrequest_title').hasClass('autogenerated-title') ||
333 $('#pullrequest_title').hasClass('autogenerated-title') ||
334 $('#pullrequest_title').val() === "");
334 $('#pullrequest_title').val() === "");
335
335
336 if (title && useGeneratedTitle) {
336 if (title && useGeneratedTitle) {
337 // use generated title if we haven't specified our own
337 // use generated title if we haven't specified our own
338 $('#pullrequest_title').val(title);
338 $('#pullrequest_title').val(title);
339 $('#pullrequest_title').addClass('autogenerated-title');
339 $('#pullrequest_title').addClass('autogenerated-title');
340
340
341 }
341 }
342
342
343 var useGeneratedDescription = (
343 var useGeneratedDescription = (
344 !codeMirrorInstance._userDefinedDesc ||
344 !codeMirrorInstance._userDefinedDesc ||
345 codeMirrorInstance.getValue() === "");
345 codeMirrorInstance.getValue() === "");
346
346
347 if (proposedDescription && useGeneratedDescription) {
347 if (proposedDescription && useGeneratedDescription) {
348 // set proposed content, if we haven't defined our own,
348 // set proposed content, if we haven't defined our own,
349 // or we don't have description written
349 // or we don't have description written
350 codeMirrorInstance._userDefinedDesc = false; // reset state
350 codeMirrorInstance._userDefinedDesc = false; // reset state
351 codeMirrorInstance.setValue(proposedDescription);
351 codeMirrorInstance.setValue(proposedDescription);
352 }
352 }
353
353
354 var msg = '';
354 var msg = '';
355 if (commitElements.length === 1) {
355 if (commitElements.length === 1) {
356 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 1)}";
356 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 1)}";
357 } else {
357 } else {
358 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 2)}";
358 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 2)}";
359 }
359 }
360
360
361 msg += ' <a id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
361 msg += ' <a id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
362
362
363 if (commitElements.length) {
363 if (commitElements.length) {
364 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
364 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
365 prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare');
365 prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare');
366 }
366 }
367 else {
367 else {
368 prButtonLock(true, "${_('There are no commits to merge.')}", 'compare');
368 prButtonLock(true, "${_('There are no commits to merge.')}", 'compare');
369 }
369 }
370
370
371
371
372 });
372 });
373 };
373 };
374
374
375 var Select2Box = function(element, overrides) {
375 var Select2Box = function(element, overrides) {
376 var globalDefaults = {
376 var globalDefaults = {
377 dropdownAutoWidth: true,
377 dropdownAutoWidth: true,
378 containerCssClass: "drop-menu",
378 containerCssClass: "drop-menu",
379 dropdownCssClass: "drop-menu-dropdown"
379 dropdownCssClass: "drop-menu-dropdown"
380 };
380 };
381
381
382 var initSelect2 = function(defaultOptions) {
382 var initSelect2 = function(defaultOptions) {
383 var options = jQuery.extend(globalDefaults, defaultOptions, overrides);
383 var options = jQuery.extend(globalDefaults, defaultOptions, overrides);
384 element.select2(options);
384 element.select2(options);
385 };
385 };
386
386
387 return {
387 return {
388 initRef: function() {
388 initRef: function() {
389 var defaultOptions = {
389 var defaultOptions = {
390 minimumResultsForSearch: 5,
390 minimumResultsForSearch: 5,
391 formatSelection: formatRefSelection
391 formatSelection: formatRefSelection
392 };
392 };
393
393
394 initSelect2(defaultOptions);
394 initSelect2(defaultOptions);
395 },
395 },
396
396
397 initRepo: function(defaultValue, readOnly) {
397 initRepo: function(defaultValue, readOnly) {
398 var defaultOptions = {
398 var defaultOptions = {
399 initSelection : function (element, callback) {
399 initSelection : function (element, callback) {
400 var data = {id: defaultValue, text: defaultValue};
400 var data = {id: defaultValue, text: defaultValue};
401 callback(data);
401 callback(data);
402 }
402 }
403 };
403 };
404
404
405 initSelect2(defaultOptions);
405 initSelect2(defaultOptions);
406
406
407 element.select2('val', defaultSourceRepo);
407 element.select2('val', defaultSourceRepo);
408 if (readOnly === true) {
408 if (readOnly === true) {
409 element.select2('readonly', true);
409 element.select2('readonly', true);
410 }
410 }
411 }
411 }
412 };
412 };
413 };
413 };
414
414
415 var initTargetRefs = function(refsData, selectedRef){
415 var initTargetRefs = function(refsData, selectedRef){
416 Select2Box($targetRef, {
416 Select2Box($targetRef, {
417 query: function(query) {
417 query: function(query) {
418 queryTargetRefs(refsData, query);
418 queryTargetRefs(refsData, query);
419 },
419 },
420 initSelection : initRefSelection(selectedRef)
420 initSelection : initRefSelection(selectedRef)
421 }).initRef();
421 }).initRef();
422
422
423 if (!(selectedRef === undefined)) {
423 if (!(selectedRef === undefined)) {
424 $targetRef.select2('val', selectedRef);
424 $targetRef.select2('val', selectedRef);
425 }
425 }
426 };
426 };
427
427
428 var targetRepoChanged = function(repoData) {
428 var targetRepoChanged = function(repoData) {
429 // generate new DESC of target repo displayed next to select
429 // generate new DESC of target repo displayed next to select
430 $('#target_repo_desc').html(
430 $('#target_repo_desc').html(
431 "<strong>${_('Target repository')}</strong>: {0}".format(repoData['description'])
431 "<strong>${_('Target repository')}</strong>: {0}".format(repoData['description'])
432 );
432 );
433
433
434 // generate dynamic select2 for refs.
434 // generate dynamic select2 for refs.
435 initTargetRefs(repoData['refs']['select2_refs'],
435 initTargetRefs(repoData['refs']['select2_refs'],
436 repoData['refs']['selected_ref']);
436 repoData['refs']['selected_ref']);
437
437
438 };
438 };
439
439
440 var sourceRefSelect2 = Select2Box($sourceRef, {
440 var sourceRefSelect2 = Select2Box($sourceRef, {
441 placeholder: "${_('Select commit reference')}",
441 placeholder: "${_('Select commit reference')}",
442 query: function(query) {
442 query: function(query) {
443 var initialData = defaultSourceRepoData['refs']['select2_refs'];
443 var initialData = defaultSourceRepoData['refs']['select2_refs'];
444 queryTargetRefs(initialData, query)
444 queryTargetRefs(initialData, query)
445 },
445 },
446 initSelection: initRefSelection()
446 initSelection: initRefSelection()
447 }
447 }
448 );
448 );
449
449
450 var sourceRepoSelect2 = Select2Box($sourceRepo, {
450 var sourceRepoSelect2 = Select2Box($sourceRepo, {
451 query: function(query) {}
451 query: function(query) {}
452 });
452 });
453
453
454 var targetRepoSelect2 = Select2Box($targetRepo, {
454 var targetRepoSelect2 = Select2Box($targetRepo, {
455 cachedDataSource: {},
455 cachedDataSource: {},
456 query: $.debounce(250, function(query) {
456 query: $.debounce(250, function(query) {
457 queryTargetRepo(this, query);
457 queryTargetRepo(this, query);
458 }),
458 }),
459 formatResult: formatResult
459 formatResult: formatResult
460 });
460 });
461
461
462 sourceRefSelect2.initRef();
462 sourceRefSelect2.initRef();
463
463
464 sourceRepoSelect2.initRepo(defaultSourceRepo, true);
464 sourceRepoSelect2.initRepo(defaultSourceRepo, true);
465
465
466 targetRepoSelect2.initRepo(defaultTargetRepo, false);
466 targetRepoSelect2.initRepo(defaultTargetRepo, false);
467
467
468 $sourceRef.on('change', function(e){
468 $sourceRef.on('change', function(e){
469 loadRepoRefDiffPreview();
469 loadRepoRefDiffPreview();
470 reviewersController.loadDefaultReviewers(
470 reviewersController.loadDefaultReviewers(
471 sourceRepo(), sourceRef(), targetRepo(), targetRef());
471 sourceRepo(), sourceRef(), targetRepo(), targetRef());
472 });
472 });
473
473
474 $targetRef.on('change', function(e){
474 $targetRef.on('change', function(e){
475 loadRepoRefDiffPreview();
475 loadRepoRefDiffPreview();
476 reviewersController.loadDefaultReviewers(
476 reviewersController.loadDefaultReviewers(
477 sourceRepo(), sourceRef(), targetRepo(), targetRef());
477 sourceRepo(), sourceRef(), targetRepo(), targetRef());
478 });
478 });
479
479
480 $targetRepo.on('change', function(e){
480 $targetRepo.on('change', function(e){
481 var repoName = $(this).val();
481 var repoName = $(this).val();
482 calculateContainerWidth();
482 calculateContainerWidth();
483 $targetRef.select2('destroy');
483 $targetRef.select2('destroy');
484 $('#target_ref_loading').show();
484 $('#target_ref_loading').show();
485
485
486 $.ajax({
486 $.ajax({
487 url: pyroutes.url('pullrequest_repo_refs',
487 url: pyroutes.url('pullrequest_repo_refs',
488 {'repo_name': templateContext.repo_name, 'target_repo_name':repoName}),
488 {'repo_name': templateContext.repo_name, 'target_repo_name':repoName}),
489 data: {},
489 data: {},
490 dataType: 'json',
490 dataType: 'json',
491 type: 'GET',
491 type: 'GET',
492 success: function(data) {
492 success: function(data) {
493 $('#target_ref_loading').hide();
493 $('#target_ref_loading').hide();
494 targetRepoChanged(data);
494 targetRepoChanged(data);
495 loadRepoRefDiffPreview();
495 loadRepoRefDiffPreview();
496 },
496 },
497 error: function(data, textStatus, errorThrown) {
497 error: function(data, textStatus, errorThrown) {
498 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
498 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
499 }
499 }
500 })
500 })
501
501
502 });
502 });
503
503
504 prButtonLock(true, "${_('Please select source and target')}", 'all');
504 prButtonLock(true, "${_('Please select source and target')}", 'all');
505
505
506 // auto-load on init, the target refs select2
506 // auto-load on init, the target refs select2
507 calculateContainerWidth();
507 calculateContainerWidth();
508 targetRepoChanged(defaultTargetRepoData);
508 targetRepoChanged(defaultTargetRepoData);
509
509
510 $('#pullrequest_title').on('keyup', function(e){
510 $('#pullrequest_title').on('keyup', function(e){
511 $(this).removeClass('autogenerated-title');
511 $(this).removeClass('autogenerated-title');
512 });
512 });
513
513
514 % if c.default_source_ref:
514 % if c.default_source_ref:
515 // in case we have a pre-selected value, use it now
515 // in case we have a pre-selected value, use it now
516 $sourceRef.select2('val', '${c.default_source_ref}');
516 $sourceRef.select2('val', '${c.default_source_ref}');
517 loadRepoRefDiffPreview();
517 loadRepoRefDiffPreview();
518 reviewersController.loadDefaultReviewers(
518 reviewersController.loadDefaultReviewers(
519 sourceRepo(), sourceRef(), targetRepo(), targetRef());
519 sourceRepo(), sourceRef(), targetRepo(), targetRef());
520 % endif
520 % endif
521
521
522 ReviewerAutoComplete('#user');
522 ReviewerAutoComplete('#user');
523 });
523 });
524 </script>
524 </script>
525
525
526 </%def>
526 </%def>
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now