##// END OF EJS Templates
repoview: discard filtered changelog if index isn't shared with unfiltered...
FUJIWARA Katsunori -
r28265:33292621 default
parent child Browse files
Show More
@@ -1,353 +1,356 b''
1 # repoview.py - Filtered view of a localrepo object
1 # repoview.py - Filtered view of a localrepo object
2 #
2 #
3 # Copyright 2012 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
3 # Copyright 2012 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
4 # Logilab SA <contact@logilab.fr>
4 # Logilab SA <contact@logilab.fr>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import copy
11 import copy
12 import heapq
12 import heapq
13 import struct
13 import struct
14
14
15 from .node import nullrev
15 from .node import nullrev
16 from . import (
16 from . import (
17 error,
17 error,
18 obsolete,
18 obsolete,
19 phases,
19 phases,
20 tags as tagsmod,
20 tags as tagsmod,
21 util,
21 util,
22 )
22 )
23
23
24 def hideablerevs(repo):
24 def hideablerevs(repo):
25 """Revisions candidates to be hidden
25 """Revisions candidates to be hidden
26
26
27 This is a standalone function to help extensions to wrap it."""
27 This is a standalone function to help extensions to wrap it."""
28 return obsolete.getrevs(repo, 'obsolete')
28 return obsolete.getrevs(repo, 'obsolete')
29
29
30 def _getstatichidden(repo):
30 def _getstatichidden(repo):
31 """Revision to be hidden (disregarding dynamic blocker)
31 """Revision to be hidden (disregarding dynamic blocker)
32
32
33 To keep a consistent graph, we cannot hide any revisions with
33 To keep a consistent graph, we cannot hide any revisions with
34 non-hidden descendants. This function computes the set of
34 non-hidden descendants. This function computes the set of
35 revisions that could be hidden while keeping the graph consistent.
35 revisions that could be hidden while keeping the graph consistent.
36
36
37 A second pass will be done to apply "dynamic blocker" like bookmarks or
37 A second pass will be done to apply "dynamic blocker" like bookmarks or
38 working directory parents.
38 working directory parents.
39
39
40 """
40 """
41 assert not repo.changelog.filteredrevs
41 assert not repo.changelog.filteredrevs
42 hidden = set(hideablerevs(repo))
42 hidden = set(hideablerevs(repo))
43 if hidden:
43 if hidden:
44 getphase = repo._phasecache.phase
44 getphase = repo._phasecache.phase
45 getparentrevs = repo.changelog.parentrevs
45 getparentrevs = repo.changelog.parentrevs
46 # Skip heads which are public (guaranteed to not be hidden)
46 # Skip heads which are public (guaranteed to not be hidden)
47 heap = [-r for r in repo.changelog.headrevs() if getphase(repo, r)]
47 heap = [-r for r in repo.changelog.headrevs() if getphase(repo, r)]
48 heapq.heapify(heap)
48 heapq.heapify(heap)
49 heappop = heapq.heappop
49 heappop = heapq.heappop
50 heappush = heapq.heappush
50 heappush = heapq.heappush
51 seen = set() # no need to init it with heads, they have no children
51 seen = set() # no need to init it with heads, they have no children
52 while heap:
52 while heap:
53 rev = -heappop(heap)
53 rev = -heappop(heap)
54 # All children have been processed so at that point, if no children
54 # All children have been processed so at that point, if no children
55 # removed 'rev' from the 'hidden' set, 'rev' is going to be hidden.
55 # removed 'rev' from the 'hidden' set, 'rev' is going to be hidden.
56 blocker = rev not in hidden
56 blocker = rev not in hidden
57 for parent in getparentrevs(rev):
57 for parent in getparentrevs(rev):
58 if parent == nullrev:
58 if parent == nullrev:
59 continue
59 continue
60 if blocker:
60 if blocker:
61 # If visible, ensure parent will be visible too
61 # If visible, ensure parent will be visible too
62 hidden.discard(parent)
62 hidden.discard(parent)
63 # - Avoid adding the same revision twice
63 # - Avoid adding the same revision twice
64 # - Skip nodes which are public (guaranteed to not be hidden)
64 # - Skip nodes which are public (guaranteed to not be hidden)
65 pre = len(seen)
65 pre = len(seen)
66 seen.add(parent)
66 seen.add(parent)
67 if pre < len(seen) and getphase(repo, rev):
67 if pre < len(seen) and getphase(repo, rev):
68 heappush(heap, -parent)
68 heappush(heap, -parent)
69 return hidden
69 return hidden
70
70
71 def _getdynamicblockers(repo):
71 def _getdynamicblockers(repo):
72 """Non-cacheable revisions blocking hidden changesets from being filtered.
72 """Non-cacheable revisions blocking hidden changesets from being filtered.
73
73
74 Get revisions that will block hidden changesets and are likely to change,
74 Get revisions that will block hidden changesets and are likely to change,
75 but unlikely to create hidden blockers. They won't be cached, so be careful
75 but unlikely to create hidden blockers. They won't be cached, so be careful
76 with adding additional computation."""
76 with adding additional computation."""
77
77
78 cl = repo.changelog
78 cl = repo.changelog
79 blockers = set()
79 blockers = set()
80 blockers.update([par.rev() for par in repo[None].parents()])
80 blockers.update([par.rev() for par in repo[None].parents()])
81 blockers.update([cl.rev(bm) for bm in repo._bookmarks.values()])
81 blockers.update([cl.rev(bm) for bm in repo._bookmarks.values()])
82
82
83 tags = {}
83 tags = {}
84 tagsmod.readlocaltags(repo.ui, repo, tags, {})
84 tagsmod.readlocaltags(repo.ui, repo, tags, {})
85 if tags:
85 if tags:
86 rev, nodemap = cl.rev, cl.nodemap
86 rev, nodemap = cl.rev, cl.nodemap
87 blockers.update(rev(t[0]) for t in tags.values() if t[0] in nodemap)
87 blockers.update(rev(t[0]) for t in tags.values() if t[0] in nodemap)
88 return blockers
88 return blockers
89
89
90 cacheversion = 1
90 cacheversion = 1
91 cachefile = 'cache/hidden'
91 cachefile = 'cache/hidden'
92
92
93 def cachehash(repo, hideable):
93 def cachehash(repo, hideable):
94 """return sha1 hash of repository data to identify a valid cache.
94 """return sha1 hash of repository data to identify a valid cache.
95
95
96 We calculate a sha1 of repo heads and the content of the obsstore and write
96 We calculate a sha1 of repo heads and the content of the obsstore and write
97 it to the cache. Upon reading we can easily validate by checking the hash
97 it to the cache. Upon reading we can easily validate by checking the hash
98 against the stored one and discard the cache in case the hashes don't match.
98 against the stored one and discard the cache in case the hashes don't match.
99 """
99 """
100 h = util.sha1()
100 h = util.sha1()
101 h.update(''.join(repo.heads()))
101 h.update(''.join(repo.heads()))
102 h.update(str(hash(frozenset(hideable))))
102 h.update(str(hash(frozenset(hideable))))
103 return h.digest()
103 return h.digest()
104
104
105 def _writehiddencache(cachefile, cachehash, hidden):
105 def _writehiddencache(cachefile, cachehash, hidden):
106 """write hidden data to a cache file"""
106 """write hidden data to a cache file"""
107 data = struct.pack('>%ii' % len(hidden), *sorted(hidden))
107 data = struct.pack('>%ii' % len(hidden), *sorted(hidden))
108 cachefile.write(struct.pack(">H", cacheversion))
108 cachefile.write(struct.pack(">H", cacheversion))
109 cachefile.write(cachehash)
109 cachefile.write(cachehash)
110 cachefile.write(data)
110 cachefile.write(data)
111
111
112 def trywritehiddencache(repo, hideable, hidden):
112 def trywritehiddencache(repo, hideable, hidden):
113 """write cache of hidden changesets to disk
113 """write cache of hidden changesets to disk
114
114
115 Will not write the cache if a wlock cannot be obtained lazily.
115 Will not write the cache if a wlock cannot be obtained lazily.
116 The cache consists of a head of 22byte:
116 The cache consists of a head of 22byte:
117 2 byte version number of the cache
117 2 byte version number of the cache
118 20 byte sha1 to validate the cache
118 20 byte sha1 to validate the cache
119 n*4 byte hidden revs
119 n*4 byte hidden revs
120 """
120 """
121 wlock = fh = None
121 wlock = fh = None
122 try:
122 try:
123 wlock = repo.wlock(wait=False)
123 wlock = repo.wlock(wait=False)
124 # write cache to file
124 # write cache to file
125 newhash = cachehash(repo, hideable)
125 newhash = cachehash(repo, hideable)
126 fh = repo.vfs.open(cachefile, 'w+b', atomictemp=True)
126 fh = repo.vfs.open(cachefile, 'w+b', atomictemp=True)
127 _writehiddencache(fh, newhash, hidden)
127 _writehiddencache(fh, newhash, hidden)
128 except (IOError, OSError):
128 except (IOError, OSError):
129 repo.ui.debug('error writing hidden changesets cache\n')
129 repo.ui.debug('error writing hidden changesets cache\n')
130 except error.LockHeld:
130 except error.LockHeld:
131 repo.ui.debug('cannot obtain lock to write hidden changesets cache\n')
131 repo.ui.debug('cannot obtain lock to write hidden changesets cache\n')
132 finally:
132 finally:
133 if fh:
133 if fh:
134 fh.close()
134 fh.close()
135 if wlock:
135 if wlock:
136 wlock.release()
136 wlock.release()
137
137
138 def tryreadcache(repo, hideable):
138 def tryreadcache(repo, hideable):
139 """read a cache if the cache exists and is valid, otherwise returns None."""
139 """read a cache if the cache exists and is valid, otherwise returns None."""
140 hidden = fh = None
140 hidden = fh = None
141 try:
141 try:
142 if repo.vfs.exists(cachefile):
142 if repo.vfs.exists(cachefile):
143 fh = repo.vfs.open(cachefile, 'rb')
143 fh = repo.vfs.open(cachefile, 'rb')
144 version, = struct.unpack(">H", fh.read(2))
144 version, = struct.unpack(">H", fh.read(2))
145 oldhash = fh.read(20)
145 oldhash = fh.read(20)
146 newhash = cachehash(repo, hideable)
146 newhash = cachehash(repo, hideable)
147 if (cacheversion, oldhash) == (version, newhash):
147 if (cacheversion, oldhash) == (version, newhash):
148 # cache is valid, so we can start reading the hidden revs
148 # cache is valid, so we can start reading the hidden revs
149 data = fh.read()
149 data = fh.read()
150 count = len(data) / 4
150 count = len(data) / 4
151 hidden = frozenset(struct.unpack('>%ii' % count, data))
151 hidden = frozenset(struct.unpack('>%ii' % count, data))
152 return hidden
152 return hidden
153 except struct.error:
153 except struct.error:
154 repo.ui.debug('corrupted hidden cache\n')
154 repo.ui.debug('corrupted hidden cache\n')
155 # No need to fix the content as it will get rewritten
155 # No need to fix the content as it will get rewritten
156 return None
156 return None
157 except (IOError, OSError):
157 except (IOError, OSError):
158 repo.ui.debug('cannot read hidden cache\n')
158 repo.ui.debug('cannot read hidden cache\n')
159 return None
159 return None
160 finally:
160 finally:
161 if fh:
161 if fh:
162 fh.close()
162 fh.close()
163
163
164 def computehidden(repo):
164 def computehidden(repo):
165 """compute the set of hidden revision to filter
165 """compute the set of hidden revision to filter
166
166
167 During most operation hidden should be filtered."""
167 During most operation hidden should be filtered."""
168 assert not repo.changelog.filteredrevs
168 assert not repo.changelog.filteredrevs
169
169
170 hidden = frozenset()
170 hidden = frozenset()
171 hideable = hideablerevs(repo)
171 hideable = hideablerevs(repo)
172 if hideable:
172 if hideable:
173 cl = repo.changelog
173 cl = repo.changelog
174 hidden = tryreadcache(repo, hideable)
174 hidden = tryreadcache(repo, hideable)
175 if hidden is None:
175 if hidden is None:
176 hidden = frozenset(_getstatichidden(repo))
176 hidden = frozenset(_getstatichidden(repo))
177 trywritehiddencache(repo, hideable, hidden)
177 trywritehiddencache(repo, hideable, hidden)
178
178
179 # check if we have wd parents, bookmarks or tags pointing to hidden
179 # check if we have wd parents, bookmarks or tags pointing to hidden
180 # changesets and remove those.
180 # changesets and remove those.
181 dynamic = hidden & _getdynamicblockers(repo)
181 dynamic = hidden & _getdynamicblockers(repo)
182 if dynamic:
182 if dynamic:
183 blocked = cl.ancestors(dynamic, inclusive=True)
183 blocked = cl.ancestors(dynamic, inclusive=True)
184 hidden = frozenset(r for r in hidden if r not in blocked)
184 hidden = frozenset(r for r in hidden if r not in blocked)
185 return hidden
185 return hidden
186
186
187 def computeunserved(repo):
187 def computeunserved(repo):
188 """compute the set of revision that should be filtered when used a server
188 """compute the set of revision that should be filtered when used a server
189
189
190 Secret and hidden changeset should not pretend to be here."""
190 Secret and hidden changeset should not pretend to be here."""
191 assert not repo.changelog.filteredrevs
191 assert not repo.changelog.filteredrevs
192 # fast path in simple case to avoid impact of non optimised code
192 # fast path in simple case to avoid impact of non optimised code
193 hiddens = filterrevs(repo, 'visible')
193 hiddens = filterrevs(repo, 'visible')
194 if phases.hassecret(repo):
194 if phases.hassecret(repo):
195 cl = repo.changelog
195 cl = repo.changelog
196 secret = phases.secret
196 secret = phases.secret
197 getphase = repo._phasecache.phase
197 getphase = repo._phasecache.phase
198 first = min(cl.rev(n) for n in repo._phasecache.phaseroots[secret])
198 first = min(cl.rev(n) for n in repo._phasecache.phaseroots[secret])
199 revs = cl.revs(start=first)
199 revs = cl.revs(start=first)
200 secrets = set(r for r in revs if getphase(repo, r) >= secret)
200 secrets = set(r for r in revs if getphase(repo, r) >= secret)
201 return frozenset(hiddens | secrets)
201 return frozenset(hiddens | secrets)
202 else:
202 else:
203 return hiddens
203 return hiddens
204
204
205 def computemutable(repo):
205 def computemutable(repo):
206 """compute the set of revision that should be filtered when used a server
206 """compute the set of revision that should be filtered when used a server
207
207
208 Secret and hidden changeset should not pretend to be here."""
208 Secret and hidden changeset should not pretend to be here."""
209 assert not repo.changelog.filteredrevs
209 assert not repo.changelog.filteredrevs
210 # fast check to avoid revset call on huge repo
210 # fast check to avoid revset call on huge repo
211 if any(repo._phasecache.phaseroots[1:]):
211 if any(repo._phasecache.phaseroots[1:]):
212 getphase = repo._phasecache.phase
212 getphase = repo._phasecache.phase
213 maymutable = filterrevs(repo, 'base')
213 maymutable = filterrevs(repo, 'base')
214 return frozenset(r for r in maymutable if getphase(repo, r))
214 return frozenset(r for r in maymutable if getphase(repo, r))
215 return frozenset()
215 return frozenset()
216
216
217 def computeimpactable(repo):
217 def computeimpactable(repo):
218 """Everything impactable by mutable revision
218 """Everything impactable by mutable revision
219
219
220 The immutable filter still have some chance to get invalidated. This will
220 The immutable filter still have some chance to get invalidated. This will
221 happen when:
221 happen when:
222
222
223 - you garbage collect hidden changeset,
223 - you garbage collect hidden changeset,
224 - public phase is moved backward,
224 - public phase is moved backward,
225 - something is changed in the filtering (this could be fixed)
225 - something is changed in the filtering (this could be fixed)
226
226
227 This filter out any mutable changeset and any public changeset that may be
227 This filter out any mutable changeset and any public changeset that may be
228 impacted by something happening to a mutable revision.
228 impacted by something happening to a mutable revision.
229
229
230 This is achieved by filtered everything with a revision number egal or
230 This is achieved by filtered everything with a revision number egal or
231 higher than the first mutable changeset is filtered."""
231 higher than the first mutable changeset is filtered."""
232 assert not repo.changelog.filteredrevs
232 assert not repo.changelog.filteredrevs
233 cl = repo.changelog
233 cl = repo.changelog
234 firstmutable = len(cl)
234 firstmutable = len(cl)
235 for roots in repo._phasecache.phaseroots[1:]:
235 for roots in repo._phasecache.phaseroots[1:]:
236 if roots:
236 if roots:
237 firstmutable = min(firstmutable, min(cl.rev(r) for r in roots))
237 firstmutable = min(firstmutable, min(cl.rev(r) for r in roots))
238 # protect from nullrev root
238 # protect from nullrev root
239 firstmutable = max(0, firstmutable)
239 firstmutable = max(0, firstmutable)
240 return frozenset(xrange(firstmutable, len(cl)))
240 return frozenset(xrange(firstmutable, len(cl)))
241
241
242 # function to compute filtered set
242 # function to compute filtered set
243 #
243 #
244 # When adding a new filter you MUST update the table at:
244 # When adding a new filter you MUST update the table at:
245 # mercurial.branchmap.subsettable
245 # mercurial.branchmap.subsettable
246 # Otherwise your filter will have to recompute all its branches cache
246 # Otherwise your filter will have to recompute all its branches cache
247 # from scratch (very slow).
247 # from scratch (very slow).
248 filtertable = {'visible': computehidden,
248 filtertable = {'visible': computehidden,
249 'served': computeunserved,
249 'served': computeunserved,
250 'immutable': computemutable,
250 'immutable': computemutable,
251 'base': computeimpactable}
251 'base': computeimpactable}
252
252
253 def filterrevs(repo, filtername):
253 def filterrevs(repo, filtername):
254 """returns set of filtered revision for this filter name"""
254 """returns set of filtered revision for this filter name"""
255 if filtername not in repo.filteredrevcache:
255 if filtername not in repo.filteredrevcache:
256 func = filtertable[filtername]
256 func = filtertable[filtername]
257 repo.filteredrevcache[filtername] = func(repo.unfiltered())
257 repo.filteredrevcache[filtername] = func(repo.unfiltered())
258 return repo.filteredrevcache[filtername]
258 return repo.filteredrevcache[filtername]
259
259
260 class repoview(object):
260 class repoview(object):
261 """Provide a read/write view of a repo through a filtered changelog
261 """Provide a read/write view of a repo through a filtered changelog
262
262
263 This object is used to access a filtered version of a repository without
263 This object is used to access a filtered version of a repository without
264 altering the original repository object itself. We can not alter the
264 altering the original repository object itself. We can not alter the
265 original object for two main reasons:
265 original object for two main reasons:
266 - It prevents the use of a repo with multiple filters at the same time. In
266 - It prevents the use of a repo with multiple filters at the same time. In
267 particular when multiple threads are involved.
267 particular when multiple threads are involved.
268 - It makes scope of the filtering harder to control.
268 - It makes scope of the filtering harder to control.
269
269
270 This object behaves very closely to the original repository. All attribute
270 This object behaves very closely to the original repository. All attribute
271 operations are done on the original repository:
271 operations are done on the original repository:
272 - An access to `repoview.someattr` actually returns `repo.someattr`,
272 - An access to `repoview.someattr` actually returns `repo.someattr`,
273 - A write to `repoview.someattr` actually sets value of `repo.someattr`,
273 - A write to `repoview.someattr` actually sets value of `repo.someattr`,
274 - A deletion of `repoview.someattr` actually drops `someattr`
274 - A deletion of `repoview.someattr` actually drops `someattr`
275 from `repo.__dict__`.
275 from `repo.__dict__`.
276
276
277 The only exception is the `changelog` property. It is overridden to return
277 The only exception is the `changelog` property. It is overridden to return
278 a (surface) copy of `repo.changelog` with some revisions filtered. The
278 a (surface) copy of `repo.changelog` with some revisions filtered. The
279 `filtername` attribute of the view control the revisions that need to be
279 `filtername` attribute of the view control the revisions that need to be
280 filtered. (the fact the changelog is copied is an implementation detail).
280 filtered. (the fact the changelog is copied is an implementation detail).
281
281
282 Unlike attributes, this object intercepts all method calls. This means that
282 Unlike attributes, this object intercepts all method calls. This means that
283 all methods are run on the `repoview` object with the filtered `changelog`
283 all methods are run on the `repoview` object with the filtered `changelog`
284 property. For this purpose the simple `repoview` class must be mixed with
284 property. For this purpose the simple `repoview` class must be mixed with
285 the actual class of the repository. This ensures that the resulting
285 the actual class of the repository. This ensures that the resulting
286 `repoview` object have the very same methods than the repo object. This
286 `repoview` object have the very same methods than the repo object. This
287 leads to the property below.
287 leads to the property below.
288
288
289 repoview.method() --> repo.__class__.method(repoview)
289 repoview.method() --> repo.__class__.method(repoview)
290
290
291 The inheritance has to be done dynamically because `repo` can be of any
291 The inheritance has to be done dynamically because `repo` can be of any
292 subclasses of `localrepo`. Eg: `bundlerepo` or `statichttprepo`.
292 subclasses of `localrepo`. Eg: `bundlerepo` or `statichttprepo`.
293 """
293 """
294
294
295 def __init__(self, repo, filtername):
295 def __init__(self, repo, filtername):
296 object.__setattr__(self, '_unfilteredrepo', repo)
296 object.__setattr__(self, '_unfilteredrepo', repo)
297 object.__setattr__(self, 'filtername', filtername)
297 object.__setattr__(self, 'filtername', filtername)
298 object.__setattr__(self, '_clcachekey', None)
298 object.__setattr__(self, '_clcachekey', None)
299 object.__setattr__(self, '_clcache', None)
299 object.__setattr__(self, '_clcache', None)
300
300
301 # not a propertycache on purpose we shall implement a proper cache later
301 # not a propertycache on purpose we shall implement a proper cache later
302 @property
302 @property
303 def changelog(self):
303 def changelog(self):
304 """return a filtered version of the changeset
304 """return a filtered version of the changeset
305
305
306 this changelog must not be used for writing"""
306 this changelog must not be used for writing"""
307 # some cache may be implemented later
307 # some cache may be implemented later
308 unfi = self._unfilteredrepo
308 unfi = self._unfilteredrepo
309 unfichangelog = unfi.changelog
309 unfichangelog = unfi.changelog
310 # bypass call to changelog.method
310 # bypass call to changelog.method
311 unfiindex = unfichangelog.index
311 unfiindex = unfichangelog.index
312 unfilen = len(unfiindex) - 1
312 unfilen = len(unfiindex) - 1
313 unfinode = unfiindex[unfilen - 1][7]
313 unfinode = unfiindex[unfilen - 1][7]
314
314
315 revs = filterrevs(unfi, self.filtername)
315 revs = filterrevs(unfi, self.filtername)
316 cl = self._clcache
316 cl = self._clcache
317 newkey = (unfilen, unfinode, hash(revs), unfichangelog._delayed)
317 newkey = (unfilen, unfinode, hash(revs), unfichangelog._delayed)
318 if cl is not None and newkey != self._clcachekey:
318 # if cl.index is not unfiindex, unfi.changelog would be
319 # recreated, and our clcache refers to garbage object
320 if (cl is not None and
321 (cl.index is not unfiindex or newkey != self._clcachekey)):
319 cl = None
322 cl = None
320 # could have been made None by the previous if
323 # could have been made None by the previous if
321 if cl is None:
324 if cl is None:
322 cl = copy.copy(unfichangelog)
325 cl = copy.copy(unfichangelog)
323 cl.filteredrevs = revs
326 cl.filteredrevs = revs
324 object.__setattr__(self, '_clcache', cl)
327 object.__setattr__(self, '_clcache', cl)
325 object.__setattr__(self, '_clcachekey', newkey)
328 object.__setattr__(self, '_clcachekey', newkey)
326 return cl
329 return cl
327
330
328 def unfiltered(self):
331 def unfiltered(self):
329 """Return an unfiltered version of a repo"""
332 """Return an unfiltered version of a repo"""
330 return self._unfilteredrepo
333 return self._unfilteredrepo
331
334
332 def filtered(self, name):
335 def filtered(self, name):
333 """Return a filtered version of a repository"""
336 """Return a filtered version of a repository"""
334 if name == self.filtername:
337 if name == self.filtername:
335 return self
338 return self
336 return self.unfiltered().filtered(name)
339 return self.unfiltered().filtered(name)
337
340
338 # everything access are forwarded to the proxied repo
341 # everything access are forwarded to the proxied repo
339 def __getattr__(self, attr):
342 def __getattr__(self, attr):
340 return getattr(self._unfilteredrepo, attr)
343 return getattr(self._unfilteredrepo, attr)
341
344
342 def __setattr__(self, attr, value):
345 def __setattr__(self, attr, value):
343 return setattr(self._unfilteredrepo, attr, value)
346 return setattr(self._unfilteredrepo, attr, value)
344
347
345 def __delattr__(self, attr):
348 def __delattr__(self, attr):
346 return delattr(self._unfilteredrepo, attr)
349 return delattr(self._unfilteredrepo, attr)
347
350
348 # The `requirements` attribute is initialized during __init__. But
351 # The `requirements` attribute is initialized during __init__. But
349 # __getattr__ won't be called as it also exists on the class. We need
352 # __getattr__ won't be called as it also exists on the class. We need
350 # explicit forwarding to main repo here
353 # explicit forwarding to main repo here
351 @property
354 @property
352 def requirements(self):
355 def requirements(self):
353 return self._unfilteredrepo.requirements
356 return self._unfilteredrepo.requirements
@@ -1,727 +1,857 b''
1 #if windows
1 #if windows
2 $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH"
2 $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH"
3 #else
3 #else
4 $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH"
4 $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH"
5 #endif
5 #endif
6 $ export PYTHONPATH
6 $ export PYTHONPATH
7
7
8 typical client does not want echo-back messages, so test without it:
8 typical client does not want echo-back messages, so test without it:
9
9
10 $ grep -v '^promptecho ' < $HGRCPATH >> $HGRCPATH.new
10 $ grep -v '^promptecho ' < $HGRCPATH >> $HGRCPATH.new
11 $ mv $HGRCPATH.new $HGRCPATH
11 $ mv $HGRCPATH.new $HGRCPATH
12
12
13 $ hg init repo
13 $ hg init repo
14 $ cd repo
14 $ cd repo
15
15
16 >>> from hgclient import readchannel, runcommand, check
16 >>> from hgclient import readchannel, runcommand, check
17 >>> @check
17 >>> @check
18 ... def hellomessage(server):
18 ... def hellomessage(server):
19 ... ch, data = readchannel(server)
19 ... ch, data = readchannel(server)
20 ... print '%c, %r' % (ch, data)
20 ... print '%c, %r' % (ch, data)
21 ... # run an arbitrary command to make sure the next thing the server
21 ... # run an arbitrary command to make sure the next thing the server
22 ... # sends isn't part of the hello message
22 ... # sends isn't part of the hello message
23 ... runcommand(server, ['id'])
23 ... runcommand(server, ['id'])
24 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
24 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
25 *** runcommand id
25 *** runcommand id
26 000000000000 tip
26 000000000000 tip
27
27
28 >>> from hgclient import check
28 >>> from hgclient import check
29 >>> @check
29 >>> @check
30 ... def unknowncommand(server):
30 ... def unknowncommand(server):
31 ... server.stdin.write('unknowncommand\n')
31 ... server.stdin.write('unknowncommand\n')
32 abort: unknown command unknowncommand
32 abort: unknown command unknowncommand
33
33
34 >>> from hgclient import readchannel, runcommand, check
34 >>> from hgclient import readchannel, runcommand, check
35 >>> @check
35 >>> @check
36 ... def checkruncommand(server):
36 ... def checkruncommand(server):
37 ... # hello block
37 ... # hello block
38 ... readchannel(server)
38 ... readchannel(server)
39 ...
39 ...
40 ... # no args
40 ... # no args
41 ... runcommand(server, [])
41 ... runcommand(server, [])
42 ...
42 ...
43 ... # global options
43 ... # global options
44 ... runcommand(server, ['id', '--quiet'])
44 ... runcommand(server, ['id', '--quiet'])
45 ...
45 ...
46 ... # make sure global options don't stick through requests
46 ... # make sure global options don't stick through requests
47 ... runcommand(server, ['id'])
47 ... runcommand(server, ['id'])
48 ...
48 ...
49 ... # --config
49 ... # --config
50 ... runcommand(server, ['id', '--config', 'ui.quiet=True'])
50 ... runcommand(server, ['id', '--config', 'ui.quiet=True'])
51 ...
51 ...
52 ... # make sure --config doesn't stick
52 ... # make sure --config doesn't stick
53 ... runcommand(server, ['id'])
53 ... runcommand(server, ['id'])
54 ...
54 ...
55 ... # negative return code should be masked
55 ... # negative return code should be masked
56 ... runcommand(server, ['id', '-runknown'])
56 ... runcommand(server, ['id', '-runknown'])
57 *** runcommand
57 *** runcommand
58 Mercurial Distributed SCM
58 Mercurial Distributed SCM
59
59
60 basic commands:
60 basic commands:
61
61
62 add add the specified files on the next commit
62 add add the specified files on the next commit
63 annotate show changeset information by line for each file
63 annotate show changeset information by line for each file
64 clone make a copy of an existing repository
64 clone make a copy of an existing repository
65 commit commit the specified files or all outstanding changes
65 commit commit the specified files or all outstanding changes
66 diff diff repository (or selected files)
66 diff diff repository (or selected files)
67 export dump the header and diffs for one or more changesets
67 export dump the header and diffs for one or more changesets
68 forget forget the specified files on the next commit
68 forget forget the specified files on the next commit
69 init create a new repository in the given directory
69 init create a new repository in the given directory
70 log show revision history of entire repository or files
70 log show revision history of entire repository or files
71 merge merge another revision into working directory
71 merge merge another revision into working directory
72 pull pull changes from the specified source
72 pull pull changes from the specified source
73 push push changes to the specified destination
73 push push changes to the specified destination
74 remove remove the specified files on the next commit
74 remove remove the specified files on the next commit
75 serve start stand-alone webserver
75 serve start stand-alone webserver
76 status show changed files in the working directory
76 status show changed files in the working directory
77 summary summarize working directory state
77 summary summarize working directory state
78 update update working directory (or switch revisions)
78 update update working directory (or switch revisions)
79
79
80 (use "hg help" for the full list of commands or "hg -v" for details)
80 (use "hg help" for the full list of commands or "hg -v" for details)
81 *** runcommand id --quiet
81 *** runcommand id --quiet
82 000000000000
82 000000000000
83 *** runcommand id
83 *** runcommand id
84 000000000000 tip
84 000000000000 tip
85 *** runcommand id --config ui.quiet=True
85 *** runcommand id --config ui.quiet=True
86 000000000000
86 000000000000
87 *** runcommand id
87 *** runcommand id
88 000000000000 tip
88 000000000000 tip
89 *** runcommand id -runknown
89 *** runcommand id -runknown
90 abort: unknown revision 'unknown'!
90 abort: unknown revision 'unknown'!
91 [255]
91 [255]
92
92
93 >>> from hgclient import readchannel, check
93 >>> from hgclient import readchannel, check
94 >>> @check
94 >>> @check
95 ... def inputeof(server):
95 ... def inputeof(server):
96 ... readchannel(server)
96 ... readchannel(server)
97 ... server.stdin.write('runcommand\n')
97 ... server.stdin.write('runcommand\n')
98 ... # close stdin while server is waiting for input
98 ... # close stdin while server is waiting for input
99 ... server.stdin.close()
99 ... server.stdin.close()
100 ...
100 ...
101 ... # server exits with 1 if the pipe closed while reading the command
101 ... # server exits with 1 if the pipe closed while reading the command
102 ... print 'server exit code =', server.wait()
102 ... print 'server exit code =', server.wait()
103 server exit code = 1
103 server exit code = 1
104
104
105 >>> import cStringIO
105 >>> import cStringIO
106 >>> from hgclient import readchannel, runcommand, check
106 >>> from hgclient import readchannel, runcommand, check
107 >>> @check
107 >>> @check
108 ... def serverinput(server):
108 ... def serverinput(server):
109 ... readchannel(server)
109 ... readchannel(server)
110 ...
110 ...
111 ... patch = """
111 ... patch = """
112 ... # HG changeset patch
112 ... # HG changeset patch
113 ... # User test
113 ... # User test
114 ... # Date 0 0
114 ... # Date 0 0
115 ... # Node ID c103a3dec114d882c98382d684d8af798d09d857
115 ... # Node ID c103a3dec114d882c98382d684d8af798d09d857
116 ... # Parent 0000000000000000000000000000000000000000
116 ... # Parent 0000000000000000000000000000000000000000
117 ... 1
117 ... 1
118 ...
118 ...
119 ... diff -r 000000000000 -r c103a3dec114 a
119 ... diff -r 000000000000 -r c103a3dec114 a
120 ... --- /dev/null Thu Jan 01 00:00:00 1970 +0000
120 ... --- /dev/null Thu Jan 01 00:00:00 1970 +0000
121 ... +++ b/a Thu Jan 01 00:00:00 1970 +0000
121 ... +++ b/a Thu Jan 01 00:00:00 1970 +0000
122 ... @@ -0,0 +1,1 @@
122 ... @@ -0,0 +1,1 @@
123 ... +1
123 ... +1
124 ... """
124 ... """
125 ...
125 ...
126 ... runcommand(server, ['import', '-'], input=cStringIO.StringIO(patch))
126 ... runcommand(server, ['import', '-'], input=cStringIO.StringIO(patch))
127 ... runcommand(server, ['log'])
127 ... runcommand(server, ['log'])
128 *** runcommand import -
128 *** runcommand import -
129 applying patch from stdin
129 applying patch from stdin
130 *** runcommand log
130 *** runcommand log
131 changeset: 0:eff892de26ec
131 changeset: 0:eff892de26ec
132 tag: tip
132 tag: tip
133 user: test
133 user: test
134 date: Thu Jan 01 00:00:00 1970 +0000
134 date: Thu Jan 01 00:00:00 1970 +0000
135 summary: 1
135 summary: 1
136
136
137
137
138 check that --cwd doesn't persist between requests:
138 check that --cwd doesn't persist between requests:
139
139
140 $ mkdir foo
140 $ mkdir foo
141 $ touch foo/bar
141 $ touch foo/bar
142 >>> from hgclient import readchannel, runcommand, check
142 >>> from hgclient import readchannel, runcommand, check
143 >>> @check
143 >>> @check
144 ... def cwd(server):
144 ... def cwd(server):
145 ... readchannel(server)
145 ... readchannel(server)
146 ... runcommand(server, ['--cwd', 'foo', 'st', 'bar'])
146 ... runcommand(server, ['--cwd', 'foo', 'st', 'bar'])
147 ... runcommand(server, ['st', 'foo/bar'])
147 ... runcommand(server, ['st', 'foo/bar'])
148 *** runcommand --cwd foo st bar
148 *** runcommand --cwd foo st bar
149 ? bar
149 ? bar
150 *** runcommand st foo/bar
150 *** runcommand st foo/bar
151 ? foo/bar
151 ? foo/bar
152
152
153 $ rm foo/bar
153 $ rm foo/bar
154
154
155
155
156 check that local configs for the cached repo aren't inherited when -R is used:
156 check that local configs for the cached repo aren't inherited when -R is used:
157
157
158 $ cat <<EOF >> .hg/hgrc
158 $ cat <<EOF >> .hg/hgrc
159 > [ui]
159 > [ui]
160 > foo = bar
160 > foo = bar
161 > EOF
161 > EOF
162
162
163 >>> from hgclient import readchannel, sep, runcommand, check
163 >>> from hgclient import readchannel, sep, runcommand, check
164 >>> @check
164 >>> @check
165 ... def localhgrc(server):
165 ... def localhgrc(server):
166 ... readchannel(server)
166 ... readchannel(server)
167 ...
167 ...
168 ... # the cached repo local hgrc contains ui.foo=bar, so showconfig should
168 ... # the cached repo local hgrc contains ui.foo=bar, so showconfig should
169 ... # show it
169 ... # show it
170 ... runcommand(server, ['showconfig'], outfilter=sep)
170 ... runcommand(server, ['showconfig'], outfilter=sep)
171 ...
171 ...
172 ... # but not for this repo
172 ... # but not for this repo
173 ... runcommand(server, ['init', 'foo'])
173 ... runcommand(server, ['init', 'foo'])
174 ... runcommand(server, ['-R', 'foo', 'showconfig', 'ui', 'defaults'])
174 ... runcommand(server, ['-R', 'foo', 'showconfig', 'ui', 'defaults'])
175 *** runcommand showconfig
175 *** runcommand showconfig
176 bundle.mainreporoot=$TESTTMP/repo
176 bundle.mainreporoot=$TESTTMP/repo
177 defaults.backout=-d "0 0"
177 defaults.backout=-d "0 0"
178 defaults.commit=-d "0 0"
178 defaults.commit=-d "0 0"
179 defaults.shelve=--date "0 0"
179 defaults.shelve=--date "0 0"
180 defaults.tag=-d "0 0"
180 defaults.tag=-d "0 0"
181 devel.all-warnings=true
181 devel.all-warnings=true
182 largefiles.usercache=$TESTTMP/.cache/largefiles
182 largefiles.usercache=$TESTTMP/.cache/largefiles
183 ui.slash=True
183 ui.slash=True
184 ui.interactive=False
184 ui.interactive=False
185 ui.mergemarkers=detailed
185 ui.mergemarkers=detailed
186 ui.foo=bar
186 ui.foo=bar
187 ui.nontty=true
187 ui.nontty=true
188 *** runcommand init foo
188 *** runcommand init foo
189 *** runcommand -R foo showconfig ui defaults
189 *** runcommand -R foo showconfig ui defaults
190 defaults.backout=-d "0 0"
190 defaults.backout=-d "0 0"
191 defaults.commit=-d "0 0"
191 defaults.commit=-d "0 0"
192 defaults.shelve=--date "0 0"
192 defaults.shelve=--date "0 0"
193 defaults.tag=-d "0 0"
193 defaults.tag=-d "0 0"
194 ui.slash=True
194 ui.slash=True
195 ui.interactive=False
195 ui.interactive=False
196 ui.mergemarkers=detailed
196 ui.mergemarkers=detailed
197 ui.nontty=true
197 ui.nontty=true
198
198
199 $ rm -R foo
199 $ rm -R foo
200
200
201 #if windows
201 #if windows
202 $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH"
202 $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH"
203 #else
203 #else
204 $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH"
204 $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH"
205 #endif
205 #endif
206
206
207 $ cat <<EOF > hook.py
207 $ cat <<EOF > hook.py
208 > import sys
208 > import sys
209 > def hook(**args):
209 > def hook(**args):
210 > print 'hook talking'
210 > print 'hook talking'
211 > print 'now try to read something: %r' % sys.stdin.read()
211 > print 'now try to read something: %r' % sys.stdin.read()
212 > EOF
212 > EOF
213
213
214 >>> import cStringIO
214 >>> import cStringIO
215 >>> from hgclient import readchannel, runcommand, check
215 >>> from hgclient import readchannel, runcommand, check
216 >>> @check
216 >>> @check
217 ... def hookoutput(server):
217 ... def hookoutput(server):
218 ... readchannel(server)
218 ... readchannel(server)
219 ... runcommand(server, ['--config',
219 ... runcommand(server, ['--config',
220 ... 'hooks.pre-identify=python:hook.hook',
220 ... 'hooks.pre-identify=python:hook.hook',
221 ... 'id'],
221 ... 'id'],
222 ... input=cStringIO.StringIO('some input'))
222 ... input=cStringIO.StringIO('some input'))
223 *** runcommand --config hooks.pre-identify=python:hook.hook id
223 *** runcommand --config hooks.pre-identify=python:hook.hook id
224 hook talking
224 hook talking
225 now try to read something: 'some input'
225 now try to read something: 'some input'
226 eff892de26ec tip
226 eff892de26ec tip
227
227
228 $ rm hook.py*
228 $ rm hook.py*
229
229
230 $ echo a >> a
230 $ echo a >> a
231 >>> import os
231 >>> import os
232 >>> from hgclient import readchannel, runcommand, check
232 >>> from hgclient import readchannel, runcommand, check
233 >>> @check
233 >>> @check
234 ... def outsidechanges(server):
234 ... def outsidechanges(server):
235 ... readchannel(server)
235 ... readchannel(server)
236 ... runcommand(server, ['status'])
236 ... runcommand(server, ['status'])
237 ... os.system('hg ci -Am2')
237 ... os.system('hg ci -Am2')
238 ... runcommand(server, ['tip'])
238 ... runcommand(server, ['tip'])
239 ... runcommand(server, ['status'])
239 ... runcommand(server, ['status'])
240 *** runcommand status
240 *** runcommand status
241 M a
241 M a
242 *** runcommand tip
242 *** runcommand tip
243 changeset: 1:d3a0a68be6de
243 changeset: 1:d3a0a68be6de
244 tag: tip
244 tag: tip
245 user: test
245 user: test
246 date: Thu Jan 01 00:00:00 1970 +0000
246 date: Thu Jan 01 00:00:00 1970 +0000
247 summary: 2
247 summary: 2
248
248
249 *** runcommand status
249 *** runcommand status
250
250
251 >>> import os
251 >>> import os
252 >>> from hgclient import readchannel, runcommand, check
252 >>> from hgclient import readchannel, runcommand, check
253 >>> @check
253 >>> @check
254 ... def bookmarks(server):
254 ... def bookmarks(server):
255 ... readchannel(server)
255 ... readchannel(server)
256 ... runcommand(server, ['bookmarks'])
256 ... runcommand(server, ['bookmarks'])
257 ...
257 ...
258 ... # changes .hg/bookmarks
258 ... # changes .hg/bookmarks
259 ... os.system('hg bookmark -i bm1')
259 ... os.system('hg bookmark -i bm1')
260 ... os.system('hg bookmark -i bm2')
260 ... os.system('hg bookmark -i bm2')
261 ... runcommand(server, ['bookmarks'])
261 ... runcommand(server, ['bookmarks'])
262 ...
262 ...
263 ... # changes .hg/bookmarks.current
263 ... # changes .hg/bookmarks.current
264 ... os.system('hg upd bm1 -q')
264 ... os.system('hg upd bm1 -q')
265 ... runcommand(server, ['bookmarks'])
265 ... runcommand(server, ['bookmarks'])
266 ...
266 ...
267 ... runcommand(server, ['bookmarks', 'bm3'])
267 ... runcommand(server, ['bookmarks', 'bm3'])
268 ... f = open('a', 'ab')
268 ... f = open('a', 'ab')
269 ... f.write('a\n')
269 ... f.write('a\n')
270 ... f.close()
270 ... f.close()
271 ... runcommand(server, ['commit', '-Amm'])
271 ... runcommand(server, ['commit', '-Amm'])
272 ... runcommand(server, ['bookmarks'])
272 ... runcommand(server, ['bookmarks'])
273 *** runcommand bookmarks
273 *** runcommand bookmarks
274 no bookmarks set
274 no bookmarks set
275 *** runcommand bookmarks
275 *** runcommand bookmarks
276 bm1 1:d3a0a68be6de
276 bm1 1:d3a0a68be6de
277 bm2 1:d3a0a68be6de
277 bm2 1:d3a0a68be6de
278 *** runcommand bookmarks
278 *** runcommand bookmarks
279 * bm1 1:d3a0a68be6de
279 * bm1 1:d3a0a68be6de
280 bm2 1:d3a0a68be6de
280 bm2 1:d3a0a68be6de
281 *** runcommand bookmarks bm3
281 *** runcommand bookmarks bm3
282 *** runcommand commit -Amm
282 *** runcommand commit -Amm
283 *** runcommand bookmarks
283 *** runcommand bookmarks
284 bm1 1:d3a0a68be6de
284 bm1 1:d3a0a68be6de
285 bm2 1:d3a0a68be6de
285 bm2 1:d3a0a68be6de
286 * bm3 2:aef17e88f5f0
286 * bm3 2:aef17e88f5f0
287
287
288 >>> import os
288 >>> import os
289 >>> from hgclient import readchannel, runcommand, check
289 >>> from hgclient import readchannel, runcommand, check
290 >>> @check
290 >>> @check
291 ... def tagscache(server):
291 ... def tagscache(server):
292 ... readchannel(server)
292 ... readchannel(server)
293 ... runcommand(server, ['id', '-t', '-r', '0'])
293 ... runcommand(server, ['id', '-t', '-r', '0'])
294 ... os.system('hg tag -r 0 foo')
294 ... os.system('hg tag -r 0 foo')
295 ... runcommand(server, ['id', '-t', '-r', '0'])
295 ... runcommand(server, ['id', '-t', '-r', '0'])
296 *** runcommand id -t -r 0
296 *** runcommand id -t -r 0
297
297
298 *** runcommand id -t -r 0
298 *** runcommand id -t -r 0
299 foo
299 foo
300
300
301 >>> import os
301 >>> import os
302 >>> from hgclient import readchannel, runcommand, check
302 >>> from hgclient import readchannel, runcommand, check
303 >>> @check
303 >>> @check
304 ... def setphase(server):
304 ... def setphase(server):
305 ... readchannel(server)
305 ... readchannel(server)
306 ... runcommand(server, ['phase', '-r', '.'])
306 ... runcommand(server, ['phase', '-r', '.'])
307 ... os.system('hg phase -r . -p')
307 ... os.system('hg phase -r . -p')
308 ... runcommand(server, ['phase', '-r', '.'])
308 ... runcommand(server, ['phase', '-r', '.'])
309 *** runcommand phase -r .
309 *** runcommand phase -r .
310 3: draft
310 3: draft
311 *** runcommand phase -r .
311 *** runcommand phase -r .
312 3: public
312 3: public
313
313
314 $ echo a >> a
314 $ echo a >> a
315 >>> from hgclient import readchannel, runcommand, check
315 >>> from hgclient import readchannel, runcommand, check
316 >>> @check
316 >>> @check
317 ... def rollback(server):
317 ... def rollback(server):
318 ... readchannel(server)
318 ... readchannel(server)
319 ... runcommand(server, ['phase', '-r', '.', '-p'])
319 ... runcommand(server, ['phase', '-r', '.', '-p'])
320 ... runcommand(server, ['commit', '-Am.'])
320 ... runcommand(server, ['commit', '-Am.'])
321 ... runcommand(server, ['rollback'])
321 ... runcommand(server, ['rollback'])
322 ... runcommand(server, ['phase', '-r', '.'])
322 ... runcommand(server, ['phase', '-r', '.'])
323 *** runcommand phase -r . -p
323 *** runcommand phase -r . -p
324 no phases changed
324 no phases changed
325 *** runcommand commit -Am.
325 *** runcommand commit -Am.
326 *** runcommand rollback
326 *** runcommand rollback
327 repository tip rolled back to revision 3 (undo commit)
327 repository tip rolled back to revision 3 (undo commit)
328 working directory now based on revision 3
328 working directory now based on revision 3
329 *** runcommand phase -r .
329 *** runcommand phase -r .
330 3: public
330 3: public
331
331
332 >>> import os
332 >>> import os
333 >>> from hgclient import readchannel, runcommand, check
333 >>> from hgclient import readchannel, runcommand, check
334 >>> @check
334 >>> @check
335 ... def branch(server):
335 ... def branch(server):
336 ... readchannel(server)
336 ... readchannel(server)
337 ... runcommand(server, ['branch'])
337 ... runcommand(server, ['branch'])
338 ... os.system('hg branch foo')
338 ... os.system('hg branch foo')
339 ... runcommand(server, ['branch'])
339 ... runcommand(server, ['branch'])
340 ... os.system('hg branch default')
340 ... os.system('hg branch default')
341 *** runcommand branch
341 *** runcommand branch
342 default
342 default
343 marked working directory as branch foo
343 marked working directory as branch foo
344 (branches are permanent and global, did you want a bookmark?)
344 (branches are permanent and global, did you want a bookmark?)
345 *** runcommand branch
345 *** runcommand branch
346 foo
346 foo
347 marked working directory as branch default
347 marked working directory as branch default
348 (branches are permanent and global, did you want a bookmark?)
348 (branches are permanent and global, did you want a bookmark?)
349
349
350 $ touch .hgignore
350 $ touch .hgignore
351 >>> import os
351 >>> import os
352 >>> from hgclient import readchannel, runcommand, check
352 >>> from hgclient import readchannel, runcommand, check
353 >>> @check
353 >>> @check
354 ... def hgignore(server):
354 ... def hgignore(server):
355 ... readchannel(server)
355 ... readchannel(server)
356 ... runcommand(server, ['commit', '-Am.'])
356 ... runcommand(server, ['commit', '-Am.'])
357 ... f = open('ignored-file', 'ab')
357 ... f = open('ignored-file', 'ab')
358 ... f.write('')
358 ... f.write('')
359 ... f.close()
359 ... f.close()
360 ... f = open('.hgignore', 'ab')
360 ... f = open('.hgignore', 'ab')
361 ... f.write('ignored-file')
361 ... f.write('ignored-file')
362 ... f.close()
362 ... f.close()
363 ... runcommand(server, ['status', '-i', '-u'])
363 ... runcommand(server, ['status', '-i', '-u'])
364 *** runcommand commit -Am.
364 *** runcommand commit -Am.
365 adding .hgignore
365 adding .hgignore
366 *** runcommand status -i -u
366 *** runcommand status -i -u
367 I ignored-file
367 I ignored-file
368
368
369 cache of non-public revisions should be invalidated on repository change
369 cache of non-public revisions should be invalidated on repository change
370 (issue4855):
370 (issue4855):
371
371
372 >>> import os
372 >>> import os
373 >>> from hgclient import readchannel, runcommand, check
373 >>> from hgclient import readchannel, runcommand, check
374 >>> @check
374 >>> @check
375 ... def phasesetscacheaftercommit(server):
375 ... def phasesetscacheaftercommit(server):
376 ... readchannel(server)
376 ... readchannel(server)
377 ... # load _phasecache._phaserevs and _phasesets
377 ... # load _phasecache._phaserevs and _phasesets
378 ... runcommand(server, ['log', '-qr', 'draft()'])
378 ... runcommand(server, ['log', '-qr', 'draft()'])
379 ... # create draft commits by another process
379 ... # create draft commits by another process
380 ... for i in xrange(5, 7):
380 ... for i in xrange(5, 7):
381 ... f = open('a', 'ab')
381 ... f = open('a', 'ab')
382 ... f.seek(0, os.SEEK_END)
382 ... f.seek(0, os.SEEK_END)
383 ... f.write('a\n')
383 ... f.write('a\n')
384 ... f.close()
384 ... f.close()
385 ... os.system('hg commit -Aqm%d' % i)
385 ... os.system('hg commit -Aqm%d' % i)
386 ... # new commits should be listed as draft revisions
386 ... # new commits should be listed as draft revisions
387 ... runcommand(server, ['log', '-qr', 'draft()'])
387 ... runcommand(server, ['log', '-qr', 'draft()'])
388 *** runcommand log -qr draft()
388 *** runcommand log -qr draft()
389 4:7966c8e3734d
389 4:7966c8e3734d
390 *** runcommand log -qr draft()
390 *** runcommand log -qr draft()
391 4:7966c8e3734d
391 4:7966c8e3734d
392 5:41f6602d1c4f
392 5:41f6602d1c4f
393 6:10501e202c35
393 6:10501e202c35
394
394
395 >>> import os
395 >>> import os
396 >>> from hgclient import readchannel, runcommand, check
396 >>> from hgclient import readchannel, runcommand, check
397 >>> @check
397 >>> @check
398 ... def phasesetscacheafterstrip(server):
398 ... def phasesetscacheafterstrip(server):
399 ... readchannel(server)
399 ... readchannel(server)
400 ... # load _phasecache._phaserevs and _phasesets
400 ... # load _phasecache._phaserevs and _phasesets
401 ... runcommand(server, ['log', '-qr', 'draft()'])
401 ... runcommand(server, ['log', '-qr', 'draft()'])
402 ... # strip cached revisions by another process
402 ... # strip cached revisions by another process
403 ... os.system('hg --config extensions.strip= strip -q 5')
403 ... os.system('hg --config extensions.strip= strip -q 5')
404 ... # shouldn't abort by "unknown revision '6'"
404 ... # shouldn't abort by "unknown revision '6'"
405 ... runcommand(server, ['log', '-qr', 'draft()'])
405 ... runcommand(server, ['log', '-qr', 'draft()'])
406 *** runcommand log -qr draft()
406 *** runcommand log -qr draft()
407 4:7966c8e3734d
407 4:7966c8e3734d
408 5:41f6602d1c4f
408 5:41f6602d1c4f
409 6:10501e202c35
409 6:10501e202c35
410 *** runcommand log -qr draft()
410 *** runcommand log -qr draft()
411 4:7966c8e3734d
411 4:7966c8e3734d
412
412
413 cache of phase roots should be invalidated on strip (issue3827):
413 cache of phase roots should be invalidated on strip (issue3827):
414
414
415 >>> import os
415 >>> import os
416 >>> from hgclient import readchannel, sep, runcommand, check
416 >>> from hgclient import readchannel, sep, runcommand, check
417 >>> @check
417 >>> @check
418 ... def phasecacheafterstrip(server):
418 ... def phasecacheafterstrip(server):
419 ... readchannel(server)
419 ... readchannel(server)
420 ...
420 ...
421 ... # create new head, 5:731265503d86
421 ... # create new head, 5:731265503d86
422 ... runcommand(server, ['update', '-C', '0'])
422 ... runcommand(server, ['update', '-C', '0'])
423 ... f = open('a', 'ab')
423 ... f = open('a', 'ab')
424 ... f.write('a\n')
424 ... f.write('a\n')
425 ... f.close()
425 ... f.close()
426 ... runcommand(server, ['commit', '-Am.', 'a'])
426 ... runcommand(server, ['commit', '-Am.', 'a'])
427 ... runcommand(server, ['log', '-Gq'])
427 ... runcommand(server, ['log', '-Gq'])
428 ...
428 ...
429 ... # make it public; draft marker moves to 4:7966c8e3734d
429 ... # make it public; draft marker moves to 4:7966c8e3734d
430 ... runcommand(server, ['phase', '-p', '.'])
430 ... runcommand(server, ['phase', '-p', '.'])
431 ... # load _phasecache.phaseroots
431 ... # load _phasecache.phaseroots
432 ... runcommand(server, ['phase', '.'], outfilter=sep)
432 ... runcommand(server, ['phase', '.'], outfilter=sep)
433 ...
433 ...
434 ... # strip 1::4 outside server
434 ... # strip 1::4 outside server
435 ... os.system('hg -q --config extensions.mq= strip 1')
435 ... os.system('hg -q --config extensions.mq= strip 1')
436 ...
436 ...
437 ... # shouldn't raise "7966c8e3734d: no node!"
437 ... # shouldn't raise "7966c8e3734d: no node!"
438 ... runcommand(server, ['branches'])
438 ... runcommand(server, ['branches'])
439 *** runcommand update -C 0
439 *** runcommand update -C 0
440 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
440 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
441 (leaving bookmark bm3)
441 (leaving bookmark bm3)
442 *** runcommand commit -Am. a
442 *** runcommand commit -Am. a
443 created new head
443 created new head
444 *** runcommand log -Gq
444 *** runcommand log -Gq
445 @ 5:731265503d86
445 @ 5:731265503d86
446 |
446 |
447 | o 4:7966c8e3734d
447 | o 4:7966c8e3734d
448 | |
448 | |
449 | o 3:b9b85890c400
449 | o 3:b9b85890c400
450 | |
450 | |
451 | o 2:aef17e88f5f0
451 | o 2:aef17e88f5f0
452 | |
452 | |
453 | o 1:d3a0a68be6de
453 | o 1:d3a0a68be6de
454 |/
454 |/
455 o 0:eff892de26ec
455 o 0:eff892de26ec
456
456
457 *** runcommand phase -p .
457 *** runcommand phase -p .
458 *** runcommand phase .
458 *** runcommand phase .
459 5: public
459 5: public
460 *** runcommand branches
460 *** runcommand branches
461 default 1:731265503d86
461 default 1:731265503d86
462
462
463 in-memory cache must be reloaded if transaction is aborted. otherwise
463 in-memory cache must be reloaded if transaction is aborted. otherwise
464 changelog and manifest would have invalid node:
464 changelog and manifest would have invalid node:
465
465
466 $ echo a >> a
466 $ echo a >> a
467 >>> from hgclient import readchannel, runcommand, check
467 >>> from hgclient import readchannel, runcommand, check
468 >>> @check
468 >>> @check
469 ... def txabort(server):
469 ... def txabort(server):
470 ... readchannel(server)
470 ... readchannel(server)
471 ... runcommand(server, ['commit', '--config', 'hooks.pretxncommit=false',
471 ... runcommand(server, ['commit', '--config', 'hooks.pretxncommit=false',
472 ... '-mfoo'])
472 ... '-mfoo'])
473 ... runcommand(server, ['verify'])
473 ... runcommand(server, ['verify'])
474 *** runcommand commit --config hooks.pretxncommit=false -mfoo
474 *** runcommand commit --config hooks.pretxncommit=false -mfoo
475 transaction abort!
475 transaction abort!
476 rollback completed
476 rollback completed
477 abort: pretxncommit hook exited with status 1
477 abort: pretxncommit hook exited with status 1
478 [255]
478 [255]
479 *** runcommand verify
479 *** runcommand verify
480 checking changesets
480 checking changesets
481 checking manifests
481 checking manifests
482 crosschecking files in changesets and manifests
482 crosschecking files in changesets and manifests
483 checking files
483 checking files
484 1 files, 2 changesets, 2 total revisions
484 1 files, 2 changesets, 2 total revisions
485 $ hg revert --no-backup -aq
485 $ hg revert --no-backup -aq
486
486
487 $ cat >> .hg/hgrc << EOF
487 $ cat >> .hg/hgrc << EOF
488 > [experimental]
488 > [experimental]
489 > evolution=createmarkers
489 > evolution=createmarkers
490 > EOF
490 > EOF
491
491
492 >>> import os
492 >>> import os
493 >>> from hgclient import readchannel, runcommand, check
493 >>> from hgclient import readchannel, runcommand, check
494 >>> @check
494 >>> @check
495 ... def obsolete(server):
495 ... def obsolete(server):
496 ... readchannel(server)
496 ... readchannel(server)
497 ...
497 ...
498 ... runcommand(server, ['up', 'null'])
498 ... runcommand(server, ['up', 'null'])
499 ... runcommand(server, ['phase', '-df', 'tip'])
499 ... runcommand(server, ['phase', '-df', 'tip'])
500 ... cmd = 'hg debugobsolete `hg log -r tip --template {node}`'
500 ... cmd = 'hg debugobsolete `hg log -r tip --template {node}`'
501 ... if os.name == 'nt':
501 ... if os.name == 'nt':
502 ... cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe
502 ... cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe
503 ... os.system(cmd)
503 ... os.system(cmd)
504 ... runcommand(server, ['log', '--hidden'])
504 ... runcommand(server, ['log', '--hidden'])
505 ... runcommand(server, ['log'])
505 ... runcommand(server, ['log'])
506 *** runcommand up null
506 *** runcommand up null
507 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
507 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
508 *** runcommand phase -df tip
508 *** runcommand phase -df tip
509 *** runcommand log --hidden
509 *** runcommand log --hidden
510 changeset: 1:731265503d86
510 changeset: 1:731265503d86
511 tag: tip
511 tag: tip
512 user: test
512 user: test
513 date: Thu Jan 01 00:00:00 1970 +0000
513 date: Thu Jan 01 00:00:00 1970 +0000
514 summary: .
514 summary: .
515
515
516 changeset: 0:eff892de26ec
516 changeset: 0:eff892de26ec
517 bookmark: bm1
517 bookmark: bm1
518 bookmark: bm2
518 bookmark: bm2
519 bookmark: bm3
519 bookmark: bm3
520 user: test
520 user: test
521 date: Thu Jan 01 00:00:00 1970 +0000
521 date: Thu Jan 01 00:00:00 1970 +0000
522 summary: 1
522 summary: 1
523
523
524 *** runcommand log
524 *** runcommand log
525 changeset: 0:eff892de26ec
525 changeset: 0:eff892de26ec
526 bookmark: bm1
526 bookmark: bm1
527 bookmark: bm2
527 bookmark: bm2
528 bookmark: bm3
528 bookmark: bm3
529 tag: tip
529 tag: tip
530 user: test
530 user: test
531 date: Thu Jan 01 00:00:00 1970 +0000
531 date: Thu Jan 01 00:00:00 1970 +0000
532 summary: 1
532 summary: 1
533
533
534
534
535 $ cat <<EOF >> .hg/hgrc
535 $ cat <<EOF >> .hg/hgrc
536 > [extensions]
536 > [extensions]
537 > mq =
537 > mq =
538 > EOF
538 > EOF
539
539
540 >>> import os
540 >>> import os
541 >>> from hgclient import readchannel, runcommand, check
541 >>> from hgclient import readchannel, runcommand, check
542 >>> @check
542 >>> @check
543 ... def mqoutsidechanges(server):
543 ... def mqoutsidechanges(server):
544 ... readchannel(server)
544 ... readchannel(server)
545 ...
545 ...
546 ... # load repo.mq
546 ... # load repo.mq
547 ... runcommand(server, ['qapplied'])
547 ... runcommand(server, ['qapplied'])
548 ... os.system('hg qnew 0.diff')
548 ... os.system('hg qnew 0.diff')
549 ... # repo.mq should be invalidated
549 ... # repo.mq should be invalidated
550 ... runcommand(server, ['qapplied'])
550 ... runcommand(server, ['qapplied'])
551 ...
551 ...
552 ... runcommand(server, ['qpop', '--all'])
552 ... runcommand(server, ['qpop', '--all'])
553 ... os.system('hg qqueue --create foo')
553 ... os.system('hg qqueue --create foo')
554 ... # repo.mq should be recreated to point to new queue
554 ... # repo.mq should be recreated to point to new queue
555 ... runcommand(server, ['qqueue', '--active'])
555 ... runcommand(server, ['qqueue', '--active'])
556 *** runcommand qapplied
556 *** runcommand qapplied
557 *** runcommand qapplied
557 *** runcommand qapplied
558 0.diff
558 0.diff
559 *** runcommand qpop --all
559 *** runcommand qpop --all
560 popping 0.diff
560 popping 0.diff
561 patch queue now empty
561 patch queue now empty
562 *** runcommand qqueue --active
562 *** runcommand qqueue --active
563 foo
563 foo
564
564
565 $ cat <<EOF > dbgui.py
565 $ cat <<EOF > dbgui.py
566 > import os, sys
566 > import os, sys
567 > from mercurial import cmdutil, commands
567 > from mercurial import cmdutil, commands
568 > cmdtable = {}
568 > cmdtable = {}
569 > command = cmdutil.command(cmdtable)
569 > command = cmdutil.command(cmdtable)
570 > @command("debuggetpass", norepo=True)
570 > @command("debuggetpass", norepo=True)
571 > def debuggetpass(ui):
571 > def debuggetpass(ui):
572 > ui.write("%s\\n" % ui.getpass())
572 > ui.write("%s\\n" % ui.getpass())
573 > @command("debugprompt", norepo=True)
573 > @command("debugprompt", norepo=True)
574 > def debugprompt(ui):
574 > def debugprompt(ui):
575 > ui.write("%s\\n" % ui.prompt("prompt:"))
575 > ui.write("%s\\n" % ui.prompt("prompt:"))
576 > @command("debugreadstdin", norepo=True)
576 > @command("debugreadstdin", norepo=True)
577 > def debugreadstdin(ui):
577 > def debugreadstdin(ui):
578 > ui.write("read: %r\n" % sys.stdin.read(1))
578 > ui.write("read: %r\n" % sys.stdin.read(1))
579 > @command("debugwritestdout", norepo=True)
579 > @command("debugwritestdout", norepo=True)
580 > def debugwritestdout(ui):
580 > def debugwritestdout(ui):
581 > os.write(1, "low-level stdout fd and\n")
581 > os.write(1, "low-level stdout fd and\n")
582 > sys.stdout.write("stdout should be redirected to /dev/null\n")
582 > sys.stdout.write("stdout should be redirected to /dev/null\n")
583 > sys.stdout.flush()
583 > sys.stdout.flush()
584 > EOF
584 > EOF
585 $ cat <<EOF >> .hg/hgrc
585 $ cat <<EOF >> .hg/hgrc
586 > [extensions]
586 > [extensions]
587 > dbgui = dbgui.py
587 > dbgui = dbgui.py
588 > EOF
588 > EOF
589
589
590 >>> import cStringIO
590 >>> import cStringIO
591 >>> from hgclient import readchannel, runcommand, check
591 >>> from hgclient import readchannel, runcommand, check
592 >>> @check
592 >>> @check
593 ... def getpass(server):
593 ... def getpass(server):
594 ... readchannel(server)
594 ... readchannel(server)
595 ... runcommand(server, ['debuggetpass', '--config',
595 ... runcommand(server, ['debuggetpass', '--config',
596 ... 'ui.interactive=True'],
596 ... 'ui.interactive=True'],
597 ... input=cStringIO.StringIO('1234\n'))
597 ... input=cStringIO.StringIO('1234\n'))
598 ... runcommand(server, ['debugprompt', '--config',
598 ... runcommand(server, ['debugprompt', '--config',
599 ... 'ui.interactive=True'],
599 ... 'ui.interactive=True'],
600 ... input=cStringIO.StringIO('5678\n'))
600 ... input=cStringIO.StringIO('5678\n'))
601 ... runcommand(server, ['debugreadstdin'])
601 ... runcommand(server, ['debugreadstdin'])
602 ... runcommand(server, ['debugwritestdout'])
602 ... runcommand(server, ['debugwritestdout'])
603 *** runcommand debuggetpass --config ui.interactive=True
603 *** runcommand debuggetpass --config ui.interactive=True
604 password: 1234
604 password: 1234
605 *** runcommand debugprompt --config ui.interactive=True
605 *** runcommand debugprompt --config ui.interactive=True
606 prompt: 5678
606 prompt: 5678
607 *** runcommand debugreadstdin
607 *** runcommand debugreadstdin
608 read: ''
608 read: ''
609 *** runcommand debugwritestdout
609 *** runcommand debugwritestdout
610
610
611
611
612 run commandserver in commandserver, which is silly but should work:
612 run commandserver in commandserver, which is silly but should work:
613
613
614 >>> import cStringIO
614 >>> import cStringIO
615 >>> from hgclient import readchannel, runcommand, check
615 >>> from hgclient import readchannel, runcommand, check
616 >>> @check
616 >>> @check
617 ... def nested(server):
617 ... def nested(server):
618 ... print '%c, %r' % readchannel(server)
618 ... print '%c, %r' % readchannel(server)
619 ... class nestedserver(object):
619 ... class nestedserver(object):
620 ... stdin = cStringIO.StringIO('getencoding\n')
620 ... stdin = cStringIO.StringIO('getencoding\n')
621 ... stdout = cStringIO.StringIO()
621 ... stdout = cStringIO.StringIO()
622 ... runcommand(server, ['serve', '--cmdserver', 'pipe'],
622 ... runcommand(server, ['serve', '--cmdserver', 'pipe'],
623 ... output=nestedserver.stdout, input=nestedserver.stdin)
623 ... output=nestedserver.stdout, input=nestedserver.stdin)
624 ... nestedserver.stdout.seek(0)
624 ... nestedserver.stdout.seek(0)
625 ... print '%c, %r' % readchannel(nestedserver) # hello
625 ... print '%c, %r' % readchannel(nestedserver) # hello
626 ... print '%c, %r' % readchannel(nestedserver) # getencoding
626 ... print '%c, %r' % readchannel(nestedserver) # getencoding
627 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
627 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
628 *** runcommand serve --cmdserver pipe
628 *** runcommand serve --cmdserver pipe
629 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
629 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
630 r, '*' (glob)
630 r, '*' (glob)
631
631
632
632
633 start without repository:
633 start without repository:
634
634
635 $ cd ..
635 $ cd ..
636
636
637 >>> from hgclient import readchannel, runcommand, check
637 >>> from hgclient import readchannel, runcommand, check
638 >>> @check
638 >>> @check
639 ... def hellomessage(server):
639 ... def hellomessage(server):
640 ... ch, data = readchannel(server)
640 ... ch, data = readchannel(server)
641 ... print '%c, %r' % (ch, data)
641 ... print '%c, %r' % (ch, data)
642 ... # run an arbitrary command to make sure the next thing the server
642 ... # run an arbitrary command to make sure the next thing the server
643 ... # sends isn't part of the hello message
643 ... # sends isn't part of the hello message
644 ... runcommand(server, ['id'])
644 ... runcommand(server, ['id'])
645 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
645 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
646 *** runcommand id
646 *** runcommand id
647 abort: there is no Mercurial repository here (.hg not found)
647 abort: there is no Mercurial repository here (.hg not found)
648 [255]
648 [255]
649
649
650 >>> from hgclient import readchannel, runcommand, check
650 >>> from hgclient import readchannel, runcommand, check
651 >>> @check
651 >>> @check
652 ... def startwithoutrepo(server):
652 ... def startwithoutrepo(server):
653 ... readchannel(server)
653 ... readchannel(server)
654 ... runcommand(server, ['init', 'repo2'])
654 ... runcommand(server, ['init', 'repo2'])
655 ... runcommand(server, ['id', '-R', 'repo2'])
655 ... runcommand(server, ['id', '-R', 'repo2'])
656 *** runcommand init repo2
656 *** runcommand init repo2
657 *** runcommand id -R repo2
657 *** runcommand id -R repo2
658 000000000000 tip
658 000000000000 tip
659
659
660
660
661 don't fall back to cwd if invalid -R path is specified (issue4805):
661 don't fall back to cwd if invalid -R path is specified (issue4805):
662
662
663 $ cd repo
663 $ cd repo
664 $ hg serve --cmdserver pipe -R ../nonexistent
664 $ hg serve --cmdserver pipe -R ../nonexistent
665 abort: repository ../nonexistent not found!
665 abort: repository ../nonexistent not found!
666 [255]
666 [255]
667 $ cd ..
667 $ cd ..
668
668
669
669
670 unix domain socket:
670 unix domain socket:
671
671
672 $ cd repo
672 $ cd repo
673 $ hg update -q
673 $ hg update -q
674
674
675 #if unix-socket unix-permissions
675 #if unix-socket unix-permissions
676
676
677 >>> import cStringIO
677 >>> import cStringIO
678 >>> from hgclient import unixserver, readchannel, runcommand, check
678 >>> from hgclient import unixserver, readchannel, runcommand, check
679 >>> server = unixserver('.hg/server.sock', '.hg/server.log')
679 >>> server = unixserver('.hg/server.sock', '.hg/server.log')
680 >>> def hellomessage(conn):
680 >>> def hellomessage(conn):
681 ... ch, data = readchannel(conn)
681 ... ch, data = readchannel(conn)
682 ... print '%c, %r' % (ch, data)
682 ... print '%c, %r' % (ch, data)
683 ... runcommand(conn, ['id'])
683 ... runcommand(conn, ['id'])
684 >>> check(hellomessage, server.connect)
684 >>> check(hellomessage, server.connect)
685 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
685 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
686 *** runcommand id
686 *** runcommand id
687 eff892de26ec tip bm1/bm2/bm3
687 eff892de26ec tip bm1/bm2/bm3
688 >>> def unknowncommand(conn):
688 >>> def unknowncommand(conn):
689 ... readchannel(conn)
689 ... readchannel(conn)
690 ... conn.stdin.write('unknowncommand\n')
690 ... conn.stdin.write('unknowncommand\n')
691 >>> check(unknowncommand, server.connect) # error sent to server.log
691 >>> check(unknowncommand, server.connect) # error sent to server.log
692 >>> def serverinput(conn):
692 >>> def serverinput(conn):
693 ... readchannel(conn)
693 ... readchannel(conn)
694 ... patch = """
694 ... patch = """
695 ... # HG changeset patch
695 ... # HG changeset patch
696 ... # User test
696 ... # User test
697 ... # Date 0 0
697 ... # Date 0 0
698 ... 2
698 ... 2
699 ...
699 ...
700 ... diff -r eff892de26ec -r 1ed24be7e7a0 a
700 ... diff -r eff892de26ec -r 1ed24be7e7a0 a
701 ... --- a/a
701 ... --- a/a
702 ... +++ b/a
702 ... +++ b/a
703 ... @@ -1,1 +1,2 @@
703 ... @@ -1,1 +1,2 @@
704 ... 1
704 ... 1
705 ... +2
705 ... +2
706 ... """
706 ... """
707 ... runcommand(conn, ['import', '-'], input=cStringIO.StringIO(patch))
707 ... runcommand(conn, ['import', '-'], input=cStringIO.StringIO(patch))
708 ... runcommand(conn, ['log', '-rtip', '-q'])
708 ... runcommand(conn, ['log', '-rtip', '-q'])
709 >>> check(serverinput, server.connect)
709 >>> check(serverinput, server.connect)
710 *** runcommand import -
710 *** runcommand import -
711 applying patch from stdin
711 applying patch from stdin
712 *** runcommand log -rtip -q
712 *** runcommand log -rtip -q
713 2:1ed24be7e7a0
713 2:1ed24be7e7a0
714 >>> server.shutdown()
714 >>> server.shutdown()
715
715
716 $ cat .hg/server.log
716 $ cat .hg/server.log
717 listening at .hg/server.sock
717 listening at .hg/server.sock
718 abort: unknown command unknowncommand
718 abort: unknown command unknowncommand
719 killed!
719 killed!
720 #endif
720 #endif
721 #if no-unix-socket
721 #if no-unix-socket
722
722
723 $ hg serve --cmdserver unix -a .hg/server.sock
723 $ hg serve --cmdserver unix -a .hg/server.sock
724 abort: unsupported platform
724 abort: unsupported platform
725 [255]
725 [255]
726
726
727 #endif
727 #endif
728
729 $ cd ..
730
731 Test that accessing to invalid changelog cache is avoided at
732 subsequent operations even if repo object is reused even after failure
733 of transaction (see 0a7610758c42 also)
734
735 "hg log" after failure of transaction is needed to detect invalid
736 cache in repoview: this can't detect by "hg verify" only.
737
738 Combination of "finalization" and "empty-ness of changelog" (2 x 2 =
739 4) are tested, because '00changelog.i' are differently changed in each
740 cases.
741
742 $ cat > $TESTTMP/failafterfinalize.py <<EOF
743 > # extension to abort transaction after finalization forcibly
744 > from mercurial import commands, error, extensions, lock as lockmod
745 > def fail(tr):
746 > raise error.Abort('fail after finalization')
747 > def reposetup(ui, repo):
748 > class failrepo(repo.__class__):
749 > def commitctx(self, ctx, error=False):
750 > if self.ui.configbool('failafterfinalize', 'fail'):
751 > # 'sorted()' by ASCII code on category names causes
752 > # invoking 'fail' after finalization of changelog
753 > # using "'cl-%i' % id(self)" as category name
754 > self.currenttransaction().addfinalize('zzzzzzzz', fail)
755 > return super(failrepo, self).commitctx(ctx, error)
756 > repo.__class__ = failrepo
757 > EOF
758
759 $ hg init repo3
760 $ cd repo3
761
762 $ cat <<EOF >> $HGRCPATH
763 > [ui]
764 > logtemplate = {rev} {desc|firstline} ({files})\n
765 >
766 > [extensions]
767 > failafterfinalize = $TESTTMP/failafterfinalize.py
768 > EOF
769
770 - test failure with "empty changelog"
771
772 $ echo foo > foo
773 $ hg add foo
774
775 (failuer before finalization)
776
777 >>> from hgclient import readchannel, runcommand, check
778 >>> @check
779 ... def abort(server):
780 ... readchannel(server)
781 ... runcommand(server, ['commit',
782 ... '--config', 'hooks.pretxncommit=false',
783 ... '-mfoo'])
784 ... runcommand(server, ['log'])
785 ... runcommand(server, ['verify', '-q'])
786 *** runcommand commit --config hooks.pretxncommit=false -mfoo
787 transaction abort!
788 rollback completed
789 abort: pretxncommit hook exited with status 1
790 [255]
791 *** runcommand log
792 *** runcommand verify -q
793
794 (failuer after finalization)
795
796 >>> from hgclient import readchannel, runcommand, check
797 >>> @check
798 ... def abort(server):
799 ... readchannel(server)
800 ... runcommand(server, ['commit',
801 ... '--config', 'failafterfinalize.fail=true',
802 ... '-mfoo'])
803 ... runcommand(server, ['log'])
804 ... runcommand(server, ['verify', '-q'])
805 *** runcommand commit --config failafterfinalize.fail=true -mfoo
806 transaction abort!
807 rollback completed
808 abort: fail after finalization
809 [255]
810 *** runcommand log
811 *** runcommand verify -q
812
813 - test failure with "not-empty changelog"
814
815 $ echo bar > bar
816 $ hg add bar
817 $ hg commit -mbar bar
818
819 (failure before finalization)
820
821 >>> from hgclient import readchannel, runcommand, check
822 >>> @check
823 ... def abort(server):
824 ... readchannel(server)
825 ... runcommand(server, ['commit',
826 ... '--config', 'hooks.pretxncommit=false',
827 ... '-mfoo', 'foo'])
828 ... runcommand(server, ['log'])
829 ... runcommand(server, ['verify', '-q'])
830 *** runcommand commit --config hooks.pretxncommit=false -mfoo foo
831 transaction abort!
832 rollback completed
833 abort: pretxncommit hook exited with status 1
834 [255]
835 *** runcommand log
836 0 bar (bar)
837 *** runcommand verify -q
838
839 (failure after finalization)
840
841 >>> from hgclient import readchannel, runcommand, check
842 >>> @check
843 ... def abort(server):
844 ... readchannel(server)
845 ... runcommand(server, ['commit',
846 ... '--config', 'failafterfinalize.fail=true',
847 ... '-mfoo', 'foo'])
848 ... runcommand(server, ['log'])
849 ... runcommand(server, ['verify', '-q'])
850 *** runcommand commit --config failafterfinalize.fail=true -mfoo foo
851 transaction abort!
852 rollback completed
853 abort: fail after finalization
854 [255]
855 *** runcommand log
856 0 bar (bar)
857 *** runcommand verify -q
General Comments 0
You need to be logged in to leave comments. Login now