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