##// END OF EJS Templates
hg: improve implementations of `successors` and `precursors` properties of Mercurial changesets...
Manuel Jacob -
r7451:75f746df default
parent child Browse files
Show More
@@ -1,451 +1,455 b''
1 import os
1 import os
2 import posixpath
2 import posixpath
3
3
4 from kallithea.lib.vcs.conf import settings
4 from kallithea.lib.vcs.conf import settings
5 from kallithea.lib.vcs.backends.base import BaseChangeset
5 from kallithea.lib.vcs.backends.base import BaseChangeset
6 from kallithea.lib.vcs.exceptions import (
6 from kallithea.lib.vcs.exceptions import (
7 ChangesetDoesNotExistError, ChangesetError, ImproperArchiveTypeError,
7 ChangesetDoesNotExistError, ChangesetError, ImproperArchiveTypeError,
8 NodeDoesNotExistError, VCSError
8 NodeDoesNotExistError, VCSError
9 )
9 )
10 from kallithea.lib.vcs.nodes import (
10 from kallithea.lib.vcs.nodes import (
11 AddedFileNodesGenerator, ChangedFileNodesGenerator, DirNode, FileNode,
11 AddedFileNodesGenerator, ChangedFileNodesGenerator, DirNode, FileNode,
12 NodeKind, RemovedFileNodesGenerator, RootNode, SubModuleNode
12 NodeKind, RemovedFileNodesGenerator, RootNode, SubModuleNode
13 )
13 )
14 from kallithea.lib.vcs.utils import safe_str, safe_unicode, date_fromtimestamp
14 from kallithea.lib.vcs.utils import safe_str, safe_unicode, date_fromtimestamp
15 from kallithea.lib.vcs.utils.lazy import LazyProperty
15 from kallithea.lib.vcs.utils.lazy import LazyProperty
16 from kallithea.lib.vcs.utils.paths import get_dirs_for_path
16 from kallithea.lib.vcs.utils.paths import get_dirs_for_path
17 from kallithea.lib.vcs.utils.hgcompat import archival, hex
17 from kallithea.lib.vcs.utils.hgcompat import archival, hex
18
18
19 from mercurial import obsolete
19 from mercurial import obsolete
20
20
21
21
22 class MercurialChangeset(BaseChangeset):
22 class MercurialChangeset(BaseChangeset):
23 """
23 """
24 Represents state of the repository at the single revision.
24 Represents state of the repository at the single revision.
25 """
25 """
26
26
27 def __init__(self, repository, revision):
27 def __init__(self, repository, revision):
28 self.repository = repository
28 self.repository = repository
29 assert isinstance(revision, basestring), repr(revision)
29 assert isinstance(revision, basestring), repr(revision)
30 self.raw_id = revision
30 self.raw_id = revision
31 self._ctx = repository._repo[revision]
31 self._ctx = repository._repo[revision]
32 self.revision = self._ctx._rev
32 self.revision = self._ctx._rev
33 self.nodes = {}
33 self.nodes = {}
34
34
35 @LazyProperty
35 @LazyProperty
36 def tags(self):
36 def tags(self):
37 return map(safe_unicode, self._ctx.tags())
37 return map(safe_unicode, self._ctx.tags())
38
38
39 @LazyProperty
39 @LazyProperty
40 def branch(self):
40 def branch(self):
41 return safe_unicode(self._ctx.branch())
41 return safe_unicode(self._ctx.branch())
42
42
43 @LazyProperty
43 @LazyProperty
44 def branches(self):
44 def branches(self):
45 return [safe_unicode(self._ctx.branch())]
45 return [safe_unicode(self._ctx.branch())]
46
46
47 @LazyProperty
47 @LazyProperty
48 def closesbranch(self):
48 def closesbranch(self):
49 return self._ctx.closesbranch()
49 return self._ctx.closesbranch()
50
50
51 @LazyProperty
51 @LazyProperty
52 def obsolete(self):
52 def obsolete(self):
53 return self._ctx.obsolete()
53 return self._ctx.obsolete()
54
54
55 @LazyProperty
55 @LazyProperty
56 def bumped(self):
56 def bumped(self):
57 try:
57 try:
58 return self._ctx.phasedivergent()
58 return self._ctx.phasedivergent()
59 except AttributeError: # renamed in Mercurial 4.6 (9fa874fb34e1)
59 except AttributeError: # renamed in Mercurial 4.6 (9fa874fb34e1)
60 return self._ctx.bumped()
60 return self._ctx.bumped()
61
61
62 @LazyProperty
62 @LazyProperty
63 def divergent(self):
63 def divergent(self):
64 try:
64 try:
65 return self._ctx.contentdivergent()
65 return self._ctx.contentdivergent()
66 except AttributeError: # renamed in Mercurial 4.6 (8b2d7684407b)
66 except AttributeError: # renamed in Mercurial 4.6 (8b2d7684407b)
67 return self._ctx.divergent()
67 return self._ctx.divergent()
68
68
69 @LazyProperty
69 @LazyProperty
70 def extinct(self):
70 def extinct(self):
71 return self._ctx.extinct()
71 return self._ctx.extinct()
72
72
73 @LazyProperty
73 @LazyProperty
74 def unstable(self):
74 def unstable(self):
75 try:
75 try:
76 return self._ctx.orphan()
76 return self._ctx.orphan()
77 except AttributeError: # renamed in Mercurial 4.6 (03039ff3082b)
77 except AttributeError: # renamed in Mercurial 4.6 (03039ff3082b)
78 return self._ctx.unstable()
78 return self._ctx.unstable()
79
79
80 @LazyProperty
80 @LazyProperty
81 def phase(self):
81 def phase(self):
82 if(self._ctx.phase() == 1):
82 if(self._ctx.phase() == 1):
83 return 'Draft'
83 return 'Draft'
84 elif(self._ctx.phase() == 2):
84 elif(self._ctx.phase() == 2):
85 return 'Secret'
85 return 'Secret'
86 else:
86 else:
87 return ''
87 return ''
88
88
89 @LazyProperty
89 @LazyProperty
90 def successors(self):
90 def successors(self):
91 try:
91 try:
92 # This works starting from Mercurial 4.3: the function `successorssets` was moved to the mercurial.obsutil module and gained the `closest` parameter.
92 from mercurial import obsutil
93 from mercurial import obsutil
93 successors = obsutil.successorssets(self._ctx._repo, self._ctx.node())
94 successors = obsutil.successorssets(self._ctx._repo, self._ctx.node(), closest=True)
94 except ImportError: # moved in Mercurial 4.3 (4f49810a1011)
95 except ImportError:
96 # fallback for older versions
95 successors = obsolete.successorssets(self._ctx._repo, self._ctx.node())
97 successors = obsolete.successorssets(self._ctx._repo, self._ctx.node())
96 if successors:
98 if successors:
97 # flatten the list here handles both divergent (len > 1)
99 # flatten the list here handles both divergent (len > 1)
98 # and the usual case (len = 1)
100 # and the usual case (len = 1)
99 successors = [hex(n)[:12] for sub in successors for n in sub if n != self._ctx.node()]
101 successors = [hex(n)[:12] for sub in successors for n in sub if n != self._ctx.node()]
100
102
101 return successors
103 return successors
102
104
103 @LazyProperty
105 @LazyProperty
104 def predecessors(self):
106 def predecessors(self):
105 predecessors = set()
106 nm = self._ctx._repo.changelog.nodemap
107 try:
107 try:
108 raw_predecessors = self._ctx._repo.obsstore.predecessors
108 # This works starting from Mercurial 4.3: the function `closestpredecessors` was added.
109 except AttributeError: # renamed in Mercurial 4.4 (d5acd967f95a)
109 from mercurial import obsutil
110 raw_predecessors = self._ctx._repo.obsstore.precursors
110 return [hex(n)[:12] for n in obsutil.closestpredecessors(self._ctx._repo, self._ctx.node())]
111 for p in raw_predecessors.get(self._ctx.node(), ()):
111 except ImportError:
112 pr = nm.get(p[0])
112 # fallback for older versions
113 if pr is not None:
113 predecessors = set()
114 predecessors.add(hex(p[0])[:12])
114 nm = self._ctx._repo.changelog.nodemap
115 return predecessors
115 for p in self._ctx._repo.obsstore.precursors.get(self._ctx.node(), ()):
116 pr = nm.get(p[0])
117 if pr is not None:
118 predecessors.add(hex(p[0])[:12])
119 return predecessors
116
120
117 @LazyProperty
121 @LazyProperty
118 def bookmarks(self):
122 def bookmarks(self):
119 return map(safe_unicode, self._ctx.bookmarks())
123 return map(safe_unicode, self._ctx.bookmarks())
120
124
121 @LazyProperty
125 @LazyProperty
122 def message(self):
126 def message(self):
123 return safe_unicode(self._ctx.description())
127 return safe_unicode(self._ctx.description())
124
128
125 @LazyProperty
129 @LazyProperty
126 def committer(self):
130 def committer(self):
127 return safe_unicode(self.author)
131 return safe_unicode(self.author)
128
132
129 @LazyProperty
133 @LazyProperty
130 def author(self):
134 def author(self):
131 return safe_unicode(self._ctx.user())
135 return safe_unicode(self._ctx.user())
132
136
133 @LazyProperty
137 @LazyProperty
134 def date(self):
138 def date(self):
135 return date_fromtimestamp(*self._ctx.date())
139 return date_fromtimestamp(*self._ctx.date())
136
140
137 @LazyProperty
141 @LazyProperty
138 def _timestamp(self):
142 def _timestamp(self):
139 return self._ctx.date()[0]
143 return self._ctx.date()[0]
140
144
141 @LazyProperty
145 @LazyProperty
142 def status(self):
146 def status(self):
143 """
147 """
144 Returns modified, added, removed, deleted files for current changeset
148 Returns modified, added, removed, deleted files for current changeset
145 """
149 """
146 return self.repository._repo.status(self._ctx.p1().node(),
150 return self.repository._repo.status(self._ctx.p1().node(),
147 self._ctx.node())
151 self._ctx.node())
148
152
149 @LazyProperty
153 @LazyProperty
150 def _file_paths(self):
154 def _file_paths(self):
151 return list(self._ctx)
155 return list(self._ctx)
152
156
153 @LazyProperty
157 @LazyProperty
154 def _dir_paths(self):
158 def _dir_paths(self):
155 p = list(set(get_dirs_for_path(*self._file_paths)))
159 p = list(set(get_dirs_for_path(*self._file_paths)))
156 p.insert(0, '')
160 p.insert(0, '')
157 return p
161 return p
158
162
159 @LazyProperty
163 @LazyProperty
160 def _paths(self):
164 def _paths(self):
161 return self._dir_paths + self._file_paths
165 return self._dir_paths + self._file_paths
162
166
163 @LazyProperty
167 @LazyProperty
164 def id(self):
168 def id(self):
165 if self.last:
169 if self.last:
166 return u'tip'
170 return u'tip'
167 return self.short_id
171 return self.short_id
168
172
169 @LazyProperty
173 @LazyProperty
170 def short_id(self):
174 def short_id(self):
171 return self.raw_id[:12]
175 return self.raw_id[:12]
172
176
173 @LazyProperty
177 @LazyProperty
174 def parents(self):
178 def parents(self):
175 """
179 """
176 Returns list of parents changesets.
180 Returns list of parents changesets.
177 """
181 """
178 return [self.repository.get_changeset(parent.rev())
182 return [self.repository.get_changeset(parent.rev())
179 for parent in self._ctx.parents() if parent.rev() >= 0]
183 for parent in self._ctx.parents() if parent.rev() >= 0]
180
184
181 @LazyProperty
185 @LazyProperty
182 def children(self):
186 def children(self):
183 """
187 """
184 Returns list of children changesets.
188 Returns list of children changesets.
185 """
189 """
186 return [self.repository.get_changeset(child.rev())
190 return [self.repository.get_changeset(child.rev())
187 for child in self._ctx.children() if child.rev() >= 0]
191 for child in self._ctx.children() if child.rev() >= 0]
188
192
189 def next(self, branch=None):
193 def next(self, branch=None):
190 if branch and self.branch != branch:
194 if branch and self.branch != branch:
191 raise VCSError('Branch option used on changeset not belonging '
195 raise VCSError('Branch option used on changeset not belonging '
192 'to that branch')
196 'to that branch')
193
197
194 cs = self
198 cs = self
195 while True:
199 while True:
196 try:
200 try:
197 next_ = cs.repository.revisions.index(cs.raw_id) + 1
201 next_ = cs.repository.revisions.index(cs.raw_id) + 1
198 next_rev = cs.repository.revisions[next_]
202 next_rev = cs.repository.revisions[next_]
199 except IndexError:
203 except IndexError:
200 raise ChangesetDoesNotExistError
204 raise ChangesetDoesNotExistError
201 cs = cs.repository.get_changeset(next_rev)
205 cs = cs.repository.get_changeset(next_rev)
202
206
203 if not branch or branch == cs.branch:
207 if not branch or branch == cs.branch:
204 return cs
208 return cs
205
209
206 def prev(self, branch=None):
210 def prev(self, branch=None):
207 if branch and self.branch != branch:
211 if branch and self.branch != branch:
208 raise VCSError('Branch option used on changeset not belonging '
212 raise VCSError('Branch option used on changeset not belonging '
209 'to that branch')
213 'to that branch')
210
214
211 cs = self
215 cs = self
212 while True:
216 while True:
213 try:
217 try:
214 prev_ = cs.repository.revisions.index(cs.raw_id) - 1
218 prev_ = cs.repository.revisions.index(cs.raw_id) - 1
215 if prev_ < 0:
219 if prev_ < 0:
216 raise IndexError
220 raise IndexError
217 prev_rev = cs.repository.revisions[prev_]
221 prev_rev = cs.repository.revisions[prev_]
218 except IndexError:
222 except IndexError:
219 raise ChangesetDoesNotExistError
223 raise ChangesetDoesNotExistError
220 cs = cs.repository.get_changeset(prev_rev)
224 cs = cs.repository.get_changeset(prev_rev)
221
225
222 if not branch or branch == cs.branch:
226 if not branch or branch == cs.branch:
223 return cs
227 return cs
224
228
225 def diff(self):
229 def diff(self):
226 # Only used for feed diffstat
230 # Only used for feed diffstat
227 return ''.join(self._ctx.diff())
231 return ''.join(self._ctx.diff())
228
232
229 def _fix_path(self, path):
233 def _fix_path(self, path):
230 """
234 """
231 Paths are stored without trailing slash so we need to get rid off it if
235 Paths are stored without trailing slash so we need to get rid off it if
232 needed. Also mercurial keeps filenodes as str so we need to decode
236 needed. Also mercurial keeps filenodes as str so we need to decode
233 from unicode to str
237 from unicode to str
234 """
238 """
235 if path.endswith('/'):
239 if path.endswith('/'):
236 path = path.rstrip('/')
240 path = path.rstrip('/')
237
241
238 return safe_str(path)
242 return safe_str(path)
239
243
240 def _get_kind(self, path):
244 def _get_kind(self, path):
241 path = self._fix_path(path)
245 path = self._fix_path(path)
242 if path in self._file_paths:
246 if path in self._file_paths:
243 return NodeKind.FILE
247 return NodeKind.FILE
244 elif path in self._dir_paths:
248 elif path in self._dir_paths:
245 return NodeKind.DIR
249 return NodeKind.DIR
246 else:
250 else:
247 raise ChangesetError("Node does not exist at the given path '%s'"
251 raise ChangesetError("Node does not exist at the given path '%s'"
248 % (path))
252 % (path))
249
253
250 def _get_filectx(self, path):
254 def _get_filectx(self, path):
251 path = self._fix_path(path)
255 path = self._fix_path(path)
252 if self._get_kind(path) != NodeKind.FILE:
256 if self._get_kind(path) != NodeKind.FILE:
253 raise ChangesetError("File does not exist for revision %s at "
257 raise ChangesetError("File does not exist for revision %s at "
254 " '%s'" % (self.raw_id, path))
258 " '%s'" % (self.raw_id, path))
255 return self._ctx.filectx(path)
259 return self._ctx.filectx(path)
256
260
257 def _extract_submodules(self):
261 def _extract_submodules(self):
258 """
262 """
259 returns a dictionary with submodule information from substate file
263 returns a dictionary with submodule information from substate file
260 of hg repository
264 of hg repository
261 """
265 """
262 return self._ctx.substate
266 return self._ctx.substate
263
267
264 def get_file_mode(self, path):
268 def get_file_mode(self, path):
265 """
269 """
266 Returns stat mode of the file at the given ``path``.
270 Returns stat mode of the file at the given ``path``.
267 """
271 """
268 fctx = self._get_filectx(path)
272 fctx = self._get_filectx(path)
269 if 'x' in fctx.flags():
273 if 'x' in fctx.flags():
270 return 0100755
274 return 0100755
271 else:
275 else:
272 return 0100644
276 return 0100644
273
277
274 def get_file_content(self, path):
278 def get_file_content(self, path):
275 """
279 """
276 Returns content of the file at given ``path``.
280 Returns content of the file at given ``path``.
277 """
281 """
278 fctx = self._get_filectx(path)
282 fctx = self._get_filectx(path)
279 return fctx.data()
283 return fctx.data()
280
284
281 def get_file_size(self, path):
285 def get_file_size(self, path):
282 """
286 """
283 Returns size of the file at given ``path``.
287 Returns size of the file at given ``path``.
284 """
288 """
285 fctx = self._get_filectx(path)
289 fctx = self._get_filectx(path)
286 return fctx.size()
290 return fctx.size()
287
291
288 def get_file_changeset(self, path):
292 def get_file_changeset(self, path):
289 """
293 """
290 Returns last commit of the file at the given ``path``.
294 Returns last commit of the file at the given ``path``.
291 """
295 """
292 return self.get_file_history(path, limit=1)[0]
296 return self.get_file_history(path, limit=1)[0]
293
297
294 def get_file_history(self, path, limit=None):
298 def get_file_history(self, path, limit=None):
295 """
299 """
296 Returns history of file as reversed list of ``Changeset`` objects for
300 Returns history of file as reversed list of ``Changeset`` objects for
297 which file at given ``path`` has been modified.
301 which file at given ``path`` has been modified.
298 """
302 """
299 fctx = self._get_filectx(path)
303 fctx = self._get_filectx(path)
300 hist = []
304 hist = []
301 cnt = 0
305 cnt = 0
302 for cs in reversed([x for x in fctx.filelog()]):
306 for cs in reversed([x for x in fctx.filelog()]):
303 cnt += 1
307 cnt += 1
304 hist.append(hex(fctx.filectx(cs).node()))
308 hist.append(hex(fctx.filectx(cs).node()))
305 if limit is not None and cnt == limit:
309 if limit is not None and cnt == limit:
306 break
310 break
307
311
308 return [self.repository.get_changeset(node) for node in hist]
312 return [self.repository.get_changeset(node) for node in hist]
309
313
310 def get_file_annotate(self, path):
314 def get_file_annotate(self, path):
311 """
315 """
312 Returns a generator of four element tuples with
316 Returns a generator of four element tuples with
313 lineno, sha, changeset lazy loader and line
317 lineno, sha, changeset lazy loader and line
314 """
318 """
315 annotations = self._get_filectx(path).annotate()
319 annotations = self._get_filectx(path).annotate()
316 try:
320 try:
317 annotation_lines = [(annotateline.fctx, annotateline.text) for annotateline in annotations]
321 annotation_lines = [(annotateline.fctx, annotateline.text) for annotateline in annotations]
318 except AttributeError: # annotateline was introduced in Mercurial 4.6 (b33b91ca2ec2)
322 except AttributeError: # annotateline was introduced in Mercurial 4.6 (b33b91ca2ec2)
319 try:
323 try:
320 annotation_lines = [(aline.fctx, l) for aline, l in annotations]
324 annotation_lines = [(aline.fctx, l) for aline, l in annotations]
321 except AttributeError: # aline.fctx was introduced in Mercurial 4.4
325 except AttributeError: # aline.fctx was introduced in Mercurial 4.4
322 annotation_lines = [(aline[0], l) for aline, l in annotations]
326 annotation_lines = [(aline[0], l) for aline, l in annotations]
323 for i, (fctx, l) in enumerate(annotation_lines):
327 for i, (fctx, l) in enumerate(annotation_lines):
324 sha = fctx.hex()
328 sha = fctx.hex()
325 yield (i + 1, sha, lambda sha=sha, l=l: self.repository.get_changeset(sha), l)
329 yield (i + 1, sha, lambda sha=sha, l=l: self.repository.get_changeset(sha), l)
326
330
327 def fill_archive(self, stream=None, kind='tgz', prefix=None,
331 def fill_archive(self, stream=None, kind='tgz', prefix=None,
328 subrepos=False):
332 subrepos=False):
329 """
333 """
330 Fills up given stream.
334 Fills up given stream.
331
335
332 :param stream: file like object.
336 :param stream: file like object.
333 :param kind: one of following: ``zip``, ``tgz`` or ``tbz2``.
337 :param kind: one of following: ``zip``, ``tgz`` or ``tbz2``.
334 Default: ``tgz``.
338 Default: ``tgz``.
335 :param prefix: name of root directory in archive.
339 :param prefix: name of root directory in archive.
336 Default is repository name and changeset's raw_id joined with dash
340 Default is repository name and changeset's raw_id joined with dash
337 (``repo-tip.<KIND>``).
341 (``repo-tip.<KIND>``).
338 :param subrepos: include subrepos in this archive.
342 :param subrepos: include subrepos in this archive.
339
343
340 :raise ImproperArchiveTypeError: If given kind is wrong.
344 :raise ImproperArchiveTypeError: If given kind is wrong.
341 :raise VcsError: If given stream is None
345 :raise VcsError: If given stream is None
342 """
346 """
343
347
344 allowed_kinds = settings.ARCHIVE_SPECS.keys()
348 allowed_kinds = settings.ARCHIVE_SPECS.keys()
345 if kind not in allowed_kinds:
349 if kind not in allowed_kinds:
346 raise ImproperArchiveTypeError('Archive kind not supported use one'
350 raise ImproperArchiveTypeError('Archive kind not supported use one'
347 'of %s', allowed_kinds)
351 'of %s', allowed_kinds)
348
352
349 if stream is None:
353 if stream is None:
350 raise VCSError('You need to pass in a valid stream for filling'
354 raise VCSError('You need to pass in a valid stream for filling'
351 ' with archival data')
355 ' with archival data')
352
356
353 if prefix is None:
357 if prefix is None:
354 prefix = '%s-%s' % (self.repository.name, self.short_id)
358 prefix = '%s-%s' % (self.repository.name, self.short_id)
355 elif prefix.startswith('/'):
359 elif prefix.startswith('/'):
356 raise VCSError("Prefix cannot start with leading slash")
360 raise VCSError("Prefix cannot start with leading slash")
357 elif prefix.strip() == '':
361 elif prefix.strip() == '':
358 raise VCSError("Prefix cannot be empty")
362 raise VCSError("Prefix cannot be empty")
359
363
360 archival.archive(self.repository._repo, stream, self.raw_id,
364 archival.archive(self.repository._repo, stream, self.raw_id,
361 kind, prefix=prefix, subrepos=subrepos)
365 kind, prefix=prefix, subrepos=subrepos)
362
366
363 def get_nodes(self, path):
367 def get_nodes(self, path):
364 """
368 """
365 Returns combined ``DirNode`` and ``FileNode`` objects list representing
369 Returns combined ``DirNode`` and ``FileNode`` objects list representing
366 state of changeset at the given ``path``. If node at the given ``path``
370 state of changeset at the given ``path``. If node at the given ``path``
367 is not instance of ``DirNode``, ChangesetError would be raised.
371 is not instance of ``DirNode``, ChangesetError would be raised.
368 """
372 """
369
373
370 if self._get_kind(path) != NodeKind.DIR:
374 if self._get_kind(path) != NodeKind.DIR:
371 raise ChangesetError("Directory does not exist for revision %s at "
375 raise ChangesetError("Directory does not exist for revision %s at "
372 " '%s'" % (self.revision, path))
376 " '%s'" % (self.revision, path))
373 path = self._fix_path(path)
377 path = self._fix_path(path)
374
378
375 filenodes = [FileNode(f, changeset=self) for f in self._file_paths
379 filenodes = [FileNode(f, changeset=self) for f in self._file_paths
376 if os.path.dirname(f) == path]
380 if os.path.dirname(f) == path]
377 dirs = path == '' and '' or [d for d in self._dir_paths
381 dirs = path == '' and '' or [d for d in self._dir_paths
378 if d and posixpath.dirname(d) == path]
382 if d and posixpath.dirname(d) == path]
379 dirnodes = [DirNode(d, changeset=self) for d in dirs
383 dirnodes = [DirNode(d, changeset=self) for d in dirs
380 if os.path.dirname(d) == path]
384 if os.path.dirname(d) == path]
381
385
382 als = self.repository.alias
386 als = self.repository.alias
383 for k, vals in self._extract_submodules().iteritems():
387 for k, vals in self._extract_submodules().iteritems():
384 #vals = url,rev,type
388 #vals = url,rev,type
385 loc = vals[0]
389 loc = vals[0]
386 cs = vals[1]
390 cs = vals[1]
387 dirnodes.append(SubModuleNode(k, url=loc, changeset=cs,
391 dirnodes.append(SubModuleNode(k, url=loc, changeset=cs,
388 alias=als))
392 alias=als))
389 nodes = dirnodes + filenodes
393 nodes = dirnodes + filenodes
390 # cache nodes
394 # cache nodes
391 for node in nodes:
395 for node in nodes:
392 self.nodes[node.path] = node
396 self.nodes[node.path] = node
393 nodes.sort()
397 nodes.sort()
394
398
395 return nodes
399 return nodes
396
400
397 def get_node(self, path):
401 def get_node(self, path):
398 """
402 """
399 Returns ``Node`` object from the given ``path``. If there is no node at
403 Returns ``Node`` object from the given ``path``. If there is no node at
400 the given ``path``, ``ChangesetError`` would be raised.
404 the given ``path``, ``ChangesetError`` would be raised.
401 """
405 """
402
406
403 path = self._fix_path(path)
407 path = self._fix_path(path)
404
408
405 if path not in self.nodes:
409 if path not in self.nodes:
406 if path in self._file_paths:
410 if path in self._file_paths:
407 node = FileNode(path, changeset=self)
411 node = FileNode(path, changeset=self)
408 elif path in self._dir_paths or path in self._dir_paths:
412 elif path in self._dir_paths or path in self._dir_paths:
409 if path == '':
413 if path == '':
410 node = RootNode(changeset=self)
414 node = RootNode(changeset=self)
411 else:
415 else:
412 node = DirNode(path, changeset=self)
416 node = DirNode(path, changeset=self)
413 else:
417 else:
414 raise NodeDoesNotExistError("There is no file nor directory "
418 raise NodeDoesNotExistError("There is no file nor directory "
415 "at the given path: '%s' at revision %s"
419 "at the given path: '%s' at revision %s"
416 % (path, self.short_id))
420 % (path, self.short_id))
417 # cache node
421 # cache node
418 self.nodes[path] = node
422 self.nodes[path] = node
419 return self.nodes[path]
423 return self.nodes[path]
420
424
421 @LazyProperty
425 @LazyProperty
422 def affected_files(self):
426 def affected_files(self):
423 """
427 """
424 Gets a fast accessible file changes for given changeset
428 Gets a fast accessible file changes for given changeset
425 """
429 """
426 return self._ctx.files()
430 return self._ctx.files()
427
431
428 @property
432 @property
429 def added(self):
433 def added(self):
430 """
434 """
431 Returns list of added ``FileNode`` objects.
435 Returns list of added ``FileNode`` objects.
432 """
436 """
433 return AddedFileNodesGenerator([n for n in self.status[1]], self)
437 return AddedFileNodesGenerator([n for n in self.status[1]], self)
434
438
435 @property
439 @property
436 def changed(self):
440 def changed(self):
437 """
441 """
438 Returns list of modified ``FileNode`` objects.
442 Returns list of modified ``FileNode`` objects.
439 """
443 """
440 return ChangedFileNodesGenerator([n for n in self.status[0]], self)
444 return ChangedFileNodesGenerator([n for n in self.status[0]], self)
441
445
442 @property
446 @property
443 def removed(self):
447 def removed(self):
444 """
448 """
445 Returns list of removed ``FileNode`` objects.
449 Returns list of removed ``FileNode`` objects.
446 """
450 """
447 return RemovedFileNodesGenerator([n for n in self.status[2]], self)
451 return RemovedFileNodesGenerator([n for n in self.status[2]], self)
448
452
449 @LazyProperty
453 @LazyProperty
450 def extra(self):
454 def extra(self):
451 return self._ctx.extra()
455 return self._ctx.extra()
@@ -1,594 +1,594 b''
1 import os
1 import os
2
2
3 import pytest
3 import pytest
4 import mock
4 import mock
5
5
6 from kallithea.lib.utils2 import safe_str
6 from kallithea.lib.utils2 import safe_str
7 from kallithea.lib.vcs.backends.hg import MercurialRepository, MercurialChangeset
7 from kallithea.lib.vcs.backends.hg import MercurialRepository, MercurialChangeset
8 from kallithea.lib.vcs.exceptions import RepositoryError, VCSError, NodeDoesNotExistError
8 from kallithea.lib.vcs.exceptions import RepositoryError, VCSError, NodeDoesNotExistError
9 from kallithea.lib.vcs.nodes import NodeKind, NodeState
9 from kallithea.lib.vcs.nodes import NodeKind, NodeState
10
10
11 from kallithea.tests.vcs.conf import TEST_HG_REPO, TEST_HG_REPO_CLONE, \
11 from kallithea.tests.vcs.conf import TEST_HG_REPO, TEST_HG_REPO_CLONE, \
12 TEST_HG_REPO_PULL, TESTS_TMP_PATH
12 TEST_HG_REPO_PULL, TESTS_TMP_PATH
13
13
14
14
15 class TestMercurialRepository(object):
15 class TestMercurialRepository(object):
16
16
17 def __check_for_existing_repo(self):
17 def __check_for_existing_repo(self):
18 if os.path.exists(TEST_HG_REPO_CLONE):
18 if os.path.exists(TEST_HG_REPO_CLONE):
19 pytest.fail('Cannot test mercurial clone repo as location %s already '
19 pytest.fail('Cannot test mercurial clone repo as location %s already '
20 'exists. You should manually remove it first.'
20 'exists. You should manually remove it first.'
21 % TEST_HG_REPO_CLONE)
21 % TEST_HG_REPO_CLONE)
22
22
23 def setup_method(self):
23 def setup_method(self):
24 self.repo = MercurialRepository(safe_str(TEST_HG_REPO))
24 self.repo = MercurialRepository(safe_str(TEST_HG_REPO))
25
25
26 def test_wrong_repo_path(self):
26 def test_wrong_repo_path(self):
27 wrong_repo_path = os.path.join(TESTS_TMP_PATH, 'errorrepo')
27 wrong_repo_path = os.path.join(TESTS_TMP_PATH, 'errorrepo')
28 with pytest.raises(RepositoryError):
28 with pytest.raises(RepositoryError):
29 MercurialRepository(wrong_repo_path)
29 MercurialRepository(wrong_repo_path)
30
30
31 def test_unicode_path_repo(self):
31 def test_unicode_path_repo(self):
32 with pytest.raises(VCSError):
32 with pytest.raises(VCSError):
33 MercurialRepository(u'iShouldFail')
33 MercurialRepository(u'iShouldFail')
34
34
35 def test_repo_clone(self):
35 def test_repo_clone(self):
36 self.__check_for_existing_repo()
36 self.__check_for_existing_repo()
37 repo = MercurialRepository(safe_str(TEST_HG_REPO))
37 repo = MercurialRepository(safe_str(TEST_HG_REPO))
38 repo_clone = MercurialRepository(TEST_HG_REPO_CLONE,
38 repo_clone = MercurialRepository(TEST_HG_REPO_CLONE,
39 src_url=TEST_HG_REPO, update_after_clone=True)
39 src_url=TEST_HG_REPO, update_after_clone=True)
40 assert len(repo.revisions) == len(repo_clone.revisions)
40 assert len(repo.revisions) == len(repo_clone.revisions)
41 # Checking hashes of changesets should be enough
41 # Checking hashes of changesets should be enough
42 for changeset in repo.get_changesets():
42 for changeset in repo.get_changesets():
43 raw_id = changeset.raw_id
43 raw_id = changeset.raw_id
44 assert raw_id == repo_clone.get_changeset(raw_id).raw_id
44 assert raw_id == repo_clone.get_changeset(raw_id).raw_id
45
45
46 def test_repo_clone_with_update(self):
46 def test_repo_clone_with_update(self):
47 repo = MercurialRepository(safe_str(TEST_HG_REPO))
47 repo = MercurialRepository(safe_str(TEST_HG_REPO))
48 repo_clone = MercurialRepository(TEST_HG_REPO_CLONE + '_w_update',
48 repo_clone = MercurialRepository(TEST_HG_REPO_CLONE + '_w_update',
49 src_url=TEST_HG_REPO, update_after_clone=True)
49 src_url=TEST_HG_REPO, update_after_clone=True)
50 assert len(repo.revisions) == len(repo_clone.revisions)
50 assert len(repo.revisions) == len(repo_clone.revisions)
51
51
52 # check if current workdir was updated
52 # check if current workdir was updated
53 assert os.path.isfile(
53 assert os.path.isfile(
54 os.path.join(
54 os.path.join(
55 TEST_HG_REPO_CLONE + '_w_update', 'MANIFEST.in'
55 TEST_HG_REPO_CLONE + '_w_update', 'MANIFEST.in'
56 )
56 )
57 )
57 )
58
58
59 def test_repo_clone_without_update(self):
59 def test_repo_clone_without_update(self):
60 repo = MercurialRepository(safe_str(TEST_HG_REPO))
60 repo = MercurialRepository(safe_str(TEST_HG_REPO))
61 repo_clone = MercurialRepository(TEST_HG_REPO_CLONE + '_wo_update',
61 repo_clone = MercurialRepository(TEST_HG_REPO_CLONE + '_wo_update',
62 src_url=TEST_HG_REPO, update_after_clone=False)
62 src_url=TEST_HG_REPO, update_after_clone=False)
63 assert len(repo.revisions) == len(repo_clone.revisions)
63 assert len(repo.revisions) == len(repo_clone.revisions)
64 assert not os.path.isfile(
64 assert not os.path.isfile(
65 os.path.join(
65 os.path.join(
66 TEST_HG_REPO_CLONE + '_wo_update', 'MANIFEST.in'
66 TEST_HG_REPO_CLONE + '_wo_update', 'MANIFEST.in'
67 )
67 )
68 )
68 )
69
69
70 def test_pull(self):
70 def test_pull(self):
71 if os.path.exists(TEST_HG_REPO_PULL):
71 if os.path.exists(TEST_HG_REPO_PULL):
72 pytest.fail('Cannot test mercurial pull command as location %s '
72 pytest.fail('Cannot test mercurial pull command as location %s '
73 'already exists. You should manually remove it first'
73 'already exists. You should manually remove it first'
74 % TEST_HG_REPO_PULL)
74 % TEST_HG_REPO_PULL)
75 repo_new = MercurialRepository(TEST_HG_REPO_PULL, create=True)
75 repo_new = MercurialRepository(TEST_HG_REPO_PULL, create=True)
76 assert len(self.repo.revisions) > len(repo_new.revisions)
76 assert len(self.repo.revisions) > len(repo_new.revisions)
77
77
78 repo_new.pull(self.repo.path)
78 repo_new.pull(self.repo.path)
79 repo_new = MercurialRepository(TEST_HG_REPO_PULL)
79 repo_new = MercurialRepository(TEST_HG_REPO_PULL)
80 assert len(self.repo.revisions) == len(repo_new.revisions)
80 assert len(self.repo.revisions) == len(repo_new.revisions)
81
81
82 def test_revisions(self):
82 def test_revisions(self):
83 # there are 21 revisions at bitbucket now
83 # there are 21 revisions at bitbucket now
84 # so we can assume they would be available from now on
84 # so we can assume they would be available from now on
85 subset = set(['b986218ba1c9b0d6a259fac9b050b1724ed8e545',
85 subset = set(['b986218ba1c9b0d6a259fac9b050b1724ed8e545',
86 '3d8f361e72ab303da48d799ff1ac40d5ac37c67e',
86 '3d8f361e72ab303da48d799ff1ac40d5ac37c67e',
87 '6cba7170863a2411822803fa77a0a264f1310b35',
87 '6cba7170863a2411822803fa77a0a264f1310b35',
88 '56349e29c2af3ac913b28bde9a2c6154436e615b',
88 '56349e29c2af3ac913b28bde9a2c6154436e615b',
89 '2dda4e345facb0ccff1a191052dd1606dba6781d',
89 '2dda4e345facb0ccff1a191052dd1606dba6781d',
90 '6fff84722075f1607a30f436523403845f84cd9e',
90 '6fff84722075f1607a30f436523403845f84cd9e',
91 '7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7',
91 '7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7',
92 '3803844fdbd3b711175fc3da9bdacfcd6d29a6fb',
92 '3803844fdbd3b711175fc3da9bdacfcd6d29a6fb',
93 'dc5d2c0661b61928834a785d3e64a3f80d3aad9c',
93 'dc5d2c0661b61928834a785d3e64a3f80d3aad9c',
94 'be90031137367893f1c406e0a8683010fd115b79',
94 'be90031137367893f1c406e0a8683010fd115b79',
95 'db8e58be770518cbb2b1cdfa69146e47cd481481',
95 'db8e58be770518cbb2b1cdfa69146e47cd481481',
96 '84478366594b424af694a6c784cb991a16b87c21',
96 '84478366594b424af694a6c784cb991a16b87c21',
97 '17f8e105dddb9f339600389c6dc7175d395a535c',
97 '17f8e105dddb9f339600389c6dc7175d395a535c',
98 '20a662e756499bde3095ffc9bc0643d1def2d0eb',
98 '20a662e756499bde3095ffc9bc0643d1def2d0eb',
99 '2e319b85e70a707bba0beff866d9f9de032aa4f9',
99 '2e319b85e70a707bba0beff866d9f9de032aa4f9',
100 '786facd2c61deb9cf91e9534735124fb8fc11842',
100 '786facd2c61deb9cf91e9534735124fb8fc11842',
101 '94593d2128d38210a2fcd1aabff6dda0d6d9edf8',
101 '94593d2128d38210a2fcd1aabff6dda0d6d9edf8',
102 'aa6a0de05b7612707db567078e130a6cd114a9a7',
102 'aa6a0de05b7612707db567078e130a6cd114a9a7',
103 'eada5a770da98ab0dd7325e29d00e0714f228d09'
103 'eada5a770da98ab0dd7325e29d00e0714f228d09'
104 ])
104 ])
105 assert subset.issubset(set(self.repo.revisions))
105 assert subset.issubset(set(self.repo.revisions))
106
106
107 # check if we have the proper order of revisions
107 # check if we have the proper order of revisions
108 org = ['b986218ba1c9b0d6a259fac9b050b1724ed8e545',
108 org = ['b986218ba1c9b0d6a259fac9b050b1724ed8e545',
109 '3d8f361e72ab303da48d799ff1ac40d5ac37c67e',
109 '3d8f361e72ab303da48d799ff1ac40d5ac37c67e',
110 '6cba7170863a2411822803fa77a0a264f1310b35',
110 '6cba7170863a2411822803fa77a0a264f1310b35',
111 '56349e29c2af3ac913b28bde9a2c6154436e615b',
111 '56349e29c2af3ac913b28bde9a2c6154436e615b',
112 '2dda4e345facb0ccff1a191052dd1606dba6781d',
112 '2dda4e345facb0ccff1a191052dd1606dba6781d',
113 '6fff84722075f1607a30f436523403845f84cd9e',
113 '6fff84722075f1607a30f436523403845f84cd9e',
114 '7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7',
114 '7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7',
115 '3803844fdbd3b711175fc3da9bdacfcd6d29a6fb',
115 '3803844fdbd3b711175fc3da9bdacfcd6d29a6fb',
116 'dc5d2c0661b61928834a785d3e64a3f80d3aad9c',
116 'dc5d2c0661b61928834a785d3e64a3f80d3aad9c',
117 'be90031137367893f1c406e0a8683010fd115b79',
117 'be90031137367893f1c406e0a8683010fd115b79',
118 'db8e58be770518cbb2b1cdfa69146e47cd481481',
118 'db8e58be770518cbb2b1cdfa69146e47cd481481',
119 '84478366594b424af694a6c784cb991a16b87c21',
119 '84478366594b424af694a6c784cb991a16b87c21',
120 '17f8e105dddb9f339600389c6dc7175d395a535c',
120 '17f8e105dddb9f339600389c6dc7175d395a535c',
121 '20a662e756499bde3095ffc9bc0643d1def2d0eb',
121 '20a662e756499bde3095ffc9bc0643d1def2d0eb',
122 '2e319b85e70a707bba0beff866d9f9de032aa4f9',
122 '2e319b85e70a707bba0beff866d9f9de032aa4f9',
123 '786facd2c61deb9cf91e9534735124fb8fc11842',
123 '786facd2c61deb9cf91e9534735124fb8fc11842',
124 '94593d2128d38210a2fcd1aabff6dda0d6d9edf8',
124 '94593d2128d38210a2fcd1aabff6dda0d6d9edf8',
125 'aa6a0de05b7612707db567078e130a6cd114a9a7',
125 'aa6a0de05b7612707db567078e130a6cd114a9a7',
126 'eada5a770da98ab0dd7325e29d00e0714f228d09',
126 'eada5a770da98ab0dd7325e29d00e0714f228d09',
127 '2c1885c735575ca478bf9e17b0029dca68824458',
127 '2c1885c735575ca478bf9e17b0029dca68824458',
128 'd9bcd465040bf869799b09ad732c04e0eea99fe9',
128 'd9bcd465040bf869799b09ad732c04e0eea99fe9',
129 '469e9c847fe1f6f7a697b8b25b4bc5b48780c1a7',
129 '469e9c847fe1f6f7a697b8b25b4bc5b48780c1a7',
130 '4fb8326d78e5120da2c7468dcf7098997be385da',
130 '4fb8326d78e5120da2c7468dcf7098997be385da',
131 '62b4a097164940bd66030c4db51687f3ec035eed',
131 '62b4a097164940bd66030c4db51687f3ec035eed',
132 '536c1a19428381cfea92ac44985304f6a8049569',
132 '536c1a19428381cfea92ac44985304f6a8049569',
133 '965e8ab3c44b070cdaa5bf727ddef0ada980ecc4',
133 '965e8ab3c44b070cdaa5bf727ddef0ada980ecc4',
134 '9bb326a04ae5d98d437dece54be04f830cf1edd9',
134 '9bb326a04ae5d98d437dece54be04f830cf1edd9',
135 'f8940bcb890a98c4702319fbe36db75ea309b475',
135 'f8940bcb890a98c4702319fbe36db75ea309b475',
136 'ff5ab059786ebc7411e559a2cc309dfae3625a3b',
136 'ff5ab059786ebc7411e559a2cc309dfae3625a3b',
137 '6b6ad5f82ad5bb6190037671bd254bd4e1f4bf08',
137 '6b6ad5f82ad5bb6190037671bd254bd4e1f4bf08',
138 'ee87846a61c12153b51543bf860e1026c6d3dcba', ]
138 'ee87846a61c12153b51543bf860e1026c6d3dcba', ]
139 assert org == self.repo.revisions[:31]
139 assert org == self.repo.revisions[:31]
140
140
141 def test_iter_slice(self):
141 def test_iter_slice(self):
142 sliced = list(self.repo[:10])
142 sliced = list(self.repo[:10])
143 itered = list(self.repo)[:10]
143 itered = list(self.repo)[:10]
144 assert sliced == itered
144 assert sliced == itered
145
145
146 def test_slicing(self):
146 def test_slicing(self):
147 # 4 1 5 10 95
147 # 4 1 5 10 95
148 for sfrom, sto, size in [(0, 4, 4), (1, 2, 1), (10, 15, 5),
148 for sfrom, sto, size in [(0, 4, 4), (1, 2, 1), (10, 15, 5),
149 (10, 20, 10), (5, 100, 95)]:
149 (10, 20, 10), (5, 100, 95)]:
150 revs = list(self.repo[sfrom:sto])
150 revs = list(self.repo[sfrom:sto])
151 assert len(revs) == size
151 assert len(revs) == size
152 assert revs[0] == self.repo.get_changeset(sfrom)
152 assert revs[0] == self.repo.get_changeset(sfrom)
153 assert revs[-1] == self.repo.get_changeset(sto - 1)
153 assert revs[-1] == self.repo.get_changeset(sto - 1)
154
154
155 def test_branches(self):
155 def test_branches(self):
156 # TODO: Need more tests here
156 # TODO: Need more tests here
157
157
158 # active branches
158 # active branches
159 assert 'default' in self.repo.branches
159 assert 'default' in self.repo.branches
160 assert 'stable' in self.repo.branches
160 assert 'stable' in self.repo.branches
161
161
162 # closed
162 # closed
163 assert 'git' in self.repo._get_branches(closed=True)
163 assert 'git' in self.repo._get_branches(closed=True)
164 assert 'web' in self.repo._get_branches(closed=True)
164 assert 'web' in self.repo._get_branches(closed=True)
165
165
166 for name, id in self.repo.branches.items():
166 for name, id in self.repo.branches.items():
167 assert isinstance(self.repo.get_changeset(id), MercurialChangeset)
167 assert isinstance(self.repo.get_changeset(id), MercurialChangeset)
168
168
169 def test_tip_in_tags(self):
169 def test_tip_in_tags(self):
170 # tip is always a tag
170 # tip is always a tag
171 assert 'tip' in self.repo.tags
171 assert 'tip' in self.repo.tags
172
172
173 def test_tip_changeset_in_tags(self):
173 def test_tip_changeset_in_tags(self):
174 tip = self.repo.get_changeset()
174 tip = self.repo.get_changeset()
175 assert self.repo.tags['tip'] == tip.raw_id
175 assert self.repo.tags['tip'] == tip.raw_id
176
176
177 def test_initial_changeset(self):
177 def test_initial_changeset(self):
178
178
179 init_chset = self.repo.get_changeset(0)
179 init_chset = self.repo.get_changeset(0)
180 assert init_chset.message == 'initial import'
180 assert init_chset.message == 'initial import'
181 assert init_chset.author == 'Marcin Kuzminski <marcin@python-blog.com>'
181 assert init_chset.author == 'Marcin Kuzminski <marcin@python-blog.com>'
182 assert sorted(init_chset._file_paths) == sorted([
182 assert sorted(init_chset._file_paths) == sorted([
183 'vcs/__init__.py',
183 'vcs/__init__.py',
184 'vcs/backends/BaseRepository.py',
184 'vcs/backends/BaseRepository.py',
185 'vcs/backends/__init__.py',
185 'vcs/backends/__init__.py',
186 ])
186 ])
187
187
188 assert sorted(init_chset._dir_paths) == sorted(['', 'vcs', 'vcs/backends'])
188 assert sorted(init_chset._dir_paths) == sorted(['', 'vcs', 'vcs/backends'])
189
189
190 with pytest.raises(NodeDoesNotExistError):
190 with pytest.raises(NodeDoesNotExistError):
191 init_chset.get_node(path='foobar')
191 init_chset.get_node(path='foobar')
192
192
193 node = init_chset.get_node('vcs/')
193 node = init_chset.get_node('vcs/')
194 assert hasattr(node, 'kind')
194 assert hasattr(node, 'kind')
195 assert node.kind == NodeKind.DIR
195 assert node.kind == NodeKind.DIR
196
196
197 node = init_chset.get_node('vcs')
197 node = init_chset.get_node('vcs')
198 assert hasattr(node, 'kind')
198 assert hasattr(node, 'kind')
199 assert node.kind == NodeKind.DIR
199 assert node.kind == NodeKind.DIR
200
200
201 node = init_chset.get_node('vcs/__init__.py')
201 node = init_chset.get_node('vcs/__init__.py')
202 assert hasattr(node, 'kind')
202 assert hasattr(node, 'kind')
203 assert node.kind == NodeKind.FILE
203 assert node.kind == NodeKind.FILE
204
204
205 def test_not_existing_changeset(self):
205 def test_not_existing_changeset(self):
206 # rawid
206 # rawid
207 with pytest.raises(RepositoryError):
207 with pytest.raises(RepositoryError):
208 self.repo.get_changeset('abcd' * 10)
208 self.repo.get_changeset('abcd' * 10)
209 # shortid
209 # shortid
210 with pytest.raises(RepositoryError):
210 with pytest.raises(RepositoryError):
211 self.repo.get_changeset('erro' * 4)
211 self.repo.get_changeset('erro' * 4)
212 # numeric
212 # numeric
213 with pytest.raises(RepositoryError):
213 with pytest.raises(RepositoryError):
214 self.repo.get_changeset(self.repo.count() + 1)
214 self.repo.get_changeset(self.repo.count() + 1)
215
215
216 # Small chance we ever get to this one
216 # Small chance we ever get to this one
217 revision = pow(2, 30)
217 revision = pow(2, 30)
218 with pytest.raises(RepositoryError):
218 with pytest.raises(RepositoryError):
219 self.repo.get_changeset(revision)
219 self.repo.get_changeset(revision)
220
220
221 def test_changeset10(self):
221 def test_changeset10(self):
222
222
223 chset10 = self.repo.get_changeset(10)
223 chset10 = self.repo.get_changeset(10)
224 readme = """===
224 readme = """===
225 VCS
225 VCS
226 ===
226 ===
227
227
228 Various Version Control System management abstraction layer for Python.
228 Various Version Control System management abstraction layer for Python.
229
229
230 Introduction
230 Introduction
231 ------------
231 ------------
232
232
233 TODO: To be written...
233 TODO: To be written...
234
234
235 """
235 """
236 node = chset10.get_node('README.rst')
236 node = chset10.get_node('README.rst')
237 assert node.kind == NodeKind.FILE
237 assert node.kind == NodeKind.FILE
238 assert node.content == readme
238 assert node.content == readme
239
239
240 @mock.patch('kallithea.lib.vcs.backends.hg.repository.diffopts')
240 @mock.patch('kallithea.lib.vcs.backends.hg.repository.diffopts')
241 def test_get_diff_does_not_sanitize_zero_context(self, mock_diffopts):
241 def test_get_diff_does_not_sanitize_zero_context(self, mock_diffopts):
242 zero_context = 0
242 zero_context = 0
243
243
244 self.repo.get_diff(0, 1, 'foo', context=zero_context)
244 self.repo.get_diff(0, 1, 'foo', context=zero_context)
245
245
246 mock_diffopts.assert_called_once_with(git=True, showfunc=True, ignorews=False, context=zero_context)
246 mock_diffopts.assert_called_once_with(git=True, showfunc=True, ignorews=False, context=zero_context)
247
247
248 @mock.patch('kallithea.lib.vcs.backends.hg.repository.diffopts')
248 @mock.patch('kallithea.lib.vcs.backends.hg.repository.diffopts')
249 def test_get_diff_sanitizes_negative_context(self, mock_diffopts):
249 def test_get_diff_sanitizes_negative_context(self, mock_diffopts):
250 negative_context = -10
250 negative_context = -10
251 zero_context = 0
251 zero_context = 0
252
252
253 self.repo.get_diff(0, 1, 'foo', context=negative_context)
253 self.repo.get_diff(0, 1, 'foo', context=negative_context)
254
254
255 mock_diffopts.assert_called_once_with(git=True, showfunc=True, ignorews=False, context=zero_context)
255 mock_diffopts.assert_called_once_with(git=True, showfunc=True, ignorews=False, context=zero_context)
256
256
257
257
258 class TestMercurialChangeset(object):
258 class TestMercurialChangeset(object):
259
259
260 def setup_method(self):
260 def setup_method(self):
261 self.repo = MercurialRepository(safe_str(TEST_HG_REPO))
261 self.repo = MercurialRepository(safe_str(TEST_HG_REPO))
262
262
263 def _test_equality(self, changeset):
263 def _test_equality(self, changeset):
264 revision = changeset.revision
264 revision = changeset.revision
265 assert changeset == self.repo.get_changeset(revision)
265 assert changeset == self.repo.get_changeset(revision)
266
266
267 def test_equality(self):
267 def test_equality(self):
268 revs = [0, 10, 20]
268 revs = [0, 10, 20]
269 changesets = [self.repo.get_changeset(rev) for rev in revs]
269 changesets = [self.repo.get_changeset(rev) for rev in revs]
270 for changeset in changesets:
270 for changeset in changesets:
271 self._test_equality(changeset)
271 self._test_equality(changeset)
272
272
273 def test_default_changeset(self):
273 def test_default_changeset(self):
274 tip = self.repo.get_changeset('tip')
274 tip = self.repo.get_changeset('tip')
275 assert tip == self.repo.get_changeset()
275 assert tip == self.repo.get_changeset()
276 assert tip == self.repo.get_changeset(revision=None)
276 assert tip == self.repo.get_changeset(revision=None)
277 assert tip == list(self.repo[-1:])[0]
277 assert tip == list(self.repo[-1:])[0]
278
278
279 def test_root_node(self):
279 def test_root_node(self):
280 tip = self.repo.get_changeset('tip')
280 tip = self.repo.get_changeset('tip')
281 assert tip.root is tip.get_node('')
281 assert tip.root is tip.get_node('')
282
282
283 def test_lazy_fetch(self):
283 def test_lazy_fetch(self):
284 """
284 """
285 Test if changeset's nodes expands and are cached as we walk through
285 Test if changeset's nodes expands and are cached as we walk through
286 the revision. This test is somewhat hard to write as order of tests
286 the revision. This test is somewhat hard to write as order of tests
287 is a key here. Written by running command after command in a shell.
287 is a key here. Written by running command after command in a shell.
288 """
288 """
289 chset = self.repo.get_changeset(45)
289 chset = self.repo.get_changeset(45)
290 assert len(chset.nodes) == 0
290 assert len(chset.nodes) == 0
291 root = chset.root
291 root = chset.root
292 assert len(chset.nodes) == 1
292 assert len(chset.nodes) == 1
293 assert len(root.nodes) == 8
293 assert len(root.nodes) == 8
294 # accessing root.nodes updates chset.nodes
294 # accessing root.nodes updates chset.nodes
295 assert len(chset.nodes) == 9
295 assert len(chset.nodes) == 9
296
296
297 docs = root.get_node('docs')
297 docs = root.get_node('docs')
298 # we haven't yet accessed anything new as docs dir was already cached
298 # we haven't yet accessed anything new as docs dir was already cached
299 assert len(chset.nodes) == 9
299 assert len(chset.nodes) == 9
300 assert len(docs.nodes) == 8
300 assert len(docs.nodes) == 8
301 # accessing docs.nodes updates chset.nodes
301 # accessing docs.nodes updates chset.nodes
302 assert len(chset.nodes) == 17
302 assert len(chset.nodes) == 17
303
303
304 assert docs is chset.get_node('docs')
304 assert docs is chset.get_node('docs')
305 assert docs is root.nodes[0]
305 assert docs is root.nodes[0]
306 assert docs is root.dirs[0]
306 assert docs is root.dirs[0]
307 assert docs is chset.get_node('docs')
307 assert docs is chset.get_node('docs')
308
308
309 def test_nodes_with_changeset(self):
309 def test_nodes_with_changeset(self):
310 chset = self.repo.get_changeset(45)
310 chset = self.repo.get_changeset(45)
311 root = chset.root
311 root = chset.root
312 docs = root.get_node('docs')
312 docs = root.get_node('docs')
313 assert docs is chset.get_node('docs')
313 assert docs is chset.get_node('docs')
314 api = docs.get_node('api')
314 api = docs.get_node('api')
315 assert api is chset.get_node('docs/api')
315 assert api is chset.get_node('docs/api')
316 index = api.get_node('index.rst')
316 index = api.get_node('index.rst')
317 assert index is chset.get_node('docs/api/index.rst')
317 assert index is chset.get_node('docs/api/index.rst')
318 assert index is chset.get_node('docs').get_node('api').get_node('index.rst')
318 assert index is chset.get_node('docs').get_node('api').get_node('index.rst')
319
319
320 def test_branch_and_tags(self):
320 def test_branch_and_tags(self):
321 chset0 = self.repo.get_changeset(0)
321 chset0 = self.repo.get_changeset(0)
322 assert chset0.branch == 'default'
322 assert chset0.branch == 'default'
323 assert chset0.branches == ['default']
323 assert chset0.branches == ['default']
324 assert chset0.tags == []
324 assert chset0.tags == []
325
325
326 chset10 = self.repo.get_changeset(10)
326 chset10 = self.repo.get_changeset(10)
327 assert chset10.branch == 'default'
327 assert chset10.branch == 'default'
328 assert chset10.branches == ['default']
328 assert chset10.branches == ['default']
329 assert chset10.tags == []
329 assert chset10.tags == []
330
330
331 chset44 = self.repo.get_changeset(44)
331 chset44 = self.repo.get_changeset(44)
332 assert chset44.branch == 'web'
332 assert chset44.branch == 'web'
333 assert chset44.branches == ['web']
333 assert chset44.branches == ['web']
334
334
335 tip = self.repo.get_changeset('tip')
335 tip = self.repo.get_changeset('tip')
336 assert 'tip' in tip.tags
336 assert 'tip' in tip.tags
337
337
338 def _test_file_size(self, revision, path, size):
338 def _test_file_size(self, revision, path, size):
339 node = self.repo.get_changeset(revision).get_node(path)
339 node = self.repo.get_changeset(revision).get_node(path)
340 assert node.is_file()
340 assert node.is_file()
341 assert node.size == size
341 assert node.size == size
342
342
343 def test_file_size(self):
343 def test_file_size(self):
344 to_check = (
344 to_check = (
345 (10, 'setup.py', 1068),
345 (10, 'setup.py', 1068),
346 (20, 'setup.py', 1106),
346 (20, 'setup.py', 1106),
347 (60, 'setup.py', 1074),
347 (60, 'setup.py', 1074),
348
348
349 (10, 'vcs/backends/base.py', 2921),
349 (10, 'vcs/backends/base.py', 2921),
350 (20, 'vcs/backends/base.py', 3936),
350 (20, 'vcs/backends/base.py', 3936),
351 (60, 'vcs/backends/base.py', 6189),
351 (60, 'vcs/backends/base.py', 6189),
352 )
352 )
353 for revision, path, size in to_check:
353 for revision, path, size in to_check:
354 self._test_file_size(revision, path, size)
354 self._test_file_size(revision, path, size)
355
355
356 def _test_dir_size(self, revision, path, size):
356 def _test_dir_size(self, revision, path, size):
357 node = self.repo.get_changeset(revision).get_node(path)
357 node = self.repo.get_changeset(revision).get_node(path)
358 assert not node.is_file()
358 assert not node.is_file()
359 assert node.size == size
359 assert node.size == size
360
360
361 def test_dir_size(self):
361 def test_dir_size(self):
362 to_check = (
362 to_check = (
363 ('96507bd11ecc', '/', 682421),
363 ('96507bd11ecc', '/', 682421),
364 ('a53d9201d4bc', '/', 682410),
364 ('a53d9201d4bc', '/', 682410),
365 ('90243de06161', '/', 682006),
365 ('90243de06161', '/', 682006),
366 )
366 )
367 for revision, path, size in to_check:
367 for revision, path, size in to_check:
368 self._test_dir_size(revision, path, size)
368 self._test_dir_size(revision, path, size)
369
369
370 def test_repo_size(self):
370 def test_repo_size(self):
371 assert self.repo.size == 682421
371 assert self.repo.size == 682421
372
372
373 def test_file_history(self):
373 def test_file_history(self):
374 # we can only check if those revisions are present in the history
374 # we can only check if those revisions are present in the history
375 # as we cannot update this test every time file is changed
375 # as we cannot update this test every time file is changed
376 files = {
376 files = {
377 'setup.py': [7, 18, 45, 46, 47, 69, 77],
377 'setup.py': [7, 18, 45, 46, 47, 69, 77],
378 'vcs/nodes.py': [7, 8, 24, 26, 30, 45, 47, 49, 56, 57, 58, 59, 60,
378 'vcs/nodes.py': [7, 8, 24, 26, 30, 45, 47, 49, 56, 57, 58, 59, 60,
379 61, 73, 76],
379 61, 73, 76],
380 'vcs/backends/hg.py': [4, 5, 6, 11, 12, 13, 14, 15, 16, 21, 22, 23,
380 'vcs/backends/hg.py': [4, 5, 6, 11, 12, 13, 14, 15, 16, 21, 22, 23,
381 26, 27, 28, 30, 31, 33, 35, 36, 37, 38, 39, 40, 41, 44, 45, 47,
381 26, 27, 28, 30, 31, 33, 35, 36, 37, 38, 39, 40, 41, 44, 45, 47,
382 48, 49, 53, 54, 55, 58, 60, 61, 67, 68, 69, 70, 73, 77, 78, 79,
382 48, 49, 53, 54, 55, 58, 60, 61, 67, 68, 69, 70, 73, 77, 78, 79,
383 82],
383 82],
384 }
384 }
385 for path, revs in files.items():
385 for path, revs in files.items():
386 tip = self.repo.get_changeset(revs[-1])
386 tip = self.repo.get_changeset(revs[-1])
387 node = tip.get_node(path)
387 node = tip.get_node(path)
388 node_revs = [chset.revision for chset in node.history]
388 node_revs = [chset.revision for chset in node.history]
389 assert set(revs).issubset(set(node_revs)), \
389 assert set(revs).issubset(set(node_revs)), \
390 "We assumed that %s is subset of revisions for which file %s " \
390 "We assumed that %s is subset of revisions for which file %s " \
391 "has been changed, and history of that node returned: %s" \
391 "has been changed, and history of that node returned: %s" \
392 % (revs, path, node_revs)
392 % (revs, path, node_revs)
393
393
394 def test_file_annotate(self):
394 def test_file_annotate(self):
395 files = {
395 files = {
396 'vcs/backends/__init__.py':
396 'vcs/backends/__init__.py':
397 {89: {'lines_no': 31,
397 {89: {'lines_no': 31,
398 'changesets': [32, 32, 61, 32, 32, 37, 32, 32, 32, 44,
398 'changesets': [32, 32, 61, 32, 32, 37, 32, 32, 32, 44,
399 37, 37, 37, 37, 45, 37, 44, 37, 37, 37,
399 37, 37, 37, 37, 45, 37, 44, 37, 37, 37,
400 32, 32, 32, 32, 37, 32, 37, 37, 32,
400 32, 32, 32, 32, 37, 32, 37, 37, 32,
401 32, 32]},
401 32, 32]},
402 20: {'lines_no': 1,
402 20: {'lines_no': 1,
403 'changesets': [4]},
403 'changesets': [4]},
404 55: {'lines_no': 31,
404 55: {'lines_no': 31,
405 'changesets': [32, 32, 45, 32, 32, 37, 32, 32, 32, 44,
405 'changesets': [32, 32, 45, 32, 32, 37, 32, 32, 32, 44,
406 37, 37, 37, 37, 45, 37, 44, 37, 37, 37,
406 37, 37, 37, 37, 45, 37, 44, 37, 37, 37,
407 32, 32, 32, 32, 37, 32, 37, 37, 32,
407 32, 32, 32, 32, 37, 32, 37, 37, 32,
408 32, 32]}},
408 32, 32]}},
409 'vcs/exceptions.py':
409 'vcs/exceptions.py':
410 {89: {'lines_no': 18,
410 {89: {'lines_no': 18,
411 'changesets': [16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
411 'changesets': [16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
412 16, 16, 17, 16, 16, 18, 18, 18]},
412 16, 16, 17, 16, 16, 18, 18, 18]},
413 20: {'lines_no': 18,
413 20: {'lines_no': 18,
414 'changesets': [16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
414 'changesets': [16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
415 16, 16, 17, 16, 16, 18, 18, 18]},
415 16, 16, 17, 16, 16, 18, 18, 18]},
416 55: {'lines_no': 18, 'changesets': [16, 16, 16, 16, 16, 16,
416 55: {'lines_no': 18, 'changesets': [16, 16, 16, 16, 16, 16,
417 16, 16, 16, 16, 16, 16,
417 16, 16, 16, 16, 16, 16,
418 17, 16, 16, 18, 18, 18]}},
418 17, 16, 16, 18, 18, 18]}},
419 'MANIFEST.in': {89: {'lines_no': 5,
419 'MANIFEST.in': {89: {'lines_no': 5,
420 'changesets': [7, 7, 7, 71, 71]},
420 'changesets': [7, 7, 7, 71, 71]},
421 20: {'lines_no': 3,
421 20: {'lines_no': 3,
422 'changesets': [7, 7, 7]},
422 'changesets': [7, 7, 7]},
423 55: {'lines_no': 3,
423 55: {'lines_no': 3,
424 'changesets': [7, 7, 7]}}}
424 'changesets': [7, 7, 7]}}}
425
425
426 for fname, revision_dict in files.items():
426 for fname, revision_dict in files.items():
427 for rev, data in revision_dict.items():
427 for rev, data in revision_dict.items():
428 cs = self.repo.get_changeset(rev)
428 cs = self.repo.get_changeset(rev)
429 l1_1 = [x[1] for x in cs.get_file_annotate(fname)]
429 l1_1 = [x[1] for x in cs.get_file_annotate(fname)]
430 l1_2 = [x[2]().raw_id for x in cs.get_file_annotate(fname)]
430 l1_2 = [x[2]().raw_id for x in cs.get_file_annotate(fname)]
431 assert l1_1 == l1_2
431 assert l1_1 == l1_2
432 l1 = l1_2 = [x[2]().revision for x in cs.get_file_annotate(fname)]
432 l1 = l1_2 = [x[2]().revision for x in cs.get_file_annotate(fname)]
433 l2 = files[fname][rev]['changesets']
433 l2 = files[fname][rev]['changesets']
434 assert l1 == l2, "The lists of revision for %s@rev%s" \
434 assert l1 == l2, "The lists of revision for %s@rev%s" \
435 "from annotation list should match each other," \
435 "from annotation list should match each other," \
436 "got \n%s \nvs \n%s " % (fname, rev, l1, l2)
436 "got \n%s \nvs \n%s " % (fname, rev, l1, l2)
437
437
438 def test_changeset_state(self):
438 def test_changeset_state(self):
439 """
439 """
440 Tests which files have been added/changed/removed at particular revision
440 Tests which files have been added/changed/removed at particular revision
441 """
441 """
442
442
443 # rev 46ad32a4f974:
443 # rev 46ad32a4f974:
444 # hg st --rev 46ad32a4f974
444 # hg st --rev 46ad32a4f974
445 # changed: 13
445 # changed: 13
446 # added: 20
446 # added: 20
447 # removed: 1
447 # removed: 1
448 changed = set(['.hgignore'
448 changed = set(['.hgignore'
449 , 'README.rst' , 'docs/conf.py' , 'docs/index.rst' , 'setup.py'
449 , 'README.rst' , 'docs/conf.py' , 'docs/index.rst' , 'setup.py'
450 , 'tests/test_hg.py' , 'tests/test_nodes.py' , 'vcs/__init__.py'
450 , 'tests/test_hg.py' , 'tests/test_nodes.py' , 'vcs/__init__.py'
451 , 'vcs/backends/__init__.py' , 'vcs/backends/base.py'
451 , 'vcs/backends/__init__.py' , 'vcs/backends/base.py'
452 , 'vcs/backends/hg.py' , 'vcs/nodes.py' , 'vcs/utils/__init__.py'])
452 , 'vcs/backends/hg.py' , 'vcs/nodes.py' , 'vcs/utils/__init__.py'])
453
453
454 added = set(['docs/api/backends/hg.rst'
454 added = set(['docs/api/backends/hg.rst'
455 , 'docs/api/backends/index.rst' , 'docs/api/index.rst'
455 , 'docs/api/backends/index.rst' , 'docs/api/index.rst'
456 , 'docs/api/nodes.rst' , 'docs/api/web/index.rst'
456 , 'docs/api/nodes.rst' , 'docs/api/web/index.rst'
457 , 'docs/api/web/simplevcs.rst' , 'docs/installation.rst'
457 , 'docs/api/web/simplevcs.rst' , 'docs/installation.rst'
458 , 'docs/quickstart.rst' , 'setup.cfg' , 'vcs/utils/baseui_config.py'
458 , 'docs/quickstart.rst' , 'setup.cfg' , 'vcs/utils/baseui_config.py'
459 , 'vcs/utils/web.py' , 'vcs/web/__init__.py' , 'vcs/web/exceptions.py'
459 , 'vcs/utils/web.py' , 'vcs/web/__init__.py' , 'vcs/web/exceptions.py'
460 , 'vcs/web/simplevcs/__init__.py' , 'vcs/web/simplevcs/exceptions.py'
460 , 'vcs/web/simplevcs/__init__.py' , 'vcs/web/simplevcs/exceptions.py'
461 , 'vcs/web/simplevcs/middleware.py' , 'vcs/web/simplevcs/models.py'
461 , 'vcs/web/simplevcs/middleware.py' , 'vcs/web/simplevcs/models.py'
462 , 'vcs/web/simplevcs/settings.py' , 'vcs/web/simplevcs/utils.py'
462 , 'vcs/web/simplevcs/settings.py' , 'vcs/web/simplevcs/utils.py'
463 , 'vcs/web/simplevcs/views.py'])
463 , 'vcs/web/simplevcs/views.py'])
464
464
465 removed = set(['docs/api.rst'])
465 removed = set(['docs/api.rst'])
466
466
467 chset64 = self.repo.get_changeset('46ad32a4f974')
467 chset64 = self.repo.get_changeset('46ad32a4f974')
468 assert set((node.path for node in chset64.added)) == added
468 assert set((node.path for node in chset64.added)) == added
469 assert set((node.path for node in chset64.changed)) == changed
469 assert set((node.path for node in chset64.changed)) == changed
470 assert set((node.path for node in chset64.removed)) == removed
470 assert set((node.path for node in chset64.removed)) == removed
471
471
472 # rev b090f22d27d6:
472 # rev b090f22d27d6:
473 # hg st --rev b090f22d27d6
473 # hg st --rev b090f22d27d6
474 # changed: 13
474 # changed: 13
475 # added: 20
475 # added: 20
476 # removed: 1
476 # removed: 1
477 chset88 = self.repo.get_changeset('b090f22d27d6')
477 chset88 = self.repo.get_changeset('b090f22d27d6')
478 assert set((node.path for node in chset88.added)) == set()
478 assert set((node.path for node in chset88.added)) == set()
479 assert set((node.path for node in chset88.changed)) == set(['.hgignore'])
479 assert set((node.path for node in chset88.changed)) == set(['.hgignore'])
480 assert set((node.path for node in chset88.removed)) == set()
480 assert set((node.path for node in chset88.removed)) == set()
481
481
482 # 85:
482 # 85:
483 # added: 2 ['vcs/utils/diffs.py', 'vcs/web/simplevcs/views/diffs.py']
483 # added: 2 ['vcs/utils/diffs.py', 'vcs/web/simplevcs/views/diffs.py']
484 # changed: 4 ['vcs/web/simplevcs/models.py', ...]
484 # changed: 4 ['vcs/web/simplevcs/models.py', ...]
485 # removed: 1 ['vcs/utils/web.py']
485 # removed: 1 ['vcs/utils/web.py']
486 chset85 = self.repo.get_changeset(85)
486 chset85 = self.repo.get_changeset(85)
487 assert set((node.path for node in chset85.added)) == set([
487 assert set((node.path for node in chset85.added)) == set([
488 'vcs/utils/diffs.py',
488 'vcs/utils/diffs.py',
489 'vcs/web/simplevcs/views/diffs.py'
489 'vcs/web/simplevcs/views/diffs.py'
490 ])
490 ])
491
491
492 assert set((node.path for node in chset85.changed)) == set([
492 assert set((node.path for node in chset85.changed)) == set([
493 'vcs/web/simplevcs/models.py',
493 'vcs/web/simplevcs/models.py',
494 'vcs/web/simplevcs/utils.py',
494 'vcs/web/simplevcs/utils.py',
495 'vcs/web/simplevcs/views/__init__.py',
495 'vcs/web/simplevcs/views/__init__.py',
496 'vcs/web/simplevcs/views/repository.py',
496 'vcs/web/simplevcs/views/repository.py',
497 ])
497 ])
498
498
499 assert set((node.path for node in chset85.removed)) == set([
499 assert set((node.path for node in chset85.removed)) == set([
500 'vcs/utils/web.py'
500 'vcs/utils/web.py'
501 ])
501 ])
502
502
503
503
504 def test_files_state(self):
504 def test_files_state(self):
505 """
505 """
506 Tests state of FileNodes.
506 Tests state of FileNodes.
507 """
507 """
508 chset = self.repo.get_changeset(85)
508 chset = self.repo.get_changeset(85)
509 node = chset.get_node('vcs/utils/diffs.py')
509 node = chset.get_node('vcs/utils/diffs.py')
510 assert node.state, NodeState.ADDED
510 assert node.state, NodeState.ADDED
511 assert node.added
511 assert node.added
512 assert not node.changed
512 assert not node.changed
513 assert not node.not_changed
513 assert not node.not_changed
514 assert not node.removed
514 assert not node.removed
515
515
516 chset = self.repo.get_changeset(88)
516 chset = self.repo.get_changeset(88)
517 node = chset.get_node('.hgignore')
517 node = chset.get_node('.hgignore')
518 assert node.state, NodeState.CHANGED
518 assert node.state, NodeState.CHANGED
519 assert not node.added
519 assert not node.added
520 assert node.changed
520 assert node.changed
521 assert not node.not_changed
521 assert not node.not_changed
522 assert not node.removed
522 assert not node.removed
523
523
524 chset = self.repo.get_changeset(85)
524 chset = self.repo.get_changeset(85)
525 node = chset.get_node('setup.py')
525 node = chset.get_node('setup.py')
526 assert node.state, NodeState.NOT_CHANGED
526 assert node.state, NodeState.NOT_CHANGED
527 assert not node.added
527 assert not node.added
528 assert not node.changed
528 assert not node.changed
529 assert node.not_changed
529 assert node.not_changed
530 assert not node.removed
530 assert not node.removed
531
531
532 # If node has REMOVED state then trying to fetch it would raise
532 # If node has REMOVED state then trying to fetch it would raise
533 # ChangesetError exception
533 # ChangesetError exception
534 chset = self.repo.get_changeset(2)
534 chset = self.repo.get_changeset(2)
535 path = 'vcs/backends/BaseRepository.py'
535 path = 'vcs/backends/BaseRepository.py'
536 with pytest.raises(NodeDoesNotExistError):
536 with pytest.raises(NodeDoesNotExistError):
537 chset.get_node(path)
537 chset.get_node(path)
538 # but it would be one of ``removed`` (changeset's attribute)
538 # but it would be one of ``removed`` (changeset's attribute)
539 assert path in [rf.path for rf in chset.removed]
539 assert path in [rf.path for rf in chset.removed]
540
540
541 def test_commit_message_is_unicode(self):
541 def test_commit_message_is_unicode(self):
542 for cm in self.repo:
542 for cm in self.repo:
543 assert type(cm.message) == unicode
543 assert type(cm.message) == unicode
544
544
545 def test_changeset_author_is_unicode(self):
545 def test_changeset_author_is_unicode(self):
546 for cm in self.repo:
546 for cm in self.repo:
547 assert type(cm.author) == unicode
547 assert type(cm.author) == unicode
548
548
549 def test_repo_files_content_is_unicode(self):
549 def test_repo_files_content_is_unicode(self):
550 test_changeset = self.repo.get_changeset(100)
550 test_changeset = self.repo.get_changeset(100)
551 for node in test_changeset.get_node('/'):
551 for node in test_changeset.get_node('/'):
552 if node.is_file():
552 if node.is_file():
553 assert type(node.content) == unicode
553 assert type(node.content) == unicode
554
554
555 def test_wrong_path(self):
555 def test_wrong_path(self):
556 # There is 'setup.py' in the root dir but not there:
556 # There is 'setup.py' in the root dir but not there:
557 path = 'foo/bar/setup.py'
557 path = 'foo/bar/setup.py'
558 with pytest.raises(VCSError):
558 with pytest.raises(VCSError):
559 self.repo.get_changeset().get_node(path)
559 self.repo.get_changeset().get_node(path)
560
560
561 def test_archival_file(self):
561 def test_archival_file(self):
562 # TODO:
562 # TODO:
563 pass
563 pass
564
564
565 def test_archival_as_generator(self):
565 def test_archival_as_generator(self):
566 # TODO:
566 # TODO:
567 pass
567 pass
568
568
569 def test_archival_wrong_kind(self):
569 def test_archival_wrong_kind(self):
570 tip = self.repo.get_changeset()
570 tip = self.repo.get_changeset()
571 with pytest.raises(VCSError):
571 with pytest.raises(VCSError):
572 tip.fill_archive(kind='error')
572 tip.fill_archive(kind='error')
573
573
574 def test_archival_empty_prefix(self):
574 def test_archival_empty_prefix(self):
575 # TODO:
575 # TODO:
576 pass
576 pass
577
577
578 def test_author_email(self):
578 def test_author_email(self):
579 assert 'marcin@python-blog.com' == self.repo.get_changeset('b986218ba1c9').author_email
579 assert 'marcin@python-blog.com' == self.repo.get_changeset('b986218ba1c9').author_email
580 assert 'lukasz.balcerzak@python-center.pl' == self.repo.get_changeset('3803844fdbd3').author_email
580 assert 'lukasz.balcerzak@python-center.pl' == self.repo.get_changeset('3803844fdbd3').author_email
581 assert '' == self.repo.get_changeset('84478366594b').author_email
581 assert '' == self.repo.get_changeset('84478366594b').author_email
582
582
583 def test_author_username(self):
583 def test_author_username(self):
584 assert 'Marcin Kuzminski' == self.repo.get_changeset('b986218ba1c9').author_name
584 assert 'Marcin Kuzminski' == self.repo.get_changeset('b986218ba1c9').author_name
585 assert 'Lukasz Balcerzak' == self.repo.get_changeset('3803844fdbd3').author_name
585 assert 'Lukasz Balcerzak' == self.repo.get_changeset('3803844fdbd3').author_name
586 assert 'marcink' == self.repo.get_changeset('84478366594b').author_name
586 assert 'marcink' == self.repo.get_changeset('84478366594b').author_name
587
587
588 def test_successors(self):
588 def test_successors(self):
589 init_chset = self.repo.get_changeset(0)
589 init_chset = self.repo.get_changeset(0)
590 assert init_chset.successors == []
590 assert init_chset.successors == []
591
591
592 def test_predecessors(self):
592 def test_predecessors(self):
593 init_chset = self.repo.get_changeset(0)
593 init_chset = self.repo.get_changeset(0)
594 assert init_chset.predecessors == set([])
594 assert len(init_chset.predecessors) == 0
General Comments 0
You need to be logged in to leave comments. Login now