##// END OF EJS Templates
fixed some unicode problems with git file path
marcink -
r3793:5135f14b default
parent child Browse files
Show More
@@ -1,551 +1,554
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 import rhodecode
5 import rhodecode
6 from rhodecode.lib.vcs.conf import settings
6 from rhodecode.lib.vcs.conf import settings
7 from rhodecode.lib.vcs.exceptions import RepositoryError
7 from rhodecode.lib.vcs.exceptions import RepositoryError
8 from rhodecode.lib.vcs.exceptions import ChangesetError
8 from rhodecode.lib.vcs.exceptions import ChangesetError
9 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
9 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
10 from rhodecode.lib.vcs.exceptions import VCSError
10 from rhodecode.lib.vcs.exceptions import VCSError
11 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
11 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
12 from rhodecode.lib.vcs.exceptions import ImproperArchiveTypeError
12 from rhodecode.lib.vcs.exceptions import ImproperArchiveTypeError
13 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
13 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
14 from rhodecode.lib.vcs.nodes import FileNode, DirNode, NodeKind, RootNode, \
14 from rhodecode.lib.vcs.nodes import FileNode, DirNode, NodeKind, RootNode, \
15 RemovedFileNode, SubModuleNode, ChangedFileNodesGenerator,\
15 RemovedFileNode, SubModuleNode, ChangedFileNodesGenerator,\
16 AddedFileNodesGenerator, RemovedFileNodesGenerator
16 AddedFileNodesGenerator, RemovedFileNodesGenerator
17 from rhodecode.lib.vcs.utils import safe_unicode
17 from rhodecode.lib.vcs.utils import safe_unicode
18 from rhodecode.lib.vcs.utils import date_fromtimestamp
18 from rhodecode.lib.vcs.utils import date_fromtimestamp
19 from rhodecode.lib.vcs.utils.lazy import LazyProperty
19 from rhodecode.lib.vcs.utils.lazy import LazyProperty
20 from rhodecode.lib.utils2 import safe_int
20 from rhodecode.lib.utils2 import safe_int, safe_str
21
21
22
22
23 class GitChangeset(BaseChangeset):
23 class GitChangeset(BaseChangeset):
24 """
24 """
25 Represents state of the repository at single revision.
25 Represents state of the repository at single revision.
26 """
26 """
27
27
28 def __init__(self, repository, revision):
28 def __init__(self, repository, revision):
29 self._stat_modes = {}
29 self._stat_modes = {}
30 self.repository = repository
30 self.repository = repository
31
31
32 try:
32 try:
33 commit = self.repository._repo.get_object(revision)
33 commit = self.repository._repo.get_object(revision)
34 if isinstance(commit, objects.Tag):
34 if isinstance(commit, objects.Tag):
35 revision = commit.object[1]
35 revision = commit.object[1]
36 commit = self.repository._repo.get_object(commit.object[1])
36 commit = self.repository._repo.get_object(commit.object[1])
37 except KeyError:
37 except KeyError:
38 raise RepositoryError("Cannot get object with id %s" % revision)
38 raise RepositoryError("Cannot get object with id %s" % revision)
39 self.raw_id = revision
39 self.raw_id = revision
40 self.id = self.raw_id
40 self.id = self.raw_id
41 self.short_id = self.raw_id[:12]
41 self.short_id = self.raw_id[:12]
42 self._commit = commit
42 self._commit = commit
43
43
44 self._tree_id = commit.tree
44 self._tree_id = commit.tree
45 self._committer_property = 'committer'
45 self._committer_property = 'committer'
46 self._author_property = 'author'
46 self._author_property = 'author'
47 self._date_property = 'commit_time'
47 self._date_property = 'commit_time'
48 self._date_tz_property = 'commit_timezone'
48 self._date_tz_property = 'commit_timezone'
49 self.revision = repository.revisions.index(revision)
49 self.revision = repository.revisions.index(revision)
50
50
51 self.message = safe_unicode(commit.message)
51 self.message = safe_unicode(commit.message)
52
52
53 self.nodes = {}
53 self.nodes = {}
54 self._paths = {}
54 self._paths = {}
55
55
56 @LazyProperty
56 @LazyProperty
57 def committer(self):
57 def committer(self):
58 return safe_unicode(getattr(self._commit, self._committer_property))
58 return safe_unicode(getattr(self._commit, self._committer_property))
59
59
60 @LazyProperty
60 @LazyProperty
61 def author(self):
61 def author(self):
62 return safe_unicode(getattr(self._commit, self._author_property))
62 return safe_unicode(getattr(self._commit, self._author_property))
63
63
64 @LazyProperty
64 @LazyProperty
65 def date(self):
65 def date(self):
66 return date_fromtimestamp(getattr(self._commit, self._date_property),
66 return date_fromtimestamp(getattr(self._commit, self._date_property),
67 getattr(self._commit, self._date_tz_property))
67 getattr(self._commit, self._date_tz_property))
68
68
69 @LazyProperty
69 @LazyProperty
70 def _timestamp(self):
70 def _timestamp(self):
71 return getattr(self._commit, self._date_property)
71 return getattr(self._commit, self._date_property)
72
72
73 @LazyProperty
73 @LazyProperty
74 def status(self):
74 def status(self):
75 """
75 """
76 Returns modified, added, removed, deleted files for current changeset
76 Returns modified, added, removed, deleted files for current changeset
77 """
77 """
78 return self.changed, self.added, self.removed
78 return self.changed, self.added, self.removed
79
79
80 @LazyProperty
80 @LazyProperty
81 def tags(self):
81 def tags(self):
82 _tags = []
82 _tags = []
83 for tname, tsha in self.repository.tags.iteritems():
83 for tname, tsha in self.repository.tags.iteritems():
84 if tsha == self.raw_id:
84 if tsha == self.raw_id:
85 _tags.append(tname)
85 _tags.append(tname)
86 return _tags
86 return _tags
87
87
88 @LazyProperty
88 @LazyProperty
89 def branch(self):
89 def branch(self):
90
90
91 heads = self.repository._heads(reverse=False)
91 heads = self.repository._heads(reverse=False)
92
92
93 ref = heads.get(self.raw_id)
93 ref = heads.get(self.raw_id)
94 if ref:
94 if ref:
95 return safe_unicode(ref)
95 return safe_unicode(ref)
96
96
97 def _fix_path(self, path):
97 def _fix_path(self, path):
98 """
98 """
99 Paths are stored without trailing slash so we need to get rid off it if
99 Paths are stored without trailing slash so we need to get rid off it if
100 needed.
100 needed.
101 """
101 """
102 if path.endswith('/'):
102 if path.endswith('/'):
103 path = path.rstrip('/')
103 path = path.rstrip('/')
104 return path
104 return path
105
105
106 def _get_id_for_path(self, path):
106 def _get_id_for_path(self, path):
107
107
108 # FIXME: Please, spare a couple of minutes and make those codes cleaner;
108 # FIXME: Please, spare a couple of minutes and make those codes cleaner;
109 if not path in self._paths:
109 if not path in self._paths:
110 path = path.strip('/')
110 path = path.strip('/')
111 # set root tree
111 # set root tree
112 tree = self.repository._repo[self._tree_id]
112 tree = self.repository._repo[self._tree_id]
113 if path == '':
113 if path == '':
114 self._paths[''] = tree.id
114 self._paths[''] = tree.id
115 return tree.id
115 return tree.id
116 splitted = path.split('/')
116 splitted = path.split('/')
117 dirs, name = splitted[:-1], splitted[-1]
117 dirs, name = splitted[:-1], splitted[-1]
118 curdir = ''
118 curdir = ''
119
119
120 # initially extract things from root dir
120 # initially extract things from root dir
121 for item, stat, id in tree.iteritems():
121 for item, stat, id in tree.iteritems():
122 if curdir:
122 if curdir:
123 name = '/'.join((curdir, item))
123 name = '/'.join((curdir, item))
124 else:
124 else:
125 name = item
125 name = item
126 self._paths[name] = id
126 self._paths[name] = id
127 self._stat_modes[name] = stat
127 self._stat_modes[name] = stat
128
128
129 for dir in dirs:
129 for dir in dirs:
130 if curdir:
130 if curdir:
131 curdir = '/'.join((curdir, dir))
131 curdir = '/'.join((curdir, dir))
132 else:
132 else:
133 curdir = dir
133 curdir = dir
134 dir_id = None
134 dir_id = None
135 for item, stat, id in tree.iteritems():
135 for item, stat, id in tree.iteritems():
136 if dir == item:
136 if dir == item:
137 dir_id = id
137 dir_id = id
138 if dir_id:
138 if dir_id:
139 # Update tree
139 # Update tree
140 tree = self.repository._repo[dir_id]
140 tree = self.repository._repo[dir_id]
141 if not isinstance(tree, objects.Tree):
141 if not isinstance(tree, objects.Tree):
142 raise ChangesetError('%s is not a directory' % curdir)
142 raise ChangesetError('%s is not a directory' % curdir)
143 else:
143 else:
144 raise ChangesetError('%s have not been found' % curdir)
144 raise ChangesetError('%s have not been found' % curdir)
145
145
146 # cache all items from the given traversed tree
146 # cache all items from the given traversed tree
147 for item, stat, id in tree.iteritems():
147 for item, stat, id in tree.iteritems():
148 if curdir:
148 if curdir:
149 name = '/'.join((curdir, item))
149 name = '/'.join((curdir, item))
150 else:
150 else:
151 name = item
151 name = item
152 self._paths[name] = id
152 self._paths[name] = id
153 self._stat_modes[name] = stat
153 self._stat_modes[name] = stat
154 if not path in self._paths:
154 if not path in self._paths:
155 raise NodeDoesNotExistError("There is no file nor directory "
155 raise NodeDoesNotExistError("There is no file nor directory "
156 "at the given path '%s' at revision %s"
156 "at the given path '%s' at revision %s"
157 % (path, self.short_id))
157 % (path, self.short_id))
158 return self._paths[path]
158 return self._paths[path]
159
159
160 def _get_kind(self, path):
160 def _get_kind(self, path):
161 obj = self.repository._repo[self._get_id_for_path(path)]
161 obj = self.repository._repo[self._get_id_for_path(path)]
162 if isinstance(obj, objects.Blob):
162 if isinstance(obj, objects.Blob):
163 return NodeKind.FILE
163 return NodeKind.FILE
164 elif isinstance(obj, objects.Tree):
164 elif isinstance(obj, objects.Tree):
165 return NodeKind.DIR
165 return NodeKind.DIR
166
166
167 def _get_filectx(self, path):
167 def _get_filectx(self, path):
168 path = self._fix_path(path)
168 path = self._fix_path(path)
169 if self._get_kind(path) != NodeKind.FILE:
169 if self._get_kind(path) != NodeKind.FILE:
170 raise ChangesetError("File does not exist for revision %s at "
170 raise ChangesetError("File does not exist for revision %s at "
171 " '%s'" % (self.raw_id, path))
171 " '%s'" % (self.raw_id, path))
172 return path
172 return path
173
173
174 def _get_file_nodes(self):
174 def _get_file_nodes(self):
175 return chain(*(t[2] for t in self.walk()))
175 return chain(*(t[2] for t in self.walk()))
176
176
177 @LazyProperty
177 @LazyProperty
178 def parents(self):
178 def parents(self):
179 """
179 """
180 Returns list of parents changesets.
180 Returns list of parents changesets.
181 """
181 """
182 return [self.repository.get_changeset(parent)
182 return [self.repository.get_changeset(parent)
183 for parent in self._commit.parents]
183 for parent in self._commit.parents]
184
184
185 @LazyProperty
185 @LazyProperty
186 def children(self):
186 def children(self):
187 """
187 """
188 Returns list of children changesets.
188 Returns list of children changesets.
189 """
189 """
190 rev_filter = _git_path = rhodecode.CONFIG.get('git_rev_filter',
190 rev_filter = _git_path = rhodecode.CONFIG.get('git_rev_filter',
191 '--all').strip()
191 '--all').strip()
192 so, se = self.repository.run_git_command(
192 so, se = self.repository.run_git_command(
193 "rev-list %s --children | grep '^%s'" % (rev_filter, self.raw_id)
193 "rev-list %s --children | grep '^%s'" % (rev_filter, self.raw_id)
194 )
194 )
195
195
196 children = []
196 children = []
197 for l in so.splitlines():
197 for l in so.splitlines():
198 childs = l.split(' ')[1:]
198 childs = l.split(' ')[1:]
199 children.extend(childs)
199 children.extend(childs)
200 return [self.repository.get_changeset(cs) for cs in children]
200 return [self.repository.get_changeset(cs) for cs in children]
201
201
202 def next(self, branch=None):
202 def next(self, branch=None):
203
203
204 if branch and self.branch != branch:
204 if branch and self.branch != branch:
205 raise VCSError('Branch option used on changeset not belonging '
205 raise VCSError('Branch option used on changeset not belonging '
206 'to that branch')
206 'to that branch')
207
207
208 def _next(changeset, branch):
208 def _next(changeset, branch):
209 try:
209 try:
210 next_ = changeset.revision + 1
210 next_ = changeset.revision + 1
211 next_rev = changeset.repository.revisions[next_]
211 next_rev = changeset.repository.revisions[next_]
212 except IndexError:
212 except IndexError:
213 raise ChangesetDoesNotExistError
213 raise ChangesetDoesNotExistError
214 cs = changeset.repository.get_changeset(next_rev)
214 cs = changeset.repository.get_changeset(next_rev)
215
215
216 if branch and branch != cs.branch:
216 if branch and branch != cs.branch:
217 return _next(cs, branch)
217 return _next(cs, branch)
218
218
219 return cs
219 return cs
220
220
221 return _next(self, branch)
221 return _next(self, branch)
222
222
223 def prev(self, branch=None):
223 def prev(self, branch=None):
224 if branch and self.branch != branch:
224 if branch and self.branch != branch:
225 raise VCSError('Branch option used on changeset not belonging '
225 raise VCSError('Branch option used on changeset not belonging '
226 'to that branch')
226 'to that branch')
227
227
228 def _prev(changeset, branch):
228 def _prev(changeset, branch):
229 try:
229 try:
230 prev_ = changeset.revision - 1
230 prev_ = changeset.revision - 1
231 if prev_ < 0:
231 if prev_ < 0:
232 raise IndexError
232 raise IndexError
233 prev_rev = changeset.repository.revisions[prev_]
233 prev_rev = changeset.repository.revisions[prev_]
234 except IndexError:
234 except IndexError:
235 raise ChangesetDoesNotExistError
235 raise ChangesetDoesNotExistError
236
236
237 cs = changeset.repository.get_changeset(prev_rev)
237 cs = changeset.repository.get_changeset(prev_rev)
238
238
239 if branch and branch != cs.branch:
239 if branch and branch != cs.branch:
240 return _prev(cs, branch)
240 return _prev(cs, branch)
241
241
242 return cs
242 return cs
243
243
244 return _prev(self, branch)
244 return _prev(self, branch)
245
245
246 def diff(self, ignore_whitespace=True, context=3):
246 def diff(self, ignore_whitespace=True, context=3):
247 rev1 = self.parents[0] if self.parents else self.repository.EMPTY_CHANGESET
247 rev1 = self.parents[0] if self.parents else self.repository.EMPTY_CHANGESET
248 rev2 = self
248 rev2 = self
249 return ''.join(self.repository.get_diff(rev1, rev2,
249 return ''.join(self.repository.get_diff(rev1, rev2,
250 ignore_whitespace=ignore_whitespace,
250 ignore_whitespace=ignore_whitespace,
251 context=context))
251 context=context))
252
252
253 def get_file_mode(self, path):
253 def get_file_mode(self, path):
254 """
254 """
255 Returns stat mode of the file at the given ``path``.
255 Returns stat mode of the file at the given ``path``.
256 """
256 """
257 # ensure path is traversed
257 # ensure path is traversed
258 self._get_id_for_path(path)
258 self._get_id_for_path(path)
259 return self._stat_modes[path]
259 return self._stat_modes[path]
260
260
261 def get_file_content(self, path):
261 def get_file_content(self, path):
262 """
262 """
263 Returns content of the file at given ``path``.
263 Returns content of the file at given ``path``.
264 """
264 """
265 id = self._get_id_for_path(path)
265 id = self._get_id_for_path(path)
266 blob = self.repository._repo[id]
266 blob = self.repository._repo[id]
267 return blob.as_pretty_string()
267 return blob.as_pretty_string()
268
268
269 def get_file_size(self, path):
269 def get_file_size(self, path):
270 """
270 """
271 Returns size of the file at given ``path``.
271 Returns size of the file at given ``path``.
272 """
272 """
273 id = self._get_id_for_path(path)
273 id = self._get_id_for_path(path)
274 blob = self.repository._repo[id]
274 blob = self.repository._repo[id]
275 return blob.raw_length()
275 return blob.raw_length()
276
276
277 def get_file_changeset(self, path):
277 def get_file_changeset(self, path):
278 """
278 """
279 Returns last commit of the file at the given ``path``.
279 Returns last commit of the file at the given ``path``.
280 """
280 """
281 return self.get_file_history(path, limit=1)[0]
281 return self.get_file_history(path, limit=1)[0]
282
282
283 def get_file_history(self, path, limit=None):
283 def get_file_history(self, path, limit=None):
284 """
284 """
285 Returns history of file as reversed list of ``Changeset`` objects for
285 Returns history of file as reversed list of ``Changeset`` objects for
286 which file at given ``path`` has been modified.
286 which file at given ``path`` has been modified.
287
287
288 TODO: This function now uses os underlying 'git' and 'grep' commands
288 TODO: This function now uses os underlying 'git' and 'grep' commands
289 which is generally not good. Should be replaced with algorithm
289 which is generally not good. Should be replaced with algorithm
290 iterating commits.
290 iterating commits.
291 """
291 """
292 self._get_filectx(path)
293 cs_id = safe_str(self.id)
294 f_path = safe_str(path)
292
295
293 self._get_filectx(path)
294 if limit:
296 if limit:
295 cmd = 'log -n %s --pretty="format: %%H" -s -p %s -- "%s"' % (
297 cmd = 'log -n %s --pretty="format: %%H" -s -p %s -- "%s"' % (
296 safe_int(limit, 0), self.id, path
298 safe_int(limit, 0), cs_id, f_path
297 )
299 )
300
298 else:
301 else:
299 cmd = 'log --pretty="format: %%H" -s -p %s -- "%s"' % (
302 cmd = 'log --pretty="format: %%H" -s -p %s -- "%s"' % (
300 self.id, path
303 cs_id, f_path
301 )
304 )
302 so, se = self.repository.run_git_command(cmd)
305 so, se = self.repository.run_git_command(cmd)
303 ids = re.findall(r'[0-9a-fA-F]{40}', so)
306 ids = re.findall(r'[0-9a-fA-F]{40}', so)
304 return [self.repository.get_changeset(id) for id in ids]
307 return [self.repository.get_changeset(id) for id in ids]
305
308
306 def get_file_history_2(self, path):
309 def get_file_history_2(self, path):
307 """
310 """
308 Returns history of file as reversed list of ``Changeset`` objects for
311 Returns history of file as reversed list of ``Changeset`` objects for
309 which file at given ``path`` has been modified.
312 which file at given ``path`` has been modified.
310
313
311 """
314 """
312 self._get_filectx(path)
315 self._get_filectx(path)
313 from dulwich.walk import Walker
316 from dulwich.walk import Walker
314 include = [self.id]
317 include = [self.id]
315 walker = Walker(self.repository._repo.object_store, include,
318 walker = Walker(self.repository._repo.object_store, include,
316 paths=[path], max_entries=1)
319 paths=[path], max_entries=1)
317 return [self.repository.get_changeset(sha)
320 return [self.repository.get_changeset(sha)
318 for sha in (x.commit.id for x in walker)]
321 for sha in (x.commit.id for x in walker)]
319
322
320 def get_file_annotate(self, path):
323 def get_file_annotate(self, path):
321 """
324 """
322 Returns a generator of four element tuples with
325 Returns a generator of four element tuples with
323 lineno, sha, changeset lazy loader and line
326 lineno, sha, changeset lazy loader and line
324
327
325 TODO: This function now uses os underlying 'git' command which is
328 TODO: This function now uses os underlying 'git' command which is
326 generally not good. Should be replaced with algorithm iterating
329 generally not good. Should be replaced with algorithm iterating
327 commits.
330 commits.
328 """
331 """
329 cmd = 'blame -l --root -r %s -- "%s"' % (self.id, path)
332 cmd = 'blame -l --root -r %s -- "%s"' % (self.id, path)
330 # -l ==> outputs long shas (and we need all 40 characters)
333 # -l ==> outputs long shas (and we need all 40 characters)
331 # --root ==> doesn't put '^' character for bounderies
334 # --root ==> doesn't put '^' character for bounderies
332 # -r sha ==> blames for the given revision
335 # -r sha ==> blames for the given revision
333 so, se = self.repository.run_git_command(cmd)
336 so, se = self.repository.run_git_command(cmd)
334
337
335 for i, blame_line in enumerate(so.split('\n')[:-1]):
338 for i, blame_line in enumerate(so.split('\n')[:-1]):
336 ln_no = i + 1
339 ln_no = i + 1
337 sha, line = re.split(r' ', blame_line, 1)
340 sha, line = re.split(r' ', blame_line, 1)
338 yield (ln_no, sha, lambda: self.repository.get_changeset(sha), line)
341 yield (ln_no, sha, lambda: self.repository.get_changeset(sha), line)
339
342
340 def fill_archive(self, stream=None, kind='tgz', prefix=None,
343 def fill_archive(self, stream=None, kind='tgz', prefix=None,
341 subrepos=False):
344 subrepos=False):
342 """
345 """
343 Fills up given stream.
346 Fills up given stream.
344
347
345 :param stream: file like object.
348 :param stream: file like object.
346 :param kind: one of following: ``zip``, ``tgz`` or ``tbz2``.
349 :param kind: one of following: ``zip``, ``tgz`` or ``tbz2``.
347 Default: ``tgz``.
350 Default: ``tgz``.
348 :param prefix: name of root directory in archive.
351 :param prefix: name of root directory in archive.
349 Default is repository name and changeset's raw_id joined with dash
352 Default is repository name and changeset's raw_id joined with dash
350 (``repo-tip.<KIND>``).
353 (``repo-tip.<KIND>``).
351 :param subrepos: include subrepos in this archive.
354 :param subrepos: include subrepos in this archive.
352
355
353 :raise ImproperArchiveTypeError: If given kind is wrong.
356 :raise ImproperArchiveTypeError: If given kind is wrong.
354 :raise VcsError: If given stream is None
357 :raise VcsError: If given stream is None
355
358
356 """
359 """
357 allowed_kinds = settings.ARCHIVE_SPECS.keys()
360 allowed_kinds = settings.ARCHIVE_SPECS.keys()
358 if kind not in allowed_kinds:
361 if kind not in allowed_kinds:
359 raise ImproperArchiveTypeError('Archive kind not supported use one'
362 raise ImproperArchiveTypeError('Archive kind not supported use one'
360 'of %s', allowed_kinds)
363 'of %s', allowed_kinds)
361
364
362 if prefix is None:
365 if prefix is None:
363 prefix = '%s-%s' % (self.repository.name, self.short_id)
366 prefix = '%s-%s' % (self.repository.name, self.short_id)
364 elif prefix.startswith('/'):
367 elif prefix.startswith('/'):
365 raise VCSError("Prefix cannot start with leading slash")
368 raise VCSError("Prefix cannot start with leading slash")
366 elif prefix.strip() == '':
369 elif prefix.strip() == '':
367 raise VCSError("Prefix cannot be empty")
370 raise VCSError("Prefix cannot be empty")
368
371
369 if kind == 'zip':
372 if kind == 'zip':
370 frmt = 'zip'
373 frmt = 'zip'
371 else:
374 else:
372 frmt = 'tar'
375 frmt = 'tar'
373 _git_path = rhodecode.CONFIG.get('git_path', 'git')
376 _git_path = rhodecode.CONFIG.get('git_path', 'git')
374 cmd = '%s archive --format=%s --prefix=%s/ %s' % (_git_path,
377 cmd = '%s archive --format=%s --prefix=%s/ %s' % (_git_path,
375 frmt, prefix, self.raw_id)
378 frmt, prefix, self.raw_id)
376 if kind == 'tgz':
379 if kind == 'tgz':
377 cmd += ' | gzip -9'
380 cmd += ' | gzip -9'
378 elif kind == 'tbz2':
381 elif kind == 'tbz2':
379 cmd += ' | bzip2 -9'
382 cmd += ' | bzip2 -9'
380
383
381 if stream is None:
384 if stream is None:
382 raise VCSError('You need to pass in a valid stream for filling'
385 raise VCSError('You need to pass in a valid stream for filling'
383 ' with archival data')
386 ' with archival data')
384 popen = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True,
387 popen = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True,
385 cwd=self.repository.path)
388 cwd=self.repository.path)
386
389
387 buffer_size = 1024 * 8
390 buffer_size = 1024 * 8
388 chunk = popen.stdout.read(buffer_size)
391 chunk = popen.stdout.read(buffer_size)
389 while chunk:
392 while chunk:
390 stream.write(chunk)
393 stream.write(chunk)
391 chunk = popen.stdout.read(buffer_size)
394 chunk = popen.stdout.read(buffer_size)
392 # Make sure all descriptors would be read
395 # Make sure all descriptors would be read
393 popen.communicate()
396 popen.communicate()
394
397
395 def get_nodes(self, path):
398 def get_nodes(self, path):
396 if self._get_kind(path) != NodeKind.DIR:
399 if self._get_kind(path) != NodeKind.DIR:
397 raise ChangesetError("Directory does not exist for revision %s at "
400 raise ChangesetError("Directory does not exist for revision %s at "
398 " '%s'" % (self.revision, path))
401 " '%s'" % (self.revision, path))
399 path = self._fix_path(path)
402 path = self._fix_path(path)
400 id = self._get_id_for_path(path)
403 id = self._get_id_for_path(path)
401 tree = self.repository._repo[id]
404 tree = self.repository._repo[id]
402 dirnodes = []
405 dirnodes = []
403 filenodes = []
406 filenodes = []
404 als = self.repository.alias
407 als = self.repository.alias
405 for name, stat, id in tree.iteritems():
408 for name, stat, id in tree.iteritems():
406 if objects.S_ISGITLINK(stat):
409 if objects.S_ISGITLINK(stat):
407 dirnodes.append(SubModuleNode(name, url=None, changeset=id,
410 dirnodes.append(SubModuleNode(name, url=None, changeset=id,
408 alias=als))
411 alias=als))
409 continue
412 continue
410
413
411 obj = self.repository._repo.get_object(id)
414 obj = self.repository._repo.get_object(id)
412 if path != '':
415 if path != '':
413 obj_path = '/'.join((path, name))
416 obj_path = '/'.join((path, name))
414 else:
417 else:
415 obj_path = name
418 obj_path = name
416 if obj_path not in self._stat_modes:
419 if obj_path not in self._stat_modes:
417 self._stat_modes[obj_path] = stat
420 self._stat_modes[obj_path] = stat
418 if isinstance(obj, objects.Tree):
421 if isinstance(obj, objects.Tree):
419 dirnodes.append(DirNode(obj_path, changeset=self))
422 dirnodes.append(DirNode(obj_path, changeset=self))
420 elif isinstance(obj, objects.Blob):
423 elif isinstance(obj, objects.Blob):
421 filenodes.append(FileNode(obj_path, changeset=self, mode=stat))
424 filenodes.append(FileNode(obj_path, changeset=self, mode=stat))
422 else:
425 else:
423 raise ChangesetError("Requested object should be Tree "
426 raise ChangesetError("Requested object should be Tree "
424 "or Blob, is %r" % type(obj))
427 "or Blob, is %r" % type(obj))
425 nodes = dirnodes + filenodes
428 nodes = dirnodes + filenodes
426 for node in nodes:
429 for node in nodes:
427 if not node.path in self.nodes:
430 if not node.path in self.nodes:
428 self.nodes[node.path] = node
431 self.nodes[node.path] = node
429 nodes.sort()
432 nodes.sort()
430 return nodes
433 return nodes
431
434
432 def get_node(self, path):
435 def get_node(self, path):
433 if isinstance(path, unicode):
436 if isinstance(path, unicode):
434 path = path.encode('utf-8')
437 path = path.encode('utf-8')
435 path = self._fix_path(path)
438 path = self._fix_path(path)
436 if not path in self.nodes:
439 if not path in self.nodes:
437 try:
440 try:
438 id_ = self._get_id_for_path(path)
441 id_ = self._get_id_for_path(path)
439 except ChangesetError:
442 except ChangesetError:
440 raise NodeDoesNotExistError("Cannot find one of parents' "
443 raise NodeDoesNotExistError("Cannot find one of parents' "
441 "directories for a given path: %s" % path)
444 "directories for a given path: %s" % path)
442
445
443 _GL = lambda m: m and objects.S_ISGITLINK(m)
446 _GL = lambda m: m and objects.S_ISGITLINK(m)
444 if _GL(self._stat_modes.get(path)):
447 if _GL(self._stat_modes.get(path)):
445 node = SubModuleNode(path, url=None, changeset=id_,
448 node = SubModuleNode(path, url=None, changeset=id_,
446 alias=self.repository.alias)
449 alias=self.repository.alias)
447 else:
450 else:
448 obj = self.repository._repo.get_object(id_)
451 obj = self.repository._repo.get_object(id_)
449
452
450 if isinstance(obj, objects.Tree):
453 if isinstance(obj, objects.Tree):
451 if path == '':
454 if path == '':
452 node = RootNode(changeset=self)
455 node = RootNode(changeset=self)
453 else:
456 else:
454 node = DirNode(path, changeset=self)
457 node = DirNode(path, changeset=self)
455 node._tree = obj
458 node._tree = obj
456 elif isinstance(obj, objects.Blob):
459 elif isinstance(obj, objects.Blob):
457 node = FileNode(path, changeset=self)
460 node = FileNode(path, changeset=self)
458 node._blob = obj
461 node._blob = obj
459 else:
462 else:
460 raise NodeDoesNotExistError("There is no file nor directory "
463 raise NodeDoesNotExistError("There is no file nor directory "
461 "at the given path '%s' at revision %s"
464 "at the given path '%s' at revision %s"
462 % (path, self.short_id))
465 % (path, self.short_id))
463 # cache node
466 # cache node
464 self.nodes[path] = node
467 self.nodes[path] = node
465 return self.nodes[path]
468 return self.nodes[path]
466
469
467 @LazyProperty
470 @LazyProperty
468 def affected_files(self):
471 def affected_files(self):
469 """
472 """
470 Get's a fast accessible file changes for given changeset
473 Get's a fast accessible file changes for given changeset
471 """
474 """
472 a, m, d = self._changes_cache
475 a, m, d = self._changes_cache
473 return list(a.union(m).union(d))
476 return list(a.union(m).union(d))
474
477
475 @LazyProperty
478 @LazyProperty
476 def _diff_name_status(self):
479 def _diff_name_status(self):
477 output = []
480 output = []
478 for parent in self.parents:
481 for parent in self.parents:
479 cmd = 'diff --name-status %s %s --encoding=utf8' % (parent.raw_id,
482 cmd = 'diff --name-status %s %s --encoding=utf8' % (parent.raw_id,
480 self.raw_id)
483 self.raw_id)
481 so, se = self.repository.run_git_command(cmd)
484 so, se = self.repository.run_git_command(cmd)
482 output.append(so.strip())
485 output.append(so.strip())
483 return '\n'.join(output)
486 return '\n'.join(output)
484
487
485 @LazyProperty
488 @LazyProperty
486 def _changes_cache(self):
489 def _changes_cache(self):
487 added = set()
490 added = set()
488 modified = set()
491 modified = set()
489 deleted = set()
492 deleted = set()
490 _r = self.repository._repo
493 _r = self.repository._repo
491
494
492 parents = self.parents
495 parents = self.parents
493 if not self.parents:
496 if not self.parents:
494 parents = [EmptyChangeset()]
497 parents = [EmptyChangeset()]
495 for parent in parents:
498 for parent in parents:
496 if isinstance(parent, EmptyChangeset):
499 if isinstance(parent, EmptyChangeset):
497 oid = None
500 oid = None
498 else:
501 else:
499 oid = _r[parent.raw_id].tree
502 oid = _r[parent.raw_id].tree
500 changes = _r.object_store.tree_changes(oid, _r[self.raw_id].tree)
503 changes = _r.object_store.tree_changes(oid, _r[self.raw_id].tree)
501 for (oldpath, newpath), (_, _), (_, _) in changes:
504 for (oldpath, newpath), (_, _), (_, _) in changes:
502 if newpath and oldpath:
505 if newpath and oldpath:
503 modified.add(newpath)
506 modified.add(newpath)
504 elif newpath and not oldpath:
507 elif newpath and not oldpath:
505 added.add(newpath)
508 added.add(newpath)
506 elif not newpath and oldpath:
509 elif not newpath and oldpath:
507 deleted.add(oldpath)
510 deleted.add(oldpath)
508 return added, modified, deleted
511 return added, modified, deleted
509
512
510 def _get_paths_for_status(self, status):
513 def _get_paths_for_status(self, status):
511 """
514 """
512 Returns sorted list of paths for given ``status``.
515 Returns sorted list of paths for given ``status``.
513
516
514 :param status: one of: *added*, *modified* or *deleted*
517 :param status: one of: *added*, *modified* or *deleted*
515 """
518 """
516 a, m, d = self._changes_cache
519 a, m, d = self._changes_cache
517 return sorted({
520 return sorted({
518 'added': list(a),
521 'added': list(a),
519 'modified': list(m),
522 'modified': list(m),
520 'deleted': list(d)}[status]
523 'deleted': list(d)}[status]
521 )
524 )
522
525
523 @LazyProperty
526 @LazyProperty
524 def added(self):
527 def added(self):
525 """
528 """
526 Returns list of added ``FileNode`` objects.
529 Returns list of added ``FileNode`` objects.
527 """
530 """
528 if not self.parents:
531 if not self.parents:
529 return list(self._get_file_nodes())
532 return list(self._get_file_nodes())
530 return AddedFileNodesGenerator([n for n in
533 return AddedFileNodesGenerator([n for n in
531 self._get_paths_for_status('added')], self)
534 self._get_paths_for_status('added')], self)
532
535
533 @LazyProperty
536 @LazyProperty
534 def changed(self):
537 def changed(self):
535 """
538 """
536 Returns list of modified ``FileNode`` objects.
539 Returns list of modified ``FileNode`` objects.
537 """
540 """
538 if not self.parents:
541 if not self.parents:
539 return []
542 return []
540 return ChangedFileNodesGenerator([n for n in
543 return ChangedFileNodesGenerator([n for n in
541 self._get_paths_for_status('modified')], self)
544 self._get_paths_for_status('modified')], self)
542
545
543 @LazyProperty
546 @LazyProperty
544 def removed(self):
547 def removed(self):
545 """
548 """
546 Returns list of removed ``FileNode`` objects.
549 Returns list of removed ``FileNode`` objects.
547 """
550 """
548 if not self.parents:
551 if not self.parents:
549 return []
552 return []
550 return RemovedFileNodesGenerator([n for n in
553 return RemovedFileNodesGenerator([n for n in
551 self._get_paths_for_status('deleted')], self)
554 self._get_paths_for_status('deleted')], self)
@@ -1,163 +1,163
1 <%inherit file="/base/base.html"/>
1 <%inherit file="/base/base.html"/>
2
2
3 <%def name="title(*args)">
3 <%def name="title(*args)">
4 ${_('%s Files') % c.repo_name}
4 ${_('%s Files') % c.repo_name}
5 %if hasattr(c,'file'):
5 %if hasattr(c,'file'):
6 &middot; ${c.file.path or '\\'}
6 &middot; ${h.safe_unicode(c.file.path) or '\\'}
7 %endif
7 %endif
8 &middot; ${c.rhodecode_name}
8 &middot; ${c.rhodecode_name}
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 ${_('Files')}
12 ${_('Files')}
13 %if c.file:
13 %if c.file:
14 @ ${h.show_id(c.changeset)}
14 @ ${h.show_id(c.changeset)}
15 %endif
15 %endif
16 </%def>
16 </%def>
17
17
18 <%def name="page_nav()">
18 <%def name="page_nav()">
19 ${self.menu('repositories')}
19 ${self.menu('repositories')}
20 </%def>
20 </%def>
21
21
22 <%def name="main()">
22 <%def name="main()">
23 ${self.context_bar('files')}
23 ${self.context_bar('files')}
24 <div class="box">
24 <div class="box">
25 <!-- box / title -->
25 <!-- box / title -->
26 <div class="title">
26 <div class="title">
27 ${self.breadcrumbs()}
27 ${self.breadcrumbs()}
28 <ul class="links">
28 <ul class="links">
29 <li>
29 <li>
30 <span style="text-transform: uppercase;"><a href="#">${_('Branch')}: ${c.changeset.branch}</a></span>
30 <span style="text-transform: uppercase;"><a href="#">${_('Branch')}: ${c.changeset.branch}</a></span>
31 </li>
31 </li>
32 </ul>
32 </ul>
33 </div>
33 </div>
34 <div class="table">
34 <div class="table">
35 <div id="files_data">
35 <div id="files_data">
36 <%include file='files_ypjax.html'/>
36 <%include file='files_ypjax.html'/>
37 </div>
37 </div>
38 </div>
38 </div>
39 </div>
39 </div>
40
40
41 <script type="text/javascript">
41 <script type="text/javascript">
42 var CACHE = {};
42 var CACHE = {};
43 var CACHE_EXPIRE = 5*60*1000; //cache for 5*60s
43 var CACHE_EXPIRE = 5*60*1000; //cache for 5*60s
44 //used to construct links from the search list
44 //used to construct links from the search list
45 var url_base = '${h.url("files_home",repo_name=c.repo_name,revision='__REV__',f_path='__FPATH__')}';
45 var url_base = '${h.url("files_home",repo_name=c.repo_name,revision='__REV__',f_path='__FPATH__')}';
46 //send the nodelist request to this url
46 //send the nodelist request to this url
47 var node_list_url = '${h.url("files_nodelist_home",repo_name=c.repo_name,revision='__REV__',f_path='__FPATH__')}';
47 var node_list_url = '${h.url("files_nodelist_home",repo_name=c.repo_name,revision='__REV__',f_path='__FPATH__')}';
48 // send the node history requst to this url
48 // send the node history requst to this url
49 var node_history_url = '${h.url("files_history_home",repo_name=c.repo_name,revision='__REV__',f_path='__FPATH__')}';
49 var node_history_url = '${h.url("files_history_home",repo_name=c.repo_name,revision='__REV__',f_path='__FPATH__')}';
50
50
51 var ypjax_links = function(){
51 var ypjax_links = function(){
52 YUE.on(YUQ('.ypjax-link'), 'click',function(e){
52 YUE.on(YUQ('.ypjax-link'), 'click',function(e){
53
53
54 //don't do ypjax on middle click
54 //don't do ypjax on middle click
55 if(e.which == 2 || !History.enabled){
55 if(e.which == 2 || !History.enabled){
56 return true;
56 return true;
57 }
57 }
58
58
59 var el = e.currentTarget;
59 var el = e.currentTarget;
60 var url = el.href;
60 var url = el.href;
61
61
62 var _base_url = '${h.url("files_home",repo_name=c.repo_name,revision='',f_path='')}';
62 var _base_url = '${h.url("files_home",repo_name=c.repo_name,revision='',f_path='')}';
63 _base_url = _base_url.replace('//','/')
63 _base_url = _base_url.replace('//','/')
64
64
65 //extract rev and the f_path from url.
65 //extract rev and the f_path from url.
66 parts = url.split(_base_url)
66 parts = url.split(_base_url)
67 if(parts.length != 2){
67 if(parts.length != 2){
68 return false;
68 return false;
69 }
69 }
70
70
71 var parts2 = parts[1].split('/');
71 var parts2 = parts[1].split('/');
72 var rev = parts2.shift(); // pop the first element which is the revision
72 var rev = parts2.shift(); // pop the first element which is the revision
73 var f_path = parts2.join('/');
73 var f_path = parts2.join('/');
74
74
75 //page title make this consistent with title() mako function on top
75 //page title make this consistent with title() mako function on top
76 var title = "${_('%s Files') % c.repo_name}" + " &middot; " + (f_path || '\\') + " &middot; " + "${c.rhodecode_name}";
76 var title = "${_('%s Files') % c.repo_name}" + " &middot; " + (f_path || '\\') + " &middot; " + "${c.rhodecode_name}";
77
77
78 var _node_list_url = node_list_url.replace('__REV__',rev).replace('__FPATH__', f_path);
78 var _node_list_url = node_list_url.replace('__REV__',rev).replace('__FPATH__', f_path);
79 var _url_base = url_base.replace('__REV__',rev);
79 var _url_base = url_base.replace('__REV__',rev);
80
80
81 // Change our States and save some data for handling events
81 // Change our States and save some data for handling events
82 var data = {url:url,title:title, url_base:_url_base,
82 var data = {url:url,title:title, url_base:_url_base,
83 node_list_url:_node_list_url, rev:rev, f_path:f_path};
83 node_list_url:_node_list_url, rev:rev, f_path:f_path};
84 History.pushState(data, title, url);
84 History.pushState(data, title, url);
85
85
86 //now we're sure that we can do ypjax things
86 //now we're sure that we can do ypjax things
87 YUE.preventDefault(e);
87 YUE.preventDefault(e);
88 return false;
88 return false;
89 });
89 });
90 }
90 }
91
91
92 var callbacks = function(State){
92 var callbacks = function(State){
93 ypjax_links();
93 ypjax_links();
94 tooltip_activate();
94 tooltip_activate();
95 fileBrowserListeners(State.url, State.data.node_list_url, State.data.url_base);
95 fileBrowserListeners(State.url, State.data.node_list_url, State.data.url_base);
96
96
97 if(YUD.get('hlcode')){
97 if(YUD.get('hlcode')){
98 YUE.on('hlcode', 'mouseup', getSelectionLink);
98 YUE.on('hlcode', 'mouseup', getSelectionLink);
99 }
99 }
100 //console.log(State);
100 //console.log(State);
101 if(YUD.get('load_node_history')){
101 if(YUD.get('load_node_history')){
102 //remove all listeners due to problems of history state
102 //remove all listeners due to problems of history state
103 YUE.removeListener('load_node_history', 'click');
103 YUE.removeListener('load_node_history', 'click');
104 YUE.on('load_node_history', 'click', function(e){
104 YUE.on('load_node_history', 'click', function(e){
105 var _url = node_history_url.replace('__REV__',State.data.rev).replace('__FPATH__', State.data.f_path);
105 var _url = node_history_url.replace('__REV__',State.data.rev).replace('__FPATH__', State.data.f_path);
106 ypjax(_url, 'node_history', function(o){
106 ypjax(_url, 'node_history', function(o){
107 tooltip_activate();
107 tooltip_activate();
108 })
108 })
109 });
109 });
110 }
110 }
111 // Inform Google Analytics of the change
111 // Inform Google Analytics of the change
112 if ( typeof window.pageTracker !== 'undefined' ) {
112 if ( typeof window.pageTracker !== 'undefined' ) {
113 window.pageTracker._trackPageview(State.url);
113 window.pageTracker._trackPageview(State.url);
114 }
114 }
115 }
115 }
116
116
117 YUE.onDOMReady(function(){
117 YUE.onDOMReady(function(){
118 ypjax_links();
118 ypjax_links();
119 var container = 'files_data';
119 var container = 'files_data';
120 //Bind to StateChange Event
120 //Bind to StateChange Event
121 History.Adapter.bind(window,'statechange',function(){
121 History.Adapter.bind(window,'statechange',function(){
122 var State = History.getState();
122 var State = History.getState();
123 cache_key = State.url;
123 cache_key = State.url;
124 //check if we have this request in cache maybe ?
124 //check if we have this request in cache maybe ?
125 var _cache_obj = CACHE[cache_key];
125 var _cache_obj = CACHE[cache_key];
126 var _cur_time = new Date().getTime();
126 var _cur_time = new Date().getTime();
127 // get from cache if it's there and not yet expired !
127 // get from cache if it's there and not yet expired !
128 if(_cache_obj !== undefined && _cache_obj[0] > _cur_time){
128 if(_cache_obj !== undefined && _cache_obj[0] > _cur_time){
129 YUD.get(container).innerHTML=_cache_obj[1];
129 YUD.get(container).innerHTML=_cache_obj[1];
130 YUD.setStyle(container,'opacity','1.0');
130 YUD.setStyle(container,'opacity','1.0');
131
131
132 //callbacks after ypjax call
132 //callbacks after ypjax call
133 callbacks(State);
133 callbacks(State);
134 }
134 }
135 else{
135 else{
136 ypjax(State.url,container,function(o){
136 ypjax(State.url,container,function(o){
137 //callbacks after ypjax call
137 //callbacks after ypjax call
138 callbacks(State);
138 callbacks(State);
139 if (o !== undefined){
139 if (o !== undefined){
140 //store our request in cache
140 //store our request in cache
141 var _expire_on = new Date().getTime()+CACHE_EXPIRE;
141 var _expire_on = new Date().getTime()+CACHE_EXPIRE;
142 CACHE[cache_key] = [_expire_on, o.responseText];
142 CACHE[cache_key] = [_expire_on, o.responseText];
143 }
143 }
144 });
144 });
145 }
145 }
146 });
146 });
147
147
148 // init the search filter
148 // init the search filter
149 var _State = {
149 var _State = {
150 url: "${h.url.current()}",
150 url: "${h.url.current()}",
151 data: {
151 data: {
152 node_list_url: node_list_url.replace('__REV__',"${c.changeset.raw_id}").replace('__FPATH__', "${h.safe_unicode(c.file.path)}"),
152 node_list_url: node_list_url.replace('__REV__',"${c.changeset.raw_id}").replace('__FPATH__', "${h.safe_unicode(c.file.path)}"),
153 url_base: url_base.replace('__REV__',"${c.changeset.raw_id}"),
153 url_base: url_base.replace('__REV__',"${c.changeset.raw_id}"),
154 rev:"${c.changeset.raw_id}",
154 rev:"${c.changeset.raw_id}",
155 f_path: "${h.safe_unicode(c.file.path)}"
155 f_path: "${h.safe_unicode(c.file.path)}"
156 }
156 }
157 }
157 }
158 fileBrowserListeners(_State.url, _State.data.node_list_url, _State.data.url_base);
158 fileBrowserListeners(_State.url, _State.data.node_list_url, _State.data.url_base);
159 });
159 });
160
160
161 </script>
161 </script>
162
162
163 </%def>
163 </%def>
General Comments 0
You need to be logged in to leave comments. Login now