##// END OF EJS Templates
clone: use "official" API to create local clone destination...
marmoute -
r48235:d1c1fd7a default
parent child Browse files
Show More
@@ -1,1602 +1,1599 b''
1 1 # hg.py - repository classes for mercurial
2 2 #
3 3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
4 4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from __future__ import absolute_import
10 10
11 11 import errno
12 12 import os
13 13 import shutil
14 14 import stat
15 15
16 16 from .i18n import _
17 17 from .node import (
18 18 hex,
19 19 sha1nodeconstants,
20 20 short,
21 21 )
22 22 from .pycompat import getattr
23 23
24 24 from . import (
25 25 bookmarks,
26 26 bundlerepo,
27 27 cacheutil,
28 28 cmdutil,
29 29 destutil,
30 30 discovery,
31 31 error,
32 32 exchange,
33 33 extensions,
34 34 graphmod,
35 35 httppeer,
36 36 localrepo,
37 37 lock,
38 38 logcmdutil,
39 39 logexchange,
40 40 merge as mergemod,
41 41 mergestate as mergestatemod,
42 42 narrowspec,
43 43 phases,
44 44 requirements,
45 45 scmutil,
46 46 sshpeer,
47 47 statichttprepo,
48 48 ui as uimod,
49 49 unionrepo,
50 50 url,
51 51 util,
52 52 verify as verifymod,
53 53 vfs as vfsmod,
54 54 )
55 55 from .interfaces import repository as repositorymod
56 56 from .utils import (
57 57 hashutil,
58 58 stringutil,
59 59 urlutil,
60 60 )
61 61
62 62
63 63 release = lock.release
64 64
65 65 # shared features
66 66 sharedbookmarks = b'bookmarks'
67 67
68 68
69 69 def _local(path):
70 70 path = util.expandpath(urlutil.urllocalpath(path))
71 71
72 72 try:
73 73 # we use os.stat() directly here instead of os.path.isfile()
74 74 # because the latter started returning `False` on invalid path
75 75 # exceptions starting in 3.8 and we care about handling
76 76 # invalid paths specially here.
77 77 st = os.stat(path)
78 78 isfile = stat.S_ISREG(st.st_mode)
79 79 # Python 2 raises TypeError, Python 3 ValueError.
80 80 except (TypeError, ValueError) as e:
81 81 raise error.Abort(
82 82 _(b'invalid path %s: %s') % (path, stringutil.forcebytestr(e))
83 83 )
84 84 except OSError:
85 85 isfile = False
86 86
87 87 return isfile and bundlerepo or localrepo
88 88
89 89
90 90 def addbranchrevs(lrepo, other, branches, revs):
91 91 peer = other.peer() # a courtesy to callers using a localrepo for other
92 92 hashbranch, branches = branches
93 93 if not hashbranch and not branches:
94 94 x = revs or None
95 95 if revs:
96 96 y = revs[0]
97 97 else:
98 98 y = None
99 99 return x, y
100 100 if revs:
101 101 revs = list(revs)
102 102 else:
103 103 revs = []
104 104
105 105 if not peer.capable(b'branchmap'):
106 106 if branches:
107 107 raise error.Abort(_(b"remote branch lookup not supported"))
108 108 revs.append(hashbranch)
109 109 return revs, revs[0]
110 110
111 111 with peer.commandexecutor() as e:
112 112 branchmap = e.callcommand(b'branchmap', {}).result()
113 113
114 114 def primary(branch):
115 115 if branch == b'.':
116 116 if not lrepo:
117 117 raise error.Abort(_(b"dirstate branch not accessible"))
118 118 branch = lrepo.dirstate.branch()
119 119 if branch in branchmap:
120 120 revs.extend(hex(r) for r in reversed(branchmap[branch]))
121 121 return True
122 122 else:
123 123 return False
124 124
125 125 for branch in branches:
126 126 if not primary(branch):
127 127 raise error.RepoLookupError(_(b"unknown branch '%s'") % branch)
128 128 if hashbranch:
129 129 if not primary(hashbranch):
130 130 revs.append(hashbranch)
131 131 return revs, revs[0]
132 132
133 133
134 134 def parseurl(path, branches=None):
135 135 '''parse url#branch, returning (url, (branch, branches))'''
136 136 msg = b'parseurl(...) moved to mercurial.utils.urlutil'
137 137 util.nouideprecwarn(msg, b'6.0', stacklevel=2)
138 138 return urlutil.parseurl(path, branches=branches)
139 139
140 140
141 141 schemes = {
142 142 b'bundle': bundlerepo,
143 143 b'union': unionrepo,
144 144 b'file': _local,
145 145 b'http': httppeer,
146 146 b'https': httppeer,
147 147 b'ssh': sshpeer,
148 148 b'static-http': statichttprepo,
149 149 }
150 150
151 151
152 152 def _peerlookup(path):
153 153 u = urlutil.url(path)
154 154 scheme = u.scheme or b'file'
155 155 thing = schemes.get(scheme) or schemes[b'file']
156 156 try:
157 157 return thing(path)
158 158 except TypeError:
159 159 # we can't test callable(thing) because 'thing' can be an unloaded
160 160 # module that implements __call__
161 161 if not util.safehasattr(thing, b'instance'):
162 162 raise
163 163 return thing
164 164
165 165
166 166 def islocal(repo):
167 167 '''return true if repo (or path pointing to repo) is local'''
168 168 if isinstance(repo, bytes):
169 169 try:
170 170 return _peerlookup(repo).islocal(repo)
171 171 except AttributeError:
172 172 return False
173 173 return repo.local()
174 174
175 175
176 176 def openpath(ui, path, sendaccept=True):
177 177 '''open path with open if local, url.open if remote'''
178 178 pathurl = urlutil.url(path, parsequery=False, parsefragment=False)
179 179 if pathurl.islocal():
180 180 return util.posixfile(pathurl.localpath(), b'rb')
181 181 else:
182 182 return url.open(ui, path, sendaccept=sendaccept)
183 183
184 184
185 185 # a list of (ui, repo) functions called for wire peer initialization
186 186 wirepeersetupfuncs = []
187 187
188 188
189 189 def _peerorrepo(
190 190 ui, path, create=False, presetupfuncs=None, intents=None, createopts=None
191 191 ):
192 192 """return a repository object for the specified path"""
193 193 obj = _peerlookup(path).instance(
194 194 ui, path, create, intents=intents, createopts=createopts
195 195 )
196 196 ui = getattr(obj, "ui", ui)
197 197 for f in presetupfuncs or []:
198 198 f(ui, obj)
199 199 ui.log(b'extension', b'- executing reposetup hooks\n')
200 200 with util.timedcm('all reposetup') as allreposetupstats:
201 201 for name, module in extensions.extensions(ui):
202 202 ui.log(b'extension', b' - running reposetup for %s\n', name)
203 203 hook = getattr(module, 'reposetup', None)
204 204 if hook:
205 205 with util.timedcm('reposetup %r', name) as stats:
206 206 hook(ui, obj)
207 207 ui.log(
208 208 b'extension', b' > reposetup for %s took %s\n', name, stats
209 209 )
210 210 ui.log(b'extension', b'> all reposetup took %s\n', allreposetupstats)
211 211 if not obj.local():
212 212 for f in wirepeersetupfuncs:
213 213 f(ui, obj)
214 214 return obj
215 215
216 216
217 217 def repository(
218 218 ui,
219 219 path=b'',
220 220 create=False,
221 221 presetupfuncs=None,
222 222 intents=None,
223 223 createopts=None,
224 224 ):
225 225 """return a repository object for the specified path"""
226 226 peer = _peerorrepo(
227 227 ui,
228 228 path,
229 229 create,
230 230 presetupfuncs=presetupfuncs,
231 231 intents=intents,
232 232 createopts=createopts,
233 233 )
234 234 repo = peer.local()
235 235 if not repo:
236 236 raise error.Abort(
237 237 _(b"repository '%s' is not local") % (path or peer.url())
238 238 )
239 239 return repo.filtered(b'visible')
240 240
241 241
242 242 def peer(uiorrepo, opts, path, create=False, intents=None, createopts=None):
243 243 '''return a repository peer for the specified path'''
244 244 rui = remoteui(uiorrepo, opts)
245 245 return _peerorrepo(
246 246 rui, path, create, intents=intents, createopts=createopts
247 247 ).peer()
248 248
249 249
250 250 def defaultdest(source):
251 251 """return default destination of clone if none is given
252 252
253 253 >>> defaultdest(b'foo')
254 254 'foo'
255 255 >>> defaultdest(b'/foo/bar')
256 256 'bar'
257 257 >>> defaultdest(b'/')
258 258 ''
259 259 >>> defaultdest(b'')
260 260 ''
261 261 >>> defaultdest(b'http://example.org/')
262 262 ''
263 263 >>> defaultdest(b'http://example.org/foo/')
264 264 'foo'
265 265 """
266 266 path = urlutil.url(source).path
267 267 if not path:
268 268 return b''
269 269 return os.path.basename(os.path.normpath(path))
270 270
271 271
272 272 def sharedreposource(repo):
273 273 """Returns repository object for source repository of a shared repo.
274 274
275 275 If repo is not a shared repository, returns None.
276 276 """
277 277 if repo.sharedpath == repo.path:
278 278 return None
279 279
280 280 if util.safehasattr(repo, b'srcrepo') and repo.srcrepo:
281 281 return repo.srcrepo
282 282
283 283 # the sharedpath always ends in the .hg; we want the path to the repo
284 284 source = repo.vfs.split(repo.sharedpath)[0]
285 285 srcurl, branches = urlutil.parseurl(source)
286 286 srcrepo = repository(repo.ui, srcurl)
287 287 repo.srcrepo = srcrepo
288 288 return srcrepo
289 289
290 290
291 291 def share(
292 292 ui,
293 293 source,
294 294 dest=None,
295 295 update=True,
296 296 bookmarks=True,
297 297 defaultpath=None,
298 298 relative=False,
299 299 ):
300 300 '''create a shared repository'''
301 301
302 302 if not islocal(source):
303 303 raise error.Abort(_(b'can only share local repositories'))
304 304
305 305 if not dest:
306 306 dest = defaultdest(source)
307 307 else:
308 308 dest = urlutil.get_clone_path(ui, dest)[1]
309 309
310 310 if isinstance(source, bytes):
311 311 origsource, source, branches = urlutil.get_clone_path(ui, source)
312 312 srcrepo = repository(ui, source)
313 313 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
314 314 else:
315 315 srcrepo = source.local()
316 316 checkout = None
317 317
318 318 shareditems = set()
319 319 if bookmarks:
320 320 shareditems.add(sharedbookmarks)
321 321
322 322 r = repository(
323 323 ui,
324 324 dest,
325 325 create=True,
326 326 createopts={
327 327 b'sharedrepo': srcrepo,
328 328 b'sharedrelative': relative,
329 329 b'shareditems': shareditems,
330 330 },
331 331 )
332 332
333 333 postshare(srcrepo, r, defaultpath=defaultpath)
334 334 r = repository(ui, dest)
335 335 _postshareupdate(r, update, checkout=checkout)
336 336 return r
337 337
338 338
339 339 def _prependsourcehgrc(repo):
340 340 """copies the source repo config and prepend it in current repo .hg/hgrc
341 341 on unshare. This is only done if the share was perfomed using share safe
342 342 method where we share config of source in shares"""
343 343 srcvfs = vfsmod.vfs(repo.sharedpath)
344 344 dstvfs = vfsmod.vfs(repo.path)
345 345
346 346 if not srcvfs.exists(b'hgrc'):
347 347 return
348 348
349 349 currentconfig = b''
350 350 if dstvfs.exists(b'hgrc'):
351 351 currentconfig = dstvfs.read(b'hgrc')
352 352
353 353 with dstvfs(b'hgrc', b'wb') as fp:
354 354 sourceconfig = srcvfs.read(b'hgrc')
355 355 fp.write(b"# Config copied from shared source\n")
356 356 fp.write(sourceconfig)
357 357 fp.write(b'\n')
358 358 fp.write(currentconfig)
359 359
360 360
361 361 def unshare(ui, repo):
362 362 """convert a shared repository to a normal one
363 363
364 364 Copy the store data to the repo and remove the sharedpath data.
365 365
366 366 Returns a new repository object representing the unshared repository.
367 367
368 368 The passed repository object is not usable after this function is
369 369 called.
370 370 """
371 371
372 372 with repo.lock():
373 373 # we use locks here because if we race with commit, we
374 374 # can end up with extra data in the cloned revlogs that's
375 375 # not pointed to by changesets, thus causing verify to
376 376 # fail
377 377 destlock = copystore(ui, repo, repo.path)
378 378 with destlock or util.nullcontextmanager():
379 379 if requirements.SHARESAFE_REQUIREMENT in repo.requirements:
380 380 # we were sharing .hg/hgrc of the share source with the current
381 381 # repo. We need to copy that while unsharing otherwise it can
382 382 # disable hooks and other checks
383 383 _prependsourcehgrc(repo)
384 384
385 385 sharefile = repo.vfs.join(b'sharedpath')
386 386 util.rename(sharefile, sharefile + b'.old')
387 387
388 388 repo.requirements.discard(requirements.SHARED_REQUIREMENT)
389 389 repo.requirements.discard(requirements.RELATIVE_SHARED_REQUIREMENT)
390 390 scmutil.writereporequirements(repo)
391 391
392 392 # Removing share changes some fundamental properties of the repo instance.
393 393 # So we instantiate a new repo object and operate on it rather than
394 394 # try to keep the existing repo usable.
395 395 newrepo = repository(repo.baseui, repo.root, create=False)
396 396
397 397 # TODO: figure out how to access subrepos that exist, but were previously
398 398 # removed from .hgsub
399 399 c = newrepo[b'.']
400 400 subs = c.substate
401 401 for s in sorted(subs):
402 402 c.sub(s).unshare()
403 403
404 404 localrepo.poisonrepository(repo)
405 405
406 406 return newrepo
407 407
408 408
409 409 def postshare(sourcerepo, destrepo, defaultpath=None):
410 410 """Called after a new shared repo is created.
411 411
412 412 The new repo only has a requirements file and pointer to the source.
413 413 This function configures additional shared data.
414 414
415 415 Extensions can wrap this function and write additional entries to
416 416 destrepo/.hg/shared to indicate additional pieces of data to be shared.
417 417 """
418 418 default = defaultpath or sourcerepo.ui.config(b'paths', b'default')
419 419 if default:
420 420 template = b'[paths]\ndefault = %s\n'
421 421 destrepo.vfs.write(b'hgrc', util.tonativeeol(template % default))
422 422 if requirements.NARROW_REQUIREMENT in sourcerepo.requirements:
423 423 with destrepo.wlock():
424 424 narrowspec.copytoworkingcopy(destrepo)
425 425
426 426
427 427 def _postshareupdate(repo, update, checkout=None):
428 428 """Maybe perform a working directory update after a shared repo is created.
429 429
430 430 ``update`` can be a boolean or a revision to update to.
431 431 """
432 432 if not update:
433 433 return
434 434
435 435 repo.ui.status(_(b"updating working directory\n"))
436 436 if update is not True:
437 437 checkout = update
438 438 for test in (checkout, b'default', b'tip'):
439 439 if test is None:
440 440 continue
441 441 try:
442 442 uprev = repo.lookup(test)
443 443 break
444 444 except error.RepoLookupError:
445 445 continue
446 446 _update(repo, uprev)
447 447
448 448
449 449 def copystore(ui, srcrepo, destpath):
450 450 """copy files from store of srcrepo in destpath
451 451
452 452 returns destlock
453 453 """
454 454 destlock = None
455 455 try:
456 456 hardlink = None
457 457 topic = _(b'linking') if hardlink else _(b'copying')
458 458 with ui.makeprogress(topic, unit=_(b'files')) as progress:
459 459 num = 0
460 460 srcpublishing = srcrepo.publishing()
461 461 srcvfs = vfsmod.vfs(srcrepo.sharedpath)
462 462 dstvfs = vfsmod.vfs(destpath)
463 463 for f in srcrepo.store.copylist():
464 464 if srcpublishing and f.endswith(b'phaseroots'):
465 465 continue
466 466 dstbase = os.path.dirname(f)
467 467 if dstbase and not dstvfs.exists(dstbase):
468 468 dstvfs.mkdir(dstbase)
469 469 if srcvfs.exists(f):
470 470 if f.endswith(b'data'):
471 471 # 'dstbase' may be empty (e.g. revlog format 0)
472 472 lockfile = os.path.join(dstbase, b"lock")
473 473 # lock to avoid premature writing to the target
474 474 destlock = lock.lock(dstvfs, lockfile)
475 475 hardlink, n = util.copyfiles(
476 476 srcvfs.join(f), dstvfs.join(f), hardlink, progress
477 477 )
478 478 num += n
479 479 if hardlink:
480 480 ui.debug(b"linked %d files\n" % num)
481 481 else:
482 482 ui.debug(b"copied %d files\n" % num)
483 483 return destlock
484 484 except: # re-raises
485 485 release(destlock)
486 486 raise
487 487
488 488
489 489 def clonewithshare(
490 490 ui,
491 491 peeropts,
492 492 sharepath,
493 493 source,
494 494 srcpeer,
495 495 dest,
496 496 pull=False,
497 497 rev=None,
498 498 update=True,
499 499 stream=False,
500 500 ):
501 501 """Perform a clone using a shared repo.
502 502
503 503 The store for the repository will be located at <sharepath>/.hg. The
504 504 specified revisions will be cloned or pulled from "source". A shared repo
505 505 will be created at "dest" and a working copy will be created if "update" is
506 506 True.
507 507 """
508 508 revs = None
509 509 if rev:
510 510 if not srcpeer.capable(b'lookup'):
511 511 raise error.Abort(
512 512 _(
513 513 b"src repository does not support "
514 514 b"revision lookup and so doesn't "
515 515 b"support clone by revision"
516 516 )
517 517 )
518 518
519 519 # TODO this is batchable.
520 520 remoterevs = []
521 521 for r in rev:
522 522 with srcpeer.commandexecutor() as e:
523 523 remoterevs.append(
524 524 e.callcommand(
525 525 b'lookup',
526 526 {
527 527 b'key': r,
528 528 },
529 529 ).result()
530 530 )
531 531 revs = remoterevs
532 532
533 533 # Obtain a lock before checking for or cloning the pooled repo otherwise
534 534 # 2 clients may race creating or populating it.
535 535 pooldir = os.path.dirname(sharepath)
536 536 # lock class requires the directory to exist.
537 537 try:
538 538 util.makedir(pooldir, False)
539 539 except OSError as e:
540 540 if e.errno != errno.EEXIST:
541 541 raise
542 542
543 543 poolvfs = vfsmod.vfs(pooldir)
544 544 basename = os.path.basename(sharepath)
545 545
546 546 with lock.lock(poolvfs, b'%s.lock' % basename):
547 547 if os.path.exists(sharepath):
548 548 ui.status(
549 549 _(b'(sharing from existing pooled repository %s)\n') % basename
550 550 )
551 551 else:
552 552 ui.status(
553 553 _(b'(sharing from new pooled repository %s)\n') % basename
554 554 )
555 555 # Always use pull mode because hardlinks in share mode don't work
556 556 # well. Never update because working copies aren't necessary in
557 557 # share mode.
558 558 clone(
559 559 ui,
560 560 peeropts,
561 561 source,
562 562 dest=sharepath,
563 563 pull=True,
564 564 revs=rev,
565 565 update=False,
566 566 stream=stream,
567 567 )
568 568
569 569 # Resolve the value to put in [paths] section for the source.
570 570 if islocal(source):
571 571 defaultpath = os.path.abspath(urlutil.urllocalpath(source))
572 572 else:
573 573 defaultpath = source
574 574
575 575 sharerepo = repository(ui, path=sharepath)
576 576 destrepo = share(
577 577 ui,
578 578 sharerepo,
579 579 dest=dest,
580 580 update=False,
581 581 bookmarks=False,
582 582 defaultpath=defaultpath,
583 583 )
584 584
585 585 # We need to perform a pull against the dest repo to fetch bookmarks
586 586 # and other non-store data that isn't shared by default. In the case of
587 587 # non-existing shared repo, this means we pull from the remote twice. This
588 588 # is a bit weird. But at the time it was implemented, there wasn't an easy
589 589 # way to pull just non-changegroup data.
590 590 exchange.pull(destrepo, srcpeer, heads=revs)
591 591
592 592 _postshareupdate(destrepo, update)
593 593
594 594 return srcpeer, peer(ui, peeropts, dest)
595 595
596 596
597 597 # Recomputing caches is often slow on big repos, so copy them.
598 598 def _copycache(srcrepo, dstcachedir, fname):
599 599 """copy a cache from srcrepo to destcachedir (if it exists)"""
600 600 srcfname = srcrepo.cachevfs.join(fname)
601 601 dstfname = os.path.join(dstcachedir, fname)
602 602 if os.path.exists(srcfname):
603 603 if not os.path.exists(dstcachedir):
604 604 os.mkdir(dstcachedir)
605 605 util.copyfile(srcfname, dstfname)
606 606
607 607
608 608 def clone(
609 609 ui,
610 610 peeropts,
611 611 source,
612 612 dest=None,
613 613 pull=False,
614 614 revs=None,
615 615 update=True,
616 616 stream=False,
617 617 branch=None,
618 618 shareopts=None,
619 619 storeincludepats=None,
620 620 storeexcludepats=None,
621 621 depth=None,
622 622 ):
623 623 """Make a copy of an existing repository.
624 624
625 625 Create a copy of an existing repository in a new directory. The
626 626 source and destination are URLs, as passed to the repository
627 627 function. Returns a pair of repository peers, the source and
628 628 newly created destination.
629 629
630 630 The location of the source is added to the new repository's
631 631 .hg/hgrc file, as the default to be used for future pulls and
632 632 pushes.
633 633
634 634 If an exception is raised, the partly cloned/updated destination
635 635 repository will be deleted.
636 636
637 637 Arguments:
638 638
639 639 source: repository object or URL
640 640
641 641 dest: URL of destination repository to create (defaults to base
642 642 name of source repository)
643 643
644 644 pull: always pull from source repository, even in local case or if the
645 645 server prefers streaming
646 646
647 647 stream: stream raw data uncompressed from repository (fast over
648 648 LAN, slow over WAN)
649 649
650 650 revs: revision to clone up to (implies pull=True)
651 651
652 652 update: update working directory after clone completes, if
653 653 destination is local repository (True means update to default rev,
654 654 anything else is treated as a revision)
655 655
656 656 branch: branches to clone
657 657
658 658 shareopts: dict of options to control auto sharing behavior. The "pool" key
659 659 activates auto sharing mode and defines the directory for stores. The
660 660 "mode" key determines how to construct the directory name of the shared
661 661 repository. "identity" means the name is derived from the node of the first
662 662 changeset in the repository. "remote" means the name is derived from the
663 663 remote's path/URL. Defaults to "identity."
664 664
665 665 storeincludepats and storeexcludepats: sets of file patterns to include and
666 666 exclude in the repository copy, respectively. If not defined, all files
667 667 will be included (a "full" clone). Otherwise a "narrow" clone containing
668 668 only the requested files will be performed. If ``storeincludepats`` is not
669 669 defined but ``storeexcludepats`` is, ``storeincludepats`` is assumed to be
670 670 ``path:.``. If both are empty sets, no files will be cloned.
671 671 """
672 672
673 673 if isinstance(source, bytes):
674 674 src = urlutil.get_clone_path(ui, source, branch)
675 675 origsource, source, branches = src
676 676 srcpeer = peer(ui, peeropts, source)
677 677 else:
678 678 srcpeer = source.peer() # in case we were called with a localrepo
679 679 branches = (None, branch or [])
680 680 origsource = source = srcpeer.url()
681 681 srclock = destlock = cleandir = None
682 682 destpeer = None
683 683 try:
684 684 revs, checkout = addbranchrevs(srcpeer, srcpeer, branches, revs)
685 685
686 686 if dest is None:
687 687 dest = defaultdest(source)
688 688 if dest:
689 689 ui.status(_(b"destination directory: %s\n") % dest)
690 690 else:
691 691 dest = urlutil.get_clone_path(ui, dest)[0]
692 692
693 693 dest = urlutil.urllocalpath(dest)
694 694 source = urlutil.urllocalpath(source)
695 695
696 696 if not dest:
697 697 raise error.InputError(_(b"empty destination path is not valid"))
698 698
699 699 destvfs = vfsmod.vfs(dest, expandpath=True)
700 700 if destvfs.lexists():
701 701 if not destvfs.isdir():
702 702 raise error.InputError(
703 703 _(b"destination '%s' already exists") % dest
704 704 )
705 705 elif destvfs.listdir():
706 706 raise error.InputError(
707 707 _(b"destination '%s' is not empty") % dest
708 708 )
709 709
710 710 createopts = {}
711 711 narrow = False
712 712
713 713 if storeincludepats is not None:
714 714 narrowspec.validatepatterns(storeincludepats)
715 715 narrow = True
716 716
717 717 if storeexcludepats is not None:
718 718 narrowspec.validatepatterns(storeexcludepats)
719 719 narrow = True
720 720
721 721 if narrow:
722 722 # Include everything by default if only exclusion patterns defined.
723 723 if storeexcludepats and not storeincludepats:
724 724 storeincludepats = {b'path:.'}
725 725
726 726 createopts[b'narrowfiles'] = True
727 727
728 728 if depth:
729 729 createopts[b'shallowfilestore'] = True
730 730
731 731 if srcpeer.capable(b'lfs-serve'):
732 732 # Repository creation honors the config if it disabled the extension, so
733 733 # we can't just announce that lfs will be enabled. This check avoids
734 734 # saying that lfs will be enabled, and then saying it's an unknown
735 735 # feature. The lfs creation option is set in either case so that a
736 736 # requirement is added. If the extension is explicitly disabled but the
737 737 # requirement is set, the clone aborts early, before transferring any
738 738 # data.
739 739 createopts[b'lfs'] = True
740 740
741 741 if extensions.disabled_help(b'lfs'):
742 742 ui.status(
743 743 _(
744 744 b'(remote is using large file support (lfs), but it is '
745 745 b'explicitly disabled in the local configuration)\n'
746 746 )
747 747 )
748 748 else:
749 749 ui.status(
750 750 _(
751 751 b'(remote is using large file support (lfs); lfs will '
752 752 b'be enabled for this repository)\n'
753 753 )
754 754 )
755 755
756 756 shareopts = shareopts or {}
757 757 sharepool = shareopts.get(b'pool')
758 758 sharenamemode = shareopts.get(b'mode')
759 759 if sharepool and islocal(dest):
760 760 sharepath = None
761 761 if sharenamemode == b'identity':
762 762 # Resolve the name from the initial changeset in the remote
763 763 # repository. This returns nullid when the remote is empty. It
764 764 # raises RepoLookupError if revision 0 is filtered or otherwise
765 765 # not available. If we fail to resolve, sharing is not enabled.
766 766 try:
767 767 with srcpeer.commandexecutor() as e:
768 768 rootnode = e.callcommand(
769 769 b'lookup',
770 770 {
771 771 b'key': b'0',
772 772 },
773 773 ).result()
774 774
775 775 if rootnode != sha1nodeconstants.nullid:
776 776 sharepath = os.path.join(sharepool, hex(rootnode))
777 777 else:
778 778 ui.status(
779 779 _(
780 780 b'(not using pooled storage: '
781 781 b'remote appears to be empty)\n'
782 782 )
783 783 )
784 784 except error.RepoLookupError:
785 785 ui.status(
786 786 _(
787 787 b'(not using pooled storage: '
788 788 b'unable to resolve identity of remote)\n'
789 789 )
790 790 )
791 791 elif sharenamemode == b'remote':
792 792 sharepath = os.path.join(
793 793 sharepool, hex(hashutil.sha1(source).digest())
794 794 )
795 795 else:
796 796 raise error.Abort(
797 797 _(b'unknown share naming mode: %s') % sharenamemode
798 798 )
799 799
800 800 # TODO this is a somewhat arbitrary restriction.
801 801 if narrow:
802 802 ui.status(
803 803 _(b'(pooled storage not supported for narrow clones)\n')
804 804 )
805 805 sharepath = None
806 806
807 807 if sharepath:
808 808 return clonewithshare(
809 809 ui,
810 810 peeropts,
811 811 sharepath,
812 812 source,
813 813 srcpeer,
814 814 dest,
815 815 pull=pull,
816 816 rev=revs,
817 817 update=update,
818 818 stream=stream,
819 819 )
820 820
821 821 srcrepo = srcpeer.local()
822 822
823 823 abspath = origsource
824 824 if islocal(origsource):
825 825 abspath = os.path.abspath(urlutil.urllocalpath(origsource))
826 826
827 827 if islocal(dest):
828 828 if os.path.exists(dest):
829 829 # only clean up directories we create ourselves
830 830 hgdir = os.path.realpath(os.path.join(dest, b".hg"))
831 831 cleandir = hgdir
832 832 else:
833 833 cleandir = dest
834 834
835 835 copy = False
836 836 if (
837 837 srcrepo
838 838 and srcrepo.cancopy()
839 839 and islocal(dest)
840 840 and not phases.hassecret(srcrepo)
841 841 ):
842 842 copy = not pull and not revs
843 843
844 844 # TODO this is a somewhat arbitrary restriction.
845 845 if narrow:
846 846 copy = False
847 847
848 848 if copy:
849 849 try:
850 850 # we use a lock here because if we race with commit, we
851 851 # can end up with extra data in the cloned revlogs that's
852 852 # not pointed to by changesets, thus causing verify to
853 853 # fail
854 854 srclock = srcrepo.lock(wait=False)
855 855 except error.LockError:
856 856 copy = False
857 857
858 858 if copy:
859 859 srcrepo.hook(b'preoutgoing', throw=True, source=b'clone')
860 hgdir = os.path.realpath(os.path.join(dest, b".hg"))
861 if not os.path.exists(dest):
862 util.makedirs(dest)
863 try:
864 destpath = hgdir
865 util.makedir(destpath, notindexed=True)
866 except OSError as inst:
867 if inst.errno == errno.EEXIST:
868 cleandir = None
869 raise error.Abort(
870 _(b"destination '%s' already exists") % dest
871 )
872 raise
873 860
861 destrootpath = urlutil.urllocalpath(dest)
862 dest_reqs = localrepo.clone_requirements(ui, createopts, srcrepo)
863 localrepo.createrepository(
864 ui,
865 destrootpath,
866 requirements=dest_reqs,
867 )
868 destrepo = localrepo.makelocalrepository(ui, destrootpath)
869
870 destpath = destrepo.vfs.base
874 871 destlock = copystore(ui, srcrepo, destpath)
875 872 # copy bookmarks over
876 873 srcbookmarks = srcrepo.vfs.join(b'bookmarks')
877 874 dstbookmarks = os.path.join(destpath, b'bookmarks')
878 875 if os.path.exists(srcbookmarks):
879 876 util.copyfile(srcbookmarks, dstbookmarks)
880 877
881 878 dstcachedir = os.path.join(destpath, b'cache')
882 879 for cache in cacheutil.cachetocopy(srcrepo):
883 880 _copycache(srcrepo, dstcachedir, cache)
884 881
885 882 # we need to re-init the repo after manually copying the data
886 883 # into it
887 884 destpeer = peer(srcrepo, peeropts, dest)
888 885 srcrepo.hook(
889 886 b'outgoing', source=b'clone', node=srcrepo.nodeconstants.nullhex
890 887 )
891 888 else:
892 889 try:
893 890 # only pass ui when no srcrepo
894 891 destpeer = peer(
895 892 srcrepo or ui,
896 893 peeropts,
897 894 dest,
898 895 create=True,
899 896 createopts=createopts,
900 897 )
901 898 except OSError as inst:
902 899 if inst.errno == errno.EEXIST:
903 900 cleandir = None
904 901 raise error.Abort(
905 902 _(b"destination '%s' already exists") % dest
906 903 )
907 904 raise
908 905
909 906 if revs:
910 907 if not srcpeer.capable(b'lookup'):
911 908 raise error.Abort(
912 909 _(
913 910 b"src repository does not support "
914 911 b"revision lookup and so doesn't "
915 912 b"support clone by revision"
916 913 )
917 914 )
918 915
919 916 # TODO this is batchable.
920 917 remoterevs = []
921 918 for rev in revs:
922 919 with srcpeer.commandexecutor() as e:
923 920 remoterevs.append(
924 921 e.callcommand(
925 922 b'lookup',
926 923 {
927 924 b'key': rev,
928 925 },
929 926 ).result()
930 927 )
931 928 revs = remoterevs
932 929
933 930 checkout = revs[0]
934 931 else:
935 932 revs = None
936 933 local = destpeer.local()
937 934 if local:
938 935 if narrow:
939 936 with local.wlock(), local.lock():
940 937 local.setnarrowpats(storeincludepats, storeexcludepats)
941 938 narrowspec.copytoworkingcopy(local)
942 939
943 940 u = urlutil.url(abspath)
944 941 defaulturl = bytes(u)
945 942 local.ui.setconfig(b'paths', b'default', defaulturl, b'clone')
946 943 if not stream:
947 944 if pull:
948 945 stream = False
949 946 else:
950 947 stream = None
951 948 # internal config: ui.quietbookmarkmove
952 949 overrides = {(b'ui', b'quietbookmarkmove'): True}
953 950 with local.ui.configoverride(overrides, b'clone'):
954 951 exchange.pull(
955 952 local,
956 953 srcpeer,
957 954 revs,
958 955 streamclonerequested=stream,
959 956 includepats=storeincludepats,
960 957 excludepats=storeexcludepats,
961 958 depth=depth,
962 959 )
963 960 elif srcrepo:
964 961 # TODO lift restriction once exchange.push() accepts narrow
965 962 # push.
966 963 if narrow:
967 964 raise error.Abort(
968 965 _(
969 966 b'narrow clone not available for '
970 967 b'remote destinations'
971 968 )
972 969 )
973 970
974 971 exchange.push(
975 972 srcrepo,
976 973 destpeer,
977 974 revs=revs,
978 975 bookmarks=srcrepo._bookmarks.keys(),
979 976 )
980 977 else:
981 978 raise error.Abort(
982 979 _(b"clone from remote to remote not supported")
983 980 )
984 981
985 982 cleandir = None
986 983
987 984 destrepo = destpeer.local()
988 985 if destrepo:
989 986 template = uimod.samplehgrcs[b'cloned']
990 987 u = urlutil.url(abspath)
991 988 u.passwd = None
992 989 defaulturl = bytes(u)
993 990 destrepo.vfs.write(b'hgrc', util.tonativeeol(template % defaulturl))
994 991 destrepo.ui.setconfig(b'paths', b'default', defaulturl, b'clone')
995 992
996 993 if ui.configbool(b'experimental', b'remotenames'):
997 994 logexchange.pullremotenames(destrepo, srcpeer)
998 995
999 996 if update:
1000 997 if update is not True:
1001 998 with srcpeer.commandexecutor() as e:
1002 999 checkout = e.callcommand(
1003 1000 b'lookup',
1004 1001 {
1005 1002 b'key': update,
1006 1003 },
1007 1004 ).result()
1008 1005
1009 1006 uprev = None
1010 1007 status = None
1011 1008 if checkout is not None:
1012 1009 # Some extensions (at least hg-git and hg-subversion) have
1013 1010 # a peer.lookup() implementation that returns a name instead
1014 1011 # of a nodeid. We work around it here until we've figured
1015 1012 # out a better solution.
1016 1013 if len(checkout) == 20 and checkout in destrepo:
1017 1014 uprev = checkout
1018 1015 elif scmutil.isrevsymbol(destrepo, checkout):
1019 1016 uprev = scmutil.revsymbol(destrepo, checkout).node()
1020 1017 else:
1021 1018 if update is not True:
1022 1019 try:
1023 1020 uprev = destrepo.lookup(update)
1024 1021 except error.RepoLookupError:
1025 1022 pass
1026 1023 if uprev is None:
1027 1024 try:
1028 1025 if destrepo._activebookmark:
1029 1026 uprev = destrepo.lookup(destrepo._activebookmark)
1030 1027 update = destrepo._activebookmark
1031 1028 else:
1032 1029 uprev = destrepo._bookmarks[b'@']
1033 1030 update = b'@'
1034 1031 bn = destrepo[uprev].branch()
1035 1032 if bn == b'default':
1036 1033 status = _(b"updating to bookmark %s\n" % update)
1037 1034 else:
1038 1035 status = (
1039 1036 _(b"updating to bookmark %s on branch %s\n")
1040 1037 ) % (update, bn)
1041 1038 except KeyError:
1042 1039 try:
1043 1040 uprev = destrepo.branchtip(b'default')
1044 1041 except error.RepoLookupError:
1045 1042 uprev = destrepo.lookup(b'tip')
1046 1043 if not status:
1047 1044 bn = destrepo[uprev].branch()
1048 1045 status = _(b"updating to branch %s\n") % bn
1049 1046 destrepo.ui.status(status)
1050 1047 _update(destrepo, uprev)
1051 1048 if update in destrepo._bookmarks:
1052 1049 bookmarks.activate(destrepo, update)
1053 1050 if destlock is not None:
1054 1051 release(destlock)
1055 1052 # here is a tiny windows were someone could end up writing the
1056 1053 # repository before the cache are sure to be warm. This is "fine"
1057 1054 # as the only "bad" outcome would be some slowness. That potential
1058 1055 # slowness already affect reader.
1059 1056 with destrepo.lock():
1060 1057 destrepo.updatecaches(caches=repositorymod.CACHES_POST_CLONE)
1061 1058 finally:
1062 1059 release(srclock, destlock)
1063 1060 if cleandir is not None:
1064 1061 shutil.rmtree(cleandir, True)
1065 1062 if srcpeer is not None:
1066 1063 srcpeer.close()
1067 1064 if destpeer and destpeer.local() is None:
1068 1065 destpeer.close()
1069 1066 return srcpeer, destpeer
1070 1067
1071 1068
1072 1069 def _showstats(repo, stats, quietempty=False):
1073 1070 if quietempty and stats.isempty():
1074 1071 return
1075 1072 repo.ui.status(
1076 1073 _(
1077 1074 b"%d files updated, %d files merged, "
1078 1075 b"%d files removed, %d files unresolved\n"
1079 1076 )
1080 1077 % (
1081 1078 stats.updatedcount,
1082 1079 stats.mergedcount,
1083 1080 stats.removedcount,
1084 1081 stats.unresolvedcount,
1085 1082 )
1086 1083 )
1087 1084
1088 1085
1089 1086 def updaterepo(repo, node, overwrite, updatecheck=None):
1090 1087 """Update the working directory to node.
1091 1088
1092 1089 When overwrite is set, changes are clobbered, merged else
1093 1090
1094 1091 returns stats (see pydoc mercurial.merge.applyupdates)"""
1095 1092 repo.ui.deprecwarn(
1096 1093 b'prefer merge.update() or merge.clean_update() over hg.updaterepo()',
1097 1094 b'5.7',
1098 1095 )
1099 1096 return mergemod._update(
1100 1097 repo,
1101 1098 node,
1102 1099 branchmerge=False,
1103 1100 force=overwrite,
1104 1101 labels=[b'working copy', b'destination'],
1105 1102 updatecheck=updatecheck,
1106 1103 )
1107 1104
1108 1105
1109 1106 def update(repo, node, quietempty=False, updatecheck=None):
1110 1107 """update the working directory to node"""
1111 1108 stats = mergemod.update(repo[node], updatecheck=updatecheck)
1112 1109 _showstats(repo, stats, quietempty)
1113 1110 if stats.unresolvedcount:
1114 1111 repo.ui.status(_(b"use 'hg resolve' to retry unresolved file merges\n"))
1115 1112 return stats.unresolvedcount > 0
1116 1113
1117 1114
1118 1115 # naming conflict in clone()
1119 1116 _update = update
1120 1117
1121 1118
1122 1119 def clean(repo, node, show_stats=True, quietempty=False):
1123 1120 """forcibly switch the working directory to node, clobbering changes"""
1124 1121 stats = mergemod.clean_update(repo[node])
1125 1122 assert stats.unresolvedcount == 0
1126 1123 if show_stats:
1127 1124 _showstats(repo, stats, quietempty)
1128 1125 return False
1129 1126
1130 1127
1131 1128 # naming conflict in updatetotally()
1132 1129 _clean = clean
1133 1130
1134 1131 _VALID_UPDATECHECKS = {
1135 1132 mergemod.UPDATECHECK_ABORT,
1136 1133 mergemod.UPDATECHECK_NONE,
1137 1134 mergemod.UPDATECHECK_LINEAR,
1138 1135 mergemod.UPDATECHECK_NO_CONFLICT,
1139 1136 }
1140 1137
1141 1138
1142 1139 def updatetotally(ui, repo, checkout, brev, clean=False, updatecheck=None):
1143 1140 """Update the working directory with extra care for non-file components
1144 1141
1145 1142 This takes care of non-file components below:
1146 1143
1147 1144 :bookmark: might be advanced or (in)activated
1148 1145
1149 1146 This takes arguments below:
1150 1147
1151 1148 :checkout: to which revision the working directory is updated
1152 1149 :brev: a name, which might be a bookmark to be activated after updating
1153 1150 :clean: whether changes in the working directory can be discarded
1154 1151 :updatecheck: how to deal with a dirty working directory
1155 1152
1156 1153 Valid values for updatecheck are the UPDATECHECK_* constants
1157 1154 defined in the merge module. Passing `None` will result in using the
1158 1155 configured default.
1159 1156
1160 1157 * ABORT: abort if the working directory is dirty
1161 1158 * NONE: don't check (merge working directory changes into destination)
1162 1159 * LINEAR: check that update is linear before merging working directory
1163 1160 changes into destination
1164 1161 * NO_CONFLICT: check that the update does not result in file merges
1165 1162
1166 1163 This returns whether conflict is detected at updating or not.
1167 1164 """
1168 1165 if updatecheck is None:
1169 1166 updatecheck = ui.config(b'commands', b'update.check')
1170 1167 if updatecheck not in _VALID_UPDATECHECKS:
1171 1168 # If not configured, or invalid value configured
1172 1169 updatecheck = mergemod.UPDATECHECK_LINEAR
1173 1170 if updatecheck not in _VALID_UPDATECHECKS:
1174 1171 raise ValueError(
1175 1172 r'Invalid updatecheck value %r (can accept %r)'
1176 1173 % (updatecheck, _VALID_UPDATECHECKS)
1177 1174 )
1178 1175 with repo.wlock():
1179 1176 movemarkfrom = None
1180 1177 warndest = False
1181 1178 if checkout is None:
1182 1179 updata = destutil.destupdate(repo, clean=clean)
1183 1180 checkout, movemarkfrom, brev = updata
1184 1181 warndest = True
1185 1182
1186 1183 if clean:
1187 1184 ret = _clean(repo, checkout)
1188 1185 else:
1189 1186 if updatecheck == mergemod.UPDATECHECK_ABORT:
1190 1187 cmdutil.bailifchanged(repo, merge=False)
1191 1188 updatecheck = mergemod.UPDATECHECK_NONE
1192 1189 ret = _update(repo, checkout, updatecheck=updatecheck)
1193 1190
1194 1191 if not ret and movemarkfrom:
1195 1192 if movemarkfrom == repo[b'.'].node():
1196 1193 pass # no-op update
1197 1194 elif bookmarks.update(repo, [movemarkfrom], repo[b'.'].node()):
1198 1195 b = ui.label(repo._activebookmark, b'bookmarks.active')
1199 1196 ui.status(_(b"updating bookmark %s\n") % b)
1200 1197 else:
1201 1198 # this can happen with a non-linear update
1202 1199 b = ui.label(repo._activebookmark, b'bookmarks')
1203 1200 ui.status(_(b"(leaving bookmark %s)\n") % b)
1204 1201 bookmarks.deactivate(repo)
1205 1202 elif brev in repo._bookmarks:
1206 1203 if brev != repo._activebookmark:
1207 1204 b = ui.label(brev, b'bookmarks.active')
1208 1205 ui.status(_(b"(activating bookmark %s)\n") % b)
1209 1206 bookmarks.activate(repo, brev)
1210 1207 elif brev:
1211 1208 if repo._activebookmark:
1212 1209 b = ui.label(repo._activebookmark, b'bookmarks')
1213 1210 ui.status(_(b"(leaving bookmark %s)\n") % b)
1214 1211 bookmarks.deactivate(repo)
1215 1212
1216 1213 if warndest:
1217 1214 destutil.statusotherdests(ui, repo)
1218 1215
1219 1216 return ret
1220 1217
1221 1218
1222 1219 def merge(
1223 1220 ctx,
1224 1221 force=False,
1225 1222 remind=True,
1226 1223 labels=None,
1227 1224 ):
1228 1225 """Branch merge with node, resolving changes. Return true if any
1229 1226 unresolved conflicts."""
1230 1227 repo = ctx.repo()
1231 1228 stats = mergemod.merge(ctx, force=force, labels=labels)
1232 1229 _showstats(repo, stats)
1233 1230 if stats.unresolvedcount:
1234 1231 repo.ui.status(
1235 1232 _(
1236 1233 b"use 'hg resolve' to retry unresolved file merges "
1237 1234 b"or 'hg merge --abort' to abandon\n"
1238 1235 )
1239 1236 )
1240 1237 elif remind:
1241 1238 repo.ui.status(_(b"(branch merge, don't forget to commit)\n"))
1242 1239 return stats.unresolvedcount > 0
1243 1240
1244 1241
1245 1242 def abortmerge(ui, repo):
1246 1243 ms = mergestatemod.mergestate.read(repo)
1247 1244 if ms.active():
1248 1245 # there were conflicts
1249 1246 node = ms.localctx.hex()
1250 1247 else:
1251 1248 # there were no conficts, mergestate was not stored
1252 1249 node = repo[b'.'].hex()
1253 1250
1254 1251 repo.ui.status(_(b"aborting the merge, updating back to %s\n") % node[:12])
1255 1252 stats = mergemod.clean_update(repo[node])
1256 1253 assert stats.unresolvedcount == 0
1257 1254 _showstats(repo, stats)
1258 1255
1259 1256
1260 1257 def _incoming(
1261 1258 displaychlist,
1262 1259 subreporecurse,
1263 1260 ui,
1264 1261 repo,
1265 1262 source,
1266 1263 opts,
1267 1264 buffered=False,
1268 1265 subpath=None,
1269 1266 ):
1270 1267 """
1271 1268 Helper for incoming / gincoming.
1272 1269 displaychlist gets called with
1273 1270 (remoterepo, incomingchangesetlist, displayer) parameters,
1274 1271 and is supposed to contain only code that can't be unified.
1275 1272 """
1276 1273 srcs = urlutil.get_pull_paths(repo, ui, [source], opts.get(b'branch'))
1277 1274 srcs = list(srcs)
1278 1275 if len(srcs) != 1:
1279 1276 msg = _(b'for now, incoming supports only a single source, %d provided')
1280 1277 msg %= len(srcs)
1281 1278 raise error.Abort(msg)
1282 1279 source, branches = srcs[0]
1283 1280 if subpath is not None:
1284 1281 subpath = urlutil.url(subpath)
1285 1282 if subpath.isabs():
1286 1283 source = bytes(subpath)
1287 1284 else:
1288 1285 p = urlutil.url(source)
1289 1286 p.path = os.path.normpath(b'%s/%s' % (p.path, subpath))
1290 1287 source = bytes(p)
1291 1288 other = peer(repo, opts, source)
1292 1289 cleanupfn = other.close
1293 1290 try:
1294 1291 ui.status(_(b'comparing with %s\n') % urlutil.hidepassword(source))
1295 1292 revs, checkout = addbranchrevs(repo, other, branches, opts.get(b'rev'))
1296 1293
1297 1294 if revs:
1298 1295 revs = [other.lookup(rev) for rev in revs]
1299 1296 other, chlist, cleanupfn = bundlerepo.getremotechanges(
1300 1297 ui, repo, other, revs, opts[b"bundle"], opts[b"force"]
1301 1298 )
1302 1299
1303 1300 if not chlist:
1304 1301 ui.status(_(b"no changes found\n"))
1305 1302 return subreporecurse()
1306 1303 ui.pager(b'incoming')
1307 1304 displayer = logcmdutil.changesetdisplayer(
1308 1305 ui, other, opts, buffered=buffered
1309 1306 )
1310 1307 displaychlist(other, chlist, displayer)
1311 1308 displayer.close()
1312 1309 finally:
1313 1310 cleanupfn()
1314 1311 subreporecurse()
1315 1312 return 0 # exit code is zero since we found incoming changes
1316 1313
1317 1314
1318 1315 def incoming(ui, repo, source, opts, subpath=None):
1319 1316 def subreporecurse():
1320 1317 ret = 1
1321 1318 if opts.get(b'subrepos'):
1322 1319 ctx = repo[None]
1323 1320 for subpath in sorted(ctx.substate):
1324 1321 sub = ctx.sub(subpath)
1325 1322 ret = min(ret, sub.incoming(ui, source, opts))
1326 1323 return ret
1327 1324
1328 1325 def display(other, chlist, displayer):
1329 1326 limit = logcmdutil.getlimit(opts)
1330 1327 if opts.get(b'newest_first'):
1331 1328 chlist.reverse()
1332 1329 count = 0
1333 1330 for n in chlist:
1334 1331 if limit is not None and count >= limit:
1335 1332 break
1336 1333 parents = [
1337 1334 p for p in other.changelog.parents(n) if p != repo.nullid
1338 1335 ]
1339 1336 if opts.get(b'no_merges') and len(parents) == 2:
1340 1337 continue
1341 1338 count += 1
1342 1339 displayer.show(other[n])
1343 1340
1344 1341 return _incoming(
1345 1342 display, subreporecurse, ui, repo, source, opts, subpath=subpath
1346 1343 )
1347 1344
1348 1345
1349 1346 def _outgoing(ui, repo, dests, opts, subpath=None):
1350 1347 out = set()
1351 1348 others = []
1352 1349 for path in urlutil.get_push_paths(repo, ui, dests):
1353 1350 dest = path.pushloc or path.loc
1354 1351 if subpath is not None:
1355 1352 subpath = urlutil.url(subpath)
1356 1353 if subpath.isabs():
1357 1354 dest = bytes(subpath)
1358 1355 else:
1359 1356 p = urlutil.url(dest)
1360 1357 p.path = os.path.normpath(b'%s/%s' % (p.path, subpath))
1361 1358 dest = bytes(p)
1362 1359 branches = path.branch, opts.get(b'branch') or []
1363 1360
1364 1361 ui.status(_(b'comparing with %s\n') % urlutil.hidepassword(dest))
1365 1362 revs, checkout = addbranchrevs(repo, repo, branches, opts.get(b'rev'))
1366 1363 if revs:
1367 1364 revs = [repo[rev].node() for rev in scmutil.revrange(repo, revs)]
1368 1365
1369 1366 other = peer(repo, opts, dest)
1370 1367 try:
1371 1368 outgoing = discovery.findcommonoutgoing(
1372 1369 repo, other, revs, force=opts.get(b'force')
1373 1370 )
1374 1371 o = outgoing.missing
1375 1372 out.update(o)
1376 1373 if not o:
1377 1374 scmutil.nochangesfound(repo.ui, repo, outgoing.excluded)
1378 1375 others.append(other)
1379 1376 except: # re-raises
1380 1377 other.close()
1381 1378 raise
1382 1379 # make sure this is ordered by revision number
1383 1380 outgoing_revs = list(out)
1384 1381 cl = repo.changelog
1385 1382 outgoing_revs.sort(key=cl.rev)
1386 1383 return outgoing_revs, others
1387 1384
1388 1385
1389 1386 def _outgoing_recurse(ui, repo, dests, opts):
1390 1387 ret = 1
1391 1388 if opts.get(b'subrepos'):
1392 1389 ctx = repo[None]
1393 1390 for subpath in sorted(ctx.substate):
1394 1391 sub = ctx.sub(subpath)
1395 1392 ret = min(ret, sub.outgoing(ui, dests, opts))
1396 1393 return ret
1397 1394
1398 1395
1399 1396 def _outgoing_filter(repo, revs, opts):
1400 1397 """apply revision filtering/ordering option for outgoing"""
1401 1398 limit = logcmdutil.getlimit(opts)
1402 1399 no_merges = opts.get(b'no_merges')
1403 1400 if opts.get(b'newest_first'):
1404 1401 revs.reverse()
1405 1402 if limit is None and not no_merges:
1406 1403 for r in revs:
1407 1404 yield r
1408 1405 return
1409 1406
1410 1407 count = 0
1411 1408 cl = repo.changelog
1412 1409 for n in revs:
1413 1410 if limit is not None and count >= limit:
1414 1411 break
1415 1412 parents = [p for p in cl.parents(n) if p != repo.nullid]
1416 1413 if no_merges and len(parents) == 2:
1417 1414 continue
1418 1415 count += 1
1419 1416 yield n
1420 1417
1421 1418
1422 1419 def outgoing(ui, repo, dests, opts, subpath=None):
1423 1420 if opts.get(b'graph'):
1424 1421 logcmdutil.checkunsupportedgraphflags([], opts)
1425 1422 o, others = _outgoing(ui, repo, dests, opts, subpath=subpath)
1426 1423 ret = 1
1427 1424 try:
1428 1425 if o:
1429 1426 ret = 0
1430 1427
1431 1428 if opts.get(b'graph'):
1432 1429 revdag = logcmdutil.graphrevs(repo, o, opts)
1433 1430 ui.pager(b'outgoing')
1434 1431 displayer = logcmdutil.changesetdisplayer(
1435 1432 ui, repo, opts, buffered=True
1436 1433 )
1437 1434 logcmdutil.displaygraph(
1438 1435 ui, repo, revdag, displayer, graphmod.asciiedges
1439 1436 )
1440 1437 else:
1441 1438 ui.pager(b'outgoing')
1442 1439 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
1443 1440 for n in _outgoing_filter(repo, o, opts):
1444 1441 displayer.show(repo[n])
1445 1442 displayer.close()
1446 1443 for oth in others:
1447 1444 cmdutil.outgoinghooks(ui, repo, oth, opts, o)
1448 1445 ret = min(ret, _outgoing_recurse(ui, repo, dests, opts))
1449 1446 return ret # exit code is zero since we found outgoing changes
1450 1447 finally:
1451 1448 for oth in others:
1452 1449 oth.close()
1453 1450
1454 1451
1455 1452 def verify(repo, level=None):
1456 1453 """verify the consistency of a repository"""
1457 1454 ret = verifymod.verify(repo, level=level)
1458 1455
1459 1456 # Broken subrepo references in hidden csets don't seem worth worrying about,
1460 1457 # since they can't be pushed/pulled, and --hidden can be used if they are a
1461 1458 # concern.
1462 1459
1463 1460 # pathto() is needed for -R case
1464 1461 revs = repo.revs(
1465 1462 b"filelog(%s)", util.pathto(repo.root, repo.getcwd(), b'.hgsubstate')
1466 1463 )
1467 1464
1468 1465 if revs:
1469 1466 repo.ui.status(_(b'checking subrepo links\n'))
1470 1467 for rev in revs:
1471 1468 ctx = repo[rev]
1472 1469 try:
1473 1470 for subpath in ctx.substate:
1474 1471 try:
1475 1472 ret = (
1476 1473 ctx.sub(subpath, allowcreate=False).verify() or ret
1477 1474 )
1478 1475 except error.RepoError as e:
1479 1476 repo.ui.warn(b'%d: %s\n' % (rev, e))
1480 1477 except Exception:
1481 1478 repo.ui.warn(
1482 1479 _(b'.hgsubstate is corrupt in revision %s\n')
1483 1480 % short(ctx.node())
1484 1481 )
1485 1482
1486 1483 return ret
1487 1484
1488 1485
1489 1486 def remoteui(src, opts):
1490 1487 """build a remote ui from ui or repo and opts"""
1491 1488 if util.safehasattr(src, b'baseui'): # looks like a repository
1492 1489 dst = src.baseui.copy() # drop repo-specific config
1493 1490 src = src.ui # copy target options from repo
1494 1491 else: # assume it's a global ui object
1495 1492 dst = src.copy() # keep all global options
1496 1493
1497 1494 # copy ssh-specific options
1498 1495 for o in b'ssh', b'remotecmd':
1499 1496 v = opts.get(o) or src.config(b'ui', o)
1500 1497 if v:
1501 1498 dst.setconfig(b"ui", o, v, b'copied')
1502 1499
1503 1500 # copy bundle-specific options
1504 1501 r = src.config(b'bundle', b'mainreporoot')
1505 1502 if r:
1506 1503 dst.setconfig(b'bundle', b'mainreporoot', r, b'copied')
1507 1504
1508 1505 # copy selected local settings to the remote ui
1509 1506 for sect in (b'auth', b'hostfingerprints', b'hostsecurity', b'http_proxy'):
1510 1507 for key, val in src.configitems(sect):
1511 1508 dst.setconfig(sect, key, val, b'copied')
1512 1509 v = src.config(b'web', b'cacerts')
1513 1510 if v:
1514 1511 dst.setconfig(b'web', b'cacerts', util.expandpath(v), b'copied')
1515 1512
1516 1513 return dst
1517 1514
1518 1515
1519 1516 # Files of interest
1520 1517 # Used to check if the repository has changed looking at mtime and size of
1521 1518 # these files.
1522 1519 foi = [
1523 1520 (b'spath', b'00changelog.i'),
1524 1521 (b'spath', b'phaseroots'), # ! phase can change content at the same size
1525 1522 (b'spath', b'obsstore'),
1526 1523 (b'path', b'bookmarks'), # ! bookmark can change content at the same size
1527 1524 ]
1528 1525
1529 1526
1530 1527 class cachedlocalrepo(object):
1531 1528 """Holds a localrepository that can be cached and reused."""
1532 1529
1533 1530 def __init__(self, repo):
1534 1531 """Create a new cached repo from an existing repo.
1535 1532
1536 1533 We assume the passed in repo was recently created. If the
1537 1534 repo has changed between when it was created and when it was
1538 1535 turned into a cache, it may not refresh properly.
1539 1536 """
1540 1537 assert isinstance(repo, localrepo.localrepository)
1541 1538 self._repo = repo
1542 1539 self._state, self.mtime = self._repostate()
1543 1540 self._filtername = repo.filtername
1544 1541
1545 1542 def fetch(self):
1546 1543 """Refresh (if necessary) and return a repository.
1547 1544
1548 1545 If the cached instance is out of date, it will be recreated
1549 1546 automatically and returned.
1550 1547
1551 1548 Returns a tuple of the repo and a boolean indicating whether a new
1552 1549 repo instance was created.
1553 1550 """
1554 1551 # We compare the mtimes and sizes of some well-known files to
1555 1552 # determine if the repo changed. This is not precise, as mtimes
1556 1553 # are susceptible to clock skew and imprecise filesystems and
1557 1554 # file content can change while maintaining the same size.
1558 1555
1559 1556 state, mtime = self._repostate()
1560 1557 if state == self._state:
1561 1558 return self._repo, False
1562 1559
1563 1560 repo = repository(self._repo.baseui, self._repo.url())
1564 1561 if self._filtername:
1565 1562 self._repo = repo.filtered(self._filtername)
1566 1563 else:
1567 1564 self._repo = repo.unfiltered()
1568 1565 self._state = state
1569 1566 self.mtime = mtime
1570 1567
1571 1568 return self._repo, True
1572 1569
1573 1570 def _repostate(self):
1574 1571 state = []
1575 1572 maxmtime = -1
1576 1573 for attr, fname in foi:
1577 1574 prefix = getattr(self._repo, attr)
1578 1575 p = os.path.join(prefix, fname)
1579 1576 try:
1580 1577 st = os.stat(p)
1581 1578 except OSError:
1582 1579 st = os.stat(prefix)
1583 1580 state.append((st[stat.ST_MTIME], st.st_size))
1584 1581 maxmtime = max(maxmtime, st[stat.ST_MTIME])
1585 1582
1586 1583 return tuple(state), maxmtime
1587 1584
1588 1585 def copy(self):
1589 1586 """Obtain a copy of this class instance.
1590 1587
1591 1588 A new localrepository instance is obtained. The new instance should be
1592 1589 completely independent of the original.
1593 1590 """
1594 1591 repo = repository(self._repo.baseui, self._repo.origroot)
1595 1592 if self._filtername:
1596 1593 repo = repo.filtered(self._filtername)
1597 1594 else:
1598 1595 repo = repo.unfiltered()
1599 1596 c = cachedlocalrepo(repo)
1600 1597 c._state = self._state
1601 1598 c.mtime = self.mtime
1602 1599 return c
@@ -1,56 +1,57 b''
1 1 Create an empty repo:
2 2
3 3 $ hg init a
4 4 $ cd a
5 5
6 6 Try some commands:
7 7
8 8 $ hg log
9 9 $ hg grep wah
10 10 [1]
11 11 $ hg manifest
12 12 $ hg verify
13 13 checking changesets
14 14 checking manifests
15 15 crosschecking files in changesets and manifests
16 16 checking files
17 17 checked 0 changesets with 0 changes to 0 files
18 18
19 19 Check the basic files created:
20 20
21 21 $ ls .hg
22 22 00changelog.i
23 23 cache
24 24 requires
25 25 store
26 26 wcache
27 27
28 28 Should be empty:
29 29
30 30 $ ls .hg/store
31 31
32 32 Poke at a clone:
33 33
34 34 $ cd ..
35 35 $ hg clone a b
36 36 updating to branch default
37 37 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
38 38 $ cd b
39 39 $ hg verify
40 40 checking changesets
41 41 checking manifests
42 42 crosschecking files in changesets and manifests
43 43 checking files
44 44 checked 0 changesets with 0 changes to 0 files
45 45 $ ls .hg
46 46 00changelog.i
47 47 cache
48 48 hgrc
49 49 requires
50 50 store
51 wcache
51 52
52 53 Should be empty:
53 54
54 55 $ ls .hg/store
55 56
56 57 $ cd ..
@@ -1,1908 +1,1909 b''
1 1 Test basic extension support
2 2 $ cat > unflush.py <<EOF
3 3 > import sys
4 4 > from mercurial import pycompat
5 5 > if pycompat.ispy3:
6 6 > # no changes required
7 7 > sys.exit(0)
8 8 > with open(sys.argv[1], 'rb') as f:
9 9 > data = f.read()
10 10 > with open(sys.argv[1], 'wb') as f:
11 11 > f.write(data.replace(b', flush=True', b''))
12 12 > EOF
13 13
14 14 $ cat > foobar.py <<EOF
15 15 > import os
16 16 > from mercurial import commands, exthelper, registrar
17 17 >
18 18 > eh = exthelper.exthelper()
19 19 > eh.configitem(b'tests', b'foo', default=b"Foo")
20 20 >
21 21 > uisetup = eh.finaluisetup
22 22 > uipopulate = eh.finaluipopulate
23 23 > reposetup = eh.finalreposetup
24 24 > cmdtable = eh.cmdtable
25 25 > configtable = eh.configtable
26 26 >
27 27 > @eh.uisetup
28 28 > def _uisetup(ui):
29 29 > ui.debug(b"uisetup called [debug]\\n")
30 30 > ui.write(b"uisetup called\\n")
31 31 > ui.status(b"uisetup called [status]\\n")
32 32 > ui.flush()
33 33 > @eh.uipopulate
34 34 > def _uipopulate(ui):
35 35 > ui._populatecnt = getattr(ui, "_populatecnt", 0) + 1
36 36 > ui.write(b"uipopulate called (%d times)\n" % ui._populatecnt)
37 37 > @eh.reposetup
38 38 > def _reposetup(ui, repo):
39 39 > ui.write(b"reposetup called for %s\\n" % os.path.basename(repo.root))
40 40 > ui.write(b"ui %s= repo.ui\\n" % (ui == repo.ui and b"=" or b"!"))
41 41 > ui.flush()
42 42 > @eh.command(b'foo', [], b'hg foo')
43 43 > def foo(ui, *args, **kwargs):
44 44 > foo = ui.config(b'tests', b'foo')
45 45 > ui.write(foo)
46 46 > ui.write(b"\\n")
47 47 > @eh.command(b'bar', [], b'hg bar', norepo=True)
48 48 > def bar(ui, *args, **kwargs):
49 49 > ui.write(b"Bar\\n")
50 50 > EOF
51 51 $ abspath=`pwd`/foobar.py
52 52
53 53 $ mkdir barfoo
54 54 $ cp foobar.py barfoo/__init__.py
55 55 $ barfoopath=`pwd`/barfoo
56 56
57 57 $ hg init a
58 58 $ cd a
59 59 $ echo foo > file
60 60 $ hg add file
61 61 $ hg commit -m 'add file'
62 62
63 63 $ echo '[extensions]' >> $HGRCPATH
64 64 $ echo "foobar = $abspath" >> $HGRCPATH
65 65 $ hg foo
66 66 uisetup called
67 67 uisetup called [status]
68 68 uipopulate called (1 times)
69 69 uipopulate called (1 times)
70 70 uipopulate called (1 times)
71 71 reposetup called for a
72 72 ui == repo.ui
73 73 uipopulate called (1 times) (chg !)
74 74 uipopulate called (1 times) (chg !)
75 75 uipopulate called (1 times) (chg !)
76 76 uipopulate called (1 times) (chg !)
77 77 uipopulate called (1 times) (chg !)
78 78 reposetup called for a (chg !)
79 79 ui == repo.ui (chg !)
80 80 Foo
81 81 $ hg foo --quiet
82 82 uisetup called (no-chg !)
83 83 uipopulate called (1 times)
84 84 uipopulate called (1 times)
85 85 uipopulate called (1 times) (chg !)
86 86 uipopulate called (1 times) (chg !)
87 87 uipopulate called (1 times)
88 88 reposetup called for a
89 89 ui == repo.ui
90 90 Foo
91 91 $ hg foo --debug
92 92 uisetup called [debug] (no-chg !)
93 93 uisetup called (no-chg !)
94 94 uisetup called [status] (no-chg !)
95 95 uipopulate called (1 times)
96 96 uipopulate called (1 times)
97 97 uipopulate called (1 times) (chg !)
98 98 uipopulate called (1 times) (chg !)
99 99 uipopulate called (1 times)
100 100 reposetup called for a
101 101 ui == repo.ui
102 102 Foo
103 103
104 104 $ cd ..
105 105 $ hg clone a b
106 106 uisetup called (no-chg !)
107 107 uisetup called [status] (no-chg !)
108 108 uipopulate called (1 times)
109 109 uipopulate called (1 times) (chg !)
110 110 uipopulate called (1 times)
111 111 reposetup called for a
112 112 ui == repo.ui
113 113 uipopulate called (1 times)
114 uipopulate called (1 times)
114 115 reposetup called for b
115 116 ui == repo.ui
116 117 updating to branch default
117 118 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
118 119
119 120 $ hg bar
120 121 uisetup called (no-chg !)
121 122 uisetup called [status] (no-chg !)
122 123 uipopulate called (1 times)
123 124 uipopulate called (1 times) (chg !)
124 125 Bar
125 126 $ echo 'foobar = !' >> $HGRCPATH
126 127
127 128 module/__init__.py-style
128 129
129 130 $ echo "barfoo = $barfoopath" >> $HGRCPATH
130 131 $ cd a
131 132 $ hg foo
132 133 uisetup called
133 134 uisetup called [status]
134 135 uipopulate called (1 times)
135 136 uipopulate called (1 times)
136 137 uipopulate called (1 times)
137 138 reposetup called for a
138 139 ui == repo.ui
139 140 uipopulate called (1 times) (chg !)
140 141 uipopulate called (1 times) (chg !)
141 142 uipopulate called (1 times) (chg !)
142 143 uipopulate called (1 times) (chg !)
143 144 uipopulate called (1 times) (chg !)
144 145 reposetup called for a (chg !)
145 146 ui == repo.ui (chg !)
146 147 Foo
147 148 $ echo 'barfoo = !' >> $HGRCPATH
148 149
149 150 Check that extensions are loaded in phases:
150 151
151 152 $ cat > foo.py <<EOF
152 153 > from __future__ import print_function
153 154 > import os
154 155 > from mercurial import exthelper
155 156 > from mercurial.utils import procutil
156 157 >
157 158 > def write(msg):
158 159 > procutil.stdout.write(msg)
159 160 > procutil.stdout.flush()
160 161 >
161 162 > name = os.path.basename(__file__).rsplit('.', 1)[0]
162 163 > bytesname = name.encode('utf-8')
163 164 > write(b"1) %s imported\n" % bytesname)
164 165 > eh = exthelper.exthelper()
165 166 > @eh.uisetup
166 167 > def _uisetup(ui):
167 168 > write(b"2) %s uisetup\n" % bytesname)
168 169 > @eh.extsetup
169 170 > def _extsetup(ui):
170 171 > write(b"3) %s extsetup\n" % bytesname)
171 172 > @eh.uipopulate
172 173 > def _uipopulate(ui):
173 174 > write(b"4) %s uipopulate\n" % bytesname)
174 175 > @eh.reposetup
175 176 > def _reposetup(ui, repo):
176 177 > write(b"5) %s reposetup\n" % bytesname)
177 178 >
178 179 > extsetup = eh.finalextsetup
179 180 > reposetup = eh.finalreposetup
180 181 > uipopulate = eh.finaluipopulate
181 182 > uisetup = eh.finaluisetup
182 183 > revsetpredicate = eh.revsetpredicate
183 184 >
184 185 > # custom predicate to check registration of functions at loading
185 186 > from mercurial import (
186 187 > smartset,
187 188 > )
188 189 > @eh.revsetpredicate(bytesname, safe=True) # safe=True for query via hgweb
189 190 > def custompredicate(repo, subset, x):
190 191 > return smartset.baseset([r for r in subset if r in {0}])
191 192 > EOF
192 193 $ "$PYTHON" $TESTTMP/unflush.py foo.py
193 194
194 195 $ cp foo.py bar.py
195 196 $ echo 'foo = foo.py' >> $HGRCPATH
196 197 $ echo 'bar = bar.py' >> $HGRCPATH
197 198
198 199 Check normal command's load order of extensions and registration of functions
199 200
200 201 On chg server, extension should be first set up by the server. Then
201 202 object-level setup should follow in the worker process.
202 203
203 204 $ hg log -r "foo() and bar()" -q
204 205 1) foo imported
205 206 1) bar imported
206 207 2) foo uisetup
207 208 2) bar uisetup
208 209 3) foo extsetup
209 210 3) bar extsetup
210 211 4) foo uipopulate
211 212 4) bar uipopulate
212 213 4) foo uipopulate
213 214 4) bar uipopulate
214 215 4) foo uipopulate
215 216 4) bar uipopulate
216 217 5) foo reposetup
217 218 5) bar reposetup
218 219 4) foo uipopulate (chg !)
219 220 4) bar uipopulate (chg !)
220 221 4) foo uipopulate (chg !)
221 222 4) bar uipopulate (chg !)
222 223 4) foo uipopulate (chg !)
223 224 4) bar uipopulate (chg !)
224 225 4) foo uipopulate (chg !)
225 226 4) bar uipopulate (chg !)
226 227 4) foo uipopulate (chg !)
227 228 4) bar uipopulate (chg !)
228 229 5) foo reposetup (chg !)
229 230 5) bar reposetup (chg !)
230 231 0:c24b9ac61126
231 232
232 233 Check hgweb's load order of extensions and registration of functions
233 234
234 235 $ cat > hgweb.cgi <<EOF
235 236 > #!$PYTHON
236 237 > from mercurial import demandimport; demandimport.enable()
237 238 > from mercurial.hgweb import hgweb
238 239 > from mercurial.hgweb import wsgicgi
239 240 > application = hgweb(b'.', b'test repo')
240 241 > wsgicgi.launch(application)
241 242 > EOF
242 243 $ . "$TESTDIR/cgienv"
243 244
244 245 $ PATH_INFO='/' SCRIPT_NAME='' "$PYTHON" hgweb.cgi \
245 246 > | grep '^[0-9]) ' # ignores HTML output
246 247 1) foo imported
247 248 1) bar imported
248 249 2) foo uisetup
249 250 2) bar uisetup
250 251 3) foo extsetup
251 252 3) bar extsetup
252 253 4) foo uipopulate
253 254 4) bar uipopulate
254 255 4) foo uipopulate
255 256 4) bar uipopulate
256 257 5) foo reposetup
257 258 5) bar reposetup
258 259
259 260 (check that revset predicate foo() and bar() are available)
260 261
261 262 #if msys
262 263 $ PATH_INFO='//shortlog'
263 264 #else
264 265 $ PATH_INFO='/shortlog'
265 266 #endif
266 267 $ export PATH_INFO
267 268 $ SCRIPT_NAME='' QUERY_STRING='rev=foo() and bar()' "$PYTHON" hgweb.cgi \
268 269 > | grep '<a href="/rev/[0-9a-z]*">'
269 270 <a href="/rev/c24b9ac61126">add file</a>
270 271
271 272 $ echo 'foo = !' >> $HGRCPATH
272 273 $ echo 'bar = !' >> $HGRCPATH
273 274
274 275 Check "from __future__ import absolute_import" support for external libraries
275 276
276 277 (import-checker.py reports issues for some of heredoc python code
277 278 fragments below, because import-checker.py does not know test specific
278 279 package hierarchy. NO_CHECK_* should be used as a limit mark of
279 280 heredoc, in order to make import-checker.py ignore them. For
280 281 simplicity, all python code fragments below are generated with such
281 282 limit mark, regardless of importing module or not.)
282 283
283 284 #if windows
284 285 $ PATHSEP=";"
285 286 #else
286 287 $ PATHSEP=":"
287 288 #endif
288 289 $ export PATHSEP
289 290
290 291 $ mkdir $TESTTMP/libroot
291 292 $ echo "s = 'libroot/ambig.py'" > $TESTTMP/libroot/ambig.py
292 293 $ mkdir $TESTTMP/libroot/mod
293 294 $ touch $TESTTMP/libroot/mod/__init__.py
294 295 $ echo "s = 'libroot/mod/ambig.py'" > $TESTTMP/libroot/mod/ambig.py
295 296
296 297 $ cat > $TESTTMP/libroot/mod/ambigabs.py <<NO_CHECK_EOF
297 298 > from __future__ import absolute_import, print_function
298 299 > import ambig # should load "libroot/ambig.py"
299 300 > s = ambig.s
300 301 > NO_CHECK_EOF
301 302 $ cat > loadabs.py <<NO_CHECK_EOF
302 303 > import mod.ambigabs as ambigabs
303 304 > def extsetup(ui):
304 305 > print('ambigabs.s=%s' % ambigabs.s, flush=True)
305 306 > NO_CHECK_EOF
306 307 $ "$PYTHON" $TESTTMP/unflush.py loadabs.py
307 308 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}/libroot; hg --config extensions.loadabs=loadabs.py root)
308 309 ambigabs.s=libroot/ambig.py
309 310 $TESTTMP/a
310 311
311 312 #if no-py3
312 313 $ cat > $TESTTMP/libroot/mod/ambigrel.py <<NO_CHECK_EOF
313 314 > from __future__ import print_function
314 315 > import ambig # should load "libroot/mod/ambig.py"
315 316 > s = ambig.s
316 317 > NO_CHECK_EOF
317 318 $ cat > loadrel.py <<NO_CHECK_EOF
318 319 > import mod.ambigrel as ambigrel
319 320 > def extsetup(ui):
320 321 > print('ambigrel.s=%s' % ambigrel.s, flush=True)
321 322 > NO_CHECK_EOF
322 323 $ "$PYTHON" $TESTTMP/unflush.py loadrel.py
323 324 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}/libroot; hg --config extensions.loadrel=loadrel.py root)
324 325 ambigrel.s=libroot/mod/ambig.py
325 326 $TESTTMP/a
326 327 #endif
327 328
328 329 Check absolute/relative import of extension specific modules
329 330
330 331 $ mkdir $TESTTMP/extroot
331 332 $ cat > $TESTTMP/extroot/bar.py <<NO_CHECK_EOF
332 333 > s = b'this is extroot.bar'
333 334 > NO_CHECK_EOF
334 335 $ mkdir $TESTTMP/extroot/sub1
335 336 $ cat > $TESTTMP/extroot/sub1/__init__.py <<NO_CHECK_EOF
336 337 > s = b'this is extroot.sub1.__init__'
337 338 > NO_CHECK_EOF
338 339 $ cat > $TESTTMP/extroot/sub1/baz.py <<NO_CHECK_EOF
339 340 > s = b'this is extroot.sub1.baz'
340 341 > NO_CHECK_EOF
341 342 $ cat > $TESTTMP/extroot/__init__.py <<NO_CHECK_EOF
342 343 > from __future__ import absolute_import
343 344 > s = b'this is extroot.__init__'
344 345 > from . import foo
345 346 > def extsetup(ui):
346 347 > ui.write(b'(extroot) ', foo.func(), b'\n')
347 348 > ui.flush()
348 349 > NO_CHECK_EOF
349 350
350 351 $ cat > $TESTTMP/extroot/foo.py <<NO_CHECK_EOF
351 352 > # test absolute import
352 353 > buf = []
353 354 > def func():
354 355 > # "not locals" case
355 356 > import extroot.bar
356 357 > buf.append(b'import extroot.bar in func(): %s' % extroot.bar.s)
357 358 > return b'\n(extroot) '.join(buf)
358 359 > # b"fromlist == ('*',)" case
359 360 > from extroot.bar import *
360 361 > buf.append(b'from extroot.bar import *: %s' % s)
361 362 > # "not fromlist" and "if '.' in name" case
362 363 > import extroot.sub1.baz
363 364 > buf.append(b'import extroot.sub1.baz: %s' % extroot.sub1.baz.s)
364 365 > # "not fromlist" and NOT "if '.' in name" case
365 366 > import extroot
366 367 > buf.append(b'import extroot: %s' % extroot.s)
367 368 > # NOT "not fromlist" and NOT "level != -1" case
368 369 > from extroot.bar import s
369 370 > buf.append(b'from extroot.bar import s: %s' % s)
370 371 > NO_CHECK_EOF
371 372 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.extroot=$TESTTMP/extroot root)
372 373 (extroot) from extroot.bar import *: this is extroot.bar
373 374 (extroot) import extroot.sub1.baz: this is extroot.sub1.baz
374 375 (extroot) import extroot: this is extroot.__init__
375 376 (extroot) from extroot.bar import s: this is extroot.bar
376 377 (extroot) import extroot.bar in func(): this is extroot.bar
377 378 $TESTTMP/a
378 379
379 380 #if no-py3
380 381 $ rm "$TESTTMP"/extroot/foo.*
381 382 $ rm -Rf "$TESTTMP/extroot/__pycache__"
382 383 $ cat > $TESTTMP/extroot/foo.py <<NO_CHECK_EOF
383 384 > # test relative import
384 385 > buf = []
385 386 > def func():
386 387 > # "not locals" case
387 388 > import bar
388 389 > buf.append('import bar in func(): %s' % bar.s)
389 390 > return '\n(extroot) '.join(buf)
390 391 > # "fromlist == ('*',)" case
391 392 > from bar import *
392 393 > buf.append('from bar import *: %s' % s)
393 394 > # "not fromlist" and "if '.' in name" case
394 395 > import sub1.baz
395 396 > buf.append('import sub1.baz: %s' % sub1.baz.s)
396 397 > # "not fromlist" and NOT "if '.' in name" case
397 398 > import sub1
398 399 > buf.append('import sub1: %s' % sub1.s)
399 400 > # NOT "not fromlist" and NOT "level != -1" case
400 401 > from bar import s
401 402 > buf.append('from bar import s: %s' % s)
402 403 > NO_CHECK_EOF
403 404 $ hg --config extensions.extroot=$TESTTMP/extroot root
404 405 (extroot) from bar import *: this is extroot.bar
405 406 (extroot) import sub1.baz: this is extroot.sub1.baz
406 407 (extroot) import sub1: this is extroot.sub1.__init__
407 408 (extroot) from bar import s: this is extroot.bar
408 409 (extroot) import bar in func(): this is extroot.bar
409 410 $TESTTMP/a
410 411 #endif
411 412
412 413 #if demandimport
413 414
414 415 Examine whether module loading is delayed until actual referring, even
415 416 though module is imported with "absolute_import" feature.
416 417
417 418 Files below in each packages are used for described purpose:
418 419
419 420 - "called": examine whether "from MODULE import ATTR" works correctly
420 421 - "unused": examine whether loading is delayed correctly
421 422 - "used": examine whether "from PACKAGE import MODULE" works correctly
422 423
423 424 Package hierarchy is needed to examine whether demand importing works
424 425 as expected for "from SUB.PACK.AGE import MODULE".
425 426
426 427 Setup "external library" to be imported with "absolute_import"
427 428 feature.
428 429
429 430 $ mkdir -p $TESTTMP/extlibroot/lsub1/lsub2
430 431 $ touch $TESTTMP/extlibroot/__init__.py
431 432 $ touch $TESTTMP/extlibroot/lsub1/__init__.py
432 433 $ touch $TESTTMP/extlibroot/lsub1/lsub2/__init__.py
433 434
434 435 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/called.py <<NO_CHECK_EOF
435 436 > def func():
436 437 > return b"this is extlibroot.lsub1.lsub2.called.func()"
437 438 > NO_CHECK_EOF
438 439 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/unused.py <<NO_CHECK_EOF
439 440 > raise Exception("extlibroot.lsub1.lsub2.unused is loaded unintentionally")
440 441 > NO_CHECK_EOF
441 442 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/used.py <<NO_CHECK_EOF
442 443 > detail = b"this is extlibroot.lsub1.lsub2.used"
443 444 > NO_CHECK_EOF
444 445
445 446 Setup sub-package of "external library", which causes instantiation of
446 447 demandmod in "recurse down the module chain" code path. Relative
447 448 importing with "absolute_import" feature isn't tested, because "level
448 449 >=1 " doesn't cause instantiation of demandmod.
449 450
450 451 $ mkdir -p $TESTTMP/extlibroot/recursedown/abs
451 452 $ cat > $TESTTMP/extlibroot/recursedown/abs/used.py <<NO_CHECK_EOF
452 453 > detail = b"this is extlibroot.recursedown.abs.used"
453 454 > NO_CHECK_EOF
454 455 $ cat > $TESTTMP/extlibroot/recursedown/abs/__init__.py <<NO_CHECK_EOF
455 456 > from __future__ import absolute_import
456 457 > from extlibroot.recursedown.abs.used import detail
457 458 > NO_CHECK_EOF
458 459
459 460 $ mkdir -p $TESTTMP/extlibroot/recursedown/legacy
460 461 $ cat > $TESTTMP/extlibroot/recursedown/legacy/used.py <<NO_CHECK_EOF
461 462 > detail = b"this is extlibroot.recursedown.legacy.used"
462 463 > NO_CHECK_EOF
463 464 $ cat > $TESTTMP/extlibroot/recursedown/legacy/__init__.py <<NO_CHECK_EOF
464 465 > # legacy style (level == -1) import
465 466 > from extlibroot.recursedown.legacy.used import detail
466 467 > NO_CHECK_EOF
467 468
468 469 $ cat > $TESTTMP/extlibroot/recursedown/__init__.py <<NO_CHECK_EOF
469 470 > from __future__ import absolute_import
470 471 > from extlibroot.recursedown.abs import detail as absdetail
471 472 > from .legacy import detail as legacydetail
472 473 > NO_CHECK_EOF
473 474
474 475 Setup package that re-exports an attribute of its submodule as the same
475 476 name. This leaves 'shadowing.used' pointing to 'used.detail', but still
476 477 the submodule 'used' should be somehow accessible. (issue5617)
477 478
478 479 $ mkdir -p $TESTTMP/extlibroot/shadowing
479 480 $ cat > $TESTTMP/extlibroot/shadowing/used.py <<NO_CHECK_EOF
480 481 > detail = b"this is extlibroot.shadowing.used"
481 482 > NO_CHECK_EOF
482 483 $ cat > $TESTTMP/extlibroot/shadowing/proxied.py <<NO_CHECK_EOF
483 484 > from __future__ import absolute_import
484 485 > from extlibroot.shadowing.used import detail
485 486 > NO_CHECK_EOF
486 487 $ cat > $TESTTMP/extlibroot/shadowing/__init__.py <<NO_CHECK_EOF
487 488 > from __future__ import absolute_import
488 489 > from .used import detail as used
489 490 > NO_CHECK_EOF
490 491
491 492 Setup extension local modules to be imported with "absolute_import"
492 493 feature.
493 494
494 495 $ mkdir -p $TESTTMP/absextroot/xsub1/xsub2
495 496 $ touch $TESTTMP/absextroot/xsub1/__init__.py
496 497 $ touch $TESTTMP/absextroot/xsub1/xsub2/__init__.py
497 498
498 499 $ cat > $TESTTMP/absextroot/xsub1/xsub2/called.py <<NO_CHECK_EOF
499 500 > def func():
500 501 > return b"this is absextroot.xsub1.xsub2.called.func()"
501 502 > NO_CHECK_EOF
502 503 $ cat > $TESTTMP/absextroot/xsub1/xsub2/unused.py <<NO_CHECK_EOF
503 504 > raise Exception("absextroot.xsub1.xsub2.unused is loaded unintentionally")
504 505 > NO_CHECK_EOF
505 506 $ cat > $TESTTMP/absextroot/xsub1/xsub2/used.py <<NO_CHECK_EOF
506 507 > detail = b"this is absextroot.xsub1.xsub2.used"
507 508 > NO_CHECK_EOF
508 509
509 510 Setup extension local modules to examine whether demand importing
510 511 works as expected in "level > 1" case.
511 512
512 513 $ cat > $TESTTMP/absextroot/relimportee.py <<NO_CHECK_EOF
513 514 > detail = b"this is absextroot.relimportee"
514 515 > NO_CHECK_EOF
515 516 $ cat > $TESTTMP/absextroot/xsub1/xsub2/relimporter.py <<NO_CHECK_EOF
516 517 > from __future__ import absolute_import
517 518 > from mercurial import pycompat
518 519 > from ... import relimportee
519 520 > detail = b"this relimporter imports %r" % (
520 521 > pycompat.bytestr(relimportee.detail))
521 522 > NO_CHECK_EOF
522 523
523 524 Setup modules, which actually import extension local modules at
524 525 runtime.
525 526
526 527 $ cat > $TESTTMP/absextroot/absolute.py << NO_CHECK_EOF
527 528 > from __future__ import absolute_import
528 529 >
529 530 > # import extension local modules absolutely (level = 0)
530 531 > from absextroot.xsub1.xsub2 import used, unused
531 532 > from absextroot.xsub1.xsub2.called import func
532 533 >
533 534 > def getresult():
534 535 > result = []
535 536 > result.append(used.detail)
536 537 > result.append(func())
537 538 > return result
538 539 > NO_CHECK_EOF
539 540
540 541 $ cat > $TESTTMP/absextroot/relative.py << NO_CHECK_EOF
541 542 > from __future__ import absolute_import
542 543 >
543 544 > # import extension local modules relatively (level == 1)
544 545 > from .xsub1.xsub2 import used, unused
545 546 > from .xsub1.xsub2.called import func
546 547 >
547 548 > # import a module, which implies "importing with level > 1"
548 549 > from .xsub1.xsub2 import relimporter
549 550 >
550 551 > def getresult():
551 552 > result = []
552 553 > result.append(used.detail)
553 554 > result.append(func())
554 555 > result.append(relimporter.detail)
555 556 > return result
556 557 > NO_CHECK_EOF
557 558
558 559 Setup main procedure of extension.
559 560
560 561 $ cat > $TESTTMP/absextroot/__init__.py <<NO_CHECK_EOF
561 562 > from __future__ import absolute_import
562 563 > from mercurial import registrar
563 564 > cmdtable = {}
564 565 > command = registrar.command(cmdtable)
565 566 >
566 567 > # "absolute" and "relative" shouldn't be imported before actual
567 568 > # command execution, because (1) they import same modules, and (2)
568 569 > # preceding import (= instantiate "demandmod" object instead of
569 570 > # real "module" object) might hide problem of succeeding import.
570 571 >
571 572 > @command(b'showabsolute', [], norepo=True)
572 573 > def showabsolute(ui, *args, **opts):
573 574 > from absextroot import absolute
574 575 > ui.write(b'ABS: %s\n' % b'\nABS: '.join(absolute.getresult()))
575 576 >
576 577 > @command(b'showrelative', [], norepo=True)
577 578 > def showrelative(ui, *args, **opts):
578 579 > from . import relative
579 580 > ui.write(b'REL: %s\n' % b'\nREL: '.join(relative.getresult()))
580 581 >
581 582 > # import modules from external library
582 583 > from extlibroot.lsub1.lsub2 import used as lused, unused as lunused
583 584 > from extlibroot.lsub1.lsub2.called import func as lfunc
584 585 > from extlibroot.recursedown import absdetail, legacydetail
585 586 > from extlibroot.shadowing import proxied
586 587 >
587 588 > def uisetup(ui):
588 589 > result = []
589 590 > result.append(lused.detail)
590 591 > result.append(lfunc())
591 592 > result.append(absdetail)
592 593 > result.append(legacydetail)
593 594 > result.append(proxied.detail)
594 595 > ui.write(b'LIB: %s\n' % b'\nLIB: '.join(result))
595 596 > NO_CHECK_EOF
596 597
597 598 Examine module importing.
598 599
599 600 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.absextroot=$TESTTMP/absextroot showabsolute)
600 601 LIB: this is extlibroot.lsub1.lsub2.used
601 602 LIB: this is extlibroot.lsub1.lsub2.called.func()
602 603 LIB: this is extlibroot.recursedown.abs.used
603 604 LIB: this is extlibroot.recursedown.legacy.used
604 605 LIB: this is extlibroot.shadowing.used
605 606 ABS: this is absextroot.xsub1.xsub2.used
606 607 ABS: this is absextroot.xsub1.xsub2.called.func()
607 608
608 609 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.absextroot=$TESTTMP/absextroot showrelative)
609 610 LIB: this is extlibroot.lsub1.lsub2.used
610 611 LIB: this is extlibroot.lsub1.lsub2.called.func()
611 612 LIB: this is extlibroot.recursedown.abs.used
612 613 LIB: this is extlibroot.recursedown.legacy.used
613 614 LIB: this is extlibroot.shadowing.used
614 615 REL: this is absextroot.xsub1.xsub2.used
615 616 REL: this is absextroot.xsub1.xsub2.called.func()
616 617 REL: this relimporter imports 'this is absextroot.relimportee'
617 618
618 619 Examine whether sub-module is imported relatively as expected.
619 620
620 621 See also issue5208 for detail about example case on Python 3.x.
621 622
622 623 $ f -q $TESTTMP/extlibroot/lsub1/lsub2/notexist.py
623 624 $TESTTMP/extlibroot/lsub1/lsub2/notexist.py: file not found
624 625
625 626 $ cat > $TESTTMP/notexist.py <<NO_CHECK_EOF
626 627 > text = 'notexist.py at root is loaded unintentionally\n'
627 628 > NO_CHECK_EOF
628 629
629 630 $ cat > $TESTTMP/checkrelativity.py <<NO_CHECK_EOF
630 631 > from mercurial import registrar
631 632 > cmdtable = {}
632 633 > command = registrar.command(cmdtable)
633 634 >
634 635 > # demand import avoids failure of importing notexist here, but only on
635 636 > # Python 2.
636 637 > import extlibroot.lsub1.lsub2.notexist
637 638 >
638 639 > @command(b'checkrelativity', [], norepo=True)
639 640 > def checkrelativity(ui, *args, **opts):
640 641 > try:
641 642 > ui.write(extlibroot.lsub1.lsub2.notexist.text)
642 643 > return 1 # unintentional success
643 644 > except ImportError:
644 645 > pass # intentional failure
645 646 > NO_CHECK_EOF
646 647
647 648 Python 3's lazy importer verifies modules exist before returning the lazy
648 649 module stub. Our custom lazy importer for Python 2 always returns a stub.
649 650
650 651 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.checkrelativity=$TESTTMP/checkrelativity.py checkrelativity) || true
651 652 *** failed to import extension checkrelativity from $TESTTMP/checkrelativity.py: No module named 'extlibroot.lsub1.lsub2.notexist' (py3 !)
652 653 hg: unknown command 'checkrelativity' (py3 !)
653 654 (use 'hg help' for a list of commands) (py3 !)
654 655
655 656 #endif
656 657
657 658 (Here, module importing tests are finished. Therefore, use other than
658 659 NO_CHECK_* limit mark for heredoc python files, in order to apply
659 660 import-checker.py or so on their contents)
660 661
661 662 Make sure a broken uisetup doesn't globally break hg:
662 663 $ cat > $TESTTMP/baduisetup.py <<EOF
663 664 > def uisetup(ui):
664 665 > 1 / 0
665 666 > EOF
666 667
667 668 Even though the extension fails during uisetup, hg is still basically usable:
668 669 $ hg --config extensions.baduisetup=$TESTTMP/baduisetup.py version
669 670 Traceback (most recent call last):
670 671 File "*/mercurial/extensions.py", line *, in _runuisetup (glob)
671 672 uisetup(ui)
672 673 File "$TESTTMP/baduisetup.py", line 2, in uisetup
673 674 1 / 0
674 675 ZeroDivisionError: * by zero (glob)
675 676 *** failed to set up extension baduisetup: * by zero (glob)
676 677 Mercurial Distributed SCM (version *) (glob)
677 678 (see https://mercurial-scm.org for more information)
678 679
679 680 Copyright (C) 2005-* Olivia Mackall and others (glob)
680 681 This is free software; see the source for copying conditions. There is NO
681 682 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
682 683
683 684 $ cd ..
684 685
685 686 hide outer repo
686 687 $ hg init
687 688
688 689 $ cat > empty.py <<EOF
689 690 > '''empty cmdtable
690 691 > '''
691 692 > cmdtable = {}
692 693 > EOF
693 694 $ emptypath=`pwd`/empty.py
694 695 $ echo "empty = $emptypath" >> $HGRCPATH
695 696 $ hg help empty
696 697 empty extension - empty cmdtable
697 698
698 699 no commands defined
699 700
700 701
701 702 $ echo 'empty = !' >> $HGRCPATH
702 703
703 704 $ cat > debugextension.py <<EOF
704 705 > '''only debugcommands
705 706 > '''
706 707 > from mercurial import registrar
707 708 > cmdtable = {}
708 709 > command = registrar.command(cmdtable)
709 710 > @command(b'debugfoobar', [], b'hg debugfoobar')
710 711 > def debugfoobar(ui, repo, *args, **opts):
711 712 > "yet another debug command"
712 713 > @command(b'foo', [], b'hg foo')
713 714 > def foo(ui, repo, *args, **opts):
714 715 > """yet another foo command
715 716 > This command has been DEPRECATED since forever.
716 717 > """
717 718 > EOF
718 719 $ debugpath=`pwd`/debugextension.py
719 720 $ echo "debugextension = $debugpath" >> $HGRCPATH
720 721
721 722 $ hg help debugextension
722 723 hg debugextensions
723 724
724 725 show information about active extensions
725 726
726 727 options:
727 728
728 729 -T --template TEMPLATE display with template
729 730
730 731 (some details hidden, use --verbose to show complete help)
731 732
732 733
733 734 $ hg --verbose help debugextension
734 735 hg debugextensions
735 736
736 737 show information about active extensions
737 738
738 739 options:
739 740
740 741 -T --template TEMPLATE display with template
741 742
742 743 global options ([+] can be repeated):
743 744
744 745 -R --repository REPO repository root directory or name of overlay bundle
745 746 file
746 747 --cwd DIR change working directory
747 748 -y --noninteractive do not prompt, automatically pick the first choice for
748 749 all prompts
749 750 -q --quiet suppress output
750 751 -v --verbose enable additional output
751 752 --color TYPE when to colorize (boolean, always, auto, never, or
752 753 debug)
753 754 --config CONFIG [+] set/override config option (use 'section.name=value')
754 755 --debug enable debugging output
755 756 --debugger start debugger
756 757 --encoding ENCODE set the charset encoding (default: ascii)
757 758 --encodingmode MODE set the charset encoding mode (default: strict)
758 759 --traceback always print a traceback on exception
759 760 --time time how long the command takes
760 761 --profile print command execution profile
761 762 --version output version information and exit
762 763 -h --help display help and exit
763 764 --hidden consider hidden changesets
764 765 --pager TYPE when to paginate (boolean, always, auto, or never)
765 766 (default: auto)
766 767
767 768
768 769
769 770
770 771
771 772
772 773 $ hg --debug help debugextension
773 774 hg debugextensions
774 775
775 776 show information about active extensions
776 777
777 778 options:
778 779
779 780 -T --template TEMPLATE display with template
780 781
781 782 global options ([+] can be repeated):
782 783
783 784 -R --repository REPO repository root directory or name of overlay bundle
784 785 file
785 786 --cwd DIR change working directory
786 787 -y --noninteractive do not prompt, automatically pick the first choice for
787 788 all prompts
788 789 -q --quiet suppress output
789 790 -v --verbose enable additional output
790 791 --color TYPE when to colorize (boolean, always, auto, never, or
791 792 debug)
792 793 --config CONFIG [+] set/override config option (use 'section.name=value')
793 794 --debug enable debugging output
794 795 --debugger start debugger
795 796 --encoding ENCODE set the charset encoding (default: ascii)
796 797 --encodingmode MODE set the charset encoding mode (default: strict)
797 798 --traceback always print a traceback on exception
798 799 --time time how long the command takes
799 800 --profile print command execution profile
800 801 --version output version information and exit
801 802 -h --help display help and exit
802 803 --hidden consider hidden changesets
803 804 --pager TYPE when to paginate (boolean, always, auto, or never)
804 805 (default: auto)
805 806
806 807
807 808
808 809
809 810
810 811 $ echo 'debugextension = !' >> $HGRCPATH
811 812
812 813 Asking for help about a deprecated extension should do something useful:
813 814
814 815 $ hg help glog
815 816 'glog' is provided by the following extension:
816 817
817 818 graphlog command to view revision graphs from a shell (DEPRECATED)
818 819
819 820 (use 'hg help extensions' for information on enabling extensions)
820 821
821 822 Extension module help vs command help:
822 823
823 824 $ echo 'extdiff =' >> $HGRCPATH
824 825 $ hg help extdiff
825 826 hg extdiff [OPT]... [FILE]...
826 827
827 828 use external program to diff repository (or selected files)
828 829
829 830 Show differences between revisions for the specified files, using an
830 831 external program. The default program used is diff, with default options
831 832 "-Npru".
832 833
833 834 To select a different program, use the -p/--program option. The program
834 835 will be passed the names of two directories to compare, unless the --per-
835 836 file option is specified (see below). To pass additional options to the
836 837 program, use -o/--option. These will be passed before the names of the
837 838 directories or files to compare.
838 839
839 840 The --from, --to, and --change options work the same way they do for 'hg
840 841 diff'.
841 842
842 843 The --per-file option runs the external program repeatedly on each file to
843 844 diff, instead of once on two directories. By default, this happens one by
844 845 one, where the next file diff is open in the external program only once
845 846 the previous external program (for the previous file diff) has exited. If
846 847 the external program has a graphical interface, it can open all the file
847 848 diffs at once instead of one by one. See 'hg help -e extdiff' for
848 849 information about how to tell Mercurial that a given program has a
849 850 graphical interface.
850 851
851 852 The --confirm option will prompt the user before each invocation of the
852 853 external program. It is ignored if --per-file isn't specified.
853 854
854 855 (use 'hg help -e extdiff' to show help for the extdiff extension)
855 856
856 857 options ([+] can be repeated):
857 858
858 859 -p --program CMD comparison program to run
859 860 -o --option OPT [+] pass option to comparison program
860 861 --from REV1 revision to diff from
861 862 --to REV2 revision to diff to
862 863 -c --change REV change made by revision
863 864 --per-file compare each file instead of revision snapshots
864 865 --confirm prompt user before each external program invocation
865 866 --patch compare patches for two revisions
866 867 -I --include PATTERN [+] include names matching the given patterns
867 868 -X --exclude PATTERN [+] exclude names matching the given patterns
868 869 -S --subrepos recurse into subrepositories
869 870
870 871 (some details hidden, use --verbose to show complete help)
871 872
872 873
873 874
874 875
875 876
876 877
877 878
878 879
879 880
880 881
881 882 $ hg help --extension extdiff
882 883 extdiff extension - command to allow external programs to compare revisions
883 884
884 885 The extdiff Mercurial extension allows you to use external programs to compare
885 886 revisions, or revision with working directory. The external diff programs are
886 887 called with a configurable set of options and two non-option arguments: paths
887 888 to directories containing snapshots of files to compare.
888 889
889 890 If there is more than one file being compared and the "child" revision is the
890 891 working directory, any modifications made in the external diff program will be
891 892 copied back to the working directory from the temporary directory.
892 893
893 894 The extdiff extension also allows you to configure new diff commands, so you
894 895 do not need to type 'hg extdiff -p kdiff3' always.
895 896
896 897 [extdiff]
897 898 # add new command that runs GNU diff(1) in 'context diff' mode
898 899 cdiff = gdiff -Nprc5
899 900 ## or the old way:
900 901 #cmd.cdiff = gdiff
901 902 #opts.cdiff = -Nprc5
902 903
903 904 # add new command called meld, runs meld (no need to name twice). If
904 905 # the meld executable is not available, the meld tool in [merge-tools]
905 906 # will be used, if available
906 907 meld =
907 908
908 909 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
909 910 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
910 911 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
911 912 # your .vimrc
912 913 vimdiff = gvim -f "+next" \
913 914 "+execute 'DirDiff' fnameescape(argv(0)) fnameescape(argv(1))"
914 915
915 916 Tool arguments can include variables that are expanded at runtime:
916 917
917 918 $parent1, $plabel1 - filename, descriptive label of first parent
918 919 $child, $clabel - filename, descriptive label of child revision
919 920 $parent2, $plabel2 - filename, descriptive label of second parent
920 921 $root - repository root
921 922 $parent is an alias for $parent1.
922 923
923 924 The extdiff extension will look in your [diff-tools] and [merge-tools]
924 925 sections for diff tool arguments, when none are specified in [extdiff].
925 926
926 927 [extdiff]
927 928 kdiff3 =
928 929
929 930 [diff-tools]
930 931 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
931 932
932 933 If a program has a graphical interface, it might be interesting to tell
933 934 Mercurial about it. It will prevent the program from being mistakenly used in
934 935 a terminal-only environment (such as an SSH terminal session), and will make
935 936 'hg extdiff --per-file' open multiple file diffs at once instead of one by one
936 937 (if you still want to open file diffs one by one, you can use the --confirm
937 938 option).
938 939
939 940 Declaring that a tool has a graphical interface can be done with the "gui"
940 941 flag next to where "diffargs" are specified:
941 942
942 943 [diff-tools]
943 944 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
944 945 kdiff3.gui = true
945 946
946 947 You can use -I/-X and list of file or directory names like normal 'hg diff'
947 948 command. The extdiff extension makes snapshots of only needed files, so
948 949 running the external diff program will actually be pretty fast (at least
949 950 faster than having to compare the entire tree).
950 951
951 952 list of commands:
952 953
953 954 extdiff use external program to diff repository (or selected files)
954 955
955 956 (use 'hg help -v -e extdiff' to show built-in aliases and global options)
956 957
957 958
958 959
959 960
960 961
961 962
962 963
963 964
964 965
965 966
966 967
967 968
968 969
969 970
970 971
971 972
972 973 $ echo 'extdiff = !' >> $HGRCPATH
973 974
974 975 Test help topic with same name as extension
975 976
976 977 $ cat > multirevs.py <<EOF
977 978 > from mercurial import commands, registrar
978 979 > cmdtable = {}
979 980 > command = registrar.command(cmdtable)
980 981 > """multirevs extension
981 982 > Big multi-line module docstring."""
982 983 > @command(b'multirevs', [], b'ARG', norepo=True)
983 984 > def multirevs(ui, repo, arg, *args, **opts):
984 985 > """multirevs command"""
985 986 > EOF
986 987 $ echo "multirevs = multirevs.py" >> $HGRCPATH
987 988
988 989 $ hg help multirevs | tail
989 990 used):
990 991
991 992 hg update :@
992 993
993 994 - Show diff between tags 1.3 and 1.5 (this works because the first and the
994 995 last revisions of the revset are used):
995 996
996 997 hg diff -r 1.3::1.5
997 998
998 999 use 'hg help -c multirevs' to see help for the multirevs command
999 1000
1000 1001
1001 1002
1002 1003
1003 1004
1004 1005
1005 1006 $ hg help -c multirevs
1006 1007 hg multirevs ARG
1007 1008
1008 1009 multirevs command
1009 1010
1010 1011 (some details hidden, use --verbose to show complete help)
1011 1012
1012 1013
1013 1014
1014 1015 $ hg multirevs
1015 1016 hg multirevs: invalid arguments
1016 1017 hg multirevs ARG
1017 1018
1018 1019 multirevs command
1019 1020
1020 1021 (use 'hg multirevs -h' to show more help)
1021 1022 [10]
1022 1023
1023 1024
1024 1025
1025 1026 $ echo "multirevs = !" >> $HGRCPATH
1026 1027
1027 1028 Issue811: Problem loading extensions twice (by site and by user)
1028 1029
1029 1030 $ cat <<EOF >> $HGRCPATH
1030 1031 > mq =
1031 1032 > strip =
1032 1033 > hgext.mq =
1033 1034 > hgext/mq =
1034 1035 > EOF
1035 1036
1036 1037 Show extensions:
1037 1038 (note that mq force load strip, also checking it's not loaded twice)
1038 1039
1039 1040 #if no-extraextensions
1040 1041 $ hg debugextensions
1041 1042 mq
1042 1043 strip
1043 1044 #endif
1044 1045
1045 1046 For extensions, which name matches one of its commands, help
1046 1047 message should ask '-v -e' to get list of built-in aliases
1047 1048 along with extension help itself
1048 1049
1049 1050 $ mkdir $TESTTMP/d
1050 1051 $ cat > $TESTTMP/d/dodo.py <<EOF
1051 1052 > """
1052 1053 > This is an awesome 'dodo' extension. It does nothing and
1053 1054 > writes 'Foo foo'
1054 1055 > """
1055 1056 > from mercurial import commands, registrar
1056 1057 > cmdtable = {}
1057 1058 > command = registrar.command(cmdtable)
1058 1059 > @command(b'dodo', [], b'hg dodo')
1059 1060 > def dodo(ui, *args, **kwargs):
1060 1061 > """Does nothing"""
1061 1062 > ui.write(b"I do nothing. Yay\\n")
1062 1063 > @command(b'foofoo', [], b'hg foofoo')
1063 1064 > def foofoo(ui, *args, **kwargs):
1064 1065 > """Writes 'Foo foo'"""
1065 1066 > ui.write(b"Foo foo\\n")
1066 1067 > EOF
1067 1068 $ dodopath=$TESTTMP/d/dodo.py
1068 1069
1069 1070 $ echo "dodo = $dodopath" >> $HGRCPATH
1070 1071
1071 1072 Make sure that user is asked to enter '-v -e' to get list of built-in aliases
1072 1073 $ hg help -e dodo
1073 1074 dodo extension -
1074 1075
1075 1076 This is an awesome 'dodo' extension. It does nothing and writes 'Foo foo'
1076 1077
1077 1078 list of commands:
1078 1079
1079 1080 dodo Does nothing
1080 1081 foofoo Writes 'Foo foo'
1081 1082
1082 1083 (use 'hg help -v -e dodo' to show built-in aliases and global options)
1083 1084
1084 1085 Make sure that '-v -e' prints list of built-in aliases along with
1085 1086 extension help itself
1086 1087 $ hg help -v -e dodo
1087 1088 dodo extension -
1088 1089
1089 1090 This is an awesome 'dodo' extension. It does nothing and writes 'Foo foo'
1090 1091
1091 1092 list of commands:
1092 1093
1093 1094 dodo Does nothing
1094 1095 foofoo Writes 'Foo foo'
1095 1096
1096 1097 global options ([+] can be repeated):
1097 1098
1098 1099 -R --repository REPO repository root directory or name of overlay bundle
1099 1100 file
1100 1101 --cwd DIR change working directory
1101 1102 -y --noninteractive do not prompt, automatically pick the first choice for
1102 1103 all prompts
1103 1104 -q --quiet suppress output
1104 1105 -v --verbose enable additional output
1105 1106 --color TYPE when to colorize (boolean, always, auto, never, or
1106 1107 debug)
1107 1108 --config CONFIG [+] set/override config option (use 'section.name=value')
1108 1109 --debug enable debugging output
1109 1110 --debugger start debugger
1110 1111 --encoding ENCODE set the charset encoding (default: ascii)
1111 1112 --encodingmode MODE set the charset encoding mode (default: strict)
1112 1113 --traceback always print a traceback on exception
1113 1114 --time time how long the command takes
1114 1115 --profile print command execution profile
1115 1116 --version output version information and exit
1116 1117 -h --help display help and exit
1117 1118 --hidden consider hidden changesets
1118 1119 --pager TYPE when to paginate (boolean, always, auto, or never)
1119 1120 (default: auto)
1120 1121
1121 1122 Make sure that single '-v' option shows help and built-ins only for 'dodo' command
1122 1123 $ hg help -v dodo
1123 1124 hg dodo
1124 1125
1125 1126 Does nothing
1126 1127
1127 1128 (use 'hg help -e dodo' to show help for the dodo extension)
1128 1129
1129 1130 options:
1130 1131
1131 1132 --mq operate on patch repository
1132 1133
1133 1134 global options ([+] can be repeated):
1134 1135
1135 1136 -R --repository REPO repository root directory or name of overlay bundle
1136 1137 file
1137 1138 --cwd DIR change working directory
1138 1139 -y --noninteractive do not prompt, automatically pick the first choice for
1139 1140 all prompts
1140 1141 -q --quiet suppress output
1141 1142 -v --verbose enable additional output
1142 1143 --color TYPE when to colorize (boolean, always, auto, never, or
1143 1144 debug)
1144 1145 --config CONFIG [+] set/override config option (use 'section.name=value')
1145 1146 --debug enable debugging output
1146 1147 --debugger start debugger
1147 1148 --encoding ENCODE set the charset encoding (default: ascii)
1148 1149 --encodingmode MODE set the charset encoding mode (default: strict)
1149 1150 --traceback always print a traceback on exception
1150 1151 --time time how long the command takes
1151 1152 --profile print command execution profile
1152 1153 --version output version information and exit
1153 1154 -h --help display help and exit
1154 1155 --hidden consider hidden changesets
1155 1156 --pager TYPE when to paginate (boolean, always, auto, or never)
1156 1157 (default: auto)
1157 1158
1158 1159 In case when extension name doesn't match any of its commands,
1159 1160 help message should ask for '-v' to get list of built-in aliases
1160 1161 along with extension help
1161 1162 $ cat > $TESTTMP/d/dudu.py <<EOF
1162 1163 > """
1163 1164 > This is an awesome 'dudu' extension. It does something and
1164 1165 > also writes 'Beep beep'
1165 1166 > """
1166 1167 > from mercurial import commands, registrar
1167 1168 > cmdtable = {}
1168 1169 > command = registrar.command(cmdtable)
1169 1170 > @command(b'something', [], b'hg something')
1170 1171 > def something(ui, *args, **kwargs):
1171 1172 > """Does something"""
1172 1173 > ui.write(b"I do something. Yaaay\\n")
1173 1174 > @command(b'beep', [], b'hg beep')
1174 1175 > def beep(ui, *args, **kwargs):
1175 1176 > """Writes 'Beep beep'"""
1176 1177 > ui.write(b"Beep beep\\n")
1177 1178 > EOF
1178 1179 $ dudupath=$TESTTMP/d/dudu.py
1179 1180
1180 1181 $ echo "dudu = $dudupath" >> $HGRCPATH
1181 1182
1182 1183 $ hg help -e dudu
1183 1184 dudu extension -
1184 1185
1185 1186 This is an awesome 'dudu' extension. It does something and also writes 'Beep
1186 1187 beep'
1187 1188
1188 1189 list of commands:
1189 1190
1190 1191 beep Writes 'Beep beep'
1191 1192 something Does something
1192 1193
1193 1194 (use 'hg help -v dudu' to show built-in aliases and global options)
1194 1195
1195 1196 In case when extension name doesn't match any of its commands,
1196 1197 help options '-v' and '-v -e' should be equivalent
1197 1198 $ hg help -v dudu
1198 1199 dudu extension -
1199 1200
1200 1201 This is an awesome 'dudu' extension. It does something and also writes 'Beep
1201 1202 beep'
1202 1203
1203 1204 list of commands:
1204 1205
1205 1206 beep Writes 'Beep beep'
1206 1207 something Does something
1207 1208
1208 1209 global options ([+] can be repeated):
1209 1210
1210 1211 -R --repository REPO repository root directory or name of overlay bundle
1211 1212 file
1212 1213 --cwd DIR change working directory
1213 1214 -y --noninteractive do not prompt, automatically pick the first choice for
1214 1215 all prompts
1215 1216 -q --quiet suppress output
1216 1217 -v --verbose enable additional output
1217 1218 --color TYPE when to colorize (boolean, always, auto, never, or
1218 1219 debug)
1219 1220 --config CONFIG [+] set/override config option (use 'section.name=value')
1220 1221 --debug enable debugging output
1221 1222 --debugger start debugger
1222 1223 --encoding ENCODE set the charset encoding (default: ascii)
1223 1224 --encodingmode MODE set the charset encoding mode (default: strict)
1224 1225 --traceback always print a traceback on exception
1225 1226 --time time how long the command takes
1226 1227 --profile print command execution profile
1227 1228 --version output version information and exit
1228 1229 -h --help display help and exit
1229 1230 --hidden consider hidden changesets
1230 1231 --pager TYPE when to paginate (boolean, always, auto, or never)
1231 1232 (default: auto)
1232 1233
1233 1234 $ hg help -v -e dudu
1234 1235 dudu extension -
1235 1236
1236 1237 This is an awesome 'dudu' extension. It does something and also writes 'Beep
1237 1238 beep'
1238 1239
1239 1240 list of commands:
1240 1241
1241 1242 beep Writes 'Beep beep'
1242 1243 something Does something
1243 1244
1244 1245 global options ([+] can be repeated):
1245 1246
1246 1247 -R --repository REPO repository root directory or name of overlay bundle
1247 1248 file
1248 1249 --cwd DIR change working directory
1249 1250 -y --noninteractive do not prompt, automatically pick the first choice for
1250 1251 all prompts
1251 1252 -q --quiet suppress output
1252 1253 -v --verbose enable additional output
1253 1254 --color TYPE when to colorize (boolean, always, auto, never, or
1254 1255 debug)
1255 1256 --config CONFIG [+] set/override config option (use 'section.name=value')
1256 1257 --debug enable debugging output
1257 1258 --debugger start debugger
1258 1259 --encoding ENCODE set the charset encoding (default: ascii)
1259 1260 --encodingmode MODE set the charset encoding mode (default: strict)
1260 1261 --traceback always print a traceback on exception
1261 1262 --time time how long the command takes
1262 1263 --profile print command execution profile
1263 1264 --version output version information and exit
1264 1265 -h --help display help and exit
1265 1266 --hidden consider hidden changesets
1266 1267 --pager TYPE when to paginate (boolean, always, auto, or never)
1267 1268 (default: auto)
1268 1269
1269 1270 Disabled extension commands:
1270 1271
1271 1272 $ ORGHGRCPATH=$HGRCPATH
1272 1273 $ HGRCPATH=
1273 1274 $ export HGRCPATH
1274 1275 $ hg help email
1275 1276 'email' is provided by the following extension:
1276 1277
1277 1278 patchbomb command to send changesets as (a series of) patch emails
1278 1279
1279 1280 (use 'hg help extensions' for information on enabling extensions)
1280 1281
1281 1282
1282 1283 $ hg qdel
1283 1284 hg: unknown command 'qdel'
1284 1285 'qdelete' is provided by the following extension:
1285 1286
1286 1287 mq manage a stack of patches
1287 1288
1288 1289 (use 'hg help extensions' for information on enabling extensions)
1289 1290 [255]
1290 1291
1291 1292
1292 1293 $ hg churn
1293 1294 hg: unknown command 'churn'
1294 1295 'churn' is provided by the following extension:
1295 1296
1296 1297 churn command to display statistics about repository history
1297 1298
1298 1299 (use 'hg help extensions' for information on enabling extensions)
1299 1300 [255]
1300 1301
1301 1302
1302 1303
1303 1304 Disabled extensions:
1304 1305
1305 1306 $ hg help churn
1306 1307 churn extension - command to display statistics about repository history
1307 1308
1308 1309 (use 'hg help extensions' for information on enabling extensions)
1309 1310
1310 1311 $ hg help patchbomb
1311 1312 patchbomb extension - command to send changesets as (a series of) patch emails
1312 1313
1313 1314 The series is started off with a "[PATCH 0 of N]" introduction, which
1314 1315 describes the series as a whole.
1315 1316
1316 1317 Each patch email has a Subject line of "[PATCH M of N] ...", using the first
1317 1318 line of the changeset description as the subject text. The message contains
1318 1319 two or three body parts:
1319 1320
1320 1321 - The changeset description.
1321 1322 - [Optional] The result of running diffstat on the patch.
1322 1323 - The patch itself, as generated by 'hg export'.
1323 1324
1324 1325 Each message refers to the first in the series using the In-Reply-To and
1325 1326 References headers, so they will show up as a sequence in threaded mail and
1326 1327 news readers, and in mail archives.
1327 1328
1328 1329 To configure other defaults, add a section like this to your configuration
1329 1330 file:
1330 1331
1331 1332 [email]
1332 1333 from = My Name <my@email>
1333 1334 to = recipient1, recipient2, ...
1334 1335 cc = cc1, cc2, ...
1335 1336 bcc = bcc1, bcc2, ...
1336 1337 reply-to = address1, address2, ...
1337 1338
1338 1339 Use "[patchbomb]" as configuration section name if you need to override global
1339 1340 "[email]" address settings.
1340 1341
1341 1342 Then you can use the 'hg email' command to mail a series of changesets as a
1342 1343 patchbomb.
1343 1344
1344 1345 You can also either configure the method option in the email section to be a
1345 1346 sendmail compatible mailer or fill out the [smtp] section so that the
1346 1347 patchbomb extension can automatically send patchbombs directly from the
1347 1348 commandline. See the [email] and [smtp] sections in hgrc(5) for details.
1348 1349
1349 1350 By default, 'hg email' will prompt for a "To" or "CC" header if you do not
1350 1351 supply one via configuration or the command line. You can override this to
1351 1352 never prompt by configuring an empty value:
1352 1353
1353 1354 [email]
1354 1355 cc =
1355 1356
1356 1357 You can control the default inclusion of an introduction message with the
1357 1358 "patchbomb.intro" configuration option. The configuration is always
1358 1359 overwritten by command line flags like --intro and --desc:
1359 1360
1360 1361 [patchbomb]
1361 1362 intro=auto # include introduction message if more than 1 patch (default)
1362 1363 intro=never # never include an introduction message
1363 1364 intro=always # always include an introduction message
1364 1365
1365 1366 You can specify a template for flags to be added in subject prefixes. Flags
1366 1367 specified by --flag option are exported as "{flags}" keyword:
1367 1368
1368 1369 [patchbomb]
1369 1370 flagtemplate = "{separate(' ',
1370 1371 ifeq(branch, 'default', '', branch|upper),
1371 1372 flags)}"
1372 1373
1373 1374 You can set patchbomb to always ask for confirmation by setting
1374 1375 "patchbomb.confirm" to true.
1375 1376
1376 1377 (use 'hg help extensions' for information on enabling extensions)
1377 1378
1378 1379
1379 1380 Broken disabled extension and command:
1380 1381
1381 1382 $ mkdir hgext
1382 1383 $ echo > hgext/__init__.py
1383 1384 $ cat > hgext/broken.py <<NO_CHECK_EOF
1384 1385 > "broken extension'
1385 1386 > NO_CHECK_EOF
1386 1387 $ cat > path.py <<EOF
1387 1388 > import os
1388 1389 > import sys
1389 1390 > sys.path.insert(0, os.environ['HGEXTPATH'])
1390 1391 > EOF
1391 1392 $ HGEXTPATH=`pwd`
1392 1393 $ export HGEXTPATH
1393 1394
1394 1395 $ hg --config extensions.path=./path.py help broken
1395 1396 broken extension - (no help text available)
1396 1397
1397 1398 (use 'hg help extensions' for information on enabling extensions)
1398 1399
1399 1400
1400 1401 $ cat > hgext/forest.py <<EOF
1401 1402 > cmdtable = None
1402 1403 > @command()
1403 1404 > def f():
1404 1405 > pass
1405 1406 > @command(123)
1406 1407 > def g():
1407 1408 > pass
1408 1409 > EOF
1409 1410 $ hg --config extensions.path=./path.py help foo
1410 1411 abort: no such help topic: foo
1411 1412 (try 'hg help --keyword foo')
1412 1413 [255]
1413 1414
1414 1415 $ cat > throw.py <<EOF
1415 1416 > from mercurial import commands, registrar, util
1416 1417 > cmdtable = {}
1417 1418 > command = registrar.command(cmdtable)
1418 1419 > class Bogon(Exception): pass
1419 1420 > # NB: version should be bytes; simulating extension not ported to py3
1420 1421 > __version__ = '1.0.0'
1421 1422 > @command(b'throw', [], b'hg throw', norepo=True)
1422 1423 > def throw(ui, **opts):
1423 1424 > """throws an exception"""
1424 1425 > raise Bogon()
1425 1426 > EOF
1426 1427
1427 1428 Test extension without proper byteification of key attributes doesn't crash when
1428 1429 accessed.
1429 1430
1430 1431 $ hg version -v --config extensions.throw=throw.py | grep '^ '
1431 1432 throw external 1.0.0
1432 1433
1433 1434 No declared supported version, extension complains:
1434 1435 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1435 1436 ** Unknown exception encountered with possibly-broken third-party extension "throw" 1.0.0
1436 1437 ** which supports versions unknown of Mercurial.
1437 1438 ** Please disable "throw" and try your action again.
1438 1439 ** If that fixes the bug please report it to the extension author.
1439 1440 ** Python * (glob)
1440 1441 ** Mercurial Distributed SCM * (glob)
1441 1442 ** Extensions loaded: throw 1.0.0
1442 1443
1443 1444 empty declaration of supported version, extension complains (but doesn't choke if
1444 1445 the value is improperly a str instead of bytes):
1445 1446 $ echo "testedwith = ''" >> throw.py
1446 1447 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1447 1448 ** Unknown exception encountered with possibly-broken third-party extension "throw" 1.0.0
1448 1449 ** which supports versions unknown of Mercurial.
1449 1450 ** Please disable "throw" and try your action again.
1450 1451 ** If that fixes the bug please report it to the extension author.
1451 1452 ** Python * (glob)
1452 1453 ** Mercurial Distributed SCM (*) (glob)
1453 1454 ** Extensions loaded: throw 1.0.0
1454 1455
1455 1456 If the extension specifies a buglink, show that (but don't choke if the value is
1456 1457 improperly a str instead of bytes):
1457 1458 $ echo 'buglink = "http://example.com/bts"' >> throw.py
1458 1459 $ rm -f throw.pyc throw.pyo
1459 1460 $ rm -Rf __pycache__
1460 1461 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1461 1462 ** Unknown exception encountered with possibly-broken third-party extension "throw" 1.0.0
1462 1463 ** which supports versions unknown of Mercurial.
1463 1464 ** Please disable "throw" and try your action again.
1464 1465 ** If that fixes the bug please report it to http://example.com/bts
1465 1466 ** Python * (glob)
1466 1467 ** Mercurial Distributed SCM (*) (glob)
1467 1468 ** Extensions loaded: throw 1.0.0
1468 1469
1469 1470 If the extensions declare outdated versions, accuse the older extension first:
1470 1471 $ echo "from mercurial import util" >> older.py
1471 1472 $ echo "util.version = lambda:b'2.2'" >> older.py
1472 1473 $ echo "testedwith = b'1.9.3'" >> older.py
1473 1474 $ echo "testedwith = b'2.1.1'" >> throw.py
1474 1475 $ rm -f throw.pyc throw.pyo
1475 1476 $ rm -Rf __pycache__
1476 1477 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1477 1478 > throw 2>&1 | egrep '^\*\*'
1478 1479 ** Unknown exception encountered with possibly-broken third-party extension "older" (version N/A)
1479 1480 ** which supports versions 1.9 of Mercurial.
1480 1481 ** Please disable "older" and try your action again.
1481 1482 ** If that fixes the bug please report it to the extension author.
1482 1483 ** Python * (glob)
1483 1484 ** Mercurial Distributed SCM (version 2.2)
1484 1485 ** Extensions loaded: older, throw 1.0.0
1485 1486
1486 1487 One extension only tested with older, one only with newer versions:
1487 1488 $ echo "util.version = lambda:b'2.1'" >> older.py
1488 1489 $ rm -f older.pyc older.pyo
1489 1490 $ rm -Rf __pycache__
1490 1491 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1491 1492 > throw 2>&1 | egrep '^\*\*'
1492 1493 ** Unknown exception encountered with possibly-broken third-party extension "older" (version N/A)
1493 1494 ** which supports versions 1.9 of Mercurial.
1494 1495 ** Please disable "older" and try your action again.
1495 1496 ** If that fixes the bug please report it to the extension author.
1496 1497 ** Python * (glob)
1497 1498 ** Mercurial Distributed SCM (version 2.1)
1498 1499 ** Extensions loaded: older, throw 1.0.0
1499 1500
1500 1501 Older extension is tested with current version, the other only with newer:
1501 1502 $ echo "util.version = lambda:b'1.9.3'" >> older.py
1502 1503 $ rm -f older.pyc older.pyo
1503 1504 $ rm -Rf __pycache__
1504 1505 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1505 1506 > throw 2>&1 | egrep '^\*\*'
1506 1507 ** Unknown exception encountered with possibly-broken third-party extension "throw" 1.0.0
1507 1508 ** which supports versions 2.1 of Mercurial.
1508 1509 ** Please disable "throw" and try your action again.
1509 1510 ** If that fixes the bug please report it to http://example.com/bts
1510 1511 ** Python * (glob)
1511 1512 ** Mercurial Distributed SCM (version 1.9.3)
1512 1513 ** Extensions loaded: older, throw 1.0.0
1513 1514
1514 1515 Ability to point to a different point
1515 1516 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1516 1517 > --config ui.supportcontact='Your Local Goat Lenders' throw 2>&1 | egrep '^\*\*'
1517 1518 ** unknown exception encountered, please report by visiting
1518 1519 ** Your Local Goat Lenders
1519 1520 ** Python * (glob)
1520 1521 ** Mercurial Distributed SCM (*) (glob)
1521 1522 ** Extensions loaded: older, throw 1.0.0
1522 1523
1523 1524 Declare the version as supporting this hg version, show regular bts link:
1524 1525 $ hgver=`hg debuginstall -T '{hgver}'`
1525 1526 $ echo 'testedwith = """'"$hgver"'"""' >> throw.py
1526 1527 $ if [ -z "$hgver" ]; then
1527 1528 > echo "unable to fetch a mercurial version. Make sure __version__ is correct";
1528 1529 > fi
1529 1530 $ rm -f throw.pyc throw.pyo
1530 1531 $ rm -Rf __pycache__
1531 1532 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1532 1533 ** unknown exception encountered, please report by visiting
1533 1534 ** https://mercurial-scm.org/wiki/BugTracker
1534 1535 ** Python * (glob)
1535 1536 ** Mercurial Distributed SCM (*) (glob)
1536 1537 ** Extensions loaded: throw 1.0.0
1537 1538
1538 1539 Patch version is ignored during compatibility check
1539 1540 $ echo "testedwith = b'3.2'" >> throw.py
1540 1541 $ echo "util.version = lambda:b'3.2.2'" >> throw.py
1541 1542 $ rm -f throw.pyc throw.pyo
1542 1543 $ rm -Rf __pycache__
1543 1544 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1544 1545 ** unknown exception encountered, please report by visiting
1545 1546 ** https://mercurial-scm.org/wiki/BugTracker
1546 1547 ** Python * (glob)
1547 1548 ** Mercurial Distributed SCM (*) (glob)
1548 1549 ** Extensions loaded: throw 1.0.0
1549 1550
1550 1551 Test version number support in 'hg version':
1551 1552 $ echo '__version__ = (1, 2, 3)' >> throw.py
1552 1553 $ rm -f throw.pyc throw.pyo
1553 1554 $ rm -Rf __pycache__
1554 1555 $ hg version -v
1555 1556 Mercurial Distributed SCM (version *) (glob)
1556 1557 (see https://mercurial-scm.org for more information)
1557 1558
1558 1559 Copyright (C) 2005-* Olivia Mackall and others (glob)
1559 1560 This is free software; see the source for copying conditions. There is NO
1560 1561 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1561 1562
1562 1563 Enabled extensions:
1563 1564
1564 1565
1565 1566 $ hg version -v --config extensions.throw=throw.py
1566 1567 Mercurial Distributed SCM (version *) (glob)
1567 1568 (see https://mercurial-scm.org for more information)
1568 1569
1569 1570 Copyright (C) 2005-* Olivia Mackall and others (glob)
1570 1571 This is free software; see the source for copying conditions. There is NO
1571 1572 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1572 1573
1573 1574 Enabled extensions:
1574 1575
1575 1576 throw external 1.2.3
1576 1577 $ echo 'getversion = lambda: b"1.twentythree"' >> throw.py
1577 1578 $ rm -f throw.pyc throw.pyo
1578 1579 $ rm -Rf __pycache__
1579 1580 $ hg version -v --config extensions.throw=throw.py --config extensions.strip=
1580 1581 Mercurial Distributed SCM (version *) (glob)
1581 1582 (see https://mercurial-scm.org for more information)
1582 1583
1583 1584 Copyright (C) 2005-* Olivia Mackall and others (glob)
1584 1585 This is free software; see the source for copying conditions. There is NO
1585 1586 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1586 1587
1587 1588 Enabled extensions:
1588 1589
1589 1590 strip internal
1590 1591 throw external 1.twentythree
1591 1592
1592 1593 $ hg version -q --config extensions.throw=throw.py
1593 1594 Mercurial Distributed SCM (version *) (glob)
1594 1595
1595 1596 Test template output:
1596 1597
1597 1598 $ hg version --config extensions.strip= -T'{extensions}'
1598 1599 strip
1599 1600
1600 1601 Test JSON output of version:
1601 1602
1602 1603 $ hg version -Tjson
1603 1604 [
1604 1605 {
1605 1606 "extensions": [],
1606 1607 "ver": "*" (glob)
1607 1608 }
1608 1609 ]
1609 1610
1610 1611 $ hg version --config extensions.throw=throw.py -Tjson
1611 1612 [
1612 1613 {
1613 1614 "extensions": [{"bundled": false, "name": "throw", "ver": "1.twentythree"}],
1614 1615 "ver": "3.2.2"
1615 1616 }
1616 1617 ]
1617 1618
1618 1619 $ hg version --config extensions.strip= -Tjson
1619 1620 [
1620 1621 {
1621 1622 "extensions": [{"bundled": true, "name": "strip", "ver": null}],
1622 1623 "ver": "*" (glob)
1623 1624 }
1624 1625 ]
1625 1626
1626 1627 Test template output of version:
1627 1628
1628 1629 $ hg version --config extensions.throw=throw.py --config extensions.strip= \
1629 1630 > -T'{extensions % "{name} {pad(ver, 16)} ({if(bundled, "internal", "external")})\n"}'
1630 1631 strip (internal)
1631 1632 throw 1.twentythree (external)
1632 1633
1633 1634 Refuse to load extensions with minimum version requirements
1634 1635
1635 1636 $ cat > minversion1.py << EOF
1636 1637 > from mercurial import util
1637 1638 > util.version = lambda: b'3.5.2'
1638 1639 > minimumhgversion = b'3.6'
1639 1640 > EOF
1640 1641 $ hg --config extensions.minversion=minversion1.py version
1641 1642 (third party extension minversion requires version 3.6 or newer of Mercurial (current: 3.5.2); disabling)
1642 1643 Mercurial Distributed SCM (version 3.5.2)
1643 1644 (see https://mercurial-scm.org for more information)
1644 1645
1645 1646 Copyright (C) 2005-* Olivia Mackall and others (glob)
1646 1647 This is free software; see the source for copying conditions. There is NO
1647 1648 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1648 1649
1649 1650 $ cat > minversion2.py << EOF
1650 1651 > from mercurial import util
1651 1652 > util.version = lambda: b'3.6'
1652 1653 > minimumhgversion = b'3.7'
1653 1654 > EOF
1654 1655 $ hg --config extensions.minversion=minversion2.py version 2>&1 | egrep '\(third'
1655 1656 (third party extension minversion requires version 3.7 or newer of Mercurial (current: 3.6); disabling)
1656 1657
1657 1658 Can load version that is only off by point release
1658 1659
1659 1660 $ cat > minversion2.py << EOF
1660 1661 > from mercurial import util
1661 1662 > util.version = lambda: b'3.6.1'
1662 1663 > minimumhgversion = b'3.6'
1663 1664 > EOF
1664 1665 $ hg --config extensions.minversion=minversion3.py version 2>&1 | egrep '\(third'
1665 1666 [1]
1666 1667
1667 1668 Can load minimum version identical to current
1668 1669
1669 1670 $ cat > minversion3.py << EOF
1670 1671 > from mercurial import util
1671 1672 > util.version = lambda: b'3.5'
1672 1673 > minimumhgversion = b'3.5'
1673 1674 > EOF
1674 1675 $ hg --config extensions.minversion=minversion3.py version 2>&1 | egrep '\(third'
1675 1676 [1]
1676 1677
1677 1678 Restore HGRCPATH
1678 1679
1679 1680 $ HGRCPATH=$ORGHGRCPATH
1680 1681 $ export HGRCPATH
1681 1682
1682 1683 Commands handling multiple repositories at a time should invoke only
1683 1684 "reposetup()" of extensions enabling in the target repository.
1684 1685
1685 1686 $ mkdir reposetup-test
1686 1687 $ cd reposetup-test
1687 1688
1688 1689 $ cat > $TESTTMP/reposetuptest.py <<EOF
1689 1690 > from mercurial import extensions
1690 1691 > def reposetup(ui, repo):
1691 1692 > ui.write(b'reposetup() for %s\n' % (repo.root))
1692 1693 > ui.flush()
1693 1694 > EOF
1694 1695 $ hg init src
1695 1696 $ echo a > src/a
1696 1697 $ hg -R src commit -Am '#0 at src/a'
1697 1698 adding a
1698 1699 $ echo '[extensions]' >> src/.hg/hgrc
1699 1700 $ echo '# enable extension locally' >> src/.hg/hgrc
1700 1701 $ echo "reposetuptest = $TESTTMP/reposetuptest.py" >> src/.hg/hgrc
1701 1702 $ hg -R src status
1702 1703 reposetup() for $TESTTMP/reposetup-test/src
1703 1704 reposetup() for $TESTTMP/reposetup-test/src (chg !)
1704 1705
1705 1706 #if no-extraextensions
1706 1707 $ hg --cwd src debugextensions
1707 1708 reposetup() for $TESTTMP/reposetup-test/src
1708 1709 dodo (untested!)
1709 1710 dudu (untested!)
1710 1711 mq
1711 1712 reposetuptest (untested!)
1712 1713 strip
1713 1714 #endif
1714 1715
1715 1716 $ hg clone -U src clone-dst1
1716 1717 reposetup() for $TESTTMP/reposetup-test/src
1717 1718 $ hg init push-dst1
1718 1719 $ hg -q -R src push push-dst1
1719 1720 reposetup() for $TESTTMP/reposetup-test/src
1720 1721 $ hg init pull-src1
1721 1722 $ hg -q -R pull-src1 pull src
1722 1723 reposetup() for $TESTTMP/reposetup-test/src
1723 1724
1724 1725 $ cat <<EOF >> $HGRCPATH
1725 1726 > [extensions]
1726 1727 > # disable extension globally and explicitly
1727 1728 > reposetuptest = !
1728 1729 > EOF
1729 1730 $ hg clone -U src clone-dst2
1730 1731 reposetup() for $TESTTMP/reposetup-test/src
1731 1732 $ hg init push-dst2
1732 1733 $ hg -q -R src push push-dst2
1733 1734 reposetup() for $TESTTMP/reposetup-test/src
1734 1735 $ hg init pull-src2
1735 1736 $ hg -q -R pull-src2 pull src
1736 1737 reposetup() for $TESTTMP/reposetup-test/src
1737 1738
1738 1739 $ cat <<EOF >> $HGRCPATH
1739 1740 > [extensions]
1740 1741 > # enable extension globally
1741 1742 > reposetuptest = $TESTTMP/reposetuptest.py
1742 1743 > EOF
1743 1744 $ hg clone -U src clone-dst3
1744 1745 reposetup() for $TESTTMP/reposetup-test/src
1745 1746 reposetup() for $TESTTMP/reposetup-test/clone-dst3
1746 1747 $ hg init push-dst3
1747 1748 reposetup() for $TESTTMP/reposetup-test/push-dst3
1748 1749 $ hg -q -R src push push-dst3
1749 1750 reposetup() for $TESTTMP/reposetup-test/src
1750 1751 reposetup() for $TESTTMP/reposetup-test/push-dst3
1751 1752 $ hg init pull-src3
1752 1753 reposetup() for $TESTTMP/reposetup-test/pull-src3
1753 1754 $ hg -q -R pull-src3 pull src
1754 1755 reposetup() for $TESTTMP/reposetup-test/pull-src3
1755 1756 reposetup() for $TESTTMP/reposetup-test/src
1756 1757
1757 1758 $ echo '[extensions]' >> src/.hg/hgrc
1758 1759 $ echo '# disable extension locally' >> src/.hg/hgrc
1759 1760 $ echo 'reposetuptest = !' >> src/.hg/hgrc
1760 1761 $ hg clone -U src clone-dst4
1761 1762 reposetup() for $TESTTMP/reposetup-test/clone-dst4
1762 1763 $ hg init push-dst4
1763 1764 reposetup() for $TESTTMP/reposetup-test/push-dst4
1764 1765 $ hg -q -R src push push-dst4
1765 1766 reposetup() for $TESTTMP/reposetup-test/push-dst4
1766 1767 $ hg init pull-src4
1767 1768 reposetup() for $TESTTMP/reposetup-test/pull-src4
1768 1769 $ hg -q -R pull-src4 pull src
1769 1770 reposetup() for $TESTTMP/reposetup-test/pull-src4
1770 1771
1771 1772 disabling in command line overlays with all configuration
1772 1773 $ hg --config extensions.reposetuptest=! clone -U src clone-dst5
1773 1774 $ hg --config extensions.reposetuptest=! init push-dst5
1774 1775 $ hg --config extensions.reposetuptest=! -q -R src push push-dst5
1775 1776 $ hg --config extensions.reposetuptest=! init pull-src5
1776 1777 $ hg --config extensions.reposetuptest=! -q -R pull-src5 pull src
1777 1778
1778 1779 $ cat <<EOF >> $HGRCPATH
1779 1780 > [extensions]
1780 1781 > # disable extension globally and explicitly
1781 1782 > reposetuptest = !
1782 1783 > EOF
1783 1784 $ hg init parent
1784 1785 $ hg init parent/sub1
1785 1786 $ echo 1 > parent/sub1/1
1786 1787 $ hg -R parent/sub1 commit -Am '#0 at parent/sub1'
1787 1788 adding 1
1788 1789 $ hg init parent/sub2
1789 1790 $ hg init parent/sub2/sub21
1790 1791 $ echo 21 > parent/sub2/sub21/21
1791 1792 $ hg -R parent/sub2/sub21 commit -Am '#0 at parent/sub2/sub21'
1792 1793 adding 21
1793 1794 $ cat > parent/sub2/.hgsub <<EOF
1794 1795 > sub21 = sub21
1795 1796 > EOF
1796 1797 $ hg -R parent/sub2 commit -Am '#0 at parent/sub2'
1797 1798 adding .hgsub
1798 1799 $ hg init parent/sub3
1799 1800 $ echo 3 > parent/sub3/3
1800 1801 $ hg -R parent/sub3 commit -Am '#0 at parent/sub3'
1801 1802 adding 3
1802 1803 $ cat > parent/.hgsub <<EOF
1803 1804 > sub1 = sub1
1804 1805 > sub2 = sub2
1805 1806 > sub3 = sub3
1806 1807 > EOF
1807 1808 $ hg -R parent commit -Am '#0 at parent'
1808 1809 adding .hgsub
1809 1810 $ echo '[extensions]' >> parent/.hg/hgrc
1810 1811 $ echo '# enable extension locally' >> parent/.hg/hgrc
1811 1812 $ echo "reposetuptest = $TESTTMP/reposetuptest.py" >> parent/.hg/hgrc
1812 1813 $ cp parent/.hg/hgrc parent/sub2/.hg/hgrc
1813 1814 $ hg -R parent status -S -A
1814 1815 reposetup() for $TESTTMP/reposetup-test/parent
1815 1816 reposetup() for $TESTTMP/reposetup-test/parent/sub2
1816 1817 C .hgsub
1817 1818 C .hgsubstate
1818 1819 C sub1/1
1819 1820 C sub2/.hgsub
1820 1821 C sub2/.hgsubstate
1821 1822 C sub2/sub21/21
1822 1823 C sub3/3
1823 1824
1824 1825 $ cd ..
1825 1826
1826 1827 Prohibit registration of commands that don't use @command (issue5137)
1827 1828
1828 1829 $ hg init deprecated
1829 1830 $ cd deprecated
1830 1831
1831 1832 $ cat <<EOF > deprecatedcmd.py
1832 1833 > def deprecatedcmd(repo, ui):
1833 1834 > pass
1834 1835 > cmdtable = {
1835 1836 > b'deprecatedcmd': (deprecatedcmd, [], b''),
1836 1837 > }
1837 1838 > EOF
1838 1839 $ cat <<EOF > .hg/hgrc
1839 1840 > [extensions]
1840 1841 > deprecatedcmd = `pwd`/deprecatedcmd.py
1841 1842 > mq = !
1842 1843 > hgext.mq = !
1843 1844 > hgext/mq = !
1844 1845 > EOF
1845 1846
1846 1847 $ hg deprecatedcmd > /dev/null
1847 1848 *** failed to import extension deprecatedcmd from $TESTTMP/deprecated/deprecatedcmd.py: missing attributes: norepo, optionalrepo, inferrepo
1848 1849 *** (use @command decorator to register 'deprecatedcmd')
1849 1850 hg: unknown command 'deprecatedcmd'
1850 1851 (use 'hg help' for a list of commands)
1851 1852 [10]
1852 1853
1853 1854 the extension shouldn't be loaded at all so the mq works:
1854 1855
1855 1856 $ hg qseries --config extensions.mq= > /dev/null
1856 1857 *** failed to import extension deprecatedcmd from $TESTTMP/deprecated/deprecatedcmd.py: missing attributes: norepo, optionalrepo, inferrepo
1857 1858 *** (use @command decorator to register 'deprecatedcmd')
1858 1859
1859 1860 $ cd ..
1860 1861
1861 1862 Test synopsis and docstring extending
1862 1863
1863 1864 $ hg init exthelp
1864 1865 $ cat > exthelp.py <<EOF
1865 1866 > from mercurial import commands, extensions
1866 1867 > def exbookmarks(orig, *args, **opts):
1867 1868 > return orig(*args, **opts)
1868 1869 > def uisetup(ui):
1869 1870 > synopsis = b' GREPME [--foo] [-x]'
1870 1871 > docstring = '''
1871 1872 > GREPME make sure that this is in the help!
1872 1873 > '''
1873 1874 > extensions.wrapcommand(commands.table, b'bookmarks', exbookmarks,
1874 1875 > synopsis, docstring)
1875 1876 > EOF
1876 1877 $ abspath=`pwd`/exthelp.py
1877 1878 $ echo '[extensions]' >> $HGRCPATH
1878 1879 $ echo "exthelp = $abspath" >> $HGRCPATH
1879 1880 $ cd exthelp
1880 1881 $ hg help bookmarks | grep GREPME
1881 1882 hg bookmarks [OPTIONS]... [NAME]... GREPME [--foo] [-x]
1882 1883 GREPME make sure that this is in the help!
1883 1884 $ cd ..
1884 1885
1885 1886 Prohibit the use of unicode strings as the default value of options
1886 1887
1887 1888 $ hg init $TESTTMP/opt-unicode-default
1888 1889
1889 1890 $ cat > $TESTTMP/test_unicode_default_value.py << EOF
1890 1891 > from __future__ import print_function
1891 1892 > from mercurial import registrar
1892 1893 > cmdtable = {}
1893 1894 > command = registrar.command(cmdtable)
1894 1895 > @command(b'dummy', [(b'', b'opt', u'value', u'help')], 'ext [OPTIONS]')
1895 1896 > def ext(*args, **opts):
1896 1897 > print(opts[b'opt'], flush=True)
1897 1898 > EOF
1898 1899 $ "$PYTHON" $TESTTMP/unflush.py $TESTTMP/test_unicode_default_value.py
1899 1900 $ cat > $TESTTMP/opt-unicode-default/.hg/hgrc << EOF
1900 1901 > [extensions]
1901 1902 > test_unicode_default_value = $TESTTMP/test_unicode_default_value.py
1902 1903 > EOF
1903 1904 $ hg -R $TESTTMP/opt-unicode-default dummy
1904 1905 *** failed to import extension test_unicode_default_value from $TESTTMP/test_unicode_default_value.py: unicode *'value' found in cmdtable.dummy (glob)
1905 1906 *** (use b'' to make it byte string)
1906 1907 hg: unknown command 'dummy'
1907 1908 (did you mean summary?)
1908 1909 [10]
General Comments 0
You need to be logged in to leave comments. Login now