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 == |
|
594 | assert len(init_chset.predecessors) == 0 |
General Comments 0
You need to be logged in to leave comments.
Login now