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