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