##// END OF EJS Templates
git: use safe configparser for git submodules to prevent from errors on submodules with % sign....
marcink -
r4239:7c8ad7af stable
parent child Browse files
Show More
@@ -1,484 +1,484 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2014-2019 RhodeCode GmbH
3 # Copyright (C) 2014-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 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 = value[0] if value else None
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 @LazyProperty
159 @LazyProperty
160 def branch(self):
160 def branch(self):
161 branches = self._remote.branch(self.raw_id)
161 branches = self._remote.branch(self.raw_id)
162
162
163 if branches:
163 if branches:
164 # actually commit can have multiple branches in git
164 # actually commit can have multiple branches in git
165 return safe_unicode(branches[0])
165 return safe_unicode(branches[0])
166
166
167 def _get_tree_id_for_path(self, path):
167 def _get_tree_id_for_path(self, path):
168 path = safe_str(path)
168 path = safe_str(path)
169 if path in self._paths:
169 if path in self._paths:
170 return self._paths[path]
170 return self._paths[path]
171
171
172 tree_id = self._tree_id
172 tree_id = self._tree_id
173
173
174 path = path.strip('/')
174 path = path.strip('/')
175 if path == '':
175 if path == '':
176 data = [tree_id, "tree"]
176 data = [tree_id, "tree"]
177 self._paths[''] = data
177 self._paths[''] = data
178 return data
178 return data
179
179
180 tree_id, tree_type, tree_mode = \
180 tree_id, tree_type, tree_mode = \
181 self._remote.tree_and_type_for_path(self.raw_id, path)
181 self._remote.tree_and_type_for_path(self.raw_id, path)
182 if tree_id is None:
182 if tree_id is None:
183 raise self.no_node_at_path(path)
183 raise self.no_node_at_path(path)
184
184
185 self._paths[path] = [tree_id, tree_type]
185 self._paths[path] = [tree_id, tree_type]
186 self._stat_modes[path] = tree_mode
186 self._stat_modes[path] = tree_mode
187
187
188 if path not in self._paths:
188 if path not in self._paths:
189 raise self.no_node_at_path(path)
189 raise self.no_node_at_path(path)
190
190
191 return self._paths[path]
191 return self._paths[path]
192
192
193 def _get_kind(self, path):
193 def _get_kind(self, path):
194 tree_id, type_ = self._get_tree_id_for_path(path)
194 tree_id, type_ = self._get_tree_id_for_path(path)
195 if type_ == 'blob':
195 if type_ == 'blob':
196 return NodeKind.FILE
196 return NodeKind.FILE
197 elif type_ == 'tree':
197 elif type_ == 'tree':
198 return NodeKind.DIR
198 return NodeKind.DIR
199 elif type_ == 'link':
199 elif type_ == 'link':
200 return NodeKind.SUBMODULE
200 return NodeKind.SUBMODULE
201 return None
201 return None
202
202
203 def _get_filectx(self, path):
203 def _get_filectx(self, path):
204 path = self._fix_path(path)
204 path = self._fix_path(path)
205 if self._get_kind(path) != NodeKind.FILE:
205 if self._get_kind(path) != NodeKind.FILE:
206 raise CommitError(
206 raise CommitError(
207 "File does not exist for commit %s at '%s'" % (self.raw_id, path))
207 "File does not exist for commit %s at '%s'" % (self.raw_id, path))
208 return path
208 return path
209
209
210 def _get_file_nodes(self):
210 def _get_file_nodes(self):
211 return chain(*(t[2] for t in self.walk()))
211 return chain(*(t[2] for t in self.walk()))
212
212
213 @LazyProperty
213 @LazyProperty
214 def parents(self):
214 def parents(self):
215 """
215 """
216 Returns list of parent commits.
216 Returns list of parent commits.
217 """
217 """
218 parent_ids = self._remote.parents(self.id)
218 parent_ids = self._remote.parents(self.id)
219 return self._make_commits(parent_ids)
219 return self._make_commits(parent_ids)
220
220
221 @LazyProperty
221 @LazyProperty
222 def children(self):
222 def children(self):
223 """
223 """
224 Returns list of child commits.
224 Returns list of child commits.
225 """
225 """
226
226
227 children = self._remote.children(self.raw_id)
227 children = self._remote.children(self.raw_id)
228 return self._make_commits(children)
228 return self._make_commits(children)
229
229
230 def _make_commits(self, commit_ids):
230 def _make_commits(self, commit_ids):
231 def commit_maker(_commit_id):
231 def commit_maker(_commit_id):
232 return self.repository.get_commit(commit_id=commit_id)
232 return self.repository.get_commit(commit_id=commit_id)
233
233
234 return [commit_maker(commit_id) for commit_id in commit_ids]
234 return [commit_maker(commit_id) for commit_id in commit_ids]
235
235
236 def get_file_mode(self, path):
236 def get_file_mode(self, path):
237 """
237 """
238 Returns stat mode of the file at the given `path`.
238 Returns stat mode of the file at the given `path`.
239 """
239 """
240 path = safe_str(path)
240 path = safe_str(path)
241 # ensure path is traversed
241 # ensure path is traversed
242 self._get_tree_id_for_path(path)
242 self._get_tree_id_for_path(path)
243 return self._stat_modes[path]
243 return self._stat_modes[path]
244
244
245 def is_link(self, path):
245 def is_link(self, path):
246 return stat.S_ISLNK(self.get_file_mode(path))
246 return stat.S_ISLNK(self.get_file_mode(path))
247
247
248 def is_node_binary(self, path):
248 def is_node_binary(self, path):
249 tree_id, _ = self._get_tree_id_for_path(path)
249 tree_id, _ = self._get_tree_id_for_path(path)
250 return self._remote.is_binary(tree_id)
250 return self._remote.is_binary(tree_id)
251
251
252 def get_file_content(self, path):
252 def get_file_content(self, path):
253 """
253 """
254 Returns content of the file at given `path`.
254 Returns content of the file at given `path`.
255 """
255 """
256 tree_id, _ = self._get_tree_id_for_path(path)
256 tree_id, _ = self._get_tree_id_for_path(path)
257 return self._remote.blob_as_pretty_string(tree_id)
257 return self._remote.blob_as_pretty_string(tree_id)
258
258
259 def get_file_content_streamed(self, path):
259 def get_file_content_streamed(self, path):
260 tree_id, _ = self._get_tree_id_for_path(path)
260 tree_id, _ = self._get_tree_id_for_path(path)
261 stream_method = getattr(self._remote, 'stream:blob_as_pretty_string')
261 stream_method = getattr(self._remote, 'stream:blob_as_pretty_string')
262 return stream_method(tree_id)
262 return stream_method(tree_id)
263
263
264 def get_file_size(self, path):
264 def get_file_size(self, path):
265 """
265 """
266 Returns size of the file at given `path`.
266 Returns size of the file at given `path`.
267 """
267 """
268 tree_id, _ = self._get_tree_id_for_path(path)
268 tree_id, _ = self._get_tree_id_for_path(path)
269 return self._remote.blob_raw_length(tree_id)
269 return self._remote.blob_raw_length(tree_id)
270
270
271 def get_path_history(self, path, limit=None, pre_load=None):
271 def get_path_history(self, path, limit=None, pre_load=None):
272 """
272 """
273 Returns history of file as reversed list of `GitCommit` objects for
273 Returns history of file as reversed list of `GitCommit` objects for
274 which file at given `path` has been modified.
274 which file at given `path` has been modified.
275 """
275 """
276
276
277 path = self._get_filectx(path)
277 path = self._get_filectx(path)
278 hist = self._remote.node_history(self.raw_id, path, limit)
278 hist = self._remote.node_history(self.raw_id, path, limit)
279 return [
279 return [
280 self.repository.get_commit(commit_id=commit_id, pre_load=pre_load)
280 self.repository.get_commit(commit_id=commit_id, pre_load=pre_load)
281 for commit_id in hist]
281 for commit_id in hist]
282
282
283 def get_file_annotate(self, path, pre_load=None):
283 def get_file_annotate(self, path, pre_load=None):
284 """
284 """
285 Returns a generator of four element tuples with
285 Returns a generator of four element tuples with
286 lineno, commit_id, commit lazy loader and line
286 lineno, commit_id, commit lazy loader and line
287 """
287 """
288
288
289 result = self._remote.node_annotate(self.raw_id, path)
289 result = self._remote.node_annotate(self.raw_id, path)
290
290
291 for ln_no, commit_id, content in result:
291 for ln_no, commit_id, content in result:
292 yield (
292 yield (
293 ln_no, commit_id,
293 ln_no, commit_id,
294 lambda: self.repository.get_commit(commit_id=commit_id, pre_load=pre_load),
294 lambda: self.repository.get_commit(commit_id=commit_id, pre_load=pre_load),
295 content)
295 content)
296
296
297 def get_nodes(self, path):
297 def get_nodes(self, path):
298
298
299 if self._get_kind(path) != NodeKind.DIR:
299 if self._get_kind(path) != NodeKind.DIR:
300 raise CommitError(
300 raise CommitError(
301 "Directory does not exist for commit %s at '%s'" % (self.raw_id, path))
301 "Directory does not exist for commit %s at '%s'" % (self.raw_id, path))
302 path = self._fix_path(path)
302 path = self._fix_path(path)
303
303
304 tree_id, _ = self._get_tree_id_for_path(path)
304 tree_id, _ = self._get_tree_id_for_path(path)
305
305
306 dirnodes = []
306 dirnodes = []
307 filenodes = []
307 filenodes = []
308
308
309 # extracted tree ID gives us our files...
309 # extracted tree ID gives us our files...
310 for name, stat_, id_, type_ in self._remote.tree_items(tree_id):
310 for name, stat_, id_, type_ in self._remote.tree_items(tree_id):
311 if type_ == 'link':
311 if type_ == 'link':
312 url = self._get_submodule_url('/'.join((path, name)))
312 url = self._get_submodule_url('/'.join((path, name)))
313 dirnodes.append(SubModuleNode(
313 dirnodes.append(SubModuleNode(
314 name, url=url, commit=id_, alias=self.repository.alias))
314 name, url=url, commit=id_, alias=self.repository.alias))
315 continue
315 continue
316
316
317 if path != '':
317 if path != '':
318 obj_path = '/'.join((path, name))
318 obj_path = '/'.join((path, name))
319 else:
319 else:
320 obj_path = name
320 obj_path = name
321 if obj_path not in self._stat_modes:
321 if obj_path not in self._stat_modes:
322 self._stat_modes[obj_path] = stat_
322 self._stat_modes[obj_path] = stat_
323
323
324 if type_ == 'tree':
324 if type_ == 'tree':
325 dirnodes.append(DirNode(obj_path, commit=self))
325 dirnodes.append(DirNode(obj_path, commit=self))
326 elif type_ == 'blob':
326 elif type_ == 'blob':
327 filenodes.append(FileNode(obj_path, commit=self, mode=stat_))
327 filenodes.append(FileNode(obj_path, commit=self, mode=stat_))
328 else:
328 else:
329 raise CommitError(
329 raise CommitError(
330 "Requested object should be Tree or Blob, is %s", type_)
330 "Requested object should be Tree or Blob, is %s", type_)
331
331
332 nodes = dirnodes + filenodes
332 nodes = dirnodes + filenodes
333 for node in nodes:
333 for node in nodes:
334 if node.path not in self.nodes:
334 if node.path not in self.nodes:
335 self.nodes[node.path] = node
335 self.nodes[node.path] = node
336 nodes.sort()
336 nodes.sort()
337 return nodes
337 return nodes
338
338
339 def get_node(self, path, pre_load=None):
339 def get_node(self, path, pre_load=None):
340 if isinstance(path, unicode):
340 if isinstance(path, unicode):
341 path = path.encode('utf-8')
341 path = path.encode('utf-8')
342 path = self._fix_path(path)
342 path = self._fix_path(path)
343 if path not in self.nodes:
343 if path not in self.nodes:
344 try:
344 try:
345 tree_id, type_ = self._get_tree_id_for_path(path)
345 tree_id, type_ = self._get_tree_id_for_path(path)
346 except CommitError:
346 except CommitError:
347 raise NodeDoesNotExistError(
347 raise NodeDoesNotExistError(
348 "Cannot find one of parents' directories for a given "
348 "Cannot find one of parents' directories for a given "
349 "path: %s" % path)
349 "path: %s" % path)
350
350
351 if type_ in ['link', 'commit']:
351 if type_ in ['link', 'commit']:
352 url = self._get_submodule_url(path)
352 url = self._get_submodule_url(path)
353 node = SubModuleNode(path, url=url, commit=tree_id,
353 node = SubModuleNode(path, url=url, commit=tree_id,
354 alias=self.repository.alias)
354 alias=self.repository.alias)
355 elif type_ == 'tree':
355 elif type_ == 'tree':
356 if path == '':
356 if path == '':
357 node = RootNode(commit=self)
357 node = RootNode(commit=self)
358 else:
358 else:
359 node = DirNode(path, commit=self)
359 node = DirNode(path, commit=self)
360 elif type_ == 'blob':
360 elif type_ == 'blob':
361 node = FileNode(path, commit=self, pre_load=pre_load)
361 node = FileNode(path, commit=self, pre_load=pre_load)
362 self._stat_modes[path] = node.mode
362 self._stat_modes[path] = node.mode
363 else:
363 else:
364 raise self.no_node_at_path(path)
364 raise self.no_node_at_path(path)
365
365
366 # cache node
366 # cache node
367 self.nodes[path] = node
367 self.nodes[path] = node
368
368
369 return self.nodes[path]
369 return self.nodes[path]
370
370
371 def get_largefile_node(self, path):
371 def get_largefile_node(self, path):
372 tree_id, _ = self._get_tree_id_for_path(path)
372 tree_id, _ = self._get_tree_id_for_path(path)
373 pointer_spec = self._remote.is_large_file(tree_id)
373 pointer_spec = self._remote.is_large_file(tree_id)
374
374
375 if pointer_spec:
375 if pointer_spec:
376 # content of that file regular FileNode is the hash of largefile
376 # content of that file regular FileNode is the hash of largefile
377 file_id = pointer_spec.get('oid_hash')
377 file_id = pointer_spec.get('oid_hash')
378 if self._remote.in_largefiles_store(file_id):
378 if self._remote.in_largefiles_store(file_id):
379 lf_path = self._remote.store_path(file_id)
379 lf_path = self._remote.store_path(file_id)
380 return LargeFileNode(lf_path, commit=self, org_path=path)
380 return LargeFileNode(lf_path, commit=self, org_path=path)
381
381
382 @LazyProperty
382 @LazyProperty
383 def affected_files(self):
383 def affected_files(self):
384 """
384 """
385 Gets a fast accessible file changes for given commit
385 Gets a fast accessible file changes for given commit
386 """
386 """
387 added, modified, deleted = self._changes_cache
387 added, modified, deleted = self._changes_cache
388 return list(added.union(modified).union(deleted))
388 return list(added.union(modified).union(deleted))
389
389
390 @LazyProperty
390 @LazyProperty
391 def _changes_cache(self):
391 def _changes_cache(self):
392 added = set()
392 added = set()
393 modified = set()
393 modified = set()
394 deleted = set()
394 deleted = set()
395 _r = self._remote
395 _r = self._remote
396
396
397 parents = self.parents
397 parents = self.parents
398 if not self.parents:
398 if not self.parents:
399 parents = [base.EmptyCommit()]
399 parents = [base.EmptyCommit()]
400 for parent in parents:
400 for parent in parents:
401 if isinstance(parent, base.EmptyCommit):
401 if isinstance(parent, base.EmptyCommit):
402 oid = None
402 oid = None
403 else:
403 else:
404 oid = parent.raw_id
404 oid = parent.raw_id
405 changes = _r.tree_changes(oid, self.raw_id)
405 changes = _r.tree_changes(oid, self.raw_id)
406 for (oldpath, newpath), (_, _), (_, _) in changes:
406 for (oldpath, newpath), (_, _), (_, _) in changes:
407 if newpath and oldpath:
407 if newpath and oldpath:
408 modified.add(newpath)
408 modified.add(newpath)
409 elif newpath and not oldpath:
409 elif newpath and not oldpath:
410 added.add(newpath)
410 added.add(newpath)
411 elif not newpath and oldpath:
411 elif not newpath and oldpath:
412 deleted.add(oldpath)
412 deleted.add(oldpath)
413 return added, modified, deleted
413 return added, modified, deleted
414
414
415 def _get_paths_for_status(self, status):
415 def _get_paths_for_status(self, status):
416 """
416 """
417 Returns sorted list of paths for given ``status``.
417 Returns sorted list of paths for given ``status``.
418
418
419 :param status: one of: *added*, *modified* or *deleted*
419 :param status: one of: *added*, *modified* or *deleted*
420 """
420 """
421 added, modified, deleted = self._changes_cache
421 added, modified, deleted = self._changes_cache
422 return sorted({
422 return sorted({
423 'added': list(added),
423 'added': list(added),
424 'modified': list(modified),
424 'modified': list(modified),
425 'deleted': list(deleted)}[status]
425 'deleted': list(deleted)}[status]
426 )
426 )
427
427
428 @LazyProperty
428 @LazyProperty
429 def added(self):
429 def added(self):
430 """
430 """
431 Returns list of added ``FileNode`` objects.
431 Returns list of added ``FileNode`` objects.
432 """
432 """
433 if not self.parents:
433 if not self.parents:
434 return list(self._get_file_nodes())
434 return list(self._get_file_nodes())
435 return AddedFileNodesGenerator(
435 return AddedFileNodesGenerator(
436 [n for n in self._get_paths_for_status('added')], self)
436 [n for n in self._get_paths_for_status('added')], self)
437
437
438 @LazyProperty
438 @LazyProperty
439 def changed(self):
439 def changed(self):
440 """
440 """
441 Returns list of modified ``FileNode`` objects.
441 Returns list of modified ``FileNode`` objects.
442 """
442 """
443 if not self.parents:
443 if not self.parents:
444 return []
444 return []
445 return ChangedFileNodesGenerator(
445 return ChangedFileNodesGenerator(
446 [n for n in self._get_paths_for_status('modified')], self)
446 [n for n in self._get_paths_for_status('modified')], self)
447
447
448 @LazyProperty
448 @LazyProperty
449 def removed(self):
449 def removed(self):
450 """
450 """
451 Returns list of removed ``FileNode`` objects.
451 Returns list of removed ``FileNode`` objects.
452 """
452 """
453 if not self.parents:
453 if not self.parents:
454 return []
454 return []
455 return RemovedFileNodesGenerator(
455 return RemovedFileNodesGenerator(
456 [n for n in self._get_paths_for_status('deleted')], self)
456 [n for n in self._get_paths_for_status('deleted')], self)
457
457
458 def _get_submodule_url(self, submodule_path):
458 def _get_submodule_url(self, submodule_path):
459 git_modules_path = '.gitmodules'
459 git_modules_path = '.gitmodules'
460
460
461 if self._submodules is None:
461 if self._submodules is None:
462 self._submodules = {}
462 self._submodules = {}
463
463
464 try:
464 try:
465 submodules_node = self.get_node(git_modules_path)
465 submodules_node = self.get_node(git_modules_path)
466 except NodeDoesNotExistError:
466 except NodeDoesNotExistError:
467 return None
467 return None
468
468
469 # ConfigParser fails if there are whitespaces, also it needs an iterable
469 # ConfigParser fails if there are whitespaces, also it needs an iterable
470 # file like content
470 # file like content
471 def iter_content(_content):
471 def iter_content(_content):
472 for line in _content.splitlines():
472 for line in _content.splitlines():
473 yield line
473 yield line
474
474
475 parser = configparser.ConfigParser()
475 parser = configparser.RawConfigParser()
476 parser.read_file(iter_content(submodules_node.content))
476 parser.read_file(iter_content(submodules_node.content))
477
477
478 for section in parser.sections():
478 for section in parser.sections():
479 path = parser.get(section, 'path')
479 path = parser.get(section, 'path')
480 url = parser.get(section, 'url')
480 url = parser.get(section, 'url')
481 if path and url:
481 if path and url:
482 self._submodules[path.strip('/')] = url
482 self._submodules[path.strip('/')] = url
483
483
484 return self._submodules.get(submodule_path.strip('/'))
484 return self._submodules.get(submodule_path.strip('/'))
General Comments 0
You need to be logged in to leave comments. Login now