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