##// END OF EJS Templates
changelog: don't load branches for parents as we don't need them for DAG graph.
marcink -
r3849:f735249e default
parent child Browse files
Show More
@@ -1,365 +1,365 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import logging
23 23
24 24 from pyramid.httpexceptions import HTTPNotFound, HTTPFound
25 25 from pyramid.view import view_config
26 26 from pyramid.renderers import render
27 27 from pyramid.response import Response
28 28
29 29 from rhodecode.apps._base import RepoAppView
30 30 import rhodecode.lib.helpers as h
31 31 from rhodecode.lib.auth import (
32 32 LoginRequired, HasRepoPermissionAnyDecorator)
33 33
34 34 from rhodecode.lib.ext_json import json
35 35 from rhodecode.lib.graphmod import _colored, _dagwalker
36 36 from rhodecode.lib.helpers import RepoPage
37 37 from rhodecode.lib.utils2 import safe_int, safe_str, str2bool
38 38 from rhodecode.lib.vcs.exceptions import (
39 39 RepositoryError, CommitDoesNotExistError,
40 40 CommitError, NodeDoesNotExistError, EmptyRepositoryError)
41 41
42 42 log = logging.getLogger(__name__)
43 43
44 44 DEFAULT_CHANGELOG_SIZE = 20
45 45
46 46
47 47 class RepoChangelogView(RepoAppView):
48 48
49 49 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
50 50 """
51 51 This is a safe way to get commit. If an error occurs it redirects to
52 52 tip with proper message
53 53
54 54 :param commit_id: id of commit to fetch
55 55 :param redirect_after: toggle redirection
56 56 """
57 57 _ = self.request.translate
58 58
59 59 try:
60 60 return self.rhodecode_vcs_repo.get_commit(commit_id)
61 61 except EmptyRepositoryError:
62 62 if not redirect_after:
63 63 return None
64 64
65 65 h.flash(h.literal(
66 66 _('There are no commits yet')), category='warning')
67 67 raise HTTPFound(
68 68 h.route_path('repo_summary', repo_name=self.db_repo_name))
69 69
70 70 except (CommitDoesNotExistError, LookupError):
71 71 msg = _('No such commit exists for this repository')
72 72 h.flash(msg, category='error')
73 73 raise HTTPNotFound()
74 74 except RepositoryError as e:
75 75 h.flash(safe_str(h.escape(e)), category='error')
76 76 raise HTTPNotFound()
77 77
78 78 def _graph(self, repo, commits, prev_data=None, next_data=None):
79 79 """
80 80 Generates a DAG graph for repo
81 81
82 82 :param repo: repo instance
83 83 :param commits: list of commits
84 84 """
85 85 if not commits:
86 86 return json.dumps([]), json.dumps([])
87 87
88 88 def serialize(commit, parents=True):
89 89 data = dict(
90 90 raw_id=commit.raw_id,
91 91 idx=commit.idx,
92 branch=h.escape(commit.branch),
92 branch=None,
93 93 )
94 94 if parents:
95 95 data['parents'] = [
96 96 serialize(x, parents=False) for x in commit.parents]
97 97 return data
98 98
99 99 prev_data = prev_data or []
100 100 next_data = next_data or []
101 101
102 102 current = [serialize(x) for x in commits]
103 103 commits = prev_data + current + next_data
104 104
105 105 dag = _dagwalker(repo, commits)
106 106
107 107 data = [[commit_id, vtx, edges, branch]
108 108 for commit_id, vtx, edges, branch in _colored(dag)]
109 109 return json.dumps(data), json.dumps(current)
110 110
111 111 def _check_if_valid_branch(self, branch_name, repo_name, f_path):
112 112 if branch_name not in self.rhodecode_vcs_repo.branches_all:
113 113 h.flash('Branch {} is not found.'.format(h.escape(branch_name)),
114 114 category='warning')
115 115 redirect_url = h.route_path(
116 116 'repo_commits_file', repo_name=repo_name,
117 117 commit_id=branch_name, f_path=f_path or '')
118 118 raise HTTPFound(redirect_url)
119 119
120 120 def _load_changelog_data(
121 121 self, c, collection, page, chunk_size, branch_name=None,
122 122 dynamic=False, f_path=None, commit_id=None):
123 123
124 124 def url_generator(**kw):
125 125 query_params = {}
126 126 query_params.update(kw)
127 127 if f_path:
128 128 # changelog for file
129 129 return h.route_path(
130 130 'repo_commits_file',
131 131 repo_name=c.rhodecode_db_repo.repo_name,
132 132 commit_id=commit_id, f_path=f_path,
133 133 _query=query_params)
134 134 else:
135 135 return h.route_path(
136 136 'repo_commits',
137 137 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
138 138
139 139 c.total_cs = len(collection)
140 140 c.showing_commits = min(chunk_size, c.total_cs)
141 141 c.pagination = RepoPage(collection, page=page, item_count=c.total_cs,
142 142 items_per_page=chunk_size, branch=branch_name,
143 143 url=url_generator)
144 144
145 145 c.next_page = c.pagination.next_page
146 146 c.prev_page = c.pagination.previous_page
147 147
148 148 if dynamic:
149 149 if self.request.GET.get('chunk') != 'next':
150 150 c.next_page = None
151 151 if self.request.GET.get('chunk') != 'prev':
152 152 c.prev_page = None
153 153
154 154 page_commit_ids = [x.raw_id for x in c.pagination]
155 155 c.comments = c.rhodecode_db_repo.get_comments(page_commit_ids)
156 156 c.statuses = c.rhodecode_db_repo.statuses(page_commit_ids)
157 157
158 158 def load_default_context(self):
159 159 c = self._get_local_tmpl_context(include_app_defaults=True)
160 160
161 161 c.rhodecode_repo = self.rhodecode_vcs_repo
162 162
163 163 return c
164 164
165 165 def _get_preload_attrs(self):
166 166 pre_load = ['author', 'branch', 'date', 'message', 'parents',
167 167 'obsolete', 'phase', 'hidden']
168 168 return pre_load
169 169
170 170 @LoginRequired()
171 171 @HasRepoPermissionAnyDecorator(
172 172 'repository.read', 'repository.write', 'repository.admin')
173 173 @view_config(
174 174 route_name='repo_commits', request_method='GET',
175 175 renderer='rhodecode:templates/commits/changelog.mako')
176 176 @view_config(
177 177 route_name='repo_commits_file', request_method='GET',
178 178 renderer='rhodecode:templates/commits/changelog.mako')
179 179 # old routes for backward compat
180 180 @view_config(
181 181 route_name='repo_changelog', request_method='GET',
182 182 renderer='rhodecode:templates/commits/changelog.mako')
183 183 @view_config(
184 184 route_name='repo_changelog_file', request_method='GET',
185 185 renderer='rhodecode:templates/commits/changelog.mako')
186 186 def repo_changelog(self):
187 187 c = self.load_default_context()
188 188
189 189 commit_id = self.request.matchdict.get('commit_id')
190 190 f_path = self._get_f_path(self.request.matchdict)
191 191 show_hidden = str2bool(self.request.GET.get('evolve'))
192 192
193 193 chunk_size = 20
194 194
195 195 c.branch_name = branch_name = self.request.GET.get('branch') or ''
196 196 c.book_name = book_name = self.request.GET.get('bookmark') or ''
197 197 c.f_path = f_path
198 198 c.commit_id = commit_id
199 199 c.show_hidden = show_hidden
200 200
201 201 hist_limit = safe_int(self.request.GET.get('limit')) or None
202 202
203 203 p = safe_int(self.request.GET.get('page', 1), 1)
204 204
205 205 c.selected_name = branch_name or book_name
206 206 if not commit_id and branch_name:
207 207 self._check_if_valid_branch(branch_name, self.db_repo_name, f_path)
208 208
209 209 c.changelog_for_path = f_path
210 210 pre_load = self._get_preload_attrs()
211 211
212 212 partial_xhr = self.request.environ.get('HTTP_X_PARTIAL_XHR')
213 213 try:
214 214 if f_path:
215 215 log.debug('generating changelog for path %s', f_path)
216 216 # get the history for the file !
217 217 base_commit = self.rhodecode_vcs_repo.get_commit(commit_id)
218 218
219 219 try:
220 220 collection = base_commit.get_path_history(
221 221 f_path, limit=hist_limit, pre_load=pre_load)
222 222 if collection and partial_xhr:
223 223 # for ajax call we remove first one since we're looking
224 224 # at it right now in the context of a file commit
225 225 collection.pop(0)
226 226 except (NodeDoesNotExistError, CommitError):
227 227 # this node is not present at tip!
228 228 try:
229 229 commit = self._get_commit_or_redirect(commit_id)
230 230 collection = commit.get_path_history(f_path)
231 231 except RepositoryError as e:
232 232 h.flash(safe_str(e), category='warning')
233 233 redirect_url = h.route_path(
234 234 'repo_commits', repo_name=self.db_repo_name)
235 235 raise HTTPFound(redirect_url)
236 236 collection = list(reversed(collection))
237 237 else:
238 238 collection = self.rhodecode_vcs_repo.get_commits(
239 239 branch_name=branch_name, show_hidden=show_hidden,
240 240 pre_load=pre_load, translate_tags=False)
241 241
242 242 self._load_changelog_data(
243 243 c, collection, p, chunk_size, c.branch_name,
244 244 f_path=f_path, commit_id=commit_id)
245 245
246 246 except EmptyRepositoryError as e:
247 247 h.flash(safe_str(h.escape(e)), category='warning')
248 248 raise HTTPFound(
249 249 h.route_path('repo_summary', repo_name=self.db_repo_name))
250 250 except HTTPFound:
251 251 raise
252 252 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
253 253 log.exception(safe_str(e))
254 254 h.flash(safe_str(h.escape(e)), category='error')
255 255 raise HTTPFound(
256 256 h.route_path('repo_commits', repo_name=self.db_repo_name))
257 257
258 258 if partial_xhr or self.request.environ.get('HTTP_X_PJAX'):
259 259 # case when loading dynamic file history in file view
260 260 # loading from ajax, we don't want the first result, it's popped
261 261 # in the code above
262 262 html = render(
263 263 'rhodecode:templates/commits/changelog_file_history.mako',
264 264 self._get_template_context(c), self.request)
265 265 return Response(html)
266 266
267 267 commit_ids = []
268 268 if not f_path:
269 269 # only load graph data when not in file history mode
270 270 commit_ids = c.pagination
271 271
272 272 c.graph_data, c.graph_commits = self._graph(
273 273 self.rhodecode_vcs_repo, commit_ids)
274 274
275 275 return self._get_template_context(c)
276 276
277 277 @LoginRequired()
278 278 @HasRepoPermissionAnyDecorator(
279 279 'repository.read', 'repository.write', 'repository.admin')
280 280 @view_config(
281 281 route_name='repo_commits_elements', request_method=('GET', 'POST'),
282 282 renderer='rhodecode:templates/commits/changelog_elements.mako',
283 283 xhr=True)
284 284 @view_config(
285 285 route_name='repo_commits_elements_file', request_method=('GET', 'POST'),
286 286 renderer='rhodecode:templates/commits/changelog_elements.mako',
287 287 xhr=True)
288 288 def repo_commits_elements(self):
289 289 c = self.load_default_context()
290 290 commit_id = self.request.matchdict.get('commit_id')
291 291 f_path = self._get_f_path(self.request.matchdict)
292 292 show_hidden = str2bool(self.request.GET.get('evolve'))
293 293
294 294 chunk_size = 20
295 295 hist_limit = safe_int(self.request.GET.get('limit')) or None
296 296
297 297 def wrap_for_error(err):
298 298 html = '<tr>' \
299 299 '<td colspan="9" class="alert alert-error">ERROR: {}</td>' \
300 300 '</tr>'.format(err)
301 301 return Response(html)
302 302
303 303 c.branch_name = branch_name = self.request.GET.get('branch') or ''
304 304 c.book_name = book_name = self.request.GET.get('bookmark') or ''
305 305 c.f_path = f_path
306 306 c.commit_id = commit_id
307 307 c.show_hidden = show_hidden
308 308
309 309 c.selected_name = branch_name or book_name
310 310 if branch_name and branch_name not in self.rhodecode_vcs_repo.branches_all:
311 311 return wrap_for_error(
312 312 safe_str('Branch: {} is not valid'.format(branch_name)))
313 313
314 314 pre_load = self._get_preload_attrs()
315 315
316 316 if f_path:
317 317 try:
318 318 base_commit = self.rhodecode_vcs_repo.get_commit(commit_id)
319 319 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
320 320 log.exception(safe_str(e))
321 321 raise HTTPFound(
322 322 h.route_path('repo_commits', repo_name=self.db_repo_name))
323 323
324 324 collection = base_commit.get_path_history(
325 325 f_path, limit=hist_limit, pre_load=pre_load)
326 326 collection = list(reversed(collection))
327 327 else:
328 328 collection = self.rhodecode_vcs_repo.get_commits(
329 329 branch_name=branch_name, show_hidden=show_hidden, pre_load=pre_load,
330 330 translate_tags=False)
331 331
332 332 p = safe_int(self.request.GET.get('page', 1), 1)
333 333 try:
334 334 self._load_changelog_data(
335 335 c, collection, p, chunk_size, dynamic=True,
336 336 f_path=f_path, commit_id=commit_id)
337 337 except EmptyRepositoryError as e:
338 338 return wrap_for_error(safe_str(e))
339 339 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
340 340 log.exception('Failed to fetch commits')
341 341 return wrap_for_error(safe_str(e))
342 342
343 343 prev_data = None
344 344 next_data = None
345 345
346 346 try:
347 347 prev_graph = json.loads(self.request.POST.get('graph') or '{}')
348 348 except json.JSONDecodeError:
349 349 prev_graph = {}
350 350
351 351 if self.request.GET.get('chunk') == 'prev':
352 352 next_data = prev_graph
353 353 elif self.request.GET.get('chunk') == 'next':
354 354 prev_data = prev_graph
355 355
356 356 commit_ids = []
357 357 if not f_path:
358 358 # only load graph data when not in file history mode
359 359 commit_ids = c.pagination
360 360
361 361 c.graph_data, c.graph_commits = self._graph(
362 362 self.rhodecode_vcs_repo, commit_ids,
363 363 prev_data=prev_data, next_data=next_data)
364 364
365 365 return self._get_template_context(c)
@@ -1,211 +1,211 b''
1 1 // # Copyright (C) 2016-2019 RhodeCode GmbH
2 2 // #
3 3 // # This program is free software: you can redistribute it and/or modify
4 4 // # it under the terms of the GNU Affero General Public License, version 3
5 5 // # (only), as published by the Free Software Foundation.
6 6 // #
7 7 // # This program is distributed in the hope that it will be useful,
8 8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 // # GNU General Public License for more details.
11 11 // #
12 12 // # You should have received a copy of the GNU Affero General Public License
13 13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 // #
15 15 // # This program is dual-licensed. If you wish to learn more about the
16 16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19
20 20 var CommitsController = function () {
21 21 var self = this;
22 22 this.$graphCanvas = $('#graph_canvas');
23 23 this.$commitCounter = $('#commit-counter');
24 24
25 25 this.getCurrentGraphData = function () {
26 26 // raw form
27 27 return self.$graphCanvas.data('commits');
28 28 };
29 29
30 30 this.setLabelText = function (graphData) {
31 31 var shown = $('.commit_hash').length;
32 32 var total = self.$commitCounter.data('total');
33 33
34 34 if (shown == 1) {
35 35 var text = _gettext('showing {0} out of {1} commit').format(shown, total);
36 36 } else {
37 37 var text = _gettext('showing {0} out of {1} commits').format(shown, total);
38 38 }
39 39 self.$commitCounter.html(text)
40 40 };
41 41
42 42 this.reloadGraph = function (chunk) {
43 43 chunk = chunk || 'next';
44 44
45 45 // reset state on re-render !
46 46 self.$graphCanvas.html('');
47 47
48 48 var edgeData = $("[data-graph]").data('graph') || this.$graphCanvas.data('graph') || [];
49 49 var prev_link = $('.load-more-commits').find('.prev-commits').get(0);
50 50 var next_link = $('.load-more-commits').find('.next-commits').get(0);
51 51
52 52 // Determine max number of edges per row in graph
53 53 var edgeCount = 1;
54 54 $.each(edgeData, function (i, item) {
55 55 $.each(item[2], function (key, value) {
56 56 if (value[1] > edgeCount) {
57 57 edgeCount = value[1];
58 58 }
59 59 });
60 60 });
61 61
62 62 if (prev_link && next_link) {
63 63 var graph_padding = -64;
64 64 }
65 65 else if (next_link) {
66 66 var graph_padding = -32;
67 67 } else {
68 68 var graph_padding = 0;
69 69 }
70 70
71 71 var x_step = Math.min(10, Math.floor(86 / edgeCount));
72 72 var height = $('#changesets').find('.commits-range').height() + graph_padding;
73 73 var graph_options = {
74 74 width: 100,
75 75 height: height,
76 76 x_step: x_step,
77 77 y_step: 42,
78 78 dotRadius: 3.5,
79 79 lineWidth: 2.5
80 80 };
81 81
82 82 var prevCommitsData = this.$graphCanvas.data('commits') || [];
83 83 var nextCommitsData = $("[data-graph]").data('commits') || [];
84 84
85 85 if (chunk == 'next') {
86 86 var commitData = $.merge(prevCommitsData, nextCommitsData);
87 87 } else {
88 88 var commitData = $.merge(nextCommitsData, prevCommitsData);
89 89 }
90 90
91 91 this.$graphCanvas.data('graph', edgeData);
92 92 this.$graphCanvas.data('commits', commitData);
93 93
94 94 // destroy dynamic loaded graph
95 95 $("[data-graph]").remove();
96 96
97 97 this.$graphCanvas.commits(graph_options);
98 98
99 99 this.setLabelText(edgeData);
100 100
101 101 var padding = 90;
102 102 if (prev_link) {
103 103 padding += 34;
104 104
105 105 }
106 106 $('#graph_nodes').css({'padding-top': padding});
107 107
108 108 $.each($('.message.truncate'), function(idx, value) {
109 109 if(!(value.offsetWidth < value.scrollWidth)){
110 110 var expandTd = $(this).closest('td').siblings('.expand_commit');
111 111 expandTd.find('i').hide();
112 112 expandTd.removeAttr('title');
113 113 expandTd.removeClass('expand_commit');
114 114 }
115 115 });
116 116
117 117 };
118 118
119 119 this.getChunkUrl = function (page, chunk, branch, commit_id, f_path) {
120 120 var urlData = {
121 121 'repo_name': templateContext.repo_name,
122 122 'page': page,
123 123 'chunk': chunk
124 124 };
125 125
126 126 if (branch !== undefined && branch !== '') {
127 127 urlData['branch'] = branch;
128 128 }
129 129 if (commit_id !== undefined && commit_id !== '') {
130 130 urlData['commit_id'] = commit_id;
131 131 }
132 132 if (f_path !== undefined && f_path !== '') {
133 133 urlData['f_path'] = f_path;
134 134 }
135 135
136 136 if (urlData['commit_id'] && urlData['f_path']) {
137 137 return pyroutes.url('repo_commits_elements_file', urlData);
138 138 }
139 139 else {
140 140 return pyroutes.url('repo_commits_elements', urlData);
141 141 }
142 142
143 143 };
144 144
145 145 this.loadNext = function (node, page, branch, commit_id, f_path) {
146 146 var loadUrl = this.getChunkUrl(page, 'next', branch, commit_id, f_path);
147 147 var postData = {'graph': JSON.stringify(this.getCurrentGraphData())};
148
148 $(node).html('loading...').addClass('disabled').css({'cursor':'default'});
149 149 $.post(loadUrl, postData, function (data) {
150 150 $(node).closest('tbody').append(data);
151 151 $(node).closest('td').remove();
152 152 self.reloadGraph('next');
153 153 })
154 154 };
155 155
156 156 this.loadPrev = function (node, page, branch, commit_id, f_path) {
157 157 var loadUrl = this.getChunkUrl(page, 'prev', branch, commit_id, f_path);
158 158 var postData = {'graph': JSON.stringify(this.getCurrentGraphData())};
159
159 $(node).html('loading...').addClass('disabled').css({'cursor':'default'});
160 160 $.post(loadUrl, postData, function (data) {
161 161 $(node).closest('tbody').prepend(data);
162 162 $(node).closest('td').remove();
163 163 self.reloadGraph('prev');
164 164 })
165 165 };
166 166
167 167 this.expandCommit = function (node, reloadGraph) {
168 168 reloadGraph = reloadGraph || false;
169 169
170 170 var target_expand = $(node);
171 171 var cid = target_expand.data('commitId');
172 172
173 173 if (target_expand.hasClass('open')) {
174 174 $('#c-' + cid).css({
175 175 'height': '1.5em',
176 176 'white-space': 'nowrap',
177 177 'text-overflow': 'ellipsis',
178 178 'overflow': 'hidden'
179 179 });
180 180 $('#t-' + cid).css({
181 181 'height': 'auto',
182 182 'line-height': '.9em',
183 183 'text-overflow': 'ellipsis',
184 184 'overflow': 'hidden',
185 185 'white-space': 'nowrap'
186 186 });
187 187 target_expand.removeClass('open');
188 188 }
189 189 else {
190 190 $('#c-' + cid).css({
191 191 'height': 'auto',
192 192 'white-space': 'pre-line',
193 193 'text-overflow': 'initial',
194 194 'overflow': 'visible'
195 195 });
196 196 $('#t-' + cid).css({
197 197 'height': 'auto',
198 198 'max-height': 'none',
199 199 'text-overflow': 'initial',
200 200 'overflow': 'visible',
201 201 'white-space': 'normal'
202 202 });
203 203 target_expand.addClass('open');
204 204 }
205 205
206 206 if (reloadGraph) {
207 207 // redraw the graph
208 208 self.reloadGraph();
209 209 }
210 210 }
211 211 };
General Comments 0
You need to be logged in to leave comments. Login now