##// END OF EJS Templates
temporarly fixed git _get_id_for_path problem in vcs....
marcink -
r2116:04881156 beta
parent child Browse files
Show More
@@ -1,451 +1,459
1 import re
1 import re
2 from itertools import chain
2 from itertools import chain
3 from dulwich import objects
3 from dulwich import objects
4 from subprocess import Popen, PIPE
4 from subprocess import Popen, PIPE
5 from rhodecode.lib.vcs.conf import settings
5 from rhodecode.lib.vcs.conf import settings
6 from rhodecode.lib.vcs.exceptions import RepositoryError
6 from rhodecode.lib.vcs.exceptions import RepositoryError
7 from rhodecode.lib.vcs.exceptions import ChangesetError
7 from rhodecode.lib.vcs.exceptions import ChangesetError
8 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
8 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
9 from rhodecode.lib.vcs.exceptions import VCSError
9 from rhodecode.lib.vcs.exceptions import VCSError
10 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
10 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
11 from rhodecode.lib.vcs.exceptions import ImproperArchiveTypeError
11 from rhodecode.lib.vcs.exceptions import ImproperArchiveTypeError
12 from rhodecode.lib.vcs.backends.base import BaseChangeset
12 from rhodecode.lib.vcs.backends.base import BaseChangeset
13 from rhodecode.lib.vcs.nodes import FileNode, DirNode, NodeKind, RootNode, RemovedFileNode
13 from rhodecode.lib.vcs.nodes import FileNode, DirNode, NodeKind, RootNode, RemovedFileNode
14 from rhodecode.lib.vcs.utils import safe_unicode
14 from rhodecode.lib.vcs.utils import safe_unicode
15 from rhodecode.lib.vcs.utils import date_fromtimestamp
15 from rhodecode.lib.vcs.utils import date_fromtimestamp
16 from rhodecode.lib.vcs.utils.lazy import LazyProperty
16 from rhodecode.lib.vcs.utils.lazy import LazyProperty
17
17
18
18
19 class GitChangeset(BaseChangeset):
19 class GitChangeset(BaseChangeset):
20 """
20 """
21 Represents state of the repository at single revision.
21 Represents state of the repository at single revision.
22 """
22 """
23
23
24 def __init__(self, repository, revision):
24 def __init__(self, repository, revision):
25 self._stat_modes = {}
25 self._stat_modes = {}
26 self.repository = repository
26 self.repository = repository
27 self.raw_id = revision
27 self.raw_id = revision
28 self.revision = repository.revisions.index(revision)
28 self.revision = repository.revisions.index(revision)
29
29
30 self.short_id = self.raw_id[:12]
30 self.short_id = self.raw_id[:12]
31 self.id = self.raw_id
31 self.id = self.raw_id
32 try:
32 try:
33 commit = self.repository._repo.get_object(self.raw_id)
33 commit = self.repository._repo.get_object(self.raw_id)
34 except KeyError:
34 except KeyError:
35 raise RepositoryError("Cannot get object with id %s" % self.raw_id)
35 raise RepositoryError("Cannot get object with id %s" % self.raw_id)
36 self._commit = commit
36 self._commit = commit
37 self._tree_id = commit.tree
37 self._tree_id = commit.tree
38
38
39 try:
39 try:
40 self.message = safe_unicode(commit.message[:-1])
40 self.message = safe_unicode(commit.message[:-1])
41 # Always strip last eol
41 # Always strip last eol
42 except UnicodeDecodeError:
42 except UnicodeDecodeError:
43 self.message = commit.message[:-1].decode(commit.encoding
43 self.message = commit.message[:-1].decode(commit.encoding
44 or 'utf-8')
44 or 'utf-8')
45 #self.branch = None
45 #self.branch = None
46 self.tags = []
46 self.tags = []
47 #tree = self.repository.get_object(self._tree_id)
47 #tree = self.repository.get_object(self._tree_id)
48 self.nodes = {}
48 self.nodes = {}
49 self._paths = {}
49 self._paths = {}
50
50
51 @LazyProperty
51 @LazyProperty
52 def author(self):
52 def author(self):
53 return safe_unicode(self._commit.committer)
53 return safe_unicode(self._commit.committer)
54
54
55 @LazyProperty
55 @LazyProperty
56 def date(self):
56 def date(self):
57 return date_fromtimestamp(self._commit.commit_time,
57 return date_fromtimestamp(self._commit.commit_time,
58 self._commit.commit_timezone)
58 self._commit.commit_timezone)
59
59
60 @LazyProperty
60 @LazyProperty
61 def status(self):
61 def status(self):
62 """
62 """
63 Returns modified, added, removed, deleted files for current changeset
63 Returns modified, added, removed, deleted files for current changeset
64 """
64 """
65 return self.changed, self.added, self.removed
65 return self.changed, self.added, self.removed
66
66
67 @LazyProperty
67 @LazyProperty
68 def branch(self):
68 def branch(self):
69 # TODO: Cache as we walk (id <-> branch name mapping)
69 # TODO: Cache as we walk (id <-> branch name mapping)
70 refs = self.repository._repo.get_refs()
70 refs = self.repository._repo.get_refs()
71 heads = [(key[len('refs/heads/'):], val) for key, val in refs.items()
71 heads = {}
72 if key.startswith('refs/heads/')]
72 for key, val in refs.items():
73 for ref_key in ['refs/heads/', 'refs/remotes/origin/']:
74 if key.startswith(ref_key):
75 n = key[len(ref_key):]
76 if n not in ['HEAD']:
77 heads[n] = val
73
78
74 for name, id in heads:
79 for name, id in heads.iteritems():
75 walker = self.repository._repo.object_store.get_graph_walker([id])
80 walker = self.repository._repo.object_store.get_graph_walker([id])
76 while True:
81 while True:
77 id = walker.next()
82 id_ = walker.next()
78 if not id:
83 if not id_:
79 break
84 break
80 if id == self.id:
85 if id_ == self.id:
81 return safe_unicode(name)
86 return safe_unicode(name)
82 raise ChangesetError("This should not happen... Have you manually "
87 raise ChangesetError("This should not happen... Have you manually "
83 "change id of the changeset?")
88 "change id of the changeset?")
84
89
85 def _fix_path(self, path):
90 def _fix_path(self, path):
86 """
91 """
87 Paths are stored without trailing slash so we need to get rid off it if
92 Paths are stored without trailing slash so we need to get rid off it if
88 needed.
93 needed.
89 """
94 """
90 if path.endswith('/'):
95 if path.endswith('/'):
91 path = path.rstrip('/')
96 path = path.rstrip('/')
92 return path
97 return path
93
98
94 def _get_id_for_path(self, path):
99 def _get_id_for_path(self, path):
100
95 # FIXME: Please, spare a couple of minutes and make those codes cleaner;
101 # FIXME: Please, spare a couple of minutes and make those codes cleaner;
96 if not path in self._paths:
102 if not path in self._paths:
97 path = path.strip('/')
103 path = path.strip('/')
98 # set root tree
104 # set root tree
99 tree = self.repository._repo[self._commit.tree]
105 tree = self.repository._repo[self._commit.tree]
100 if path == '':
106 if path == '':
101 self._paths[''] = tree.id
107 self._paths[''] = tree.id
102 return tree.id
108 return tree.id
103 splitted = path.split('/')
109 splitted = path.split('/')
104 dirs, name = splitted[:-1], splitted[-1]
110 dirs, name = splitted[:-1], splitted[-1]
105 curdir = ''
111 curdir = ''
112
113 # initially extract things from root dir
114 for item, stat, id in tree.iteritems():
115 if curdir:
116 name = '/'.join((curdir, item))
117 else:
118 name = item
119 self._paths[name] = id
120 self._stat_modes[name] = stat
121
106 for dir in dirs:
122 for dir in dirs:
107 if curdir:
123 if curdir:
108 curdir = '/'.join((curdir, dir))
124 curdir = '/'.join((curdir, dir))
109 else:
125 else:
110 curdir = dir
126 curdir = dir
111 #if curdir in self._paths:
112 ## This path have been already traversed
113 ## Update tree and continue
114 #tree = self.repository._repo[self._paths[curdir]]
115 #continue
116 dir_id = None
127 dir_id = None
117 for item, stat, id in tree.iteritems():
128 for item, stat, id in tree.iteritems():
118 if curdir:
119 item_path = '/'.join((curdir, item))
120 else:
121 item_path = item
122 self._paths[item_path] = id
123 self._stat_modes[item_path] = stat
124 if dir == item:
129 if dir == item:
125 dir_id = id
130 dir_id = id
126 if dir_id:
131 if dir_id:
127 # Update tree
132 # Update tree
128 tree = self.repository._repo[dir_id]
133 tree = self.repository._repo[dir_id]
129 if not isinstance(tree, objects.Tree):
134 if not isinstance(tree, objects.Tree):
130 raise ChangesetError('%s is not a directory' % curdir)
135 raise ChangesetError('%s is not a directory' % curdir)
131 else:
136 else:
132 raise ChangesetError('%s have not been found' % curdir)
137 raise ChangesetError('%s have not been found' % curdir)
133 for item, stat, id in tree.iteritems():
138
134 if curdir:
139 # cache all items from the given traversed tree
135 name = '/'.join((curdir, item))
140 for item, stat, id in tree.iteritems():
136 else:
141 if curdir:
137 name = item
142 name = '/'.join((curdir, item))
138 self._paths[name] = id
143 else:
139 self._stat_modes[name] = stat
144 name = item
145 self._paths[name] = id
146 self._stat_modes[name] = stat
147
140 if not path in self._paths:
148 if not path in self._paths:
141 raise NodeDoesNotExistError("There is no file nor directory "
149 raise NodeDoesNotExistError("There is no file nor directory "
142 "at the given path %r at revision %r"
150 "at the given path %r at revision %r"
143 % (path, self.short_id))
151 % (path, self.short_id))
144 return self._paths[path]
152 return self._paths[path]
145
153
146 def _get_kind(self, path):
154 def _get_kind(self, path):
147 id = self._get_id_for_path(path)
155 id = self._get_id_for_path(path)
148 obj = self.repository._repo[id]
156 obj = self.repository._repo[id]
149 if isinstance(obj, objects.Blob):
157 if isinstance(obj, objects.Blob):
150 return NodeKind.FILE
158 return NodeKind.FILE
151 elif isinstance(obj, objects.Tree):
159 elif isinstance(obj, objects.Tree):
152 return NodeKind.DIR
160 return NodeKind.DIR
153
161
154 def _get_file_nodes(self):
162 def _get_file_nodes(self):
155 return chain(*(t[2] for t in self.walk()))
163 return chain(*(t[2] for t in self.walk()))
156
164
157 @LazyProperty
165 @LazyProperty
158 def parents(self):
166 def parents(self):
159 """
167 """
160 Returns list of parents changesets.
168 Returns list of parents changesets.
161 """
169 """
162 return [self.repository.get_changeset(parent)
170 return [self.repository.get_changeset(parent)
163 for parent in self._commit.parents]
171 for parent in self._commit.parents]
164
172
165 def next(self, branch=None):
173 def next(self, branch=None):
166
174
167 if branch and self.branch != branch:
175 if branch and self.branch != branch:
168 raise VCSError('Branch option used on changeset not belonging '
176 raise VCSError('Branch option used on changeset not belonging '
169 'to that branch')
177 'to that branch')
170
178
171 def _next(changeset, branch):
179 def _next(changeset, branch):
172 try:
180 try:
173 next_ = changeset.revision + 1
181 next_ = changeset.revision + 1
174 next_rev = changeset.repository.revisions[next_]
182 next_rev = changeset.repository.revisions[next_]
175 except IndexError:
183 except IndexError:
176 raise ChangesetDoesNotExistError
184 raise ChangesetDoesNotExistError
177 cs = changeset.repository.get_changeset(next_rev)
185 cs = changeset.repository.get_changeset(next_rev)
178
186
179 if branch and branch != cs.branch:
187 if branch and branch != cs.branch:
180 return _next(cs, branch)
188 return _next(cs, branch)
181
189
182 return cs
190 return cs
183
191
184 return _next(self, branch)
192 return _next(self, branch)
185
193
186 def prev(self, branch=None):
194 def prev(self, branch=None):
187 if branch and self.branch != branch:
195 if branch and self.branch != branch:
188 raise VCSError('Branch option used on changeset not belonging '
196 raise VCSError('Branch option used on changeset not belonging '
189 'to that branch')
197 'to that branch')
190
198
191 def _prev(changeset, branch):
199 def _prev(changeset, branch):
192 try:
200 try:
193 prev_ = changeset.revision - 1
201 prev_ = changeset.revision - 1
194 if prev_ < 0:
202 if prev_ < 0:
195 raise IndexError
203 raise IndexError
196 prev_rev = changeset.repository.revisions[prev_]
204 prev_rev = changeset.repository.revisions[prev_]
197 except IndexError:
205 except IndexError:
198 raise ChangesetDoesNotExistError
206 raise ChangesetDoesNotExistError
199
207
200 cs = changeset.repository.get_changeset(prev_rev)
208 cs = changeset.repository.get_changeset(prev_rev)
201
209
202 if branch and branch != cs.branch:
210 if branch and branch != cs.branch:
203 return _prev(cs, branch)
211 return _prev(cs, branch)
204
212
205 return cs
213 return cs
206
214
207 return _prev(self, branch)
215 return _prev(self, branch)
208
216
209 def get_file_mode(self, path):
217 def get_file_mode(self, path):
210 """
218 """
211 Returns stat mode of the file at the given ``path``.
219 Returns stat mode of the file at the given ``path``.
212 """
220 """
213 # ensure path is traversed
221 # ensure path is traversed
214 self._get_id_for_path(path)
222 self._get_id_for_path(path)
215 return self._stat_modes[path]
223 return self._stat_modes[path]
216
224
217 def get_file_content(self, path):
225 def get_file_content(self, path):
218 """
226 """
219 Returns content of the file at given ``path``.
227 Returns content of the file at given ``path``.
220 """
228 """
221 id = self._get_id_for_path(path)
229 id = self._get_id_for_path(path)
222 blob = self.repository._repo[id]
230 blob = self.repository._repo[id]
223 return blob.as_pretty_string()
231 return blob.as_pretty_string()
224
232
225 def get_file_size(self, path):
233 def get_file_size(self, path):
226 """
234 """
227 Returns size of the file at given ``path``.
235 Returns size of the file at given ``path``.
228 """
236 """
229 id = self._get_id_for_path(path)
237 id = self._get_id_for_path(path)
230 blob = self.repository._repo[id]
238 blob = self.repository._repo[id]
231 return blob.raw_length()
239 return blob.raw_length()
232
240
233 def get_file_changeset(self, path):
241 def get_file_changeset(self, path):
234 """
242 """
235 Returns last commit of the file at the given ``path``.
243 Returns last commit of the file at the given ``path``.
236 """
244 """
237 node = self.get_node(path)
245 node = self.get_node(path)
238 return node.history[0]
246 return node.history[0]
239
247
240 def get_file_history(self, path):
248 def get_file_history(self, path):
241 """
249 """
242 Returns history of file as reversed list of ``Changeset`` objects for
250 Returns history of file as reversed list of ``Changeset`` objects for
243 which file at given ``path`` has been modified.
251 which file at given ``path`` has been modified.
244
252
245 TODO: This function now uses os underlying 'git' and 'grep' commands
253 TODO: This function now uses os underlying 'git' and 'grep' commands
246 which is generally not good. Should be replaced with algorithm
254 which is generally not good. Should be replaced with algorithm
247 iterating commits.
255 iterating commits.
248 """
256 """
249 cmd = 'log --pretty="format: %%H" --name-status -p %s -- "%s"' % (
257 cmd = 'log --pretty="format: %%H" --name-status -p %s -- "%s"' % (
250 self.id, path
258 self.id, path
251 )
259 )
252 so, se = self.repository.run_git_command(cmd)
260 so, se = self.repository.run_git_command(cmd)
253 ids = re.findall(r'\w{40}', so)
261 ids = re.findall(r'\w{40}', so)
254 return [self.repository.get_changeset(id) for id in ids]
262 return [self.repository.get_changeset(id) for id in ids]
255
263
256 def get_file_annotate(self, path):
264 def get_file_annotate(self, path):
257 """
265 """
258 Returns a list of three element tuples with lineno,changeset and line
266 Returns a list of three element tuples with lineno,changeset and line
259
267
260 TODO: This function now uses os underlying 'git' command which is
268 TODO: This function now uses os underlying 'git' command which is
261 generally not good. Should be replaced with algorithm iterating
269 generally not good. Should be replaced with algorithm iterating
262 commits.
270 commits.
263 """
271 """
264 cmd = 'blame -l --root -r %s -- "%s"' % (self.id, path)
272 cmd = 'blame -l --root -r %s -- "%s"' % (self.id, path)
265 # -l ==> outputs long shas (and we need all 40 characters)
273 # -l ==> outputs long shas (and we need all 40 characters)
266 # --root ==> doesn't put '^' character for bounderies
274 # --root ==> doesn't put '^' character for bounderies
267 # -r sha ==> blames for the given revision
275 # -r sha ==> blames for the given revision
268 so, se = self.repository.run_git_command(cmd)
276 so, se = self.repository.run_git_command(cmd)
269 annotate = []
277 annotate = []
270 for i, blame_line in enumerate(so.split('\n')[:-1]):
278 for i, blame_line in enumerate(so.split('\n')[:-1]):
271 ln_no = i + 1
279 ln_no = i + 1
272 id, line = re.split(r' \(.+?\) ', blame_line, 1)
280 id, line = re.split(r' \(.+?\) ', blame_line, 1)
273 annotate.append((ln_no, self.repository.get_changeset(id), line))
281 annotate.append((ln_no, self.repository.get_changeset(id), line))
274 return annotate
282 return annotate
275
283
276 def fill_archive(self, stream=None, kind='tgz', prefix=None,
284 def fill_archive(self, stream=None, kind='tgz', prefix=None,
277 subrepos=False):
285 subrepos=False):
278 """
286 """
279 Fills up given stream.
287 Fills up given stream.
280
288
281 :param stream: file like object.
289 :param stream: file like object.
282 :param kind: one of following: ``zip``, ``tgz`` or ``tbz2``.
290 :param kind: one of following: ``zip``, ``tgz`` or ``tbz2``.
283 Default: ``tgz``.
291 Default: ``tgz``.
284 :param prefix: name of root directory in archive.
292 :param prefix: name of root directory in archive.
285 Default is repository name and changeset's raw_id joined with dash
293 Default is repository name and changeset's raw_id joined with dash
286 (``repo-tip.<KIND>``).
294 (``repo-tip.<KIND>``).
287 :param subrepos: include subrepos in this archive.
295 :param subrepos: include subrepos in this archive.
288
296
289 :raise ImproperArchiveTypeError: If given kind is wrong.
297 :raise ImproperArchiveTypeError: If given kind is wrong.
290 :raise VcsError: If given stream is None
298 :raise VcsError: If given stream is None
291
299
292 """
300 """
293 allowed_kinds = settings.ARCHIVE_SPECS.keys()
301 allowed_kinds = settings.ARCHIVE_SPECS.keys()
294 if kind not in allowed_kinds:
302 if kind not in allowed_kinds:
295 raise ImproperArchiveTypeError('Archive kind not supported use one'
303 raise ImproperArchiveTypeError('Archive kind not supported use one'
296 'of %s', allowed_kinds)
304 'of %s', allowed_kinds)
297
305
298 if prefix is None:
306 if prefix is None:
299 prefix = '%s-%s' % (self.repository.name, self.short_id)
307 prefix = '%s-%s' % (self.repository.name, self.short_id)
300 elif prefix.startswith('/'):
308 elif prefix.startswith('/'):
301 raise VCSError("Prefix cannot start with leading slash")
309 raise VCSError("Prefix cannot start with leading slash")
302 elif prefix.strip() == '':
310 elif prefix.strip() == '':
303 raise VCSError("Prefix cannot be empty")
311 raise VCSError("Prefix cannot be empty")
304
312
305 if kind == 'zip':
313 if kind == 'zip':
306 frmt = 'zip'
314 frmt = 'zip'
307 else:
315 else:
308 frmt = 'tar'
316 frmt = 'tar'
309 cmd = 'git archive --format=%s --prefix=%s/ %s' % (frmt, prefix,
317 cmd = 'git archive --format=%s --prefix=%s/ %s' % (frmt, prefix,
310 self.raw_id)
318 self.raw_id)
311 if kind == 'tgz':
319 if kind == 'tgz':
312 cmd += ' | gzip -9'
320 cmd += ' | gzip -9'
313 elif kind == 'tbz2':
321 elif kind == 'tbz2':
314 cmd += ' | bzip2 -9'
322 cmd += ' | bzip2 -9'
315
323
316 if stream is None:
324 if stream is None:
317 raise VCSError('You need to pass in a valid stream for filling'
325 raise VCSError('You need to pass in a valid stream for filling'
318 ' with archival data')
326 ' with archival data')
319 popen = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True,
327 popen = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True,
320 cwd=self.repository.path)
328 cwd=self.repository.path)
321
329
322 buffer_size = 1024 * 8
330 buffer_size = 1024 * 8
323 chunk = popen.stdout.read(buffer_size)
331 chunk = popen.stdout.read(buffer_size)
324 while chunk:
332 while chunk:
325 stream.write(chunk)
333 stream.write(chunk)
326 chunk = popen.stdout.read(buffer_size)
334 chunk = popen.stdout.read(buffer_size)
327 # Make sure all descriptors would be read
335 # Make sure all descriptors would be read
328 popen.communicate()
336 popen.communicate()
329
337
330 def get_nodes(self, path):
338 def get_nodes(self, path):
331 if self._get_kind(path) != NodeKind.DIR:
339 if self._get_kind(path) != NodeKind.DIR:
332 raise ChangesetError("Directory does not exist for revision %r at "
340 raise ChangesetError("Directory does not exist for revision %r at "
333 " %r" % (self.revision, path))
341 " %r" % (self.revision, path))
334 path = self._fix_path(path)
342 path = self._fix_path(path)
335 id = self._get_id_for_path(path)
343 id = self._get_id_for_path(path)
336 tree = self.repository._repo[id]
344 tree = self.repository._repo[id]
337 dirnodes = []
345 dirnodes = []
338 filenodes = []
346 filenodes = []
339 for name, stat, id in tree.iteritems():
347 for name, stat, id in tree.iteritems():
340 obj = self.repository._repo.get_object(id)
348 obj = self.repository._repo.get_object(id)
341 if path != '':
349 if path != '':
342 obj_path = '/'.join((path, name))
350 obj_path = '/'.join((path, name))
343 else:
351 else:
344 obj_path = name
352 obj_path = name
345 if obj_path not in self._stat_modes:
353 if obj_path not in self._stat_modes:
346 self._stat_modes[obj_path] = stat
354 self._stat_modes[obj_path] = stat
347 if isinstance(obj, objects.Tree):
355 if isinstance(obj, objects.Tree):
348 dirnodes.append(DirNode(obj_path, changeset=self))
356 dirnodes.append(DirNode(obj_path, changeset=self))
349 elif isinstance(obj, objects.Blob):
357 elif isinstance(obj, objects.Blob):
350 filenodes.append(FileNode(obj_path, changeset=self, mode=stat))
358 filenodes.append(FileNode(obj_path, changeset=self, mode=stat))
351 else:
359 else:
352 raise ChangesetError("Requested object should be Tree "
360 raise ChangesetError("Requested object should be Tree "
353 "or Blob, is %r" % type(obj))
361 "or Blob, is %r" % type(obj))
354 nodes = dirnodes + filenodes
362 nodes = dirnodes + filenodes
355 for node in nodes:
363 for node in nodes:
356 if not node.path in self.nodes:
364 if not node.path in self.nodes:
357 self.nodes[node.path] = node
365 self.nodes[node.path] = node
358 nodes.sort()
366 nodes.sort()
359 return nodes
367 return nodes
360
368
361 def get_node(self, path):
369 def get_node(self, path):
362 if isinstance(path, unicode):
370 if isinstance(path, unicode):
363 path = path.encode('utf-8')
371 path = path.encode('utf-8')
364 path = self._fix_path(path)
372 path = self._fix_path(path)
365 if not path in self.nodes:
373 if not path in self.nodes:
366 try:
374 try:
367 id = self._get_id_for_path(path)
375 id = self._get_id_for_path(path)
368 except ChangesetError:
376 except ChangesetError:
369 raise NodeDoesNotExistError("Cannot find one of parents' "
377 raise NodeDoesNotExistError("Cannot find one of parents' "
370 "directories for a given path: %s" % path)
378 "directories for a given path: %s" % path)
371 obj = self.repository._repo.get_object(id)
379 obj = self.repository._repo.get_object(id)
372 if isinstance(obj, objects.Tree):
380 if isinstance(obj, objects.Tree):
373 if path == '':
381 if path == '':
374 node = RootNode(changeset=self)
382 node = RootNode(changeset=self)
375 else:
383 else:
376 node = DirNode(path, changeset=self)
384 node = DirNode(path, changeset=self)
377 node._tree = obj
385 node._tree = obj
378 elif isinstance(obj, objects.Blob):
386 elif isinstance(obj, objects.Blob):
379 node = FileNode(path, changeset=self)
387 node = FileNode(path, changeset=self)
380 node._blob = obj
388 node._blob = obj
381 else:
389 else:
382 raise NodeDoesNotExistError("There is no file nor directory "
390 raise NodeDoesNotExistError("There is no file nor directory "
383 "at the given path %r at revision %r"
391 "at the given path %r at revision %r"
384 % (path, self.short_id))
392 % (path, self.short_id))
385 # cache node
393 # cache node
386 self.nodes[path] = node
394 self.nodes[path] = node
387 return self.nodes[path]
395 return self.nodes[path]
388
396
389 @LazyProperty
397 @LazyProperty
390 def affected_files(self):
398 def affected_files(self):
391 """
399 """
392 Get's a fast accessible file changes for given changeset
400 Get's a fast accessible file changes for given changeset
393 """
401 """
394
402
395 return self.added + self.changed
403 return self.added + self.changed
396
404
397 @LazyProperty
405 @LazyProperty
398 def _diff_name_status(self):
406 def _diff_name_status(self):
399 output = []
407 output = []
400 for parent in self.parents:
408 for parent in self.parents:
401 cmd = 'diff --name-status %s %s' % (parent.raw_id, self.raw_id)
409 cmd = 'diff --name-status %s %s' % (parent.raw_id, self.raw_id)
402 so, se = self.repository.run_git_command(cmd)
410 so, se = self.repository.run_git_command(cmd)
403 output.append(so.strip())
411 output.append(so.strip())
404 return '\n'.join(output)
412 return '\n'.join(output)
405
413
406 def _get_paths_for_status(self, status):
414 def _get_paths_for_status(self, status):
407 """
415 """
408 Returns sorted list of paths for given ``status``.
416 Returns sorted list of paths for given ``status``.
409
417
410 :param status: one of: *added*, *modified* or *deleted*
418 :param status: one of: *added*, *modified* or *deleted*
411 """
419 """
412 paths = set()
420 paths = set()
413 char = status[0].upper()
421 char = status[0].upper()
414 for line in self._diff_name_status.splitlines():
422 for line in self._diff_name_status.splitlines():
415 if not line:
423 if not line:
416 continue
424 continue
417 if line.startswith(char):
425 if line.startswith(char):
418 splitted = line.split(char,1)
426 splitted = line.split(char,1)
419 if not len(splitted) == 2:
427 if not len(splitted) == 2:
420 raise VCSError("Couldn't parse diff result:\n%s\n\n and "
428 raise VCSError("Couldn't parse diff result:\n%s\n\n and "
421 "particularly that line: %s" % (self._diff_name_status,
429 "particularly that line: %s" % (self._diff_name_status,
422 line))
430 line))
423 paths.add(splitted[1].strip())
431 paths.add(splitted[1].strip())
424 return sorted(paths)
432 return sorted(paths)
425
433
426 @LazyProperty
434 @LazyProperty
427 def added(self):
435 def added(self):
428 """
436 """
429 Returns list of added ``FileNode`` objects.
437 Returns list of added ``FileNode`` objects.
430 """
438 """
431 if not self.parents:
439 if not self.parents:
432 return list(self._get_file_nodes())
440 return list(self._get_file_nodes())
433 return [self.get_node(path) for path in self._get_paths_for_status('added')]
441 return [self.get_node(path) for path in self._get_paths_for_status('added')]
434
442
435 @LazyProperty
443 @LazyProperty
436 def changed(self):
444 def changed(self):
437 """
445 """
438 Returns list of modified ``FileNode`` objects.
446 Returns list of modified ``FileNode`` objects.
439 """
447 """
440 if not self.parents:
448 if not self.parents:
441 return []
449 return []
442 return [self.get_node(path) for path in self._get_paths_for_status('modified')]
450 return [self.get_node(path) for path in self._get_paths_for_status('modified')]
443
451
444 @LazyProperty
452 @LazyProperty
445 def removed(self):
453 def removed(self):
446 """
454 """
447 Returns list of removed ``FileNode`` objects.
455 Returns list of removed ``FileNode`` objects.
448 """
456 """
449 if not self.parents:
457 if not self.parents:
450 return []
458 return []
451 return [RemovedFileNode(path) for path in self._get_paths_for_status('deleted')]
459 return [RemovedFileNode(path) for path in self._get_paths_for_status('deleted')]
General Comments 0
You need to be logged in to leave comments. Login now