##// END OF EJS Templates
git: fix for unicode branches
milka -
r4659:8a9c9ffb default
parent child Browse files
Show More
@@ -1,358 +1,358 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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 import logging
22 import logging
23
23
24 from pyramid.httpexceptions import HTTPNotFound, HTTPFound
24 from pyramid.httpexceptions import HTTPNotFound, HTTPFound
25
25
26 from pyramid.renderers import render
26 from pyramid.renderers import render
27 from pyramid.response import Response
27 from pyramid.response import Response
28
28
29 from rhodecode.apps._base import RepoAppView
29 from rhodecode.apps._base import RepoAppView
30 import rhodecode.lib.helpers as h
30 import rhodecode.lib.helpers as h
31 from rhodecode.lib.auth import (
31 from rhodecode.lib.auth import (
32 LoginRequired, HasRepoPermissionAnyDecorator)
32 LoginRequired, HasRepoPermissionAnyDecorator)
33
33
34 from rhodecode.lib.ext_json import json
34 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.graphmod import _colored, _dagwalker
35 from rhodecode.lib.graphmod import _colored, _dagwalker
36 from rhodecode.lib.helpers import RepoPage
36 from rhodecode.lib.helpers import RepoPage
37 from rhodecode.lib.utils2 import safe_int, safe_str, str2bool
37 from rhodecode.lib.utils2 import safe_int, safe_str, str2bool, safe_unicode
38 from rhodecode.lib.vcs.exceptions import (
38 from rhodecode.lib.vcs.exceptions import (
39 RepositoryError, CommitDoesNotExistError,
39 RepositoryError, CommitDoesNotExistError,
40 CommitError, NodeDoesNotExistError, EmptyRepositoryError)
40 CommitError, NodeDoesNotExistError, EmptyRepositoryError)
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44 DEFAULT_CHANGELOG_SIZE = 20
44 DEFAULT_CHANGELOG_SIZE = 20
45
45
46
46
47 class RepoChangelogView(RepoAppView):
47 class RepoChangelogView(RepoAppView):
48
48
49 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
49 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
50 """
50 """
51 This is a safe way to get commit. If an error occurs it redirects to
51 This is a safe way to get commit. If an error occurs it redirects to
52 tip with proper message
52 tip with proper message
53
53
54 :param commit_id: id of commit to fetch
54 :param commit_id: id of commit to fetch
55 :param redirect_after: toggle redirection
55 :param redirect_after: toggle redirection
56 """
56 """
57 _ = self.request.translate
57 _ = self.request.translate
58
58
59 try:
59 try:
60 return self.rhodecode_vcs_repo.get_commit(commit_id)
60 return self.rhodecode_vcs_repo.get_commit(commit_id)
61 except EmptyRepositoryError:
61 except EmptyRepositoryError:
62 if not redirect_after:
62 if not redirect_after:
63 return None
63 return None
64
64
65 h.flash(h.literal(
65 h.flash(h.literal(
66 _('There are no commits yet')), category='warning')
66 _('There are no commits yet')), category='warning')
67 raise HTTPFound(
67 raise HTTPFound(
68 h.route_path('repo_summary', repo_name=self.db_repo_name))
68 h.route_path('repo_summary', repo_name=self.db_repo_name))
69
69
70 except (CommitDoesNotExistError, LookupError):
70 except (CommitDoesNotExistError, LookupError):
71 msg = _('No such commit exists for this repository')
71 msg = _('No such commit exists for this repository')
72 h.flash(msg, category='error')
72 h.flash(msg, category='error')
73 raise HTTPNotFound()
73 raise HTTPNotFound()
74 except RepositoryError as e:
74 except RepositoryError as e:
75 h.flash(safe_str(h.escape(e)), category='error')
75 h.flash(safe_str(h.escape(e)), category='error')
76 raise HTTPNotFound()
76 raise HTTPNotFound()
77
77
78 def _graph(self, repo, commits, prev_data=None, next_data=None):
78 def _graph(self, repo, commits, prev_data=None, next_data=None):
79 """
79 """
80 Generates a DAG graph for repo
80 Generates a DAG graph for repo
81
81
82 :param repo: repo instance
82 :param repo: repo instance
83 :param commits: list of commits
83 :param commits: list of commits
84 """
84 """
85 if not commits:
85 if not commits:
86 return json.dumps([]), json.dumps([])
86 return json.dumps([]), json.dumps([])
87
87
88 def serialize(commit, parents=True):
88 def serialize(commit, parents=True):
89 data = dict(
89 data = dict(
90 raw_id=commit.raw_id,
90 raw_id=commit.raw_id,
91 idx=commit.idx,
91 idx=commit.idx,
92 branch=None,
92 branch=None,
93 )
93 )
94 if parents:
94 if parents:
95 data['parents'] = [
95 data['parents'] = [
96 serialize(x, parents=False) for x in commit.parents]
96 serialize(x, parents=False) for x in commit.parents]
97 return data
97 return data
98
98
99 prev_data = prev_data or []
99 prev_data = prev_data or []
100 next_data = next_data or []
100 next_data = next_data or []
101
101
102 current = [serialize(x) for x in commits]
102 current = [serialize(x) for x in commits]
103 commits = prev_data + current + next_data
103 commits = prev_data + current + next_data
104
104
105 dag = _dagwalker(repo, commits)
105 dag = _dagwalker(repo, commits)
106
106
107 data = [[commit_id, vtx, edges, branch]
107 data = [[commit_id, vtx, edges, branch]
108 for commit_id, vtx, edges, branch in _colored(dag)]
108 for commit_id, vtx, edges, branch in _colored(dag)]
109 return json.dumps(data), json.dumps(current)
109 return json.dumps(data), json.dumps(current)
110
110
111 def _check_if_valid_branch(self, branch_name, repo_name, f_path):
111 def _check_if_valid_branch(self, branch_name, repo_name, f_path):
112 if branch_name not in self.rhodecode_vcs_repo.branches_all:
112 if branch_name not in self.rhodecode_vcs_repo.branches_all:
113 h.flash('Branch {} is not found.'.format(h.escape(branch_name)),
113 h.flash(u'Branch {} is not found.'.format(h.escape(safe_unicode(branch_name))),
114 category='warning')
114 category='warning')
115 redirect_url = h.route_path(
115 redirect_url = h.route_path(
116 'repo_commits_file', repo_name=repo_name,
116 'repo_commits_file', repo_name=repo_name,
117 commit_id=branch_name, f_path=f_path or '')
117 commit_id=branch_name, f_path=f_path or '')
118 raise HTTPFound(redirect_url)
118 raise HTTPFound(redirect_url)
119
119
120 def _load_changelog_data(
120 def _load_changelog_data(
121 self, c, collection, page, chunk_size, branch_name=None,
121 self, c, collection, page, chunk_size, branch_name=None,
122 dynamic=False, f_path=None, commit_id=None):
122 dynamic=False, f_path=None, commit_id=None):
123
123
124 def url_generator(page_num):
124 def url_generator(page_num):
125 query_params = {
125 query_params = {
126 'page': page_num
126 'page': page_num
127 }
127 }
128
128
129 if branch_name:
129 if branch_name:
130 query_params.update({
130 query_params.update({
131 'branch': branch_name
131 'branch': branch_name
132 })
132 })
133
133
134 if f_path:
134 if f_path:
135 # changelog for file
135 # changelog for file
136 return h.route_path(
136 return h.route_path(
137 'repo_commits_file',
137 'repo_commits_file',
138 repo_name=c.rhodecode_db_repo.repo_name,
138 repo_name=c.rhodecode_db_repo.repo_name,
139 commit_id=commit_id, f_path=f_path,
139 commit_id=commit_id, f_path=f_path,
140 _query=query_params)
140 _query=query_params)
141 else:
141 else:
142 return h.route_path(
142 return h.route_path(
143 'repo_commits',
143 'repo_commits',
144 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
144 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
145
145
146 c.total_cs = len(collection)
146 c.total_cs = len(collection)
147 c.showing_commits = min(chunk_size, c.total_cs)
147 c.showing_commits = min(chunk_size, c.total_cs)
148 c.pagination = RepoPage(collection, page=page, item_count=c.total_cs,
148 c.pagination = RepoPage(collection, page=page, item_count=c.total_cs,
149 items_per_page=chunk_size, url_maker=url_generator)
149 items_per_page=chunk_size, url_maker=url_generator)
150
150
151 c.next_page = c.pagination.next_page
151 c.next_page = c.pagination.next_page
152 c.prev_page = c.pagination.previous_page
152 c.prev_page = c.pagination.previous_page
153
153
154 if dynamic:
154 if dynamic:
155 if self.request.GET.get('chunk') != 'next':
155 if self.request.GET.get('chunk') != 'next':
156 c.next_page = None
156 c.next_page = None
157 if self.request.GET.get('chunk') != 'prev':
157 if self.request.GET.get('chunk') != 'prev':
158 c.prev_page = None
158 c.prev_page = None
159
159
160 page_commit_ids = [x.raw_id for x in c.pagination]
160 page_commit_ids = [x.raw_id for x in c.pagination]
161 c.comments = c.rhodecode_db_repo.get_comments(page_commit_ids)
161 c.comments = c.rhodecode_db_repo.get_comments(page_commit_ids)
162 c.statuses = c.rhodecode_db_repo.statuses(page_commit_ids)
162 c.statuses = c.rhodecode_db_repo.statuses(page_commit_ids)
163
163
164 def load_default_context(self):
164 def load_default_context(self):
165 c = self._get_local_tmpl_context(include_app_defaults=True)
165 c = self._get_local_tmpl_context(include_app_defaults=True)
166
166
167 c.rhodecode_repo = self.rhodecode_vcs_repo
167 c.rhodecode_repo = self.rhodecode_vcs_repo
168
168
169 return c
169 return c
170
170
171 def _get_preload_attrs(self):
171 def _get_preload_attrs(self):
172 pre_load = ['author', 'branch', 'date', 'message', 'parents',
172 pre_load = ['author', 'branch', 'date', 'message', 'parents',
173 'obsolete', 'phase', 'hidden']
173 'obsolete', 'phase', 'hidden']
174 return pre_load
174 return pre_load
175
175
176 @LoginRequired()
176 @LoginRequired()
177 @HasRepoPermissionAnyDecorator(
177 @HasRepoPermissionAnyDecorator(
178 'repository.read', 'repository.write', 'repository.admin')
178 'repository.read', 'repository.write', 'repository.admin')
179 def repo_changelog(self):
179 def repo_changelog(self):
180 c = self.load_default_context()
180 c = self.load_default_context()
181
181
182 commit_id = self.request.matchdict.get('commit_id')
182 commit_id = self.request.matchdict.get('commit_id')
183 f_path = self._get_f_path(self.request.matchdict)
183 f_path = self._get_f_path(self.request.matchdict)
184 show_hidden = str2bool(self.request.GET.get('evolve'))
184 show_hidden = str2bool(self.request.GET.get('evolve'))
185
185
186 chunk_size = 20
186 chunk_size = 20
187
187
188 c.branch_name = branch_name = self.request.GET.get('branch') or ''
188 c.branch_name = branch_name = self.request.GET.get('branch') or ''
189 c.book_name = book_name = self.request.GET.get('bookmark') or ''
189 c.book_name = book_name = self.request.GET.get('bookmark') or ''
190 c.f_path = f_path
190 c.f_path = f_path
191 c.commit_id = commit_id
191 c.commit_id = commit_id
192 c.show_hidden = show_hidden
192 c.show_hidden = show_hidden
193
193
194 hist_limit = safe_int(self.request.GET.get('limit')) or None
194 hist_limit = safe_int(self.request.GET.get('limit')) or None
195
195
196 p = safe_int(self.request.GET.get('page', 1), 1)
196 p = safe_int(self.request.GET.get('page', 1), 1)
197
197
198 c.selected_name = branch_name or book_name
198 c.selected_name = branch_name or book_name
199 if not commit_id and branch_name:
199 if not commit_id and branch_name:
200 self._check_if_valid_branch(branch_name, self.db_repo_name, f_path)
200 self._check_if_valid_branch(branch_name, self.db_repo_name, f_path)
201
201
202 c.changelog_for_path = f_path
202 c.changelog_for_path = f_path
203 pre_load = self._get_preload_attrs()
203 pre_load = self._get_preload_attrs()
204
204
205 partial_xhr = self.request.environ.get('HTTP_X_PARTIAL_XHR')
205 partial_xhr = self.request.environ.get('HTTP_X_PARTIAL_XHR')
206
206
207 try:
207 try:
208 if f_path:
208 if f_path:
209 log.debug('generating changelog for path %s', f_path)
209 log.debug('generating changelog for path %s', f_path)
210 # get the history for the file !
210 # get the history for the file !
211 base_commit = self.rhodecode_vcs_repo.get_commit(commit_id)
211 base_commit = self.rhodecode_vcs_repo.get_commit(commit_id)
212
212
213 try:
213 try:
214 collection = base_commit.get_path_history(
214 collection = base_commit.get_path_history(
215 f_path, limit=hist_limit, pre_load=pre_load)
215 f_path, limit=hist_limit, pre_load=pre_load)
216 if collection and partial_xhr:
216 if collection and partial_xhr:
217 # for ajax call we remove first one since we're looking
217 # for ajax call we remove first one since we're looking
218 # at it right now in the context of a file commit
218 # at it right now in the context of a file commit
219 collection.pop(0)
219 collection.pop(0)
220 except (NodeDoesNotExistError, CommitError):
220 except (NodeDoesNotExistError, CommitError):
221 # this node is not present at tip!
221 # this node is not present at tip!
222 try:
222 try:
223 commit = self._get_commit_or_redirect(commit_id)
223 commit = self._get_commit_or_redirect(commit_id)
224 collection = commit.get_path_history(f_path)
224 collection = commit.get_path_history(f_path)
225 except RepositoryError as e:
225 except RepositoryError as e:
226 h.flash(safe_str(e), category='warning')
226 h.flash(safe_str(e), category='warning')
227 redirect_url = h.route_path(
227 redirect_url = h.route_path(
228 'repo_commits', repo_name=self.db_repo_name)
228 'repo_commits', repo_name=self.db_repo_name)
229 raise HTTPFound(redirect_url)
229 raise HTTPFound(redirect_url)
230 collection = list(reversed(collection))
230 collection = list(reversed(collection))
231 else:
231 else:
232 collection = self.rhodecode_vcs_repo.get_commits(
232 collection = self.rhodecode_vcs_repo.get_commits(
233 branch_name=branch_name, show_hidden=show_hidden,
233 branch_name=branch_name, show_hidden=show_hidden,
234 pre_load=pre_load, translate_tags=False)
234 pre_load=pre_load, translate_tags=False)
235
235
236 self._load_changelog_data(
236 self._load_changelog_data(
237 c, collection, p, chunk_size, c.branch_name,
237 c, collection, p, chunk_size, c.branch_name,
238 f_path=f_path, commit_id=commit_id)
238 f_path=f_path, commit_id=commit_id)
239
239
240 except EmptyRepositoryError as e:
240 except EmptyRepositoryError as e:
241 h.flash(safe_str(h.escape(e)), category='warning')
241 h.flash(safe_str(h.escape(e)), category='warning')
242 raise HTTPFound(
242 raise HTTPFound(
243 h.route_path('repo_summary', repo_name=self.db_repo_name))
243 h.route_path('repo_summary', repo_name=self.db_repo_name))
244 except HTTPFound:
244 except HTTPFound:
245 raise
245 raise
246 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
246 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
247 log.exception(safe_str(e))
247 log.exception(safe_str(e))
248 h.flash(safe_str(h.escape(e)), category='error')
248 h.flash(safe_str(h.escape(e)), category='error')
249
249
250 if commit_id:
250 if commit_id:
251 # from single commit page, we redirect to main commits
251 # from single commit page, we redirect to main commits
252 raise HTTPFound(
252 raise HTTPFound(
253 h.route_path('repo_commits', repo_name=self.db_repo_name))
253 h.route_path('repo_commits', repo_name=self.db_repo_name))
254 else:
254 else:
255 # otherwise we redirect to summary
255 # otherwise we redirect to summary
256 raise HTTPFound(
256 raise HTTPFound(
257 h.route_path('repo_summary', repo_name=self.db_repo_name))
257 h.route_path('repo_summary', repo_name=self.db_repo_name))
258
258
259 if partial_xhr or self.request.environ.get('HTTP_X_PJAX'):
259 if partial_xhr or self.request.environ.get('HTTP_X_PJAX'):
260 # case when loading dynamic file history in file view
260 # case when loading dynamic file history in file view
261 # loading from ajax, we don't want the first result, it's popped
261 # loading from ajax, we don't want the first result, it's popped
262 # in the code above
262 # in the code above
263 html = render(
263 html = render(
264 'rhodecode:templates/commits/changelog_file_history.mako',
264 'rhodecode:templates/commits/changelog_file_history.mako',
265 self._get_template_context(c), self.request)
265 self._get_template_context(c), self.request)
266 return Response(html)
266 return Response(html)
267
267
268 commit_ids = []
268 commit_ids = []
269 if not f_path:
269 if not f_path:
270 # only load graph data when not in file history mode
270 # only load graph data when not in file history mode
271 commit_ids = c.pagination
271 commit_ids = c.pagination
272
272
273 c.graph_data, c.graph_commits = self._graph(
273 c.graph_data, c.graph_commits = self._graph(
274 self.rhodecode_vcs_repo, commit_ids)
274 self.rhodecode_vcs_repo, commit_ids)
275
275
276 return self._get_template_context(c)
276 return self._get_template_context(c)
277
277
278 @LoginRequired()
278 @LoginRequired()
279 @HasRepoPermissionAnyDecorator(
279 @HasRepoPermissionAnyDecorator(
280 'repository.read', 'repository.write', 'repository.admin')
280 'repository.read', 'repository.write', 'repository.admin')
281 def repo_commits_elements(self):
281 def repo_commits_elements(self):
282 c = self.load_default_context()
282 c = self.load_default_context()
283 commit_id = self.request.matchdict.get('commit_id')
283 commit_id = self.request.matchdict.get('commit_id')
284 f_path = self._get_f_path(self.request.matchdict)
284 f_path = self._get_f_path(self.request.matchdict)
285 show_hidden = str2bool(self.request.GET.get('evolve'))
285 show_hidden = str2bool(self.request.GET.get('evolve'))
286
286
287 chunk_size = 20
287 chunk_size = 20
288 hist_limit = safe_int(self.request.GET.get('limit')) or None
288 hist_limit = safe_int(self.request.GET.get('limit')) or None
289
289
290 def wrap_for_error(err):
290 def wrap_for_error(err):
291 html = '<tr>' \
291 html = '<tr>' \
292 '<td colspan="9" class="alert alert-error">ERROR: {}</td>' \
292 '<td colspan="9" class="alert alert-error">ERROR: {}</td>' \
293 '</tr>'.format(err)
293 '</tr>'.format(err)
294 return Response(html)
294 return Response(html)
295
295
296 c.branch_name = branch_name = self.request.GET.get('branch') or ''
296 c.branch_name = branch_name = self.request.GET.get('branch') or ''
297 c.book_name = book_name = self.request.GET.get('bookmark') or ''
297 c.book_name = book_name = self.request.GET.get('bookmark') or ''
298 c.f_path = f_path
298 c.f_path = f_path
299 c.commit_id = commit_id
299 c.commit_id = commit_id
300 c.show_hidden = show_hidden
300 c.show_hidden = show_hidden
301
301
302 c.selected_name = branch_name or book_name
302 c.selected_name = branch_name or book_name
303 if branch_name and branch_name not in self.rhodecode_vcs_repo.branches_all:
303 if branch_name and branch_name not in self.rhodecode_vcs_repo.branches_all:
304 return wrap_for_error(
304 return wrap_for_error(
305 safe_str('Branch: {} is not valid'.format(branch_name)))
305 safe_str('Branch: {} is not valid'.format(branch_name)))
306
306
307 pre_load = self._get_preload_attrs()
307 pre_load = self._get_preload_attrs()
308
308
309 if f_path:
309 if f_path:
310 try:
310 try:
311 base_commit = self.rhodecode_vcs_repo.get_commit(commit_id)
311 base_commit = self.rhodecode_vcs_repo.get_commit(commit_id)
312 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
312 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
313 log.exception(safe_str(e))
313 log.exception(safe_str(e))
314 raise HTTPFound(
314 raise HTTPFound(
315 h.route_path('repo_commits', repo_name=self.db_repo_name))
315 h.route_path('repo_commits', repo_name=self.db_repo_name))
316
316
317 collection = base_commit.get_path_history(
317 collection = base_commit.get_path_history(
318 f_path, limit=hist_limit, pre_load=pre_load)
318 f_path, limit=hist_limit, pre_load=pre_load)
319 collection = list(reversed(collection))
319 collection = list(reversed(collection))
320 else:
320 else:
321 collection = self.rhodecode_vcs_repo.get_commits(
321 collection = self.rhodecode_vcs_repo.get_commits(
322 branch_name=branch_name, show_hidden=show_hidden, pre_load=pre_load,
322 branch_name=branch_name, show_hidden=show_hidden, pre_load=pre_load,
323 translate_tags=False)
323 translate_tags=False)
324
324
325 p = safe_int(self.request.GET.get('page', 1), 1)
325 p = safe_int(self.request.GET.get('page', 1), 1)
326 try:
326 try:
327 self._load_changelog_data(
327 self._load_changelog_data(
328 c, collection, p, chunk_size, dynamic=True,
328 c, collection, p, chunk_size, dynamic=True,
329 f_path=f_path, commit_id=commit_id)
329 f_path=f_path, commit_id=commit_id)
330 except EmptyRepositoryError as e:
330 except EmptyRepositoryError as e:
331 return wrap_for_error(safe_str(e))
331 return wrap_for_error(safe_str(e))
332 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
332 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
333 log.exception('Failed to fetch commits')
333 log.exception('Failed to fetch commits')
334 return wrap_for_error(safe_str(e))
334 return wrap_for_error(safe_str(e))
335
335
336 prev_data = None
336 prev_data = None
337 next_data = None
337 next_data = None
338
338
339 try:
339 try:
340 prev_graph = json.loads(self.request.POST.get('graph') or '{}')
340 prev_graph = json.loads(self.request.POST.get('graph') or '{}')
341 except json.JSONDecodeError:
341 except json.JSONDecodeError:
342 prev_graph = {}
342 prev_graph = {}
343
343
344 if self.request.GET.get('chunk') == 'prev':
344 if self.request.GET.get('chunk') == 'prev':
345 next_data = prev_graph
345 next_data = prev_graph
346 elif self.request.GET.get('chunk') == 'next':
346 elif self.request.GET.get('chunk') == 'next':
347 prev_data = prev_graph
347 prev_data = prev_graph
348
348
349 commit_ids = []
349 commit_ids = []
350 if not f_path:
350 if not f_path:
351 # only load graph data when not in file history mode
351 # only load graph data when not in file history mode
352 commit_ids = c.pagination
352 commit_ids = c.pagination
353
353
354 c.graph_data, c.graph_commits = self._graph(
354 c.graph_data, c.graph_commits = self._graph(
355 self.rhodecode_vcs_repo, commit_ids,
355 self.rhodecode_vcs_repo, commit_ids,
356 prev_data=prev_data, next_data=next_data)
356 prev_data=prev_data, next_data=next_data)
357
357
358 return self._get_template_context(c)
358 return self._get_template_context(c)
@@ -1,494 +1,496 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2014-2020 RhodeCode GmbH
3 # Copyright (C) 2014-2020 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 GIT commit module
22 GIT commit module
23 """
23 """
24
24
25 import re
25 import re
26 import stat
26 import stat
27 from itertools import chain
27 from itertools import chain
28 from StringIO import StringIO
28 from StringIO import StringIO
29
29
30 from zope.cachedescriptors.property import Lazy as LazyProperty
30 from zope.cachedescriptors.property import Lazy as LazyProperty
31
31
32 from rhodecode.lib.datelib import utcdate_fromtimestamp
32 from rhodecode.lib.datelib import utcdate_fromtimestamp
33 from rhodecode.lib.utils import safe_unicode, safe_str
33 from rhodecode.lib.utils import safe_unicode, safe_str
34 from rhodecode.lib.utils2 import safe_int
34 from rhodecode.lib.utils2 import safe_int
35 from rhodecode.lib.vcs.conf import settings
35 from rhodecode.lib.vcs.conf import settings
36 from rhodecode.lib.vcs.backends import base
36 from rhodecode.lib.vcs.backends import base
37 from rhodecode.lib.vcs.exceptions import CommitError, NodeDoesNotExistError
37 from rhodecode.lib.vcs.exceptions import CommitError, NodeDoesNotExistError
38 from rhodecode.lib.vcs.nodes import (
38 from rhodecode.lib.vcs.nodes import (
39 FileNode, DirNode, NodeKind, RootNode, SubModuleNode,
39 FileNode, DirNode, NodeKind, RootNode, SubModuleNode,
40 ChangedFileNodesGenerator, AddedFileNodesGenerator,
40 ChangedFileNodesGenerator, AddedFileNodesGenerator,
41 RemovedFileNodesGenerator, LargeFileNode)
41 RemovedFileNodesGenerator, LargeFileNode)
42 from rhodecode.lib.vcs.compat import configparser
42 from rhodecode.lib.vcs.compat import configparser
43
43
44
44
45 class GitCommit(base.BaseCommit):
45 class GitCommit(base.BaseCommit):
46 """
46 """
47 Represents state of the repository at single commit id.
47 Represents state of the repository at single commit id.
48 """
48 """
49
49
50 _filter_pre_load = [
50 _filter_pre_load = [
51 # done through a more complex tree walk on parents
51 # done through a more complex tree walk on parents
52 "affected_files",
52 "affected_files",
53 # done through subprocess not remote call
53 # done through subprocess not remote call
54 "children",
54 "children",
55 # done through a more complex tree walk on parents
55 # done through a more complex tree walk on parents
56 "status",
56 "status",
57 # mercurial specific property not supported here
57 # mercurial specific property not supported here
58 "_file_paths",
58 "_file_paths",
59 # mercurial specific property not supported here
59 # mercurial specific property not supported here
60 'obsolete',
60 'obsolete',
61 # mercurial specific property not supported here
61 # mercurial specific property not supported here
62 'phase',
62 'phase',
63 # mercurial specific property not supported here
63 # mercurial specific property not supported here
64 'hidden'
64 'hidden'
65 ]
65 ]
66
66
67 def __init__(self, repository, raw_id, idx, pre_load=None):
67 def __init__(self, repository, raw_id, idx, pre_load=None):
68 self.repository = repository
68 self.repository = repository
69 self._remote = repository._remote
69 self._remote = repository._remote
70 # TODO: johbo: Tweak of raw_id should not be necessary
70 # TODO: johbo: Tweak of raw_id should not be necessary
71 self.raw_id = safe_str(raw_id)
71 self.raw_id = safe_str(raw_id)
72 self.idx = idx
72 self.idx = idx
73
73
74 self._set_bulk_properties(pre_load)
74 self._set_bulk_properties(pre_load)
75
75
76 # caches
76 # caches
77 self._stat_modes = {} # stat info for paths
77 self._stat_modes = {} # stat info for paths
78 self._paths = {} # path processed with parse_tree
78 self._paths = {} # path processed with parse_tree
79 self.nodes = {}
79 self.nodes = {}
80 self._submodules = None
80 self._submodules = None
81
81
82 def _set_bulk_properties(self, pre_load):
82 def _set_bulk_properties(self, pre_load):
83
83
84 if not pre_load:
84 if not pre_load:
85 return
85 return
86 pre_load = [entry for entry in pre_load
86 pre_load = [entry for entry in pre_load
87 if entry not in self._filter_pre_load]
87 if entry not in self._filter_pre_load]
88 if not pre_load:
88 if not pre_load:
89 return
89 return
90
90
91 result = self._remote.bulk_request(self.raw_id, pre_load)
91 result = self._remote.bulk_request(self.raw_id, pre_load)
92 for attr, value in result.items():
92 for attr, value in result.items():
93 if attr in ["author", "message"]:
93 if attr in ["author", "message"]:
94 if value:
94 if value:
95 value = safe_unicode(value)
95 value = safe_unicode(value)
96 elif attr == "date":
96 elif attr == "date":
97 value = utcdate_fromtimestamp(*value)
97 value = utcdate_fromtimestamp(*value)
98 elif attr == "parents":
98 elif attr == "parents":
99 value = self._make_commits(value)
99 value = self._make_commits(value)
100 elif attr == "branch":
100 elif attr == "branch":
101 value = value[0] if value else None
101 value = self._set_branch(value)
102 self.__dict__[attr] = value
102 self.__dict__[attr] = value
103
103
104 @LazyProperty
104 @LazyProperty
105 def _commit(self):
105 def _commit(self):
106 return self._remote[self.raw_id]
106 return self._remote[self.raw_id]
107
107
108 @LazyProperty
108 @LazyProperty
109 def _tree_id(self):
109 def _tree_id(self):
110 return self._remote[self._commit['tree']]['id']
110 return self._remote[self._commit['tree']]['id']
111
111
112 @LazyProperty
112 @LazyProperty
113 def id(self):
113 def id(self):
114 return self.raw_id
114 return self.raw_id
115
115
116 @LazyProperty
116 @LazyProperty
117 def short_id(self):
117 def short_id(self):
118 return self.raw_id[:12]
118 return self.raw_id[:12]
119
119
120 @LazyProperty
120 @LazyProperty
121 def message(self):
121 def message(self):
122 return safe_unicode(self._remote.message(self.id))
122 return safe_unicode(self._remote.message(self.id))
123
123
124 @LazyProperty
124 @LazyProperty
125 def committer(self):
125 def committer(self):
126 return safe_unicode(self._remote.author(self.id))
126 return safe_unicode(self._remote.author(self.id))
127
127
128 @LazyProperty
128 @LazyProperty
129 def author(self):
129 def author(self):
130 return safe_unicode(self._remote.author(self.id))
130 return safe_unicode(self._remote.author(self.id))
131
131
132 @LazyProperty
132 @LazyProperty
133 def date(self):
133 def date(self):
134 unix_ts, tz = self._remote.date(self.raw_id)
134 unix_ts, tz = self._remote.date(self.raw_id)
135 return utcdate_fromtimestamp(unix_ts, tz)
135 return utcdate_fromtimestamp(unix_ts, tz)
136
136
137 @LazyProperty
137 @LazyProperty
138 def status(self):
138 def status(self):
139 """
139 """
140 Returns modified, added, removed, deleted files for current commit
140 Returns modified, added, removed, deleted files for current commit
141 """
141 """
142 return self.changed, self.added, self.removed
142 return self.changed, self.added, self.removed
143
143
144 @LazyProperty
144 @LazyProperty
145 def tags(self):
145 def tags(self):
146 tags = [safe_unicode(name) for name,
146 tags = [safe_unicode(name) for name,
147 commit_id in self.repository.tags.iteritems()
147 commit_id in self.repository.tags.iteritems()
148 if commit_id == self.raw_id]
148 if commit_id == self.raw_id]
149 return tags
149 return tags
150
150
151 @LazyProperty
151 @LazyProperty
152 def commit_branches(self):
152 def commit_branches(self):
153 branches = []
153 branches = []
154 for name, commit_id in self.repository.branches.iteritems():
154 for name, commit_id in self.repository.branches.iteritems():
155 if commit_id == self.raw_id:
155 if commit_id == self.raw_id:
156 branches.append(name)
156 branches.append(name)
157 return branches
157 return branches
158
158
159 def _set_branch(self, branches):
160 if branches:
161 # actually commit can have multiple branches in git
162 return safe_unicode(branches[0])
163
159 @LazyProperty
164 @LazyProperty
160 def branch(self):
165 def branch(self):
161 branches = self._remote.branch(self.raw_id)
166 branches = self._remote.branch(self.raw_id)
162
167 return self._set_branch(branches)
163 if branches:
164 # actually commit can have multiple branches in git
165 return safe_unicode(branches[0])
166
168
167 def _get_tree_id_for_path(self, path):
169 def _get_tree_id_for_path(self, path):
168 path = safe_str(path)
170 path = safe_str(path)
169 if path in self._paths:
171 if path in self._paths:
170 return self._paths[path]
172 return self._paths[path]
171
173
172 tree_id = self._tree_id
174 tree_id = self._tree_id
173
175
174 path = path.strip('/')
176 path = path.strip('/')
175 if path == '':
177 if path == '':
176 data = [tree_id, "tree"]
178 data = [tree_id, "tree"]
177 self._paths[''] = data
179 self._paths[''] = data
178 return data
180 return data
179
181
180 tree_id, tree_type, tree_mode = \
182 tree_id, tree_type, tree_mode = \
181 self._remote.tree_and_type_for_path(self.raw_id, path)
183 self._remote.tree_and_type_for_path(self.raw_id, path)
182 if tree_id is None:
184 if tree_id is None:
183 raise self.no_node_at_path(path)
185 raise self.no_node_at_path(path)
184
186
185 self._paths[path] = [tree_id, tree_type]
187 self._paths[path] = [tree_id, tree_type]
186 self._stat_modes[path] = tree_mode
188 self._stat_modes[path] = tree_mode
187
189
188 if path not in self._paths:
190 if path not in self._paths:
189 raise self.no_node_at_path(path)
191 raise self.no_node_at_path(path)
190
192
191 return self._paths[path]
193 return self._paths[path]
192
194
193 def _get_kind(self, path):
195 def _get_kind(self, path):
194 tree_id, type_ = self._get_tree_id_for_path(path)
196 tree_id, type_ = self._get_tree_id_for_path(path)
195 if type_ == 'blob':
197 if type_ == 'blob':
196 return NodeKind.FILE
198 return NodeKind.FILE
197 elif type_ == 'tree':
199 elif type_ == 'tree':
198 return NodeKind.DIR
200 return NodeKind.DIR
199 elif type_ == 'link':
201 elif type_ == 'link':
200 return NodeKind.SUBMODULE
202 return NodeKind.SUBMODULE
201 return None
203 return None
202
204
203 def _get_filectx(self, path):
205 def _get_filectx(self, path):
204 path = self._fix_path(path)
206 path = self._fix_path(path)
205 if self._get_kind(path) != NodeKind.FILE:
207 if self._get_kind(path) != NodeKind.FILE:
206 raise CommitError(
208 raise CommitError(
207 "File does not exist for commit %s at '%s'" % (self.raw_id, path))
209 "File does not exist for commit %s at '%s'" % (self.raw_id, path))
208 return path
210 return path
209
211
210 def _get_file_nodes(self):
212 def _get_file_nodes(self):
211 return chain(*(t[2] for t in self.walk()))
213 return chain(*(t[2] for t in self.walk()))
212
214
213 @LazyProperty
215 @LazyProperty
214 def parents(self):
216 def parents(self):
215 """
217 """
216 Returns list of parent commits.
218 Returns list of parent commits.
217 """
219 """
218 parent_ids = self._remote.parents(self.id)
220 parent_ids = self._remote.parents(self.id)
219 return self._make_commits(parent_ids)
221 return self._make_commits(parent_ids)
220
222
221 @LazyProperty
223 @LazyProperty
222 def children(self):
224 def children(self):
223 """
225 """
224 Returns list of child commits.
226 Returns list of child commits.
225 """
227 """
226
228
227 children = self._remote.children(self.raw_id)
229 children = self._remote.children(self.raw_id)
228 return self._make_commits(children)
230 return self._make_commits(children)
229
231
230 def _make_commits(self, commit_ids):
232 def _make_commits(self, commit_ids):
231 def commit_maker(_commit_id):
233 def commit_maker(_commit_id):
232 return self.repository.get_commit(commit_id=commit_id)
234 return self.repository.get_commit(commit_id=commit_id)
233
235
234 return [commit_maker(commit_id) for commit_id in commit_ids]
236 return [commit_maker(commit_id) for commit_id in commit_ids]
235
237
236 def get_file_mode(self, path):
238 def get_file_mode(self, path):
237 """
239 """
238 Returns stat mode of the file at the given `path`.
240 Returns stat mode of the file at the given `path`.
239 """
241 """
240 path = safe_str(path)
242 path = safe_str(path)
241 # ensure path is traversed
243 # ensure path is traversed
242 self._get_tree_id_for_path(path)
244 self._get_tree_id_for_path(path)
243 return self._stat_modes[path]
245 return self._stat_modes[path]
244
246
245 def is_link(self, path):
247 def is_link(self, path):
246 return stat.S_ISLNK(self.get_file_mode(path))
248 return stat.S_ISLNK(self.get_file_mode(path))
247
249
248 def is_node_binary(self, path):
250 def is_node_binary(self, path):
249 tree_id, _ = self._get_tree_id_for_path(path)
251 tree_id, _ = self._get_tree_id_for_path(path)
250 return self._remote.is_binary(tree_id)
252 return self._remote.is_binary(tree_id)
251
253
252 def get_file_content(self, path):
254 def get_file_content(self, path):
253 """
255 """
254 Returns content of the file at given `path`.
256 Returns content of the file at given `path`.
255 """
257 """
256 tree_id, _ = self._get_tree_id_for_path(path)
258 tree_id, _ = self._get_tree_id_for_path(path)
257 return self._remote.blob_as_pretty_string(tree_id)
259 return self._remote.blob_as_pretty_string(tree_id)
258
260
259 def get_file_content_streamed(self, path):
261 def get_file_content_streamed(self, path):
260 tree_id, _ = self._get_tree_id_for_path(path)
262 tree_id, _ = self._get_tree_id_for_path(path)
261 stream_method = getattr(self._remote, 'stream:blob_as_pretty_string')
263 stream_method = getattr(self._remote, 'stream:blob_as_pretty_string')
262 return stream_method(tree_id)
264 return stream_method(tree_id)
263
265
264 def get_file_size(self, path):
266 def get_file_size(self, path):
265 """
267 """
266 Returns size of the file at given `path`.
268 Returns size of the file at given `path`.
267 """
269 """
268 tree_id, _ = self._get_tree_id_for_path(path)
270 tree_id, _ = self._get_tree_id_for_path(path)
269 return self._remote.blob_raw_length(tree_id)
271 return self._remote.blob_raw_length(tree_id)
270
272
271 def get_path_history(self, path, limit=None, pre_load=None):
273 def get_path_history(self, path, limit=None, pre_load=None):
272 """
274 """
273 Returns history of file as reversed list of `GitCommit` objects for
275 Returns history of file as reversed list of `GitCommit` objects for
274 which file at given `path` has been modified.
276 which file at given `path` has been modified.
275 """
277 """
276
278
277 path = self._get_filectx(path)
279 path = self._get_filectx(path)
278 hist = self._remote.node_history(self.raw_id, path, limit)
280 hist = self._remote.node_history(self.raw_id, path, limit)
279 return [
281 return [
280 self.repository.get_commit(commit_id=commit_id, pre_load=pre_load)
282 self.repository.get_commit(commit_id=commit_id, pre_load=pre_load)
281 for commit_id in hist]
283 for commit_id in hist]
282
284
283 def get_file_annotate(self, path, pre_load=None):
285 def get_file_annotate(self, path, pre_load=None):
284 """
286 """
285 Returns a generator of four element tuples with
287 Returns a generator of four element tuples with
286 lineno, commit_id, commit lazy loader and line
288 lineno, commit_id, commit lazy loader and line
287 """
289 """
288
290
289 result = self._remote.node_annotate(self.raw_id, path)
291 result = self._remote.node_annotate(self.raw_id, path)
290
292
291 for ln_no, commit_id, content in result:
293 for ln_no, commit_id, content in result:
292 yield (
294 yield (
293 ln_no, commit_id,
295 ln_no, commit_id,
294 lambda: self.repository.get_commit(commit_id=commit_id, pre_load=pre_load),
296 lambda: self.repository.get_commit(commit_id=commit_id, pre_load=pre_load),
295 content)
297 content)
296
298
297 def get_nodes(self, path):
299 def get_nodes(self, path):
298
300
299 if self._get_kind(path) != NodeKind.DIR:
301 if self._get_kind(path) != NodeKind.DIR:
300 raise CommitError(
302 raise CommitError(
301 "Directory does not exist for commit %s at '%s'" % (self.raw_id, path))
303 "Directory does not exist for commit %s at '%s'" % (self.raw_id, path))
302 path = self._fix_path(path)
304 path = self._fix_path(path)
303
305
304 tree_id, _ = self._get_tree_id_for_path(path)
306 tree_id, _ = self._get_tree_id_for_path(path)
305
307
306 dirnodes = []
308 dirnodes = []
307 filenodes = []
309 filenodes = []
308
310
309 # extracted tree ID gives us our files...
311 # extracted tree ID gives us our files...
310 bytes_path = safe_str(path) # libgit operates on bytes
312 bytes_path = safe_str(path) # libgit operates on bytes
311 for name, stat_, id_, type_ in self._remote.tree_items(tree_id):
313 for name, stat_, id_, type_ in self._remote.tree_items(tree_id):
312 if type_ == 'link':
314 if type_ == 'link':
313 url = self._get_submodule_url('/'.join((bytes_path, name)))
315 url = self._get_submodule_url('/'.join((bytes_path, name)))
314 dirnodes.append(SubModuleNode(
316 dirnodes.append(SubModuleNode(
315 name, url=url, commit=id_, alias=self.repository.alias))
317 name, url=url, commit=id_, alias=self.repository.alias))
316 continue
318 continue
317
319
318 if bytes_path != '':
320 if bytes_path != '':
319 obj_path = '/'.join((bytes_path, name))
321 obj_path = '/'.join((bytes_path, name))
320 else:
322 else:
321 obj_path = name
323 obj_path = name
322 if obj_path not in self._stat_modes:
324 if obj_path not in self._stat_modes:
323 self._stat_modes[obj_path] = stat_
325 self._stat_modes[obj_path] = stat_
324
326
325 if type_ == 'tree':
327 if type_ == 'tree':
326 dirnodes.append(DirNode(obj_path, commit=self))
328 dirnodes.append(DirNode(obj_path, commit=self))
327 elif type_ == 'blob':
329 elif type_ == 'blob':
328 filenodes.append(FileNode(obj_path, commit=self, mode=stat_))
330 filenodes.append(FileNode(obj_path, commit=self, mode=stat_))
329 else:
331 else:
330 raise CommitError(
332 raise CommitError(
331 "Requested object should be Tree or Blob, is %s", type_)
333 "Requested object should be Tree or Blob, is %s", type_)
332
334
333 nodes = dirnodes + filenodes
335 nodes = dirnodes + filenodes
334 for node in nodes:
336 for node in nodes:
335 if node.path not in self.nodes:
337 if node.path not in self.nodes:
336 self.nodes[node.path] = node
338 self.nodes[node.path] = node
337 nodes.sort()
339 nodes.sort()
338 return nodes
340 return nodes
339
341
340 def get_node(self, path, pre_load=None):
342 def get_node(self, path, pre_load=None):
341 if isinstance(path, unicode):
343 if isinstance(path, unicode):
342 path = path.encode('utf-8')
344 path = path.encode('utf-8')
343 path = self._fix_path(path)
345 path = self._fix_path(path)
344 if path not in self.nodes:
346 if path not in self.nodes:
345 try:
347 try:
346 tree_id, type_ = self._get_tree_id_for_path(path)
348 tree_id, type_ = self._get_tree_id_for_path(path)
347 except CommitError:
349 except CommitError:
348 raise NodeDoesNotExistError(
350 raise NodeDoesNotExistError(
349 "Cannot find one of parents' directories for a given "
351 "Cannot find one of parents' directories for a given "
350 "path: %s" % path)
352 "path: %s" % path)
351
353
352 if type_ in ['link', 'commit']:
354 if type_ in ['link', 'commit']:
353 url = self._get_submodule_url(path)
355 url = self._get_submodule_url(path)
354 node = SubModuleNode(path, url=url, commit=tree_id,
356 node = SubModuleNode(path, url=url, commit=tree_id,
355 alias=self.repository.alias)
357 alias=self.repository.alias)
356 elif type_ == 'tree':
358 elif type_ == 'tree':
357 if path == '':
359 if path == '':
358 node = RootNode(commit=self)
360 node = RootNode(commit=self)
359 else:
361 else:
360 node = DirNode(path, commit=self)
362 node = DirNode(path, commit=self)
361 elif type_ == 'blob':
363 elif type_ == 'blob':
362 node = FileNode(path, commit=self, pre_load=pre_load)
364 node = FileNode(path, commit=self, pre_load=pre_load)
363 self._stat_modes[path] = node.mode
365 self._stat_modes[path] = node.mode
364 else:
366 else:
365 raise self.no_node_at_path(path)
367 raise self.no_node_at_path(path)
366
368
367 # cache node
369 # cache node
368 self.nodes[path] = node
370 self.nodes[path] = node
369
371
370 return self.nodes[path]
372 return self.nodes[path]
371
373
372 def get_largefile_node(self, path):
374 def get_largefile_node(self, path):
373 tree_id, _ = self._get_tree_id_for_path(path)
375 tree_id, _ = self._get_tree_id_for_path(path)
374 pointer_spec = self._remote.is_large_file(tree_id)
376 pointer_spec = self._remote.is_large_file(tree_id)
375
377
376 if pointer_spec:
378 if pointer_spec:
377 # content of that file regular FileNode is the hash of largefile
379 # content of that file regular FileNode is the hash of largefile
378 file_id = pointer_spec.get('oid_hash')
380 file_id = pointer_spec.get('oid_hash')
379 if self._remote.in_largefiles_store(file_id):
381 if self._remote.in_largefiles_store(file_id):
380 lf_path = self._remote.store_path(file_id)
382 lf_path = self._remote.store_path(file_id)
381 return LargeFileNode(lf_path, commit=self, org_path=path)
383 return LargeFileNode(lf_path, commit=self, org_path=path)
382
384
383 @LazyProperty
385 @LazyProperty
384 def affected_files(self):
386 def affected_files(self):
385 """
387 """
386 Gets a fast accessible file changes for given commit
388 Gets a fast accessible file changes for given commit
387 """
389 """
388 added, modified, deleted = self._changes_cache
390 added, modified, deleted = self._changes_cache
389 return list(added.union(modified).union(deleted))
391 return list(added.union(modified).union(deleted))
390
392
391 @LazyProperty
393 @LazyProperty
392 def _changes_cache(self):
394 def _changes_cache(self):
393 added = set()
395 added = set()
394 modified = set()
396 modified = set()
395 deleted = set()
397 deleted = set()
396 _r = self._remote
398 _r = self._remote
397
399
398 parents = self.parents
400 parents = self.parents
399 if not self.parents:
401 if not self.parents:
400 parents = [base.EmptyCommit()]
402 parents = [base.EmptyCommit()]
401 for parent in parents:
403 for parent in parents:
402 if isinstance(parent, base.EmptyCommit):
404 if isinstance(parent, base.EmptyCommit):
403 oid = None
405 oid = None
404 else:
406 else:
405 oid = parent.raw_id
407 oid = parent.raw_id
406 changes = _r.tree_changes(oid, self.raw_id)
408 changes = _r.tree_changes(oid, self.raw_id)
407 for (oldpath, newpath), (_, _), (_, _) in changes:
409 for (oldpath, newpath), (_, _), (_, _) in changes:
408 if newpath and oldpath:
410 if newpath and oldpath:
409 modified.add(newpath)
411 modified.add(newpath)
410 elif newpath and not oldpath:
412 elif newpath and not oldpath:
411 added.add(newpath)
413 added.add(newpath)
412 elif not newpath and oldpath:
414 elif not newpath and oldpath:
413 deleted.add(oldpath)
415 deleted.add(oldpath)
414 return added, modified, deleted
416 return added, modified, deleted
415
417
416 def _get_paths_for_status(self, status):
418 def _get_paths_for_status(self, status):
417 """
419 """
418 Returns sorted list of paths for given ``status``.
420 Returns sorted list of paths for given ``status``.
419
421
420 :param status: one of: *added*, *modified* or *deleted*
422 :param status: one of: *added*, *modified* or *deleted*
421 """
423 """
422 added, modified, deleted = self._changes_cache
424 added, modified, deleted = self._changes_cache
423 return sorted({
425 return sorted({
424 'added': list(added),
426 'added': list(added),
425 'modified': list(modified),
427 'modified': list(modified),
426 'deleted': list(deleted)}[status]
428 'deleted': list(deleted)}[status]
427 )
429 )
428
430
429 @LazyProperty
431 @LazyProperty
430 def added(self):
432 def added(self):
431 """
433 """
432 Returns list of added ``FileNode`` objects.
434 Returns list of added ``FileNode`` objects.
433 """
435 """
434 if not self.parents:
436 if not self.parents:
435 return list(self._get_file_nodes())
437 return list(self._get_file_nodes())
436 return AddedFileNodesGenerator(self.added_paths, self)
438 return AddedFileNodesGenerator(self.added_paths, self)
437
439
438 @LazyProperty
440 @LazyProperty
439 def added_paths(self):
441 def added_paths(self):
440 return [n for n in self._get_paths_for_status('added')]
442 return [n for n in self._get_paths_for_status('added')]
441
443
442 @LazyProperty
444 @LazyProperty
443 def changed(self):
445 def changed(self):
444 """
446 """
445 Returns list of modified ``FileNode`` objects.
447 Returns list of modified ``FileNode`` objects.
446 """
448 """
447 if not self.parents:
449 if not self.parents:
448 return []
450 return []
449 return ChangedFileNodesGenerator(self.changed_paths, self)
451 return ChangedFileNodesGenerator(self.changed_paths, self)
450
452
451 @LazyProperty
453 @LazyProperty
452 def changed_paths(self):
454 def changed_paths(self):
453 return [n for n in self._get_paths_for_status('modified')]
455 return [n for n in self._get_paths_for_status('modified')]
454
456
455 @LazyProperty
457 @LazyProperty
456 def removed(self):
458 def removed(self):
457 """
459 """
458 Returns list of removed ``FileNode`` objects.
460 Returns list of removed ``FileNode`` objects.
459 """
461 """
460 if not self.parents:
462 if not self.parents:
461 return []
463 return []
462 return RemovedFileNodesGenerator(self.removed_paths, self)
464 return RemovedFileNodesGenerator(self.removed_paths, self)
463
465
464 @LazyProperty
466 @LazyProperty
465 def removed_paths(self):
467 def removed_paths(self):
466 return [n for n in self._get_paths_for_status('deleted')]
468 return [n for n in self._get_paths_for_status('deleted')]
467
469
468 def _get_submodule_url(self, submodule_path):
470 def _get_submodule_url(self, submodule_path):
469 git_modules_path = '.gitmodules'
471 git_modules_path = '.gitmodules'
470
472
471 if self._submodules is None:
473 if self._submodules is None:
472 self._submodules = {}
474 self._submodules = {}
473
475
474 try:
476 try:
475 submodules_node = self.get_node(git_modules_path)
477 submodules_node = self.get_node(git_modules_path)
476 except NodeDoesNotExistError:
478 except NodeDoesNotExistError:
477 return None
479 return None
478
480
479 # ConfigParser fails if there are whitespaces, also it needs an iterable
481 # ConfigParser fails if there are whitespaces, also it needs an iterable
480 # file like content
482 # file like content
481 def iter_content(_content):
483 def iter_content(_content):
482 for line in _content.splitlines():
484 for line in _content.splitlines():
483 yield line
485 yield line
484
486
485 parser = configparser.RawConfigParser()
487 parser = configparser.RawConfigParser()
486 parser.read_file(iter_content(submodules_node.content))
488 parser.read_file(iter_content(submodules_node.content))
487
489
488 for section in parser.sections():
490 for section in parser.sections():
489 path = parser.get(section, 'path')
491 path = parser.get(section, 'path')
490 url = parser.get(section, 'url')
492 url = parser.get(section, 'url')
491 if path and url:
493 if path and url:
492 self._submodules[path.strip('/')] = url
494 self._submodules[path.strip('/')] = url
493
495
494 return self._submodules.get(submodule_path.strip('/'))
496 return self._submodules.get(submodule_path.strip('/'))
General Comments 0
You need to be logged in to leave comments. Login now