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