##// END OF EJS Templates
hg: raise Abort on invalid path...
Gregory Szorc -
r41625:7f366dd3 default
parent child Browse files
Show More
@@ -1,1225 +1,1234 b''
1 1 # hg.py - repository classes for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@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 hashlib
13 13 import os
14 14 import shutil
15 15 import stat
16 16
17 17 from .i18n import _
18 18 from .node import (
19 19 nullid,
20 20 )
21 21
22 22 from . import (
23 23 bookmarks,
24 24 bundlerepo,
25 25 cacheutil,
26 26 cmdutil,
27 27 destutil,
28 28 discovery,
29 29 error,
30 30 exchange,
31 31 extensions,
32 32 httppeer,
33 33 localrepo,
34 34 lock,
35 35 logcmdutil,
36 36 logexchange,
37 37 merge as mergemod,
38 38 narrowspec,
39 39 node,
40 40 phases,
41 pycompat,
41 42 repository as repositorymod,
42 43 scmutil,
43 44 sshpeer,
44 45 statichttprepo,
45 46 ui as uimod,
46 47 unionrepo,
47 48 url,
48 49 util,
49 50 verify as verifymod,
50 51 vfs as vfsmod,
51 52 )
52 53
53 54 release = lock.release
54 55
55 56 # shared features
56 57 sharedbookmarks = 'bookmarks'
57 58
58 59 def _local(path):
59 60 path = util.expandpath(util.urllocalpath(path))
60 return (os.path.isfile(path) and bundlerepo or localrepo)
61
62 try:
63 isfile = os.path.isfile(path)
64 # Python 2 raises TypeError, Python 3 ValueError.
65 except (TypeError, ValueError) as e:
66 raise error.Abort(_('invalid path %s: %s') % (
67 path, pycompat.bytestr(e)))
68
69 return isfile and bundlerepo or localrepo
61 70
62 71 def addbranchrevs(lrepo, other, branches, revs):
63 72 peer = other.peer() # a courtesy to callers using a localrepo for other
64 73 hashbranch, branches = branches
65 74 if not hashbranch and not branches:
66 75 x = revs or None
67 76 if revs:
68 77 y = revs[0]
69 78 else:
70 79 y = None
71 80 return x, y
72 81 if revs:
73 82 revs = list(revs)
74 83 else:
75 84 revs = []
76 85
77 86 if not peer.capable('branchmap'):
78 87 if branches:
79 88 raise error.Abort(_("remote branch lookup not supported"))
80 89 revs.append(hashbranch)
81 90 return revs, revs[0]
82 91
83 92 with peer.commandexecutor() as e:
84 93 branchmap = e.callcommand('branchmap', {}).result()
85 94
86 95 def primary(branch):
87 96 if branch == '.':
88 97 if not lrepo:
89 98 raise error.Abort(_("dirstate branch not accessible"))
90 99 branch = lrepo.dirstate.branch()
91 100 if branch in branchmap:
92 101 revs.extend(node.hex(r) for r in reversed(branchmap[branch]))
93 102 return True
94 103 else:
95 104 return False
96 105
97 106 for branch in branches:
98 107 if not primary(branch):
99 108 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
100 109 if hashbranch:
101 110 if not primary(hashbranch):
102 111 revs.append(hashbranch)
103 112 return revs, revs[0]
104 113
105 114 def parseurl(path, branches=None):
106 115 '''parse url#branch, returning (url, (branch, branches))'''
107 116
108 117 u = util.url(path)
109 118 branch = None
110 119 if u.fragment:
111 120 branch = u.fragment
112 121 u.fragment = None
113 122 return bytes(u), (branch, branches or [])
114 123
115 124 schemes = {
116 125 'bundle': bundlerepo,
117 126 'union': unionrepo,
118 127 'file': _local,
119 128 'http': httppeer,
120 129 'https': httppeer,
121 130 'ssh': sshpeer,
122 131 'static-http': statichttprepo,
123 132 }
124 133
125 134 def _peerlookup(path):
126 135 u = util.url(path)
127 136 scheme = u.scheme or 'file'
128 137 thing = schemes.get(scheme) or schemes['file']
129 138 try:
130 139 return thing(path)
131 140 except TypeError:
132 141 # we can't test callable(thing) because 'thing' can be an unloaded
133 142 # module that implements __call__
134 143 if not util.safehasattr(thing, 'instance'):
135 144 raise
136 145 return thing
137 146
138 147 def islocal(repo):
139 148 '''return true if repo (or path pointing to repo) is local'''
140 149 if isinstance(repo, bytes):
141 150 try:
142 151 return _peerlookup(repo).islocal(repo)
143 152 except AttributeError:
144 153 return False
145 154 return repo.local()
146 155
147 156 def openpath(ui, path):
148 157 '''open path with open if local, url.open if remote'''
149 158 pathurl = util.url(path, parsequery=False, parsefragment=False)
150 159 if pathurl.islocal():
151 160 return util.posixfile(pathurl.localpath(), 'rb')
152 161 else:
153 162 return url.open(ui, path)
154 163
155 164 # a list of (ui, repo) functions called for wire peer initialization
156 165 wirepeersetupfuncs = []
157 166
158 167 def _peerorrepo(ui, path, create=False, presetupfuncs=None,
159 168 intents=None, createopts=None):
160 169 """return a repository object for the specified path"""
161 170 obj = _peerlookup(path).instance(ui, path, create, intents=intents,
162 171 createopts=createopts)
163 172 ui = getattr(obj, "ui", ui)
164 173 for f in presetupfuncs or []:
165 174 f(ui, obj)
166 175 ui.log(b'extension', b'- executing reposetup hooks\n')
167 176 with util.timedcm('all reposetup') as allreposetupstats:
168 177 for name, module in extensions.extensions(ui):
169 178 ui.log(b'extension', b' - running reposetup for %s\n', name)
170 179 hook = getattr(module, 'reposetup', None)
171 180 if hook:
172 181 with util.timedcm('reposetup %r', name) as stats:
173 182 hook(ui, obj)
174 183 ui.log(b'extension', b' > reposetup for %s took %s\n',
175 184 name, stats)
176 185 ui.log(b'extension', b'> all reposetup took %s\n', allreposetupstats)
177 186 if not obj.local():
178 187 for f in wirepeersetupfuncs:
179 188 f(ui, obj)
180 189 return obj
181 190
182 191 def repository(ui, path='', create=False, presetupfuncs=None, intents=None,
183 192 createopts=None):
184 193 """return a repository object for the specified path"""
185 194 peer = _peerorrepo(ui, path, create, presetupfuncs=presetupfuncs,
186 195 intents=intents, createopts=createopts)
187 196 repo = peer.local()
188 197 if not repo:
189 198 raise error.Abort(_("repository '%s' is not local") %
190 199 (path or peer.url()))
191 200 return repo.filtered('visible')
192 201
193 202 def peer(uiorrepo, opts, path, create=False, intents=None, createopts=None):
194 203 '''return a repository peer for the specified path'''
195 204 rui = remoteui(uiorrepo, opts)
196 205 return _peerorrepo(rui, path, create, intents=intents,
197 206 createopts=createopts).peer()
198 207
199 208 def defaultdest(source):
200 209 '''return default destination of clone if none is given
201 210
202 211 >>> defaultdest(b'foo')
203 212 'foo'
204 213 >>> defaultdest(b'/foo/bar')
205 214 'bar'
206 215 >>> defaultdest(b'/')
207 216 ''
208 217 >>> defaultdest(b'')
209 218 ''
210 219 >>> defaultdest(b'http://example.org/')
211 220 ''
212 221 >>> defaultdest(b'http://example.org/foo/')
213 222 'foo'
214 223 '''
215 224 path = util.url(source).path
216 225 if not path:
217 226 return ''
218 227 return os.path.basename(os.path.normpath(path))
219 228
220 229 def sharedreposource(repo):
221 230 """Returns repository object for source repository of a shared repo.
222 231
223 232 If repo is not a shared repository, returns None.
224 233 """
225 234 if repo.sharedpath == repo.path:
226 235 return None
227 236
228 237 if util.safehasattr(repo, 'srcrepo') and repo.srcrepo:
229 238 return repo.srcrepo
230 239
231 240 # the sharedpath always ends in the .hg; we want the path to the repo
232 241 source = repo.vfs.split(repo.sharedpath)[0]
233 242 srcurl, branches = parseurl(source)
234 243 srcrepo = repository(repo.ui, srcurl)
235 244 repo.srcrepo = srcrepo
236 245 return srcrepo
237 246
238 247 def share(ui, source, dest=None, update=True, bookmarks=True, defaultpath=None,
239 248 relative=False):
240 249 '''create a shared repository'''
241 250
242 251 if not islocal(source):
243 252 raise error.Abort(_('can only share local repositories'))
244 253
245 254 if not dest:
246 255 dest = defaultdest(source)
247 256 else:
248 257 dest = ui.expandpath(dest)
249 258
250 259 if isinstance(source, bytes):
251 260 origsource = ui.expandpath(source)
252 261 source, branches = parseurl(origsource)
253 262 srcrepo = repository(ui, source)
254 263 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
255 264 else:
256 265 srcrepo = source.local()
257 266 checkout = None
258 267
259 268 shareditems = set()
260 269 if bookmarks:
261 270 shareditems.add(sharedbookmarks)
262 271
263 272 r = repository(ui, dest, create=True, createopts={
264 273 'sharedrepo': srcrepo,
265 274 'sharedrelative': relative,
266 275 'shareditems': shareditems,
267 276 })
268 277
269 278 postshare(srcrepo, r, defaultpath=defaultpath)
270 279 r = repository(ui, dest)
271 280 _postshareupdate(r, update, checkout=checkout)
272 281 return r
273 282
274 283 def unshare(ui, repo):
275 284 """convert a shared repository to a normal one
276 285
277 286 Copy the store data to the repo and remove the sharedpath data.
278 287
279 288 Returns a new repository object representing the unshared repository.
280 289
281 290 The passed repository object is not usable after this function is
282 291 called.
283 292 """
284 293
285 294 with repo.lock():
286 295 # we use locks here because if we race with commit, we
287 296 # can end up with extra data in the cloned revlogs that's
288 297 # not pointed to by changesets, thus causing verify to
289 298 # fail
290 299 destlock = copystore(ui, repo, repo.path)
291 300 with destlock or util.nullcontextmanager():
292 301
293 302 sharefile = repo.vfs.join('sharedpath')
294 303 util.rename(sharefile, sharefile + '.old')
295 304
296 305 repo.requirements.discard('shared')
297 306 repo.requirements.discard('relshared')
298 307 repo._writerequirements()
299 308
300 309 # Removing share changes some fundamental properties of the repo instance.
301 310 # So we instantiate a new repo object and operate on it rather than
302 311 # try to keep the existing repo usable.
303 312 newrepo = repository(repo.baseui, repo.root, create=False)
304 313
305 314 # TODO: figure out how to access subrepos that exist, but were previously
306 315 # removed from .hgsub
307 316 c = newrepo['.']
308 317 subs = c.substate
309 318 for s in sorted(subs):
310 319 c.sub(s).unshare()
311 320
312 321 localrepo.poisonrepository(repo)
313 322
314 323 return newrepo
315 324
316 325 def postshare(sourcerepo, destrepo, defaultpath=None):
317 326 """Called after a new shared repo is created.
318 327
319 328 The new repo only has a requirements file and pointer to the source.
320 329 This function configures additional shared data.
321 330
322 331 Extensions can wrap this function and write additional entries to
323 332 destrepo/.hg/shared to indicate additional pieces of data to be shared.
324 333 """
325 334 default = defaultpath or sourcerepo.ui.config('paths', 'default')
326 335 if default:
327 336 template = ('[paths]\n'
328 337 'default = %s\n')
329 338 destrepo.vfs.write('hgrc', util.tonativeeol(template % default))
330 339 if repositorymod.NARROW_REQUIREMENT in sourcerepo.requirements:
331 340 with destrepo.wlock():
332 341 narrowspec.copytoworkingcopy(destrepo)
333 342
334 343 def _postshareupdate(repo, update, checkout=None):
335 344 """Maybe perform a working directory update after a shared repo is created.
336 345
337 346 ``update`` can be a boolean or a revision to update to.
338 347 """
339 348 if not update:
340 349 return
341 350
342 351 repo.ui.status(_("updating working directory\n"))
343 352 if update is not True:
344 353 checkout = update
345 354 for test in (checkout, 'default', 'tip'):
346 355 if test is None:
347 356 continue
348 357 try:
349 358 uprev = repo.lookup(test)
350 359 break
351 360 except error.RepoLookupError:
352 361 continue
353 362 _update(repo, uprev)
354 363
355 364 def copystore(ui, srcrepo, destpath):
356 365 '''copy files from store of srcrepo in destpath
357 366
358 367 returns destlock
359 368 '''
360 369 destlock = None
361 370 try:
362 371 hardlink = None
363 372 topic = _('linking') if hardlink else _('copying')
364 373 with ui.makeprogress(topic, unit=_('files')) as progress:
365 374 num = 0
366 375 srcpublishing = srcrepo.publishing()
367 376 srcvfs = vfsmod.vfs(srcrepo.sharedpath)
368 377 dstvfs = vfsmod.vfs(destpath)
369 378 for f in srcrepo.store.copylist():
370 379 if srcpublishing and f.endswith('phaseroots'):
371 380 continue
372 381 dstbase = os.path.dirname(f)
373 382 if dstbase and not dstvfs.exists(dstbase):
374 383 dstvfs.mkdir(dstbase)
375 384 if srcvfs.exists(f):
376 385 if f.endswith('data'):
377 386 # 'dstbase' may be empty (e.g. revlog format 0)
378 387 lockfile = os.path.join(dstbase, "lock")
379 388 # lock to avoid premature writing to the target
380 389 destlock = lock.lock(dstvfs, lockfile)
381 390 hardlink, n = util.copyfiles(srcvfs.join(f), dstvfs.join(f),
382 391 hardlink, progress)
383 392 num += n
384 393 if hardlink:
385 394 ui.debug("linked %d files\n" % num)
386 395 else:
387 396 ui.debug("copied %d files\n" % num)
388 397 return destlock
389 398 except: # re-raises
390 399 release(destlock)
391 400 raise
392 401
393 402 def clonewithshare(ui, peeropts, sharepath, source, srcpeer, dest, pull=False,
394 403 rev=None, update=True, stream=False):
395 404 """Perform a clone using a shared repo.
396 405
397 406 The store for the repository will be located at <sharepath>/.hg. The
398 407 specified revisions will be cloned or pulled from "source". A shared repo
399 408 will be created at "dest" and a working copy will be created if "update" is
400 409 True.
401 410 """
402 411 revs = None
403 412 if rev:
404 413 if not srcpeer.capable('lookup'):
405 414 raise error.Abort(_("src repository does not support "
406 415 "revision lookup and so doesn't "
407 416 "support clone by revision"))
408 417
409 418 # TODO this is batchable.
410 419 remoterevs = []
411 420 for r in rev:
412 421 with srcpeer.commandexecutor() as e:
413 422 remoterevs.append(e.callcommand('lookup', {
414 423 'key': r,
415 424 }).result())
416 425 revs = remoterevs
417 426
418 427 # Obtain a lock before checking for or cloning the pooled repo otherwise
419 428 # 2 clients may race creating or populating it.
420 429 pooldir = os.path.dirname(sharepath)
421 430 # lock class requires the directory to exist.
422 431 try:
423 432 util.makedir(pooldir, False)
424 433 except OSError as e:
425 434 if e.errno != errno.EEXIST:
426 435 raise
427 436
428 437 poolvfs = vfsmod.vfs(pooldir)
429 438 basename = os.path.basename(sharepath)
430 439
431 440 with lock.lock(poolvfs, '%s.lock' % basename):
432 441 if os.path.exists(sharepath):
433 442 ui.status(_('(sharing from existing pooled repository %s)\n') %
434 443 basename)
435 444 else:
436 445 ui.status(_('(sharing from new pooled repository %s)\n') % basename)
437 446 # Always use pull mode because hardlinks in share mode don't work
438 447 # well. Never update because working copies aren't necessary in
439 448 # share mode.
440 449 clone(ui, peeropts, source, dest=sharepath, pull=True,
441 450 revs=rev, update=False, stream=stream)
442 451
443 452 # Resolve the value to put in [paths] section for the source.
444 453 if islocal(source):
445 454 defaultpath = os.path.abspath(util.urllocalpath(source))
446 455 else:
447 456 defaultpath = source
448 457
449 458 sharerepo = repository(ui, path=sharepath)
450 459 destrepo = share(ui, sharerepo, dest=dest, update=False, bookmarks=False,
451 460 defaultpath=defaultpath)
452 461
453 462 # We need to perform a pull against the dest repo to fetch bookmarks
454 463 # and other non-store data that isn't shared by default. In the case of
455 464 # non-existing shared repo, this means we pull from the remote twice. This
456 465 # is a bit weird. But at the time it was implemented, there wasn't an easy
457 466 # way to pull just non-changegroup data.
458 467 exchange.pull(destrepo, srcpeer, heads=revs)
459 468
460 469 _postshareupdate(destrepo, update)
461 470
462 471 return srcpeer, peer(ui, peeropts, dest)
463 472
464 473 # Recomputing branch cache might be slow on big repos,
465 474 # so just copy it
466 475 def _copycache(srcrepo, dstcachedir, fname):
467 476 """copy a cache from srcrepo to destcachedir (if it exists)"""
468 477 srcbranchcache = srcrepo.vfs.join('cache/%s' % fname)
469 478 dstbranchcache = os.path.join(dstcachedir, fname)
470 479 if os.path.exists(srcbranchcache):
471 480 if not os.path.exists(dstcachedir):
472 481 os.mkdir(dstcachedir)
473 482 util.copyfile(srcbranchcache, dstbranchcache)
474 483
475 484 def clone(ui, peeropts, source, dest=None, pull=False, revs=None,
476 485 update=True, stream=False, branch=None, shareopts=None,
477 486 storeincludepats=None, storeexcludepats=None, depth=None):
478 487 """Make a copy of an existing repository.
479 488
480 489 Create a copy of an existing repository in a new directory. The
481 490 source and destination are URLs, as passed to the repository
482 491 function. Returns a pair of repository peers, the source and
483 492 newly created destination.
484 493
485 494 The location of the source is added to the new repository's
486 495 .hg/hgrc file, as the default to be used for future pulls and
487 496 pushes.
488 497
489 498 If an exception is raised, the partly cloned/updated destination
490 499 repository will be deleted.
491 500
492 501 Arguments:
493 502
494 503 source: repository object or URL
495 504
496 505 dest: URL of destination repository to create (defaults to base
497 506 name of source repository)
498 507
499 508 pull: always pull from source repository, even in local case or if the
500 509 server prefers streaming
501 510
502 511 stream: stream raw data uncompressed from repository (fast over
503 512 LAN, slow over WAN)
504 513
505 514 revs: revision to clone up to (implies pull=True)
506 515
507 516 update: update working directory after clone completes, if
508 517 destination is local repository (True means update to default rev,
509 518 anything else is treated as a revision)
510 519
511 520 branch: branches to clone
512 521
513 522 shareopts: dict of options to control auto sharing behavior. The "pool" key
514 523 activates auto sharing mode and defines the directory for stores. The
515 524 "mode" key determines how to construct the directory name of the shared
516 525 repository. "identity" means the name is derived from the node of the first
517 526 changeset in the repository. "remote" means the name is derived from the
518 527 remote's path/URL. Defaults to "identity."
519 528
520 529 storeincludepats and storeexcludepats: sets of file patterns to include and
521 530 exclude in the repository copy, respectively. If not defined, all files
522 531 will be included (a "full" clone). Otherwise a "narrow" clone containing
523 532 only the requested files will be performed. If ``storeincludepats`` is not
524 533 defined but ``storeexcludepats`` is, ``storeincludepats`` is assumed to be
525 534 ``path:.``. If both are empty sets, no files will be cloned.
526 535 """
527 536
528 537 if isinstance(source, bytes):
529 538 origsource = ui.expandpath(source)
530 539 source, branches = parseurl(origsource, branch)
531 540 srcpeer = peer(ui, peeropts, source)
532 541 else:
533 542 srcpeer = source.peer() # in case we were called with a localrepo
534 543 branches = (None, branch or [])
535 544 origsource = source = srcpeer.url()
536 545 revs, checkout = addbranchrevs(srcpeer, srcpeer, branches, revs)
537 546
538 547 if dest is None:
539 548 dest = defaultdest(source)
540 549 if dest:
541 550 ui.status(_("destination directory: %s\n") % dest)
542 551 else:
543 552 dest = ui.expandpath(dest)
544 553
545 554 dest = util.urllocalpath(dest)
546 555 source = util.urllocalpath(source)
547 556
548 557 if not dest:
549 558 raise error.Abort(_("empty destination path is not valid"))
550 559
551 560 destvfs = vfsmod.vfs(dest, expandpath=True)
552 561 if destvfs.lexists():
553 562 if not destvfs.isdir():
554 563 raise error.Abort(_("destination '%s' already exists") % dest)
555 564 elif destvfs.listdir():
556 565 raise error.Abort(_("destination '%s' is not empty") % dest)
557 566
558 567 createopts = {}
559 568 narrow = False
560 569
561 570 if storeincludepats is not None:
562 571 narrowspec.validatepatterns(storeincludepats)
563 572 narrow = True
564 573
565 574 if storeexcludepats is not None:
566 575 narrowspec.validatepatterns(storeexcludepats)
567 576 narrow = True
568 577
569 578 if narrow:
570 579 # Include everything by default if only exclusion patterns defined.
571 580 if storeexcludepats and not storeincludepats:
572 581 storeincludepats = {'path:.'}
573 582
574 583 createopts['narrowfiles'] = True
575 584
576 585 if depth:
577 586 createopts['shallowfilestore'] = True
578 587
579 588 if srcpeer.capable(b'lfs-serve'):
580 589 # Repository creation honors the config if it disabled the extension, so
581 590 # we can't just announce that lfs will be enabled. This check avoids
582 591 # saying that lfs will be enabled, and then saying it's an unknown
583 592 # feature. The lfs creation option is set in either case so that a
584 593 # requirement is added. If the extension is explicitly disabled but the
585 594 # requirement is set, the clone aborts early, before transferring any
586 595 # data.
587 596 createopts['lfs'] = True
588 597
589 598 if extensions.disabledext('lfs'):
590 599 ui.status(_('(remote is using large file support (lfs), but it is '
591 600 'explicitly disabled in the local configuration)\n'))
592 601 else:
593 602 ui.status(_('(remote is using large file support (lfs); lfs will '
594 603 'be enabled for this repository)\n'))
595 604
596 605 shareopts = shareopts or {}
597 606 sharepool = shareopts.get('pool')
598 607 sharenamemode = shareopts.get('mode')
599 608 if sharepool and islocal(dest):
600 609 sharepath = None
601 610 if sharenamemode == 'identity':
602 611 # Resolve the name from the initial changeset in the remote
603 612 # repository. This returns nullid when the remote is empty. It
604 613 # raises RepoLookupError if revision 0 is filtered or otherwise
605 614 # not available. If we fail to resolve, sharing is not enabled.
606 615 try:
607 616 with srcpeer.commandexecutor() as e:
608 617 rootnode = e.callcommand('lookup', {
609 618 'key': '0',
610 619 }).result()
611 620
612 621 if rootnode != node.nullid:
613 622 sharepath = os.path.join(sharepool, node.hex(rootnode))
614 623 else:
615 624 ui.status(_('(not using pooled storage: '
616 625 'remote appears to be empty)\n'))
617 626 except error.RepoLookupError:
618 627 ui.status(_('(not using pooled storage: '
619 628 'unable to resolve identity of remote)\n'))
620 629 elif sharenamemode == 'remote':
621 630 sharepath = os.path.join(
622 631 sharepool, node.hex(hashlib.sha1(source).digest()))
623 632 else:
624 633 raise error.Abort(_('unknown share naming mode: %s') %
625 634 sharenamemode)
626 635
627 636 # TODO this is a somewhat arbitrary restriction.
628 637 if narrow:
629 638 ui.status(_('(pooled storage not supported for narrow clones)\n'))
630 639 sharepath = None
631 640
632 641 if sharepath:
633 642 return clonewithshare(ui, peeropts, sharepath, source, srcpeer,
634 643 dest, pull=pull, rev=revs, update=update,
635 644 stream=stream)
636 645
637 646 srclock = destlock = cleandir = None
638 647 srcrepo = srcpeer.local()
639 648 try:
640 649 abspath = origsource
641 650 if islocal(origsource):
642 651 abspath = os.path.abspath(util.urllocalpath(origsource))
643 652
644 653 if islocal(dest):
645 654 cleandir = dest
646 655
647 656 copy = False
648 657 if (srcrepo and srcrepo.cancopy() and islocal(dest)
649 658 and not phases.hassecret(srcrepo)):
650 659 copy = not pull and not revs
651 660
652 661 # TODO this is a somewhat arbitrary restriction.
653 662 if narrow:
654 663 copy = False
655 664
656 665 if copy:
657 666 try:
658 667 # we use a lock here because if we race with commit, we
659 668 # can end up with extra data in the cloned revlogs that's
660 669 # not pointed to by changesets, thus causing verify to
661 670 # fail
662 671 srclock = srcrepo.lock(wait=False)
663 672 except error.LockError:
664 673 copy = False
665 674
666 675 if copy:
667 676 srcrepo.hook('preoutgoing', throw=True, source='clone')
668 677 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
669 678 if not os.path.exists(dest):
670 679 util.makedirs(dest)
671 680 else:
672 681 # only clean up directories we create ourselves
673 682 cleandir = hgdir
674 683 try:
675 684 destpath = hgdir
676 685 util.makedir(destpath, notindexed=True)
677 686 except OSError as inst:
678 687 if inst.errno == errno.EEXIST:
679 688 cleandir = None
680 689 raise error.Abort(_("destination '%s' already exists")
681 690 % dest)
682 691 raise
683 692
684 693 destlock = copystore(ui, srcrepo, destpath)
685 694 # copy bookmarks over
686 695 srcbookmarks = srcrepo.vfs.join('bookmarks')
687 696 dstbookmarks = os.path.join(destpath, 'bookmarks')
688 697 if os.path.exists(srcbookmarks):
689 698 util.copyfile(srcbookmarks, dstbookmarks)
690 699
691 700 dstcachedir = os.path.join(destpath, 'cache')
692 701 for cache in cacheutil.cachetocopy(srcrepo):
693 702 _copycache(srcrepo, dstcachedir, cache)
694 703
695 704 # we need to re-init the repo after manually copying the data
696 705 # into it
697 706 destpeer = peer(srcrepo, peeropts, dest)
698 707 srcrepo.hook('outgoing', source='clone',
699 708 node=node.hex(node.nullid))
700 709 else:
701 710 try:
702 711 # only pass ui when no srcrepo
703 712 destpeer = peer(srcrepo or ui, peeropts, dest, create=True,
704 713 createopts=createopts)
705 714 except OSError as inst:
706 715 if inst.errno == errno.EEXIST:
707 716 cleandir = None
708 717 raise error.Abort(_("destination '%s' already exists")
709 718 % dest)
710 719 raise
711 720
712 721 if revs:
713 722 if not srcpeer.capable('lookup'):
714 723 raise error.Abort(_("src repository does not support "
715 724 "revision lookup and so doesn't "
716 725 "support clone by revision"))
717 726
718 727 # TODO this is batchable.
719 728 remoterevs = []
720 729 for rev in revs:
721 730 with srcpeer.commandexecutor() as e:
722 731 remoterevs.append(e.callcommand('lookup', {
723 732 'key': rev,
724 733 }).result())
725 734 revs = remoterevs
726 735
727 736 checkout = revs[0]
728 737 else:
729 738 revs = None
730 739 local = destpeer.local()
731 740 if local:
732 741 if narrow:
733 742 with local.wlock(), local.lock():
734 743 local.setnarrowpats(storeincludepats, storeexcludepats)
735 744 narrowspec.copytoworkingcopy(local)
736 745
737 746 u = util.url(abspath)
738 747 defaulturl = bytes(u)
739 748 local.ui.setconfig('paths', 'default', defaulturl, 'clone')
740 749 if not stream:
741 750 if pull:
742 751 stream = False
743 752 else:
744 753 stream = None
745 754 # internal config: ui.quietbookmarkmove
746 755 overrides = {('ui', 'quietbookmarkmove'): True}
747 756 with local.ui.configoverride(overrides, 'clone'):
748 757 exchange.pull(local, srcpeer, revs,
749 758 streamclonerequested=stream,
750 759 includepats=storeincludepats,
751 760 excludepats=storeexcludepats,
752 761 depth=depth)
753 762 elif srcrepo:
754 763 # TODO lift restriction once exchange.push() accepts narrow
755 764 # push.
756 765 if narrow:
757 766 raise error.Abort(_('narrow clone not available for '
758 767 'remote destinations'))
759 768
760 769 exchange.push(srcrepo, destpeer, revs=revs,
761 770 bookmarks=srcrepo._bookmarks.keys())
762 771 else:
763 772 raise error.Abort(_("clone from remote to remote not supported")
764 773 )
765 774
766 775 cleandir = None
767 776
768 777 destrepo = destpeer.local()
769 778 if destrepo:
770 779 template = uimod.samplehgrcs['cloned']
771 780 u = util.url(abspath)
772 781 u.passwd = None
773 782 defaulturl = bytes(u)
774 783 destrepo.vfs.write('hgrc', util.tonativeeol(template % defaulturl))
775 784 destrepo.ui.setconfig('paths', 'default', defaulturl, 'clone')
776 785
777 786 if ui.configbool('experimental', 'remotenames'):
778 787 logexchange.pullremotenames(destrepo, srcpeer)
779 788
780 789 if update:
781 790 if update is not True:
782 791 with srcpeer.commandexecutor() as e:
783 792 checkout = e.callcommand('lookup', {
784 793 'key': update,
785 794 }).result()
786 795
787 796 uprev = None
788 797 status = None
789 798 if checkout is not None:
790 799 # Some extensions (at least hg-git and hg-subversion) have
791 800 # a peer.lookup() implementation that returns a name instead
792 801 # of a nodeid. We work around it here until we've figured
793 802 # out a better solution.
794 803 if len(checkout) == 20 and checkout in destrepo:
795 804 uprev = checkout
796 805 elif scmutil.isrevsymbol(destrepo, checkout):
797 806 uprev = scmutil.revsymbol(destrepo, checkout).node()
798 807 else:
799 808 if update is not True:
800 809 try:
801 810 uprev = destrepo.lookup(update)
802 811 except error.RepoLookupError:
803 812 pass
804 813 if uprev is None:
805 814 try:
806 815 uprev = destrepo._bookmarks['@']
807 816 update = '@'
808 817 bn = destrepo[uprev].branch()
809 818 if bn == 'default':
810 819 status = _("updating to bookmark @\n")
811 820 else:
812 821 status = (_("updating to bookmark @ on branch %s\n")
813 822 % bn)
814 823 except KeyError:
815 824 try:
816 825 uprev = destrepo.branchtip('default')
817 826 except error.RepoLookupError:
818 827 uprev = destrepo.lookup('tip')
819 828 if not status:
820 829 bn = destrepo[uprev].branch()
821 830 status = _("updating to branch %s\n") % bn
822 831 destrepo.ui.status(status)
823 832 _update(destrepo, uprev)
824 833 if update in destrepo._bookmarks:
825 834 bookmarks.activate(destrepo, update)
826 835 finally:
827 836 release(srclock, destlock)
828 837 if cleandir is not None:
829 838 shutil.rmtree(cleandir, True)
830 839 if srcpeer is not None:
831 840 srcpeer.close()
832 841 return srcpeer, destpeer
833 842
834 843 def _showstats(repo, stats, quietempty=False):
835 844 if quietempty and stats.isempty():
836 845 return
837 846 repo.ui.status(_("%d files updated, %d files merged, "
838 847 "%d files removed, %d files unresolved\n") % (
839 848 stats.updatedcount, stats.mergedcount,
840 849 stats.removedcount, stats.unresolvedcount))
841 850
842 851 def updaterepo(repo, node, overwrite, updatecheck=None):
843 852 """Update the working directory to node.
844 853
845 854 When overwrite is set, changes are clobbered, merged else
846 855
847 856 returns stats (see pydoc mercurial.merge.applyupdates)"""
848 857 return mergemod.update(repo, node, branchmerge=False, force=overwrite,
849 858 labels=['working copy', 'destination'],
850 859 updatecheck=updatecheck)
851 860
852 861 def update(repo, node, quietempty=False, updatecheck=None):
853 862 """update the working directory to node"""
854 863 stats = updaterepo(repo, node, False, updatecheck=updatecheck)
855 864 _showstats(repo, stats, quietempty)
856 865 if stats.unresolvedcount:
857 866 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
858 867 return stats.unresolvedcount > 0
859 868
860 869 # naming conflict in clone()
861 870 _update = update
862 871
863 872 def clean(repo, node, show_stats=True, quietempty=False):
864 873 """forcibly switch the working directory to node, clobbering changes"""
865 874 stats = updaterepo(repo, node, True)
866 875 repo.vfs.unlinkpath('graftstate', ignoremissing=True)
867 876 if show_stats:
868 877 _showstats(repo, stats, quietempty)
869 878 return stats.unresolvedcount > 0
870 879
871 880 # naming conflict in updatetotally()
872 881 _clean = clean
873 882
874 883 def updatetotally(ui, repo, checkout, brev, clean=False, updatecheck=None):
875 884 """Update the working directory with extra care for non-file components
876 885
877 886 This takes care of non-file components below:
878 887
879 888 :bookmark: might be advanced or (in)activated
880 889
881 890 This takes arguments below:
882 891
883 892 :checkout: to which revision the working directory is updated
884 893 :brev: a name, which might be a bookmark to be activated after updating
885 894 :clean: whether changes in the working directory can be discarded
886 895 :updatecheck: how to deal with a dirty working directory
887 896
888 897 Valid values for updatecheck are (None => linear):
889 898
890 899 * abort: abort if the working directory is dirty
891 900 * none: don't check (merge working directory changes into destination)
892 901 * linear: check that update is linear before merging working directory
893 902 changes into destination
894 903 * noconflict: check that the update does not result in file merges
895 904
896 905 This returns whether conflict is detected at updating or not.
897 906 """
898 907 if updatecheck is None:
899 908 updatecheck = ui.config('commands', 'update.check')
900 909 if updatecheck not in ('abort', 'none', 'linear', 'noconflict'):
901 910 # If not configured, or invalid value configured
902 911 updatecheck = 'linear'
903 912 with repo.wlock():
904 913 movemarkfrom = None
905 914 warndest = False
906 915 if checkout is None:
907 916 updata = destutil.destupdate(repo, clean=clean)
908 917 checkout, movemarkfrom, brev = updata
909 918 warndest = True
910 919
911 920 if clean:
912 921 ret = _clean(repo, checkout)
913 922 else:
914 923 if updatecheck == 'abort':
915 924 cmdutil.bailifchanged(repo, merge=False)
916 925 updatecheck = 'none'
917 926 ret = _update(repo, checkout, updatecheck=updatecheck)
918 927
919 928 if not ret and movemarkfrom:
920 929 if movemarkfrom == repo['.'].node():
921 930 pass # no-op update
922 931 elif bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
923 932 b = ui.label(repo._activebookmark, 'bookmarks.active')
924 933 ui.status(_("updating bookmark %s\n") % b)
925 934 else:
926 935 # this can happen with a non-linear update
927 936 b = ui.label(repo._activebookmark, 'bookmarks')
928 937 ui.status(_("(leaving bookmark %s)\n") % b)
929 938 bookmarks.deactivate(repo)
930 939 elif brev in repo._bookmarks:
931 940 if brev != repo._activebookmark:
932 941 b = ui.label(brev, 'bookmarks.active')
933 942 ui.status(_("(activating bookmark %s)\n") % b)
934 943 bookmarks.activate(repo, brev)
935 944 elif brev:
936 945 if repo._activebookmark:
937 946 b = ui.label(repo._activebookmark, 'bookmarks')
938 947 ui.status(_("(leaving bookmark %s)\n") % b)
939 948 bookmarks.deactivate(repo)
940 949
941 950 if warndest:
942 951 destutil.statusotherdests(ui, repo)
943 952
944 953 return ret
945 954
946 955 def merge(repo, node, force=None, remind=True, mergeforce=False, labels=None,
947 956 abort=False):
948 957 """Branch merge with node, resolving changes. Return true if any
949 958 unresolved conflicts."""
950 959 if not abort:
951 960 stats = mergemod.update(repo, node, branchmerge=True, force=force,
952 961 mergeforce=mergeforce, labels=labels)
953 962 else:
954 963 ms = mergemod.mergestate.read(repo)
955 964 if ms.active():
956 965 # there were conflicts
957 966 node = ms.localctx.hex()
958 967 else:
959 968 # there were no conficts, mergestate was not stored
960 969 node = repo['.'].hex()
961 970
962 971 repo.ui.status(_("aborting the merge, updating back to"
963 972 " %s\n") % node[:12])
964 973 stats = mergemod.update(repo, node, branchmerge=False, force=True,
965 974 labels=labels)
966 975
967 976 _showstats(repo, stats)
968 977 if stats.unresolvedcount:
969 978 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
970 979 "or 'hg merge --abort' to abandon\n"))
971 980 elif remind and not abort:
972 981 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
973 982 return stats.unresolvedcount > 0
974 983
975 984 def _incoming(displaychlist, subreporecurse, ui, repo, source,
976 985 opts, buffered=False):
977 986 """
978 987 Helper for incoming / gincoming.
979 988 displaychlist gets called with
980 989 (remoterepo, incomingchangesetlist, displayer) parameters,
981 990 and is supposed to contain only code that can't be unified.
982 991 """
983 992 source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
984 993 other = peer(repo, opts, source)
985 994 ui.status(_('comparing with %s\n') % util.hidepassword(source))
986 995 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
987 996
988 997 if revs:
989 998 revs = [other.lookup(rev) for rev in revs]
990 999 other, chlist, cleanupfn = bundlerepo.getremotechanges(ui, repo, other,
991 1000 revs, opts["bundle"], opts["force"])
992 1001 try:
993 1002 if not chlist:
994 1003 ui.status(_("no changes found\n"))
995 1004 return subreporecurse()
996 1005 ui.pager('incoming')
997 1006 displayer = logcmdutil.changesetdisplayer(ui, other, opts,
998 1007 buffered=buffered)
999 1008 displaychlist(other, chlist, displayer)
1000 1009 displayer.close()
1001 1010 finally:
1002 1011 cleanupfn()
1003 1012 subreporecurse()
1004 1013 return 0 # exit code is zero since we found incoming changes
1005 1014
1006 1015 def incoming(ui, repo, source, opts):
1007 1016 def subreporecurse():
1008 1017 ret = 1
1009 1018 if opts.get('subrepos'):
1010 1019 ctx = repo[None]
1011 1020 for subpath in sorted(ctx.substate):
1012 1021 sub = ctx.sub(subpath)
1013 1022 ret = min(ret, sub.incoming(ui, source, opts))
1014 1023 return ret
1015 1024
1016 1025 def display(other, chlist, displayer):
1017 1026 limit = logcmdutil.getlimit(opts)
1018 1027 if opts.get('newest_first'):
1019 1028 chlist.reverse()
1020 1029 count = 0
1021 1030 for n in chlist:
1022 1031 if limit is not None and count >= limit:
1023 1032 break
1024 1033 parents = [p for p in other.changelog.parents(n) if p != nullid]
1025 1034 if opts.get('no_merges') and len(parents) == 2:
1026 1035 continue
1027 1036 count += 1
1028 1037 displayer.show(other[n])
1029 1038 return _incoming(display, subreporecurse, ui, repo, source, opts)
1030 1039
1031 1040 def _outgoing(ui, repo, dest, opts):
1032 1041 path = ui.paths.getpath(dest, default=('default-push', 'default'))
1033 1042 if not path:
1034 1043 raise error.Abort(_('default repository not configured!'),
1035 1044 hint=_("see 'hg help config.paths'"))
1036 1045 dest = path.pushloc or path.loc
1037 1046 branches = path.branch, opts.get('branch') or []
1038 1047
1039 1048 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
1040 1049 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
1041 1050 if revs:
1042 1051 revs = [repo[rev].node() for rev in scmutil.revrange(repo, revs)]
1043 1052
1044 1053 other = peer(repo, opts, dest)
1045 1054 outgoing = discovery.findcommonoutgoing(repo, other, revs,
1046 1055 force=opts.get('force'))
1047 1056 o = outgoing.missing
1048 1057 if not o:
1049 1058 scmutil.nochangesfound(repo.ui, repo, outgoing.excluded)
1050 1059 return o, other
1051 1060
1052 1061 def outgoing(ui, repo, dest, opts):
1053 1062 def recurse():
1054 1063 ret = 1
1055 1064 if opts.get('subrepos'):
1056 1065 ctx = repo[None]
1057 1066 for subpath in sorted(ctx.substate):
1058 1067 sub = ctx.sub(subpath)
1059 1068 ret = min(ret, sub.outgoing(ui, dest, opts))
1060 1069 return ret
1061 1070
1062 1071 limit = logcmdutil.getlimit(opts)
1063 1072 o, other = _outgoing(ui, repo, dest, opts)
1064 1073 if not o:
1065 1074 cmdutil.outgoinghooks(ui, repo, other, opts, o)
1066 1075 return recurse()
1067 1076
1068 1077 if opts.get('newest_first'):
1069 1078 o.reverse()
1070 1079 ui.pager('outgoing')
1071 1080 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
1072 1081 count = 0
1073 1082 for n in o:
1074 1083 if limit is not None and count >= limit:
1075 1084 break
1076 1085 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1077 1086 if opts.get('no_merges') and len(parents) == 2:
1078 1087 continue
1079 1088 count += 1
1080 1089 displayer.show(repo[n])
1081 1090 displayer.close()
1082 1091 cmdutil.outgoinghooks(ui, repo, other, opts, o)
1083 1092 recurse()
1084 1093 return 0 # exit code is zero since we found outgoing changes
1085 1094
1086 1095 def verify(repo):
1087 1096 """verify the consistency of a repository"""
1088 1097 ret = verifymod.verify(repo)
1089 1098
1090 1099 # Broken subrepo references in hidden csets don't seem worth worrying about,
1091 1100 # since they can't be pushed/pulled, and --hidden can be used if they are a
1092 1101 # concern.
1093 1102
1094 1103 # pathto() is needed for -R case
1095 1104 revs = repo.revs("filelog(%s)",
1096 1105 util.pathto(repo.root, repo.getcwd(), '.hgsubstate'))
1097 1106
1098 1107 if revs:
1099 1108 repo.ui.status(_('checking subrepo links\n'))
1100 1109 for rev in revs:
1101 1110 ctx = repo[rev]
1102 1111 try:
1103 1112 for subpath in ctx.substate:
1104 1113 try:
1105 1114 ret = (ctx.sub(subpath, allowcreate=False).verify()
1106 1115 or ret)
1107 1116 except error.RepoError as e:
1108 1117 repo.ui.warn(('%d: %s\n') % (rev, e))
1109 1118 except Exception:
1110 1119 repo.ui.warn(_('.hgsubstate is corrupt in revision %s\n') %
1111 1120 node.short(ctx.node()))
1112 1121
1113 1122 return ret
1114 1123
1115 1124 def remoteui(src, opts):
1116 1125 'build a remote ui from ui or repo and opts'
1117 1126 if util.safehasattr(src, 'baseui'): # looks like a repository
1118 1127 dst = src.baseui.copy() # drop repo-specific config
1119 1128 src = src.ui # copy target options from repo
1120 1129 else: # assume it's a global ui object
1121 1130 dst = src.copy() # keep all global options
1122 1131
1123 1132 # copy ssh-specific options
1124 1133 for o in 'ssh', 'remotecmd':
1125 1134 v = opts.get(o) or src.config('ui', o)
1126 1135 if v:
1127 1136 dst.setconfig("ui", o, v, 'copied')
1128 1137
1129 1138 # copy bundle-specific options
1130 1139 r = src.config('bundle', 'mainreporoot')
1131 1140 if r:
1132 1141 dst.setconfig('bundle', 'mainreporoot', r, 'copied')
1133 1142
1134 1143 # copy selected local settings to the remote ui
1135 1144 for sect in ('auth', 'hostfingerprints', 'hostsecurity', 'http_proxy'):
1136 1145 for key, val in src.configitems(sect):
1137 1146 dst.setconfig(sect, key, val, 'copied')
1138 1147 v = src.config('web', 'cacerts')
1139 1148 if v:
1140 1149 dst.setconfig('web', 'cacerts', util.expandpath(v), 'copied')
1141 1150
1142 1151 return dst
1143 1152
1144 1153 # Files of interest
1145 1154 # Used to check if the repository has changed looking at mtime and size of
1146 1155 # these files.
1147 1156 foi = [('spath', '00changelog.i'),
1148 1157 ('spath', 'phaseroots'), # ! phase can change content at the same size
1149 1158 ('spath', 'obsstore'),
1150 1159 ('path', 'bookmarks'), # ! bookmark can change content at the same size
1151 1160 ]
1152 1161
1153 1162 class cachedlocalrepo(object):
1154 1163 """Holds a localrepository that can be cached and reused."""
1155 1164
1156 1165 def __init__(self, repo):
1157 1166 """Create a new cached repo from an existing repo.
1158 1167
1159 1168 We assume the passed in repo was recently created. If the
1160 1169 repo has changed between when it was created and when it was
1161 1170 turned into a cache, it may not refresh properly.
1162 1171 """
1163 1172 assert isinstance(repo, localrepo.localrepository)
1164 1173 self._repo = repo
1165 1174 self._state, self.mtime = self._repostate()
1166 1175 self._filtername = repo.filtername
1167 1176
1168 1177 def fetch(self):
1169 1178 """Refresh (if necessary) and return a repository.
1170 1179
1171 1180 If the cached instance is out of date, it will be recreated
1172 1181 automatically and returned.
1173 1182
1174 1183 Returns a tuple of the repo and a boolean indicating whether a new
1175 1184 repo instance was created.
1176 1185 """
1177 1186 # We compare the mtimes and sizes of some well-known files to
1178 1187 # determine if the repo changed. This is not precise, as mtimes
1179 1188 # are susceptible to clock skew and imprecise filesystems and
1180 1189 # file content can change while maintaining the same size.
1181 1190
1182 1191 state, mtime = self._repostate()
1183 1192 if state == self._state:
1184 1193 return self._repo, False
1185 1194
1186 1195 repo = repository(self._repo.baseui, self._repo.url())
1187 1196 if self._filtername:
1188 1197 self._repo = repo.filtered(self._filtername)
1189 1198 else:
1190 1199 self._repo = repo.unfiltered()
1191 1200 self._state = state
1192 1201 self.mtime = mtime
1193 1202
1194 1203 return self._repo, True
1195 1204
1196 1205 def _repostate(self):
1197 1206 state = []
1198 1207 maxmtime = -1
1199 1208 for attr, fname in foi:
1200 1209 prefix = getattr(self._repo, attr)
1201 1210 p = os.path.join(prefix, fname)
1202 1211 try:
1203 1212 st = os.stat(p)
1204 1213 except OSError:
1205 1214 st = os.stat(prefix)
1206 1215 state.append((st[stat.ST_MTIME], st.st_size))
1207 1216 maxmtime = max(maxmtime, st[stat.ST_MTIME])
1208 1217
1209 1218 return tuple(state), maxmtime
1210 1219
1211 1220 def copy(self):
1212 1221 """Obtain a copy of this class instance.
1213 1222
1214 1223 A new localrepository instance is obtained. The new instance should be
1215 1224 completely independent of the original.
1216 1225 """
1217 1226 repo = repository(self._repo.baseui, self._repo.origroot)
1218 1227 if self._filtername:
1219 1228 repo = repo.filtered(self._filtername)
1220 1229 else:
1221 1230 repo = repo.unfiltered()
1222 1231 c = cachedlocalrepo(repo)
1223 1232 c._state = self._state
1224 1233 c.mtime = self.mtime
1225 1234 return c
@@ -1,2108 +1,2112 b''
1 1 # ui.py - user interface bits for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import collections
11 11 import contextlib
12 12 import errno
13 13 import getpass
14 14 import inspect
15 15 import os
16 16 import re
17 17 import signal
18 18 import socket
19 19 import subprocess
20 20 import sys
21 21 import traceback
22 22
23 23 from .i18n import _
24 24 from .node import hex
25 25
26 26 from . import (
27 27 color,
28 28 config,
29 29 configitems,
30 30 encoding,
31 31 error,
32 32 formatter,
33 33 loggingutil,
34 34 progress,
35 35 pycompat,
36 36 rcutil,
37 37 scmutil,
38 38 util,
39 39 )
40 40 from .utils import (
41 41 dateutil,
42 42 procutil,
43 43 stringutil,
44 44 )
45 45
46 46 urlreq = util.urlreq
47 47
48 48 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
49 49 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
50 50 if not c.isalnum())
51 51
52 52 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
53 53 tweakrc = b"""
54 54 [ui]
55 55 # The rollback command is dangerous. As a rule, don't use it.
56 56 rollback = False
57 57 # Make `hg status` report copy information
58 58 statuscopies = yes
59 59 # Prefer curses UIs when available. Revert to plain-text with `text`.
60 60 interface = curses
61 61
62 62 [commands]
63 63 # Grep working directory by default.
64 64 grep.all-files = True
65 65 # Make `hg status` emit cwd-relative paths by default.
66 66 status.relative = yes
67 67 # Refuse to perform an `hg update` that would cause a file content merge
68 68 update.check = noconflict
69 69 # Show conflicts information in `hg status`
70 70 status.verbose = True
71 71
72 72 [diff]
73 73 git = 1
74 74 showfunc = 1
75 75 word-diff = 1
76 76 """
77 77
78 78 samplehgrcs = {
79 79 'user':
80 80 b"""# example user config (see 'hg help config' for more info)
81 81 [ui]
82 82 # name and email, e.g.
83 83 # username = Jane Doe <jdoe@example.com>
84 84 username =
85 85
86 86 # We recommend enabling tweakdefaults to get slight improvements to
87 87 # the UI over time. Make sure to set HGPLAIN in the environment when
88 88 # writing scripts!
89 89 # tweakdefaults = True
90 90
91 91 # uncomment to disable color in command output
92 92 # (see 'hg help color' for details)
93 93 # color = never
94 94
95 95 # uncomment to disable command output pagination
96 96 # (see 'hg help pager' for details)
97 97 # paginate = never
98 98
99 99 [extensions]
100 100 # uncomment these lines to enable some popular extensions
101 101 # (see 'hg help extensions' for more info)
102 102 #
103 103 # churn =
104 104 """,
105 105
106 106 'cloned':
107 107 b"""# example repository config (see 'hg help config' for more info)
108 108 [paths]
109 109 default = %s
110 110
111 111 # path aliases to other clones of this repo in URLs or filesystem paths
112 112 # (see 'hg help config.paths' for more info)
113 113 #
114 114 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
115 115 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
116 116 # my-clone = /home/jdoe/jdoes-clone
117 117
118 118 [ui]
119 119 # name and email (local to this repository, optional), e.g.
120 120 # username = Jane Doe <jdoe@example.com>
121 121 """,
122 122
123 123 'local':
124 124 b"""# example repository config (see 'hg help config' for more info)
125 125 [paths]
126 126 # path aliases to other clones of this repo in URLs or filesystem paths
127 127 # (see 'hg help config.paths' for more info)
128 128 #
129 129 # default = http://example.com/hg/example-repo
130 130 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
131 131 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
132 132 # my-clone = /home/jdoe/jdoes-clone
133 133
134 134 [ui]
135 135 # name and email (local to this repository, optional), e.g.
136 136 # username = Jane Doe <jdoe@example.com>
137 137 """,
138 138
139 139 'global':
140 140 b"""# example system-wide hg config (see 'hg help config' for more info)
141 141
142 142 [ui]
143 143 # uncomment to disable color in command output
144 144 # (see 'hg help color' for details)
145 145 # color = never
146 146
147 147 # uncomment to disable command output pagination
148 148 # (see 'hg help pager' for details)
149 149 # paginate = never
150 150
151 151 [extensions]
152 152 # uncomment these lines to enable some popular extensions
153 153 # (see 'hg help extensions' for more info)
154 154 #
155 155 # blackbox =
156 156 # churn =
157 157 """,
158 158 }
159 159
160 160 def _maybestrurl(maybebytes):
161 161 return pycompat.rapply(pycompat.strurl, maybebytes)
162 162
163 163 def _maybebytesurl(maybestr):
164 164 return pycompat.rapply(pycompat.bytesurl, maybestr)
165 165
166 166 class httppasswordmgrdbproxy(object):
167 167 """Delays loading urllib2 until it's needed."""
168 168 def __init__(self):
169 169 self._mgr = None
170 170
171 171 def _get_mgr(self):
172 172 if self._mgr is None:
173 173 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
174 174 return self._mgr
175 175
176 176 def add_password(self, realm, uris, user, passwd):
177 177 return self._get_mgr().add_password(
178 178 _maybestrurl(realm), _maybestrurl(uris),
179 179 _maybestrurl(user), _maybestrurl(passwd))
180 180
181 181 def find_user_password(self, realm, uri):
182 182 mgr = self._get_mgr()
183 183 return _maybebytesurl(mgr.find_user_password(_maybestrurl(realm),
184 184 _maybestrurl(uri)))
185 185
186 186 def _catchterm(*args):
187 187 raise error.SignalInterrupt
188 188
189 189 # unique object used to detect no default value has been provided when
190 190 # retrieving configuration value.
191 191 _unset = object()
192 192
193 193 # _reqexithandlers: callbacks run at the end of a request
194 194 _reqexithandlers = []
195 195
196 196 class ui(object):
197 197 def __init__(self, src=None):
198 198 """Create a fresh new ui object if no src given
199 199
200 200 Use uimod.ui.load() to create a ui which knows global and user configs.
201 201 In most cases, you should use ui.copy() to create a copy of an existing
202 202 ui object.
203 203 """
204 204 # _buffers: used for temporary capture of output
205 205 self._buffers = []
206 206 # 3-tuple describing how each buffer in the stack behaves.
207 207 # Values are (capture stderr, capture subprocesses, apply labels).
208 208 self._bufferstates = []
209 209 # When a buffer is active, defines whether we are expanding labels.
210 210 # This exists to prevent an extra list lookup.
211 211 self._bufferapplylabels = None
212 212 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
213 213 self._reportuntrusted = True
214 214 self._knownconfig = configitems.coreitems
215 215 self._ocfg = config.config() # overlay
216 216 self._tcfg = config.config() # trusted
217 217 self._ucfg = config.config() # untrusted
218 218 self._trustusers = set()
219 219 self._trustgroups = set()
220 220 self.callhooks = True
221 221 # Insecure server connections requested.
222 222 self.insecureconnections = False
223 223 # Blocked time
224 224 self.logblockedtimes = False
225 225 # color mode: see mercurial/color.py for possible value
226 226 self._colormode = None
227 227 self._terminfoparams = {}
228 228 self._styles = {}
229 229 self._uninterruptible = False
230 230
231 231 if src:
232 232 self._fout = src._fout
233 233 self._ferr = src._ferr
234 234 self._fin = src._fin
235 235 self._fmsg = src._fmsg
236 236 self._fmsgout = src._fmsgout
237 237 self._fmsgerr = src._fmsgerr
238 238 self._finoutredirected = src._finoutredirected
239 239 self._loggers = src._loggers.copy()
240 240 self.pageractive = src.pageractive
241 241 self._disablepager = src._disablepager
242 242 self._tweaked = src._tweaked
243 243
244 244 self._tcfg = src._tcfg.copy()
245 245 self._ucfg = src._ucfg.copy()
246 246 self._ocfg = src._ocfg.copy()
247 247 self._trustusers = src._trustusers.copy()
248 248 self._trustgroups = src._trustgroups.copy()
249 249 self.environ = src.environ
250 250 self.callhooks = src.callhooks
251 251 self.insecureconnections = src.insecureconnections
252 252 self._colormode = src._colormode
253 253 self._terminfoparams = src._terminfoparams.copy()
254 254 self._styles = src._styles.copy()
255 255
256 256 self.fixconfig()
257 257
258 258 self.httppasswordmgrdb = src.httppasswordmgrdb
259 259 self._blockedtimes = src._blockedtimes
260 260 else:
261 261 self._fout = procutil.stdout
262 262 self._ferr = procutil.stderr
263 263 self._fin = procutil.stdin
264 264 self._fmsg = None
265 265 self._fmsgout = self.fout # configurable
266 266 self._fmsgerr = self.ferr # configurable
267 267 self._finoutredirected = False
268 268 self._loggers = {}
269 269 self.pageractive = False
270 270 self._disablepager = False
271 271 self._tweaked = False
272 272
273 273 # shared read-only environment
274 274 self.environ = encoding.environ
275 275
276 276 self.httppasswordmgrdb = httppasswordmgrdbproxy()
277 277 self._blockedtimes = collections.defaultdict(int)
278 278
279 279 allowed = self.configlist('experimental', 'exportableenviron')
280 280 if '*' in allowed:
281 281 self._exportableenviron = self.environ
282 282 else:
283 283 self._exportableenviron = {}
284 284 for k in allowed:
285 285 if k in self.environ:
286 286 self._exportableenviron[k] = self.environ[k]
287 287
288 288 @classmethod
289 289 def load(cls):
290 290 """Create a ui and load global and user configs"""
291 291 u = cls()
292 292 # we always trust global config files and environment variables
293 293 for t, f in rcutil.rccomponents():
294 294 if t == 'path':
295 295 u.readconfig(f, trust=True)
296 296 elif t == 'items':
297 297 sections = set()
298 298 for section, name, value, source in f:
299 299 # do not set u._ocfg
300 300 # XXX clean this up once immutable config object is a thing
301 301 u._tcfg.set(section, name, value, source)
302 302 u._ucfg.set(section, name, value, source)
303 303 sections.add(section)
304 304 for section in sections:
305 305 u.fixconfig(section=section)
306 306 else:
307 307 raise error.ProgrammingError('unknown rctype: %s' % t)
308 308 u._maybetweakdefaults()
309 309 return u
310 310
311 311 def _maybetweakdefaults(self):
312 312 if not self.configbool('ui', 'tweakdefaults'):
313 313 return
314 314 if self._tweaked or self.plain('tweakdefaults'):
315 315 return
316 316
317 317 # Note: it is SUPER IMPORTANT that you set self._tweaked to
318 318 # True *before* any calls to setconfig(), otherwise you'll get
319 319 # infinite recursion between setconfig and this method.
320 320 #
321 321 # TODO: We should extract an inner method in setconfig() to
322 322 # avoid this weirdness.
323 323 self._tweaked = True
324 324 tmpcfg = config.config()
325 325 tmpcfg.parse('<tweakdefaults>', tweakrc)
326 326 for section in tmpcfg:
327 327 for name, value in tmpcfg.items(section):
328 328 if not self.hasconfig(section, name):
329 329 self.setconfig(section, name, value, "<tweakdefaults>")
330 330
331 331 def copy(self):
332 332 return self.__class__(self)
333 333
334 334 def resetstate(self):
335 335 """Clear internal state that shouldn't persist across commands"""
336 336 if self._progbar:
337 337 self._progbar.resetstate() # reset last-print time of progress bar
338 338 self.httppasswordmgrdb = httppasswordmgrdbproxy()
339 339
340 340 @contextlib.contextmanager
341 341 def timeblockedsection(self, key):
342 342 # this is open-coded below - search for timeblockedsection to find them
343 343 starttime = util.timer()
344 344 try:
345 345 yield
346 346 finally:
347 347 self._blockedtimes[key + '_blocked'] += \
348 348 (util.timer() - starttime) * 1000
349 349
350 350 @contextlib.contextmanager
351 351 def uninterruptible(self):
352 352 """Mark an operation as unsafe.
353 353
354 354 Most operations on a repository are safe to interrupt, but a
355 355 few are risky (for example repair.strip). This context manager
356 356 lets you advise Mercurial that something risky is happening so
357 357 that control-C etc can be blocked if desired.
358 358 """
359 359 enabled = self.configbool('experimental', 'nointerrupt')
360 360 if (enabled and
361 361 self.configbool('experimental', 'nointerrupt-interactiveonly')):
362 362 enabled = self.interactive()
363 363 if self._uninterruptible or not enabled:
364 364 # if nointerrupt support is turned off, the process isn't
365 365 # interactive, or we're already in an uninterruptible
366 366 # block, do nothing.
367 367 yield
368 368 return
369 369 def warn():
370 370 self.warn(_("shutting down cleanly\n"))
371 371 self.warn(
372 372 _("press ^C again to terminate immediately (dangerous)\n"))
373 373 return True
374 374 with procutil.uninterruptible(warn):
375 375 try:
376 376 self._uninterruptible = True
377 377 yield
378 378 finally:
379 379 self._uninterruptible = False
380 380
381 381 def formatter(self, topic, opts):
382 382 return formatter.formatter(self, self, topic, opts)
383 383
384 384 def _trusted(self, fp, f):
385 385 st = util.fstat(fp)
386 386 if util.isowner(st):
387 387 return True
388 388
389 389 tusers, tgroups = self._trustusers, self._trustgroups
390 390 if '*' in tusers or '*' in tgroups:
391 391 return True
392 392
393 393 user = util.username(st.st_uid)
394 394 group = util.groupname(st.st_gid)
395 395 if user in tusers or group in tgroups or user == util.username():
396 396 return True
397 397
398 398 if self._reportuntrusted:
399 399 self.warn(_('not trusting file %s from untrusted '
400 400 'user %s, group %s\n') % (f, user, group))
401 401 return False
402 402
403 403 def readconfig(self, filename, root=None, trust=False,
404 404 sections=None, remap=None):
405 405 try:
406 406 fp = open(filename, r'rb')
407 407 except IOError:
408 408 if not sections: # ignore unless we were looking for something
409 409 return
410 410 raise
411 411
412 412 cfg = config.config()
413 413 trusted = sections or trust or self._trusted(fp, filename)
414 414
415 415 try:
416 416 cfg.read(filename, fp, sections=sections, remap=remap)
417 417 fp.close()
418 418 except error.ConfigError as inst:
419 419 if trusted:
420 420 raise
421 421 self.warn(_("ignored: %s\n") % stringutil.forcebytestr(inst))
422 422
423 423 if self.plain():
424 424 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
425 425 'logtemplate', 'message-output', 'statuscopies', 'style',
426 426 'traceback', 'verbose'):
427 427 if k in cfg['ui']:
428 428 del cfg['ui'][k]
429 429 for k, v in cfg.items('defaults'):
430 430 del cfg['defaults'][k]
431 431 for k, v in cfg.items('commands'):
432 432 del cfg['commands'][k]
433 433 # Don't remove aliases from the configuration if in the exceptionlist
434 434 if self.plain('alias'):
435 435 for k, v in cfg.items('alias'):
436 436 del cfg['alias'][k]
437 437 if self.plain('revsetalias'):
438 438 for k, v in cfg.items('revsetalias'):
439 439 del cfg['revsetalias'][k]
440 440 if self.plain('templatealias'):
441 441 for k, v in cfg.items('templatealias'):
442 442 del cfg['templatealias'][k]
443 443
444 444 if trusted:
445 445 self._tcfg.update(cfg)
446 446 self._tcfg.update(self._ocfg)
447 447 self._ucfg.update(cfg)
448 448 self._ucfg.update(self._ocfg)
449 449
450 450 if root is None:
451 451 root = os.path.expanduser('~')
452 452 self.fixconfig(root=root)
453 453
454 454 def fixconfig(self, root=None, section=None):
455 455 if section in (None, 'paths'):
456 456 # expand vars and ~
457 457 # translate paths relative to root (or home) into absolute paths
458 458 root = root or encoding.getcwd()
459 459 for c in self._tcfg, self._ucfg, self._ocfg:
460 460 for n, p in c.items('paths'):
461 461 # Ignore sub-options.
462 462 if ':' in n:
463 463 continue
464 464 if not p:
465 465 continue
466 466 if '%%' in p:
467 467 s = self.configsource('paths', n) or 'none'
468 468 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
469 469 % (n, p, s))
470 470 p = p.replace('%%', '%')
471 471 p = util.expandpath(p)
472 472 if not util.hasscheme(p) and not os.path.isabs(p):
473 473 p = os.path.normpath(os.path.join(root, p))
474 474 c.set("paths", n, p)
475 475
476 476 if section in (None, 'ui'):
477 477 # update ui options
478 478 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
479 479 self.debugflag = self.configbool('ui', 'debug')
480 480 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
481 481 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
482 482 if self.verbose and self.quiet:
483 483 self.quiet = self.verbose = False
484 484 self._reportuntrusted = self.debugflag or self.configbool("ui",
485 485 "report_untrusted")
486 486 self.tracebackflag = self.configbool('ui', 'traceback')
487 487 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
488 488
489 489 if section in (None, 'trusted'):
490 490 # update trust information
491 491 self._trustusers.update(self.configlist('trusted', 'users'))
492 492 self._trustgroups.update(self.configlist('trusted', 'groups'))
493 493
494 494 if section in (None, b'devel', b'ui') and self.debugflag:
495 495 tracked = set()
496 496 if self.configbool(b'devel', b'debug.extensions'):
497 497 tracked.add(b'extension')
498 498 if tracked:
499 499 logger = loggingutil.fileobjectlogger(self._ferr, tracked)
500 500 self.setlogger(b'debug', logger)
501 501
502 502 def backupconfig(self, section, item):
503 503 return (self._ocfg.backup(section, item),
504 504 self._tcfg.backup(section, item),
505 505 self._ucfg.backup(section, item),)
506 506 def restoreconfig(self, data):
507 507 self._ocfg.restore(data[0])
508 508 self._tcfg.restore(data[1])
509 509 self._ucfg.restore(data[2])
510 510
511 511 def setconfig(self, section, name, value, source=''):
512 512 for cfg in (self._ocfg, self._tcfg, self._ucfg):
513 513 cfg.set(section, name, value, source)
514 514 self.fixconfig(section=section)
515 515 self._maybetweakdefaults()
516 516
517 517 def _data(self, untrusted):
518 518 return untrusted and self._ucfg or self._tcfg
519 519
520 520 def configsource(self, section, name, untrusted=False):
521 521 return self._data(untrusted).source(section, name)
522 522
523 523 def config(self, section, name, default=_unset, untrusted=False):
524 524 """return the plain string version of a config"""
525 525 value = self._config(section, name, default=default,
526 526 untrusted=untrusted)
527 527 if value is _unset:
528 528 return None
529 529 return value
530 530
531 531 def _config(self, section, name, default=_unset, untrusted=False):
532 532 value = itemdefault = default
533 533 item = self._knownconfig.get(section, {}).get(name)
534 534 alternates = [(section, name)]
535 535
536 536 if item is not None:
537 537 alternates.extend(item.alias)
538 538 if callable(item.default):
539 539 itemdefault = item.default()
540 540 else:
541 541 itemdefault = item.default
542 542 else:
543 543 msg = ("accessing unregistered config item: '%s.%s'")
544 544 msg %= (section, name)
545 545 self.develwarn(msg, 2, 'warn-config-unknown')
546 546
547 547 if default is _unset:
548 548 if item is None:
549 549 value = default
550 550 elif item.default is configitems.dynamicdefault:
551 551 value = None
552 552 msg = "config item requires an explicit default value: '%s.%s'"
553 553 msg %= (section, name)
554 554 self.develwarn(msg, 2, 'warn-config-default')
555 555 else:
556 556 value = itemdefault
557 557 elif (item is not None
558 558 and item.default is not configitems.dynamicdefault
559 559 and default != itemdefault):
560 560 msg = ("specifying a mismatched default value for a registered "
561 561 "config item: '%s.%s' '%s'")
562 562 msg %= (section, name, pycompat.bytestr(default))
563 563 self.develwarn(msg, 2, 'warn-config-default')
564 564
565 565 for s, n in alternates:
566 566 candidate = self._data(untrusted).get(s, n, None)
567 567 if candidate is not None:
568 568 value = candidate
569 569 break
570 570
571 571 if self.debugflag and not untrusted and self._reportuntrusted:
572 572 for s, n in alternates:
573 573 uvalue = self._ucfg.get(s, n)
574 574 if uvalue is not None and uvalue != value:
575 575 self.debug("ignoring untrusted configuration option "
576 576 "%s.%s = %s\n" % (s, n, uvalue))
577 577 return value
578 578
579 579 def configsuboptions(self, section, name, default=_unset, untrusted=False):
580 580 """Get a config option and all sub-options.
581 581
582 582 Some config options have sub-options that are declared with the
583 583 format "key:opt = value". This method is used to return the main
584 584 option and all its declared sub-options.
585 585
586 586 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
587 587 is a dict of defined sub-options where keys and values are strings.
588 588 """
589 589 main = self.config(section, name, default, untrusted=untrusted)
590 590 data = self._data(untrusted)
591 591 sub = {}
592 592 prefix = '%s:' % name
593 593 for k, v in data.items(section):
594 594 if k.startswith(prefix):
595 595 sub[k[len(prefix):]] = v
596 596
597 597 if self.debugflag and not untrusted and self._reportuntrusted:
598 598 for k, v in sub.items():
599 599 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
600 600 if uvalue is not None and uvalue != v:
601 601 self.debug('ignoring untrusted configuration option '
602 602 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
603 603
604 604 return main, sub
605 605
606 606 def configpath(self, section, name, default=_unset, untrusted=False):
607 607 'get a path config item, expanded relative to repo root or config file'
608 608 v = self.config(section, name, default, untrusted)
609 609 if v is None:
610 610 return None
611 611 if not os.path.isabs(v) or "://" not in v:
612 612 src = self.configsource(section, name, untrusted)
613 613 if ':' in src:
614 614 base = os.path.dirname(src.rsplit(':')[0])
615 615 v = os.path.join(base, os.path.expanduser(v))
616 616 return v
617 617
618 618 def configbool(self, section, name, default=_unset, untrusted=False):
619 619 """parse a configuration element as a boolean
620 620
621 621 >>> u = ui(); s = b'foo'
622 622 >>> u.setconfig(s, b'true', b'yes')
623 623 >>> u.configbool(s, b'true')
624 624 True
625 625 >>> u.setconfig(s, b'false', b'no')
626 626 >>> u.configbool(s, b'false')
627 627 False
628 628 >>> u.configbool(s, b'unknown')
629 629 False
630 630 >>> u.configbool(s, b'unknown', True)
631 631 True
632 632 >>> u.setconfig(s, b'invalid', b'somevalue')
633 633 >>> u.configbool(s, b'invalid')
634 634 Traceback (most recent call last):
635 635 ...
636 636 ConfigError: foo.invalid is not a boolean ('somevalue')
637 637 """
638 638
639 639 v = self._config(section, name, default, untrusted=untrusted)
640 640 if v is None:
641 641 return v
642 642 if v is _unset:
643 643 if default is _unset:
644 644 return False
645 645 return default
646 646 if isinstance(v, bool):
647 647 return v
648 648 b = stringutil.parsebool(v)
649 649 if b is None:
650 650 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
651 651 % (section, name, v))
652 652 return b
653 653
654 654 def configwith(self, convert, section, name, default=_unset,
655 655 desc=None, untrusted=False):
656 656 """parse a configuration element with a conversion function
657 657
658 658 >>> u = ui(); s = b'foo'
659 659 >>> u.setconfig(s, b'float1', b'42')
660 660 >>> u.configwith(float, s, b'float1')
661 661 42.0
662 662 >>> u.setconfig(s, b'float2', b'-4.25')
663 663 >>> u.configwith(float, s, b'float2')
664 664 -4.25
665 665 >>> u.configwith(float, s, b'unknown', 7)
666 666 7.0
667 667 >>> u.setconfig(s, b'invalid', b'somevalue')
668 668 >>> u.configwith(float, s, b'invalid')
669 669 Traceback (most recent call last):
670 670 ...
671 671 ConfigError: foo.invalid is not a valid float ('somevalue')
672 672 >>> u.configwith(float, s, b'invalid', desc=b'womble')
673 673 Traceback (most recent call last):
674 674 ...
675 675 ConfigError: foo.invalid is not a valid womble ('somevalue')
676 676 """
677 677
678 678 v = self.config(section, name, default, untrusted)
679 679 if v is None:
680 680 return v # do not attempt to convert None
681 681 try:
682 682 return convert(v)
683 683 except (ValueError, error.ParseError):
684 684 if desc is None:
685 685 desc = pycompat.sysbytes(convert.__name__)
686 686 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
687 687 % (section, name, desc, v))
688 688
689 689 def configint(self, section, name, default=_unset, untrusted=False):
690 690 """parse a configuration element as an integer
691 691
692 692 >>> u = ui(); s = b'foo'
693 693 >>> u.setconfig(s, b'int1', b'42')
694 694 >>> u.configint(s, b'int1')
695 695 42
696 696 >>> u.setconfig(s, b'int2', b'-42')
697 697 >>> u.configint(s, b'int2')
698 698 -42
699 699 >>> u.configint(s, b'unknown', 7)
700 700 7
701 701 >>> u.setconfig(s, b'invalid', b'somevalue')
702 702 >>> u.configint(s, b'invalid')
703 703 Traceback (most recent call last):
704 704 ...
705 705 ConfigError: foo.invalid is not a valid integer ('somevalue')
706 706 """
707 707
708 708 return self.configwith(int, section, name, default, 'integer',
709 709 untrusted)
710 710
711 711 def configbytes(self, section, name, default=_unset, untrusted=False):
712 712 """parse a configuration element as a quantity in bytes
713 713
714 714 Units can be specified as b (bytes), k or kb (kilobytes), m or
715 715 mb (megabytes), g or gb (gigabytes).
716 716
717 717 >>> u = ui(); s = b'foo'
718 718 >>> u.setconfig(s, b'val1', b'42')
719 719 >>> u.configbytes(s, b'val1')
720 720 42
721 721 >>> u.setconfig(s, b'val2', b'42.5 kb')
722 722 >>> u.configbytes(s, b'val2')
723 723 43520
724 724 >>> u.configbytes(s, b'unknown', b'7 MB')
725 725 7340032
726 726 >>> u.setconfig(s, b'invalid', b'somevalue')
727 727 >>> u.configbytes(s, b'invalid')
728 728 Traceback (most recent call last):
729 729 ...
730 730 ConfigError: foo.invalid is not a byte quantity ('somevalue')
731 731 """
732 732
733 733 value = self._config(section, name, default, untrusted)
734 734 if value is _unset:
735 735 if default is _unset:
736 736 default = 0
737 737 value = default
738 738 if not isinstance(value, bytes):
739 739 return value
740 740 try:
741 741 return util.sizetoint(value)
742 742 except error.ParseError:
743 743 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
744 744 % (section, name, value))
745 745
746 746 def configlist(self, section, name, default=_unset, untrusted=False):
747 747 """parse a configuration element as a list of comma/space separated
748 748 strings
749 749
750 750 >>> u = ui(); s = b'foo'
751 751 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
752 752 >>> u.configlist(s, b'list1')
753 753 ['this', 'is', 'a small', 'test']
754 754 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
755 755 >>> u.configlist(s, b'list2')
756 756 ['this', 'is', 'a small', 'test']
757 757 """
758 758 # default is not always a list
759 759 v = self.configwith(config.parselist, section, name, default,
760 760 'list', untrusted)
761 761 if isinstance(v, bytes):
762 762 return config.parselist(v)
763 763 elif v is None:
764 764 return []
765 765 return v
766 766
767 767 def configdate(self, section, name, default=_unset, untrusted=False):
768 768 """parse a configuration element as a tuple of ints
769 769
770 770 >>> u = ui(); s = b'foo'
771 771 >>> u.setconfig(s, b'date', b'0 0')
772 772 >>> u.configdate(s, b'date')
773 773 (0, 0)
774 774 """
775 775 if self.config(section, name, default, untrusted):
776 776 return self.configwith(dateutil.parsedate, section, name, default,
777 777 'date', untrusted)
778 778 if default is _unset:
779 779 return None
780 780 return default
781 781
782 782 def hasconfig(self, section, name, untrusted=False):
783 783 return self._data(untrusted).hasitem(section, name)
784 784
785 785 def has_section(self, section, untrusted=False):
786 786 '''tell whether section exists in config.'''
787 787 return section in self._data(untrusted)
788 788
789 789 def configitems(self, section, untrusted=False, ignoresub=False):
790 790 items = self._data(untrusted).items(section)
791 791 if ignoresub:
792 792 items = [i for i in items if ':' not in i[0]]
793 793 if self.debugflag and not untrusted and self._reportuntrusted:
794 794 for k, v in self._ucfg.items(section):
795 795 if self._tcfg.get(section, k) != v:
796 796 self.debug("ignoring untrusted configuration option "
797 797 "%s.%s = %s\n" % (section, k, v))
798 798 return items
799 799
800 800 def walkconfig(self, untrusted=False):
801 801 cfg = self._data(untrusted)
802 802 for section in cfg.sections():
803 803 for name, value in self.configitems(section, untrusted):
804 804 yield section, name, value
805 805
806 806 def plain(self, feature=None):
807 807 '''is plain mode active?
808 808
809 809 Plain mode means that all configuration variables which affect
810 810 the behavior and output of Mercurial should be
811 811 ignored. Additionally, the output should be stable,
812 812 reproducible and suitable for use in scripts or applications.
813 813
814 814 The only way to trigger plain mode is by setting either the
815 815 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
816 816
817 817 The return value can either be
818 818 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
819 819 - False if feature is disabled by default and not included in HGPLAIN
820 820 - True otherwise
821 821 '''
822 822 if ('HGPLAIN' not in encoding.environ and
823 823 'HGPLAINEXCEPT' not in encoding.environ):
824 824 return False
825 825 exceptions = encoding.environ.get('HGPLAINEXCEPT',
826 826 '').strip().split(',')
827 827 # TODO: add support for HGPLAIN=+feature,-feature syntax
828 828 if '+strictflags' not in encoding.environ.get('HGPLAIN', '').split(','):
829 829 exceptions.append('strictflags')
830 830 if feature and exceptions:
831 831 return feature not in exceptions
832 832 return True
833 833
834 834 def username(self, acceptempty=False):
835 835 """Return default username to be used in commits.
836 836
837 837 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
838 838 and stop searching if one of these is set.
839 839 If not found and acceptempty is True, returns None.
840 840 If not found and ui.askusername is True, ask the user, else use
841 841 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
842 842 If no username could be found, raise an Abort error.
843 843 """
844 844 user = encoding.environ.get("HGUSER")
845 845 if user is None:
846 846 user = self.config("ui", "username")
847 847 if user is not None:
848 848 user = os.path.expandvars(user)
849 849 if user is None:
850 850 user = encoding.environ.get("EMAIL")
851 851 if user is None and acceptempty:
852 852 return user
853 853 if user is None and self.configbool("ui", "askusername"):
854 854 user = self.prompt(_("enter a commit username:"), default=None)
855 855 if user is None and not self.interactive():
856 856 try:
857 857 user = '%s@%s' % (procutil.getuser(),
858 858 encoding.strtolocal(socket.getfqdn()))
859 859 self.warn(_("no username found, using '%s' instead\n") % user)
860 860 except KeyError:
861 861 pass
862 862 if not user:
863 863 raise error.Abort(_('no username supplied'),
864 864 hint=_("use 'hg config --edit' "
865 865 'to set your username'))
866 866 if "\n" in user:
867 867 raise error.Abort(_("username %r contains a newline\n")
868 868 % pycompat.bytestr(user))
869 869 return user
870 870
871 871 def shortuser(self, user):
872 872 """Return a short representation of a user name or email address."""
873 873 if not self.verbose:
874 874 user = stringutil.shortuser(user)
875 875 return user
876 876
877 877 def expandpath(self, loc, default=None):
878 878 """Return repository location relative to cwd or from [paths]"""
879 879 try:
880 880 p = self.paths.getpath(loc)
881 881 if p:
882 882 return p.rawloc
883 883 except error.RepoError:
884 884 pass
885 885
886 886 if default:
887 887 try:
888 888 p = self.paths.getpath(default)
889 889 if p:
890 890 return p.rawloc
891 891 except error.RepoError:
892 892 pass
893 893
894 894 return loc
895 895
896 896 @util.propertycache
897 897 def paths(self):
898 898 return paths(self)
899 899
900 900 @property
901 901 def fout(self):
902 902 return self._fout
903 903
904 904 @fout.setter
905 905 def fout(self, f):
906 906 self._fout = f
907 907 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
908 908
909 909 @property
910 910 def ferr(self):
911 911 return self._ferr
912 912
913 913 @ferr.setter
914 914 def ferr(self, f):
915 915 self._ferr = f
916 916 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
917 917
918 918 @property
919 919 def fin(self):
920 920 return self._fin
921 921
922 922 @fin.setter
923 923 def fin(self, f):
924 924 self._fin = f
925 925
926 926 @property
927 927 def fmsg(self):
928 928 """Stream dedicated for status/error messages; may be None if
929 929 fout/ferr are used"""
930 930 return self._fmsg
931 931
932 932 @fmsg.setter
933 933 def fmsg(self, f):
934 934 self._fmsg = f
935 935 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
936 936
937 937 def pushbuffer(self, error=False, subproc=False, labeled=False):
938 938 """install a buffer to capture standard output of the ui object
939 939
940 940 If error is True, the error output will be captured too.
941 941
942 942 If subproc is True, output from subprocesses (typically hooks) will be
943 943 captured too.
944 944
945 945 If labeled is True, any labels associated with buffered
946 946 output will be handled. By default, this has no effect
947 947 on the output returned, but extensions and GUI tools may
948 948 handle this argument and returned styled output. If output
949 949 is being buffered so it can be captured and parsed or
950 950 processed, labeled should not be set to True.
951 951 """
952 952 self._buffers.append([])
953 953 self._bufferstates.append((error, subproc, labeled))
954 954 self._bufferapplylabels = labeled
955 955
956 956 def popbuffer(self):
957 957 '''pop the last buffer and return the buffered output'''
958 958 self._bufferstates.pop()
959 959 if self._bufferstates:
960 960 self._bufferapplylabels = self._bufferstates[-1][2]
961 961 else:
962 962 self._bufferapplylabels = None
963 963
964 964 return "".join(self._buffers.pop())
965 965
966 966 def _isbuffered(self, dest):
967 967 if dest is self._fout:
968 968 return bool(self._buffers)
969 969 if dest is self._ferr:
970 970 return bool(self._bufferstates and self._bufferstates[-1][0])
971 971 return False
972 972
973 973 def canwritewithoutlabels(self):
974 974 '''check if write skips the label'''
975 975 if self._buffers and not self._bufferapplylabels:
976 976 return True
977 977 return self._colormode is None
978 978
979 979 def canbatchlabeledwrites(self):
980 980 '''check if write calls with labels are batchable'''
981 981 # Windows color printing is special, see ``write``.
982 982 return self._colormode != 'win32'
983 983
984 984 def write(self, *args, **opts):
985 985 '''write args to output
986 986
987 987 By default, this method simply writes to the buffer or stdout.
988 988 Color mode can be set on the UI class to have the output decorated
989 989 with color modifier before being written to stdout.
990 990
991 991 The color used is controlled by an optional keyword argument, "label".
992 992 This should be a string containing label names separated by space.
993 993 Label names take the form of "topic.type". For example, ui.debug()
994 994 issues a label of "ui.debug".
995 995
996 996 When labeling output for a specific command, a label of
997 997 "cmdname.type" is recommended. For example, status issues
998 998 a label of "status.modified" for modified files.
999 999 '''
1000 1000 dest = self._fout
1001 1001
1002 1002 # inlined _write() for speed
1003 1003 if self._buffers:
1004 1004 label = opts.get(r'label', '')
1005 1005 if label and self._bufferapplylabels:
1006 1006 self._buffers[-1].extend(self.label(a, label) for a in args)
1007 1007 else:
1008 1008 self._buffers[-1].extend(args)
1009 1009 return
1010 1010
1011 1011 # inliend _writenobuf() for speed
1012 1012 self._progclear()
1013 1013 msg = b''.join(args)
1014 1014
1015 1015 # opencode timeblockedsection because this is a critical path
1016 1016 starttime = util.timer()
1017 1017 try:
1018 1018 if self._colormode == 'win32':
1019 1019 # windows color printing is its own can of crab, defer to
1020 1020 # the color module and that is it.
1021 1021 color.win32print(self, dest.write, msg, **opts)
1022 1022 else:
1023 1023 if self._colormode is not None:
1024 1024 label = opts.get(r'label', '')
1025 1025 msg = self.label(msg, label)
1026 1026 dest.write(msg)
1027 1027 except IOError as err:
1028 1028 raise error.StdioError(err)
1029 1029 finally:
1030 1030 self._blockedtimes['stdio_blocked'] += \
1031 1031 (util.timer() - starttime) * 1000
1032 1032
1033 1033 def write_err(self, *args, **opts):
1034 1034 self._write(self._ferr, *args, **opts)
1035 1035
1036 1036 def _write(self, dest, *args, **opts):
1037 1037 # update write() as well if you touch this code
1038 1038 if self._isbuffered(dest):
1039 1039 label = opts.get(r'label', '')
1040 1040 if label and self._bufferapplylabels:
1041 1041 self._buffers[-1].extend(self.label(a, label) for a in args)
1042 1042 else:
1043 1043 self._buffers[-1].extend(args)
1044 1044 else:
1045 1045 self._writenobuf(dest, *args, **opts)
1046 1046
1047 1047 def _writenobuf(self, dest, *args, **opts):
1048 1048 # update write() as well if you touch this code
1049 1049 self._progclear()
1050 1050 msg = b''.join(args)
1051 1051
1052 1052 # opencode timeblockedsection because this is a critical path
1053 1053 starttime = util.timer()
1054 1054 try:
1055 1055 if dest is self._ferr and not getattr(self._fout, 'closed', False):
1056 1056 self._fout.flush()
1057 1057 if getattr(dest, 'structured', False):
1058 1058 # channel for machine-readable output with metadata, where
1059 1059 # no extra colorization is necessary.
1060 1060 dest.write(msg, **opts)
1061 1061 elif self._colormode == 'win32':
1062 1062 # windows color printing is its own can of crab, defer to
1063 1063 # the color module and that is it.
1064 1064 color.win32print(self, dest.write, msg, **opts)
1065 1065 else:
1066 1066 if self._colormode is not None:
1067 1067 label = opts.get(r'label', '')
1068 1068 msg = self.label(msg, label)
1069 1069 dest.write(msg)
1070 1070 # stderr may be buffered under win32 when redirected to files,
1071 1071 # including stdout.
1072 1072 if dest is self._ferr and not getattr(self._ferr, 'closed', False):
1073 1073 dest.flush()
1074 1074 except IOError as err:
1075 1075 if (dest is self._ferr
1076 1076 and err.errno in (errno.EPIPE, errno.EIO, errno.EBADF)):
1077 1077 # no way to report the error, so ignore it
1078 1078 return
1079 1079 raise error.StdioError(err)
1080 1080 finally:
1081 1081 self._blockedtimes['stdio_blocked'] += \
1082 1082 (util.timer() - starttime) * 1000
1083 1083
1084 1084 def _writemsg(self, dest, *args, **opts):
1085 1085 _writemsgwith(self._write, dest, *args, **opts)
1086 1086
1087 1087 def _writemsgnobuf(self, dest, *args, **opts):
1088 1088 _writemsgwith(self._writenobuf, dest, *args, **opts)
1089 1089
1090 1090 def flush(self):
1091 1091 # opencode timeblockedsection because this is a critical path
1092 1092 starttime = util.timer()
1093 1093 try:
1094 1094 try:
1095 1095 self._fout.flush()
1096 1096 except IOError as err:
1097 1097 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1098 1098 raise error.StdioError(err)
1099 1099 finally:
1100 1100 try:
1101 1101 self._ferr.flush()
1102 1102 except IOError as err:
1103 1103 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1104 1104 raise error.StdioError(err)
1105 1105 finally:
1106 1106 self._blockedtimes['stdio_blocked'] += \
1107 1107 (util.timer() - starttime) * 1000
1108 1108
1109 1109 def _isatty(self, fh):
1110 1110 if self.configbool('ui', 'nontty'):
1111 1111 return False
1112 1112 return procutil.isatty(fh)
1113 1113
1114 1114 def protectfinout(self):
1115 1115 """Duplicate ui streams and redirect original if they are stdio
1116 1116
1117 1117 Returns (fin, fout) which point to the original ui fds, but may be
1118 1118 copy of them. The returned streams can be considered "owned" in that
1119 1119 print(), exec(), etc. never reach to them.
1120 1120 """
1121 1121 if self._finoutredirected:
1122 1122 # if already redirected, protectstdio() would just create another
1123 1123 # nullfd pair, which is equivalent to returning self._fin/_fout.
1124 1124 return self._fin, self._fout
1125 1125 fin, fout = procutil.protectstdio(self._fin, self._fout)
1126 1126 self._finoutredirected = (fin, fout) != (self._fin, self._fout)
1127 1127 return fin, fout
1128 1128
1129 1129 def restorefinout(self, fin, fout):
1130 1130 """Restore ui streams from possibly duplicated (fin, fout)"""
1131 1131 if (fin, fout) == (self._fin, self._fout):
1132 1132 return
1133 1133 procutil.restorestdio(self._fin, self._fout, fin, fout)
1134 1134 # protectfinout() won't create more than one duplicated streams,
1135 1135 # so we can just turn the redirection flag off.
1136 1136 self._finoutredirected = False
1137 1137
1138 1138 @contextlib.contextmanager
1139 1139 def protectedfinout(self):
1140 1140 """Run code block with protected standard streams"""
1141 1141 fin, fout = self.protectfinout()
1142 1142 try:
1143 1143 yield fin, fout
1144 1144 finally:
1145 1145 self.restorefinout(fin, fout)
1146 1146
1147 1147 def disablepager(self):
1148 1148 self._disablepager = True
1149 1149
1150 1150 def pager(self, command):
1151 1151 """Start a pager for subsequent command output.
1152 1152
1153 1153 Commands which produce a long stream of output should call
1154 1154 this function to activate the user's preferred pagination
1155 1155 mechanism (which may be no pager). Calling this function
1156 1156 precludes any future use of interactive functionality, such as
1157 1157 prompting the user or activating curses.
1158 1158
1159 1159 Args:
1160 1160 command: The full, non-aliased name of the command. That is, "log"
1161 1161 not "history, "summary" not "summ", etc.
1162 1162 """
1163 1163 if (self._disablepager
1164 1164 or self.pageractive):
1165 1165 # how pager should do is already determined
1166 1166 return
1167 1167
1168 1168 if not command.startswith('internal-always-') and (
1169 1169 # explicit --pager=on (= 'internal-always-' prefix) should
1170 1170 # take precedence over disabling factors below
1171 1171 command in self.configlist('pager', 'ignore')
1172 1172 or not self.configbool('ui', 'paginate')
1173 1173 or not self.configbool('pager', 'attend-' + command, True)
1174 1174 or encoding.environ.get('TERM') == 'dumb'
1175 1175 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1176 1176 # formatted() will need some adjustment.
1177 1177 or not self.formatted()
1178 1178 or self.plain()
1179 1179 or self._buffers
1180 1180 # TODO: expose debugger-enabled on the UI object
1181 1181 or '--debugger' in pycompat.sysargv):
1182 1182 # We only want to paginate if the ui appears to be
1183 1183 # interactive, the user didn't say HGPLAIN or
1184 1184 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1185 1185 return
1186 1186
1187 1187 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
1188 1188 if not pagercmd:
1189 1189 return
1190 1190
1191 1191 pagerenv = {}
1192 1192 for name, value in rcutil.defaultpagerenv().items():
1193 1193 if name not in encoding.environ:
1194 1194 pagerenv[name] = value
1195 1195
1196 1196 self.debug('starting pager for command %s\n' %
1197 1197 stringutil.pprint(command))
1198 1198 self.flush()
1199 1199
1200 1200 wasformatted = self.formatted()
1201 1201 if util.safehasattr(signal, "SIGPIPE"):
1202 1202 signal.signal(signal.SIGPIPE, _catchterm)
1203 1203 if self._runpager(pagercmd, pagerenv):
1204 1204 self.pageractive = True
1205 1205 # Preserve the formatted-ness of the UI. This is important
1206 1206 # because we mess with stdout, which might confuse
1207 1207 # auto-detection of things being formatted.
1208 1208 self.setconfig('ui', 'formatted', wasformatted, 'pager')
1209 1209 self.setconfig('ui', 'interactive', False, 'pager')
1210 1210
1211 1211 # If pagermode differs from color.mode, reconfigure color now that
1212 1212 # pageractive is set.
1213 1213 cm = self._colormode
1214 1214 if cm != self.config('color', 'pagermode', cm):
1215 1215 color.setup(self)
1216 1216 else:
1217 1217 # If the pager can't be spawned in dispatch when --pager=on is
1218 1218 # given, don't try again when the command runs, to avoid a duplicate
1219 1219 # warning about a missing pager command.
1220 1220 self.disablepager()
1221 1221
1222 1222 def _runpager(self, command, env=None):
1223 1223 """Actually start the pager and set up file descriptors.
1224 1224
1225 1225 This is separate in part so that extensions (like chg) can
1226 1226 override how a pager is invoked.
1227 1227 """
1228 1228 if command == 'cat':
1229 1229 # Save ourselves some work.
1230 1230 return False
1231 1231 # If the command doesn't contain any of these characters, we
1232 1232 # assume it's a binary and exec it directly. This means for
1233 1233 # simple pager command configurations, we can degrade
1234 1234 # gracefully and tell the user about their broken pager.
1235 1235 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1236 1236
1237 1237 if pycompat.iswindows and not shell:
1238 1238 # Window's built-in `more` cannot be invoked with shell=False, but
1239 1239 # its `more.com` can. Hide this implementation detail from the
1240 1240 # user so we can also get sane bad PAGER behavior. MSYS has
1241 1241 # `more.exe`, so do a cmd.exe style resolution of the executable to
1242 1242 # determine which one to use.
1243 1243 fullcmd = procutil.findexe(command)
1244 1244 if not fullcmd:
1245 1245 self.warn(_("missing pager command '%s', skipping pager\n")
1246 1246 % command)
1247 1247 return False
1248 1248
1249 1249 command = fullcmd
1250 1250
1251 1251 try:
1252 1252 pager = subprocess.Popen(
1253 1253 procutil.tonativestr(command), shell=shell, bufsize=-1,
1254 1254 close_fds=procutil.closefds, stdin=subprocess.PIPE,
1255 1255 stdout=procutil.stdout, stderr=procutil.stderr,
1256 1256 env=procutil.tonativeenv(procutil.shellenviron(env)))
1257 1257 except OSError as e:
1258 1258 if e.errno == errno.ENOENT and not shell:
1259 1259 self.warn(_("missing pager command '%s', skipping pager\n")
1260 1260 % command)
1261 1261 return False
1262 1262 raise
1263 1263
1264 1264 # back up original file descriptors
1265 1265 stdoutfd = os.dup(procutil.stdout.fileno())
1266 1266 stderrfd = os.dup(procutil.stderr.fileno())
1267 1267
1268 1268 os.dup2(pager.stdin.fileno(), procutil.stdout.fileno())
1269 1269 if self._isatty(procutil.stderr):
1270 1270 os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
1271 1271
1272 1272 @self.atexit
1273 1273 def killpager():
1274 1274 if util.safehasattr(signal, "SIGINT"):
1275 1275 signal.signal(signal.SIGINT, signal.SIG_IGN)
1276 1276 # restore original fds, closing pager.stdin copies in the process
1277 1277 os.dup2(stdoutfd, procutil.stdout.fileno())
1278 1278 os.dup2(stderrfd, procutil.stderr.fileno())
1279 1279 pager.stdin.close()
1280 1280 pager.wait()
1281 1281
1282 1282 return True
1283 1283
1284 1284 @property
1285 1285 def _exithandlers(self):
1286 1286 return _reqexithandlers
1287 1287
1288 1288 def atexit(self, func, *args, **kwargs):
1289 1289 '''register a function to run after dispatching a request
1290 1290
1291 1291 Handlers do not stay registered across request boundaries.'''
1292 1292 self._exithandlers.append((func, args, kwargs))
1293 1293 return func
1294 1294
1295 1295 def interface(self, feature):
1296 1296 """what interface to use for interactive console features?
1297 1297
1298 1298 The interface is controlled by the value of `ui.interface` but also by
1299 1299 the value of feature-specific configuration. For example:
1300 1300
1301 1301 ui.interface.histedit = text
1302 1302 ui.interface.chunkselector = curses
1303 1303
1304 1304 Here the features are "histedit" and "chunkselector".
1305 1305
1306 1306 The configuration above means that the default interfaces for commands
1307 1307 is curses, the interface for histedit is text and the interface for
1308 1308 selecting chunk is crecord (the best curses interface available).
1309 1309
1310 1310 Consider the following example:
1311 1311 ui.interface = curses
1312 1312 ui.interface.histedit = text
1313 1313
1314 1314 Then histedit will use the text interface and chunkselector will use
1315 1315 the default curses interface (crecord at the moment).
1316 1316 """
1317 1317 alldefaults = frozenset(["text", "curses"])
1318 1318
1319 1319 featureinterfaces = {
1320 1320 "chunkselector": [
1321 1321 "text",
1322 1322 "curses",
1323 1323 ],
1324 1324 "histedit": [
1325 1325 "text",
1326 1326 "curses",
1327 1327 ],
1328 1328 }
1329 1329
1330 1330 # Feature-specific interface
1331 1331 if feature not in featureinterfaces.keys():
1332 1332 # Programming error, not user error
1333 1333 raise ValueError("Unknown feature requested %s" % feature)
1334 1334
1335 1335 availableinterfaces = frozenset(featureinterfaces[feature])
1336 1336 if alldefaults > availableinterfaces:
1337 1337 # Programming error, not user error. We need a use case to
1338 1338 # define the right thing to do here.
1339 1339 raise ValueError(
1340 1340 "Feature %s does not handle all default interfaces" %
1341 1341 feature)
1342 1342
1343 1343 if self.plain() or encoding.environ.get('TERM') == 'dumb':
1344 1344 return "text"
1345 1345
1346 1346 # Default interface for all the features
1347 1347 defaultinterface = "text"
1348 1348 i = self.config("ui", "interface")
1349 1349 if i in alldefaults:
1350 1350 defaultinterface = i
1351 1351
1352 1352 choseninterface = defaultinterface
1353 1353 f = self.config("ui", "interface.%s" % feature)
1354 1354 if f in availableinterfaces:
1355 1355 choseninterface = f
1356 1356
1357 1357 if i is not None and defaultinterface != i:
1358 1358 if f is not None:
1359 1359 self.warn(_("invalid value for ui.interface: %s\n") %
1360 1360 (i,))
1361 1361 else:
1362 1362 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1363 1363 (i, choseninterface))
1364 1364 if f is not None and choseninterface != f:
1365 1365 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1366 1366 (feature, f, choseninterface))
1367 1367
1368 1368 return choseninterface
1369 1369
1370 1370 def interactive(self):
1371 1371 '''is interactive input allowed?
1372 1372
1373 1373 An interactive session is a session where input can be reasonably read
1374 1374 from `sys.stdin'. If this function returns false, any attempt to read
1375 1375 from stdin should fail with an error, unless a sensible default has been
1376 1376 specified.
1377 1377
1378 1378 Interactiveness is triggered by the value of the `ui.interactive'
1379 1379 configuration variable or - if it is unset - when `sys.stdin' points
1380 1380 to a terminal device.
1381 1381
1382 1382 This function refers to input only; for output, see `ui.formatted()'.
1383 1383 '''
1384 1384 i = self.configbool("ui", "interactive")
1385 1385 if i is None:
1386 1386 # some environments replace stdin without implementing isatty
1387 1387 # usually those are non-interactive
1388 1388 return self._isatty(self._fin)
1389 1389
1390 1390 return i
1391 1391
1392 1392 def termwidth(self):
1393 1393 '''how wide is the terminal in columns?
1394 1394 '''
1395 1395 if 'COLUMNS' in encoding.environ:
1396 1396 try:
1397 1397 return int(encoding.environ['COLUMNS'])
1398 1398 except ValueError:
1399 1399 pass
1400 1400 return scmutil.termsize(self)[0]
1401 1401
1402 1402 def formatted(self):
1403 1403 '''should formatted output be used?
1404 1404
1405 1405 It is often desirable to format the output to suite the output medium.
1406 1406 Examples of this are truncating long lines or colorizing messages.
1407 1407 However, this is not often not desirable when piping output into other
1408 1408 utilities, e.g. `grep'.
1409 1409
1410 1410 Formatted output is triggered by the value of the `ui.formatted'
1411 1411 configuration variable or - if it is unset - when `sys.stdout' points
1412 1412 to a terminal device. Please note that `ui.formatted' should be
1413 1413 considered an implementation detail; it is not intended for use outside
1414 1414 Mercurial or its extensions.
1415 1415
1416 1416 This function refers to output only; for input, see `ui.interactive()'.
1417 1417 This function always returns false when in plain mode, see `ui.plain()'.
1418 1418 '''
1419 1419 if self.plain():
1420 1420 return False
1421 1421
1422 1422 i = self.configbool("ui", "formatted")
1423 1423 if i is None:
1424 1424 # some environments replace stdout without implementing isatty
1425 1425 # usually those are non-interactive
1426 1426 return self._isatty(self._fout)
1427 1427
1428 1428 return i
1429 1429
1430 1430 def _readline(self):
1431 1431 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1432 1432 # because they have to be text streams with *no buffering*. Instead,
1433 1433 # we use rawinput() only if call_readline() will be invoked by
1434 1434 # PyOS_Readline(), so no I/O will be made at Python layer.
1435 1435 usereadline = (self._isatty(self._fin) and self._isatty(self._fout)
1436 1436 and procutil.isstdin(self._fin)
1437 1437 and procutil.isstdout(self._fout))
1438 1438 if usereadline:
1439 1439 try:
1440 1440 # magically add command line editing support, where
1441 1441 # available
1442 1442 import readline
1443 1443 # force demandimport to really load the module
1444 1444 readline.read_history_file
1445 1445 # windows sometimes raises something other than ImportError
1446 1446 except Exception:
1447 1447 usereadline = False
1448 1448
1449 1449 # prompt ' ' must exist; otherwise readline may delete entire line
1450 1450 # - http://bugs.python.org/issue12833
1451 1451 with self.timeblockedsection('stdio'):
1452 1452 if usereadline:
1453 1453 line = encoding.strtolocal(pycompat.rawinput(r' '))
1454 1454 # When stdin is in binary mode on Windows, it can cause
1455 1455 # raw_input() to emit an extra trailing carriage return
1456 1456 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
1457 1457 line = line[:-1]
1458 1458 else:
1459 1459 self._fout.write(b' ')
1460 1460 self._fout.flush()
1461 1461 line = self._fin.readline()
1462 1462 if not line:
1463 1463 raise EOFError
1464 1464 line = line.rstrip(pycompat.oslinesep)
1465 1465
1466 1466 return line
1467 1467
1468 1468 def prompt(self, msg, default="y"):
1469 1469 """Prompt user with msg, read response.
1470 1470 If ui is not interactive, the default is returned.
1471 1471 """
1472 1472 return self._prompt(msg, default=default)
1473 1473
1474 1474 def _prompt(self, msg, **opts):
1475 1475 default = opts[r'default']
1476 1476 if not self.interactive():
1477 1477 self._writemsg(self._fmsgout, msg, ' ', type='prompt', **opts)
1478 1478 self._writemsg(self._fmsgout, default or '', "\n",
1479 1479 type='promptecho')
1480 1480 return default
1481 1481 self._writemsgnobuf(self._fmsgout, msg, type='prompt', **opts)
1482 1482 self.flush()
1483 1483 try:
1484 1484 r = self._readline()
1485 1485 if not r:
1486 1486 r = default
1487 1487 if self.configbool('ui', 'promptecho'):
1488 1488 self._writemsg(self._fmsgout, r, "\n", type='promptecho')
1489 1489 return r
1490 1490 except EOFError:
1491 1491 raise error.ResponseExpected()
1492 1492
1493 1493 @staticmethod
1494 1494 def extractchoices(prompt):
1495 1495 """Extract prompt message and list of choices from specified prompt.
1496 1496
1497 1497 This returns tuple "(message, choices)", and "choices" is the
1498 1498 list of tuple "(response character, text without &)".
1499 1499
1500 1500 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1501 1501 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1502 1502 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1503 1503 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1504 1504 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1505 1505 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1506 1506 """
1507 1507
1508 1508 # Sadly, the prompt string may have been built with a filename
1509 1509 # containing "$$" so let's try to find the first valid-looking
1510 1510 # prompt to start parsing. Sadly, we also can't rely on
1511 1511 # choices containing spaces, ASCII, or basically anything
1512 1512 # except an ampersand followed by a character.
1513 1513 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1514 1514 msg = m.group(1)
1515 1515 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1516 1516 def choicetuple(s):
1517 1517 ampidx = s.index('&')
1518 1518 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1519 1519 return (msg, [choicetuple(s) for s in choices])
1520 1520
1521 1521 def promptchoice(self, prompt, default=0):
1522 1522 """Prompt user with a message, read response, and ensure it matches
1523 1523 one of the provided choices. The prompt is formatted as follows:
1524 1524
1525 1525 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1526 1526
1527 1527 The index of the choice is returned. Responses are case
1528 1528 insensitive. If ui is not interactive, the default is
1529 1529 returned.
1530 1530 """
1531 1531
1532 1532 msg, choices = self.extractchoices(prompt)
1533 1533 resps = [r for r, t in choices]
1534 1534 while True:
1535 1535 r = self._prompt(msg, default=resps[default], choices=choices)
1536 1536 if r.lower() in resps:
1537 1537 return resps.index(r.lower())
1538 1538 # TODO: shouldn't it be a warning?
1539 1539 self._writemsg(self._fmsgout, _("unrecognized response\n"))
1540 1540
1541 1541 def getpass(self, prompt=None, default=None):
1542 1542 if not self.interactive():
1543 1543 return default
1544 1544 try:
1545 1545 self._writemsg(self._fmsgerr, prompt or _('password: '),
1546 1546 type='prompt', password=True)
1547 1547 # disable getpass() only if explicitly specified. it's still valid
1548 1548 # to interact with tty even if fin is not a tty.
1549 1549 with self.timeblockedsection('stdio'):
1550 1550 if self.configbool('ui', 'nontty'):
1551 1551 l = self._fin.readline()
1552 1552 if not l:
1553 1553 raise EOFError
1554 1554 return l.rstrip('\n')
1555 1555 else:
1556 1556 return getpass.getpass('')
1557 1557 except EOFError:
1558 1558 raise error.ResponseExpected()
1559 1559
1560 1560 def status(self, *msg, **opts):
1561 1561 '''write status message to output (if ui.quiet is False)
1562 1562
1563 1563 This adds an output label of "ui.status".
1564 1564 '''
1565 1565 if not self.quiet:
1566 1566 self._writemsg(self._fmsgout, type='status', *msg, **opts)
1567 1567
1568 1568 def warn(self, *msg, **opts):
1569 1569 '''write warning message to output (stderr)
1570 1570
1571 1571 This adds an output label of "ui.warning".
1572 1572 '''
1573 1573 self._writemsg(self._fmsgerr, type='warning', *msg, **opts)
1574 1574
1575 1575 def error(self, *msg, **opts):
1576 1576 '''write error message to output (stderr)
1577 1577
1578 1578 This adds an output label of "ui.error".
1579 1579 '''
1580 1580 self._writemsg(self._fmsgerr, type='error', *msg, **opts)
1581 1581
1582 1582 def note(self, *msg, **opts):
1583 1583 '''write note to output (if ui.verbose is True)
1584 1584
1585 1585 This adds an output label of "ui.note".
1586 1586 '''
1587 1587 if self.verbose:
1588 1588 self._writemsg(self._fmsgout, type='note', *msg, **opts)
1589 1589
1590 1590 def debug(self, *msg, **opts):
1591 1591 '''write debug message to output (if ui.debugflag is True)
1592 1592
1593 1593 This adds an output label of "ui.debug".
1594 1594 '''
1595 1595 if self.debugflag:
1596 1596 self._writemsg(self._fmsgout, type='debug', *msg, **opts)
1597 1597 self.log(b'debug', b'%s', b''.join(msg))
1598 1598
1599 1599 def edit(self, text, user, extra=None, editform=None, pending=None,
1600 1600 repopath=None, action=None):
1601 1601 if action is None:
1602 1602 self.develwarn('action is None but will soon be a required '
1603 1603 'parameter to ui.edit()')
1604 1604 extra_defaults = {
1605 1605 'prefix': 'editor',
1606 1606 'suffix': '.txt',
1607 1607 }
1608 1608 if extra is not None:
1609 1609 if extra.get('suffix') is not None:
1610 1610 self.develwarn('extra.suffix is not None but will soon be '
1611 1611 'ignored by ui.edit()')
1612 1612 extra_defaults.update(extra)
1613 1613 extra = extra_defaults
1614 1614
1615 1615 if action == 'diff':
1616 1616 suffix = '.diff'
1617 1617 elif action:
1618 1618 suffix = '.%s.hg.txt' % action
1619 1619 else:
1620 1620 suffix = extra['suffix']
1621 1621
1622 1622 rdir = None
1623 1623 if self.configbool('experimental', 'editortmpinhg'):
1624 1624 rdir = repopath
1625 1625 (fd, name) = pycompat.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1626 1626 suffix=suffix,
1627 1627 dir=rdir)
1628 1628 try:
1629 1629 f = os.fdopen(fd, r'wb')
1630 1630 f.write(util.tonativeeol(text))
1631 1631 f.close()
1632 1632
1633 1633 environ = {'HGUSER': user}
1634 1634 if 'transplant_source' in extra:
1635 1635 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1636 1636 for label in ('intermediate-source', 'source', 'rebase_source'):
1637 1637 if label in extra:
1638 1638 environ.update({'HGREVISION': extra[label]})
1639 1639 break
1640 1640 if editform:
1641 1641 environ.update({'HGEDITFORM': editform})
1642 1642 if pending:
1643 1643 environ.update({'HG_PENDING': pending})
1644 1644
1645 1645 editor = self.geteditor()
1646 1646
1647 1647 self.system("%s \"%s\"" % (editor, name),
1648 1648 environ=environ,
1649 1649 onerr=error.Abort, errprefix=_("edit failed"),
1650 1650 blockedtag='editor')
1651 1651
1652 1652 f = open(name, r'rb')
1653 1653 t = util.fromnativeeol(f.read())
1654 1654 f.close()
1655 1655 finally:
1656 1656 os.unlink(name)
1657 1657
1658 1658 return t
1659 1659
1660 1660 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1661 1661 blockedtag=None):
1662 1662 '''execute shell command with appropriate output stream. command
1663 1663 output will be redirected if fout is not stdout.
1664 1664
1665 1665 if command fails and onerr is None, return status, else raise onerr
1666 1666 object as exception.
1667 1667 '''
1668 1668 if blockedtag is None:
1669 1669 # Long cmds tend to be because of an absolute path on cmd. Keep
1670 1670 # the tail end instead
1671 1671 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1672 1672 blockedtag = 'unknown_system_' + cmdsuffix
1673 1673 out = self._fout
1674 1674 if any(s[1] for s in self._bufferstates):
1675 1675 out = self
1676 1676 with self.timeblockedsection(blockedtag):
1677 1677 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1678 1678 if rc and onerr:
1679 1679 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1680 1680 procutil.explainexit(rc))
1681 1681 if errprefix:
1682 1682 errmsg = '%s: %s' % (errprefix, errmsg)
1683 1683 raise onerr(errmsg)
1684 1684 return rc
1685 1685
1686 1686 def _runsystem(self, cmd, environ, cwd, out):
1687 1687 """actually execute the given shell command (can be overridden by
1688 1688 extensions like chg)"""
1689 1689 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
1690 1690
1691 1691 def traceback(self, exc=None, force=False):
1692 1692 '''print exception traceback if traceback printing enabled or forced.
1693 1693 only to call in exception handler. returns true if traceback
1694 1694 printed.'''
1695 1695 if self.tracebackflag or force:
1696 1696 if exc is None:
1697 1697 exc = sys.exc_info()
1698 1698 cause = getattr(exc[1], 'cause', None)
1699 1699
1700 1700 if cause is not None:
1701 1701 causetb = traceback.format_tb(cause[2])
1702 1702 exctb = traceback.format_tb(exc[2])
1703 1703 exconly = traceback.format_exception_only(cause[0], cause[1])
1704 1704
1705 1705 # exclude frame where 'exc' was chained and rethrown from exctb
1706 1706 self.write_err('Traceback (most recent call last):\n',
1707 1707 ''.join(exctb[:-1]),
1708 1708 ''.join(causetb),
1709 1709 ''.join(exconly))
1710 1710 else:
1711 1711 output = traceback.format_exception(exc[0], exc[1], exc[2])
1712 1712 self.write_err(encoding.strtolocal(r''.join(output)))
1713 1713 return self.tracebackflag or force
1714 1714
1715 1715 def geteditor(self):
1716 1716 '''return editor to use'''
1717 1717 if pycompat.sysplatform == 'plan9':
1718 1718 # vi is the MIPS instruction simulator on Plan 9. We
1719 1719 # instead default to E to plumb commit messages to
1720 1720 # avoid confusion.
1721 1721 editor = 'E'
1722 1722 else:
1723 1723 editor = 'vi'
1724 1724 return (encoding.environ.get("HGEDITOR") or
1725 1725 self.config("ui", "editor", editor))
1726 1726
1727 1727 @util.propertycache
1728 1728 def _progbar(self):
1729 1729 """setup the progbar singleton to the ui object"""
1730 1730 if (self.quiet or self.debugflag
1731 1731 or self.configbool('progress', 'disable')
1732 1732 or not progress.shouldprint(self)):
1733 1733 return None
1734 1734 return getprogbar(self)
1735 1735
1736 1736 def _progclear(self):
1737 1737 """clear progress bar output if any. use it before any output"""
1738 1738 if not haveprogbar(): # nothing loaded yet
1739 1739 return
1740 1740 if self._progbar is not None and self._progbar.printed:
1741 1741 self._progbar.clear()
1742 1742
1743 1743 def progress(self, topic, pos, item="", unit="", total=None):
1744 1744 '''show a progress message
1745 1745
1746 1746 By default a textual progress bar will be displayed if an operation
1747 1747 takes too long. 'topic' is the current operation, 'item' is a
1748 1748 non-numeric marker of the current position (i.e. the currently
1749 1749 in-process file), 'pos' is the current numeric position (i.e.
1750 1750 revision, bytes, etc.), unit is a corresponding unit label,
1751 1751 and total is the highest expected pos.
1752 1752
1753 1753 Multiple nested topics may be active at a time.
1754 1754
1755 1755 All topics should be marked closed by setting pos to None at
1756 1756 termination.
1757 1757 '''
1758 1758 self.deprecwarn("use ui.makeprogress() instead of ui.progress()",
1759 1759 "5.1")
1760 1760 progress = self.makeprogress(topic, unit, total)
1761 1761 if pos is not None:
1762 1762 progress.update(pos, item=item)
1763 1763 else:
1764 1764 progress.complete()
1765 1765
1766 1766 def makeprogress(self, topic, unit="", total=None):
1767 1767 """Create a progress helper for the specified topic"""
1768 1768 if getattr(self._fmsgerr, 'structured', False):
1769 1769 # channel for machine-readable output with metadata, just send
1770 1770 # raw information
1771 1771 # TODO: consider porting some useful information (e.g. estimated
1772 1772 # time) from progbar. we might want to support update delay to
1773 1773 # reduce the cost of transferring progress messages.
1774 1774 def updatebar(topic, pos, item, unit, total):
1775 1775 self._fmsgerr.write(None, type=b'progress', topic=topic,
1776 1776 pos=pos, item=item, unit=unit, total=total)
1777 1777 elif self._progbar is not None:
1778 1778 updatebar = self._progbar.progress
1779 1779 else:
1780 1780 def updatebar(topic, pos, item, unit, total):
1781 1781 pass
1782 1782 return scmutil.progress(self, updatebar, topic, unit, total)
1783 1783
1784 1784 def getlogger(self, name):
1785 1785 """Returns a logger of the given name; or None if not registered"""
1786 1786 return self._loggers.get(name)
1787 1787
1788 1788 def setlogger(self, name, logger):
1789 1789 """Install logger which can be identified later by the given name
1790 1790
1791 1791 More than one loggers can be registered. Use extension or module
1792 1792 name to uniquely identify the logger instance.
1793 1793 """
1794 1794 self._loggers[name] = logger
1795 1795
1796 1796 def log(self, event, msgfmt, *msgargs, **opts):
1797 1797 '''hook for logging facility extensions
1798 1798
1799 1799 event should be a readily-identifiable subsystem, which will
1800 1800 allow filtering.
1801 1801
1802 1802 msgfmt should be a newline-terminated format string to log, and
1803 1803 *msgargs are %-formatted into it.
1804 1804
1805 1805 **opts currently has no defined meanings.
1806 1806 '''
1807 1807 if not self._loggers:
1808 1808 return
1809 1809 activeloggers = [l for l in self._loggers.itervalues()
1810 1810 if l.tracked(event)]
1811 1811 if not activeloggers:
1812 1812 return
1813 1813 msg = msgfmt % msgargs
1814 1814 opts = pycompat.byteskwargs(opts)
1815 1815 # guard against recursion from e.g. ui.debug()
1816 1816 registeredloggers = self._loggers
1817 1817 self._loggers = {}
1818 1818 try:
1819 1819 for logger in activeloggers:
1820 1820 logger.log(self, event, msg, opts)
1821 1821 finally:
1822 1822 self._loggers = registeredloggers
1823 1823
1824 1824 def label(self, msg, label):
1825 1825 '''style msg based on supplied label
1826 1826
1827 1827 If some color mode is enabled, this will add the necessary control
1828 1828 characters to apply such color. In addition, 'debug' color mode adds
1829 1829 markup showing which label affects a piece of text.
1830 1830
1831 1831 ui.write(s, 'label') is equivalent to
1832 1832 ui.write(ui.label(s, 'label')).
1833 1833 '''
1834 1834 if self._colormode is not None:
1835 1835 return color.colorlabel(self, msg, label)
1836 1836 return msg
1837 1837
1838 1838 def develwarn(self, msg, stacklevel=1, config=None):
1839 1839 """issue a developer warning message
1840 1840
1841 1841 Use 'stacklevel' to report the offender some layers further up in the
1842 1842 stack.
1843 1843 """
1844 1844 if not self.configbool('devel', 'all-warnings'):
1845 1845 if config is None or not self.configbool('devel', config):
1846 1846 return
1847 1847 msg = 'devel-warn: ' + msg
1848 1848 stacklevel += 1 # get in develwarn
1849 1849 if self.tracebackflag:
1850 1850 util.debugstacktrace(msg, stacklevel, self._ferr, self._fout)
1851 1851 self.log('develwarn', '%s at:\n%s' %
1852 1852 (msg, ''.join(util.getstackframes(stacklevel))))
1853 1853 else:
1854 1854 curframe = inspect.currentframe()
1855 1855 calframe = inspect.getouterframes(curframe, 2)
1856 1856 fname, lineno, fmsg = calframe[stacklevel][1:4]
1857 1857 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
1858 1858 self.write_err('%s at: %s:%d (%s)\n'
1859 1859 % (msg, fname, lineno, fmsg))
1860 1860 self.log('develwarn', '%s at: %s:%d (%s)\n',
1861 1861 msg, fname, lineno, fmsg)
1862 1862 curframe = calframe = None # avoid cycles
1863 1863
1864 1864 def deprecwarn(self, msg, version, stacklevel=2):
1865 1865 """issue a deprecation warning
1866 1866
1867 1867 - msg: message explaining what is deprecated and how to upgrade,
1868 1868 - version: last version where the API will be supported,
1869 1869 """
1870 1870 if not (self.configbool('devel', 'all-warnings')
1871 1871 or self.configbool('devel', 'deprec-warn')):
1872 1872 return
1873 1873 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1874 1874 " update your code.)") % version
1875 1875 self.develwarn(msg, stacklevel=stacklevel, config='deprec-warn')
1876 1876
1877 1877 def exportableenviron(self):
1878 1878 """The environment variables that are safe to export, e.g. through
1879 1879 hgweb.
1880 1880 """
1881 1881 return self._exportableenviron
1882 1882
1883 1883 @contextlib.contextmanager
1884 1884 def configoverride(self, overrides, source=""):
1885 1885 """Context manager for temporary config overrides
1886 1886 `overrides` must be a dict of the following structure:
1887 1887 {(section, name) : value}"""
1888 1888 backups = {}
1889 1889 try:
1890 1890 for (section, name), value in overrides.items():
1891 1891 backups[(section, name)] = self.backupconfig(section, name)
1892 1892 self.setconfig(section, name, value, source)
1893 1893 yield
1894 1894 finally:
1895 1895 for __, backup in backups.items():
1896 1896 self.restoreconfig(backup)
1897 1897 # just restoring ui.quiet config to the previous value is not enough
1898 1898 # as it does not update ui.quiet class member
1899 1899 if ('ui', 'quiet') in overrides:
1900 1900 self.fixconfig(section='ui')
1901 1901
1902 1902 class paths(dict):
1903 1903 """Represents a collection of paths and their configs.
1904 1904
1905 1905 Data is initially derived from ui instances and the config files they have
1906 1906 loaded.
1907 1907 """
1908 1908 def __init__(self, ui):
1909 1909 dict.__init__(self)
1910 1910
1911 1911 for name, loc in ui.configitems('paths', ignoresub=True):
1912 1912 # No location is the same as not existing.
1913 1913 if not loc:
1914 1914 continue
1915 1915 loc, sub = ui.configsuboptions('paths', name)
1916 1916 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1917 1917
1918 1918 def getpath(self, name, default=None):
1919 1919 """Return a ``path`` from a string, falling back to default.
1920 1920
1921 1921 ``name`` can be a named path or locations. Locations are filesystem
1922 1922 paths or URIs.
1923 1923
1924 1924 Returns None if ``name`` is not a registered path, a URI, or a local
1925 1925 path to a repo.
1926 1926 """
1927 1927 # Only fall back to default if no path was requested.
1928 1928 if name is None:
1929 1929 if not default:
1930 1930 default = ()
1931 1931 elif not isinstance(default, (tuple, list)):
1932 1932 default = (default,)
1933 1933 for k in default:
1934 1934 try:
1935 1935 return self[k]
1936 1936 except KeyError:
1937 1937 continue
1938 1938 return None
1939 1939
1940 1940 # Most likely empty string.
1941 1941 # This may need to raise in the future.
1942 1942 if not name:
1943 1943 return None
1944 1944
1945 1945 try:
1946 1946 return self[name]
1947 1947 except KeyError:
1948 1948 # Try to resolve as a local path or URI.
1949 1949 try:
1950 1950 # We don't pass sub-options in, so no need to pass ui instance.
1951 1951 return path(None, None, rawloc=name)
1952 1952 except ValueError:
1953 1953 raise error.RepoError(_('repository %s does not exist') %
1954 1954 name)
1955 1955
1956 1956 _pathsuboptions = {}
1957 1957
1958 1958 def pathsuboption(option, attr):
1959 1959 """Decorator used to declare a path sub-option.
1960 1960
1961 1961 Arguments are the sub-option name and the attribute it should set on
1962 1962 ``path`` instances.
1963 1963
1964 1964 The decorated function will receive as arguments a ``ui`` instance,
1965 1965 ``path`` instance, and the string value of this option from the config.
1966 1966 The function should return the value that will be set on the ``path``
1967 1967 instance.
1968 1968
1969 1969 This decorator can be used to perform additional verification of
1970 1970 sub-options and to change the type of sub-options.
1971 1971 """
1972 1972 def register(func):
1973 1973 _pathsuboptions[option] = (attr, func)
1974 1974 return func
1975 1975 return register
1976 1976
1977 1977 @pathsuboption('pushurl', 'pushloc')
1978 1978 def pushurlpathoption(ui, path, value):
1979 1979 u = util.url(value)
1980 1980 # Actually require a URL.
1981 1981 if not u.scheme:
1982 1982 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1983 1983 return None
1984 1984
1985 1985 # Don't support the #foo syntax in the push URL to declare branch to
1986 1986 # push.
1987 1987 if u.fragment:
1988 1988 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1989 1989 'ignoring)\n') % path.name)
1990 1990 u.fragment = None
1991 1991
1992 1992 return bytes(u)
1993 1993
1994 1994 @pathsuboption('pushrev', 'pushrev')
1995 1995 def pushrevpathoption(ui, path, value):
1996 1996 return value
1997 1997
1998 1998 class path(object):
1999 1999 """Represents an individual path and its configuration."""
2000 2000
2001 2001 def __init__(self, ui, name, rawloc=None, suboptions=None):
2002 2002 """Construct a path from its config options.
2003 2003
2004 2004 ``ui`` is the ``ui`` instance the path is coming from.
2005 2005 ``name`` is the symbolic name of the path.
2006 2006 ``rawloc`` is the raw location, as defined in the config.
2007 2007 ``pushloc`` is the raw locations pushes should be made to.
2008 2008
2009 2009 If ``name`` is not defined, we require that the location be a) a local
2010 2010 filesystem path with a .hg directory or b) a URL. If not,
2011 2011 ``ValueError`` is raised.
2012 2012 """
2013 2013 if not rawloc:
2014 2014 raise ValueError('rawloc must be defined')
2015 2015
2016 2016 # Locations may define branches via syntax <base>#<branch>.
2017 2017 u = util.url(rawloc)
2018 2018 branch = None
2019 2019 if u.fragment:
2020 2020 branch = u.fragment
2021 2021 u.fragment = None
2022 2022
2023 2023 self.url = u
2024 2024 self.branch = branch
2025 2025
2026 2026 self.name = name
2027 2027 self.rawloc = rawloc
2028 2028 self.loc = '%s' % u
2029 2029
2030 2030 # When given a raw location but not a symbolic name, validate the
2031 2031 # location is valid.
2032 2032 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
2033 2033 raise ValueError('location is not a URL or path to a local '
2034 2034 'repo: %s' % rawloc)
2035 2035
2036 2036 suboptions = suboptions or {}
2037 2037
2038 2038 # Now process the sub-options. If a sub-option is registered, its
2039 2039 # attribute will always be present. The value will be None if there
2040 2040 # was no valid sub-option.
2041 2041 for suboption, (attr, func) in _pathsuboptions.iteritems():
2042 2042 if suboption not in suboptions:
2043 2043 setattr(self, attr, None)
2044 2044 continue
2045 2045
2046 2046 value = func(ui, self, suboptions[suboption])
2047 2047 setattr(self, attr, value)
2048 2048
2049 2049 def _isvalidlocalpath(self, path):
2050 2050 """Returns True if the given path is a potentially valid repository.
2051 2051 This is its own function so that extensions can change the definition of
2052 2052 'valid' in this case (like when pulling from a git repo into a hg
2053 2053 one)."""
2054 return os.path.isdir(os.path.join(path, '.hg'))
2054 try:
2055 return os.path.isdir(os.path.join(path, '.hg'))
2056 # Python 2 may return TypeError. Python 3, ValueError.
2057 except (TypeError, ValueError):
2058 return False
2055 2059
2056 2060 @property
2057 2061 def suboptions(self):
2058 2062 """Return sub-options and their values for this path.
2059 2063
2060 2064 This is intended to be used for presentation purposes.
2061 2065 """
2062 2066 d = {}
2063 2067 for subopt, (attr, _func) in _pathsuboptions.iteritems():
2064 2068 value = getattr(self, attr)
2065 2069 if value is not None:
2066 2070 d[subopt] = value
2067 2071 return d
2068 2072
2069 2073 # we instantiate one globally shared progress bar to avoid
2070 2074 # competing progress bars when multiple UI objects get created
2071 2075 _progresssingleton = None
2072 2076
2073 2077 def getprogbar(ui):
2074 2078 global _progresssingleton
2075 2079 if _progresssingleton is None:
2076 2080 # passing 'ui' object to the singleton is fishy,
2077 2081 # this is how the extension used to work but feel free to rework it.
2078 2082 _progresssingleton = progress.progbar(ui)
2079 2083 return _progresssingleton
2080 2084
2081 2085 def haveprogbar():
2082 2086 return _progresssingleton is not None
2083 2087
2084 2088 def _selectmsgdests(ui):
2085 2089 name = ui.config(b'ui', b'message-output')
2086 2090 if name == b'channel':
2087 2091 if ui.fmsg:
2088 2092 return ui.fmsg, ui.fmsg
2089 2093 else:
2090 2094 # fall back to ferr if channel isn't ready so that status/error
2091 2095 # messages can be printed
2092 2096 return ui.ferr, ui.ferr
2093 2097 if name == b'stdio':
2094 2098 return ui.fout, ui.ferr
2095 2099 if name == b'stderr':
2096 2100 return ui.ferr, ui.ferr
2097 2101 raise error.Abort(b'invalid ui.message-output destination: %s' % name)
2098 2102
2099 2103 def _writemsgwith(write, dest, *args, **opts):
2100 2104 """Write ui message with the given ui._write*() function
2101 2105
2102 2106 The specified message type is translated to 'ui.<type>' label if the dest
2103 2107 isn't a structured channel, so that the message will be colorized.
2104 2108 """
2105 2109 # TODO: maybe change 'type' to a mandatory option
2106 2110 if r'type' in opts and not getattr(dest, 'structured', False):
2107 2111 opts[r'label'] = opts.get(r'label', '') + ' ui.%s' % opts.pop(r'type')
2108 2112 write(dest, *args, **opts)
@@ -1,112 +1,112 b''
1 1 #require no-windows
2 2
3 3 $ . "$TESTDIR/remotefilelog-library.sh"
4 4
5 5 $ hg init master
6 6 $ cd master
7 7 $ cat >> .hg/hgrc <<EOF
8 8 > [remotefilelog]
9 9 > server=True
10 10 > serverexpiration=-1
11 11 > EOF
12 12 $ echo x > x
13 13 $ hg commit -qAm x
14 14 $ cd ..
15 15
16 16 $ hgcloneshallow ssh://user@dummy/master shallow -q
17 17 1 files fetched over 1 fetches - (1 misses, 0.00% hit ratio) over *s (glob)
18 18
19 19 # Set the prefetchdays config to zero so that all commits are prefetched
20 20 # no matter what their creation date is.
21 21 $ cd shallow
22 22 $ cat >> .hg/hgrc <<EOF
23 23 > [remotefilelog]
24 24 > prefetchdays=0
25 25 > EOF
26 26 $ cd ..
27 27
28 28 # commit a new version of x so we can gc the old one
29 29
30 30 $ cd master
31 31 $ echo y > x
32 32 $ hg commit -qAm y
33 33 $ cd ..
34 34
35 35 $ cd shallow
36 36 $ hg pull -q
37 37 $ hg update -q
38 38 1 files fetched over 1 fetches - (1 misses, 0.00% hit ratio) over *s (glob)
39 39 $ cd ..
40 40
41 41 # gc client cache
42 42
43 43 $ lastweek=`$PYTHON -c 'import datetime,time; print(datetime.datetime.fromtimestamp(time.time() - (86400 * 7)).strftime("%y%m%d%H%M"))'`
44 44 $ find $CACHEDIR -type f -exec touch -t $lastweek {} \;
45 45
46 46 $ find $CACHEDIR -type f | sort
47 47 $TESTTMP/hgcache/master/11/f6ad8ec52a2984abaafd7c3b516503785c2072/1406e74118627694268417491f018a4a883152f0 (glob)
48 48 $TESTTMP/hgcache/master/11/f6ad8ec52a2984abaafd7c3b516503785c2072/48023ec064c1d522f0d792a5a912bb1bf7859a4a (glob)
49 49 $TESTTMP/hgcache/repos (glob)
50 50 $ hg gc
51 51 finished: removed 1 of 2 files (0.00 GB to 0.00 GB)
52 52 $ find $CACHEDIR -type f | sort
53 53 $TESTTMP/hgcache/master/11/f6ad8ec52a2984abaafd7c3b516503785c2072/48023ec064c1d522f0d792a5a912bb1bf7859a4a (glob)
54 54 $TESTTMP/hgcache/repos
55 55
56 56 # gc server cache
57 57
58 58 $ find master/.hg/remotefilelogcache -type f | sort
59 59 master/.hg/remotefilelogcache/x/1406e74118627694268417491f018a4a883152f0 (glob)
60 60 master/.hg/remotefilelogcache/x/48023ec064c1d522f0d792a5a912bb1bf7859a4a (glob)
61 61 $ hg gc master
62 62 finished: removed 0 of 1 files (0.00 GB to 0.00 GB)
63 63 $ find master/.hg/remotefilelogcache -type f | sort
64 64 master/.hg/remotefilelogcache/x/48023ec064c1d522f0d792a5a912bb1bf7859a4a (glob)
65 65
66 66 # Test that GC keepset includes pullprefetch revset if it is configured
67 67
68 68 $ cd shallow
69 69 $ cat >> .hg/hgrc <<EOF
70 70 > [remotefilelog]
71 71 > pullprefetch=all()
72 72 > EOF
73 73 $ hg prefetch
74 74 1 files fetched over 1 fetches - (1 misses, 0.00% hit ratio) over *s (glob)
75 75
76 76 $ cd ..
77 77 $ hg gc
78 78 finished: removed 0 of 2 files (0.00 GB to 0.00 GB)
79 79
80 80 # Ensure that there are 2 versions of the file in cache
81 81 $ find $CACHEDIR -type f | sort
82 82 $TESTTMP/hgcache/master/11/f6ad8ec52a2984abaafd7c3b516503785c2072/1406e74118627694268417491f018a4a883152f0 (glob)
83 83 $TESTTMP/hgcache/master/11/f6ad8ec52a2984abaafd7c3b516503785c2072/48023ec064c1d522f0d792a5a912bb1bf7859a4a (glob)
84 84 $TESTTMP/hgcache/repos (glob)
85 85
86 86 # Test that if garbage collection on repack and repack on hg gc flags are set then incremental repack with garbage collector is run
87 87
88 88 $ hg gc --config remotefilelog.gcrepack=True --config remotefilelog.repackonhggc=True
89 89
90 90 # Ensure that loose files are repacked
91 91 $ find $CACHEDIR -type f | sort
92 92 $TESTTMP/hgcache/master/packs/320dab99b7e3f60512b97f347689625263d22cf5.dataidx
93 93 $TESTTMP/hgcache/master/packs/320dab99b7e3f60512b97f347689625263d22cf5.datapack
94 94 $TESTTMP/hgcache/master/packs/837b83c1ef6485a336eb4421ac5973c0ec130fbb.histidx
95 95 $TESTTMP/hgcache/master/packs/837b83c1ef6485a336eb4421ac5973c0ec130fbb.histpack
96 96 $TESTTMP/hgcache/master/packs/repacklock
97 97 $TESTTMP/hgcache/repos
98 98
99 99 # Test that warning is displayed when there are no valid repos in repofile
100 100
101 101 $ cp $CACHEDIR/repos $CACHEDIR/repos.bak
102 102 $ echo " " > $CACHEDIR/repos
103 103 $ hg gc
104 104 warning: no valid repos in repofile
105 105 $ mv $CACHEDIR/repos.bak $CACHEDIR/repos
106 106
107 107 # Test that warning is displayed when the repo path is malformed
108 108
109 109 $ printf "asdas\0das" >> $CACHEDIR/repos
110 $ hg gc 2>&1 | head -n2
111 warning: malformed path: * (glob)
112 Traceback (most recent call last):
110 $ hg gc
111 abort: invalid path asdas\x00da: stat() argument 1 must be encoded string without null bytes, not str (esc)
112 [255]
General Comments 0
You need to be logged in to leave comments. Login now