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