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