comparison mercurial/hg.py @ 39550:65b5900f30be

hg: recognize include and exclude patterns when cloning This commit teaches clone() to accept arguments defining file patterns to clone. This is the first step in teaching core code about the existence of a narrow clone. Right now, we only perform validation of the arguments and pass additional options into createopts to influence repository creation. Nothing of consequence happens with that creation option yet, however. For now, arbitrary restrictions exist, such as not allowing patterns for shared repos and disabling local copies when patterns are defined. We can potentially lift these restrictions in the future once partial clone/storage support is more flushed out. I figure it is best to reduce the surface area for bugs for the time being. It may seem weird to prefix these arguments with "store." However, clone is effectively pull + update and file patterns could apply to both the store and the working directory. The prefix is there to disambiguate in the future when this function may want to use different sets of patterns for the store and working directory. Differential Revision: https://phab.mercurial-scm.org/D4536
author Gregory Szorc <gregory.szorc@gmail.com>
date Tue, 11 Sep 2018 17:15:35 -0700
parents 089fc0db0954
children 130e5df346d5
comparison
equal deleted inserted replaced
39549:089fc0db0954 39550:65b5900f30be
33 localrepo, 33 localrepo,
34 lock, 34 lock,
35 logcmdutil, 35 logcmdutil,
36 logexchange, 36 logexchange,
37 merge as mergemod, 37 merge as mergemod,
38 narrowspec,
38 node, 39 node,
39 phases, 40 phases,
40 scmutil, 41 scmutil,
41 sshpeer, 42 sshpeer,
42 statichttprepo, 43 statichttprepo,
498 if not os.path.exists(dstcachedir): 499 if not os.path.exists(dstcachedir):
499 os.mkdir(dstcachedir) 500 os.mkdir(dstcachedir)
500 util.copyfile(srcbranchcache, dstbranchcache) 501 util.copyfile(srcbranchcache, dstbranchcache)
501 502
502 def clone(ui, peeropts, source, dest=None, pull=False, revs=None, 503 def clone(ui, peeropts, source, dest=None, pull=False, revs=None,
503 update=True, stream=False, branch=None, shareopts=None): 504 update=True, stream=False, branch=None, shareopts=None,
505 storeincludepats=None, storeexcludepats=None):
504 """Make a copy of an existing repository. 506 """Make a copy of an existing repository.
505 507
506 Create a copy of an existing repository in a new directory. The 508 Create a copy of an existing repository in a new directory. The
507 source and destination are URLs, as passed to the repository 509 source and destination are URLs, as passed to the repository
508 function. Returns a pair of repository peers, the source and 510 function. Returns a pair of repository peers, the source and
540 activates auto sharing mode and defines the directory for stores. The 542 activates auto sharing mode and defines the directory for stores. The
541 "mode" key determines how to construct the directory name of the shared 543 "mode" key determines how to construct the directory name of the shared
542 repository. "identity" means the name is derived from the node of the first 544 repository. "identity" means the name is derived from the node of the first
543 changeset in the repository. "remote" means the name is derived from the 545 changeset in the repository. "remote" means the name is derived from the
544 remote's path/URL. Defaults to "identity." 546 remote's path/URL. Defaults to "identity."
547
548 storeincludepats and storeexcludepats: sets of file patterns to include and
549 exclude in the repository copy, respectively. If not defined, all files
550 will be included (a "full" clone). Otherwise a "narrow" clone containing
551 only the requested files will be performed. If ``storeincludepats`` is not
552 defined but ``storeexcludepats`` is, ``storeincludepats`` is assumed to be
553 ``path:.``. If both are empty sets, no files will be cloned.
545 """ 554 """
546 555
547 if isinstance(source, bytes): 556 if isinstance(source, bytes):
548 origsource = ui.expandpath(source) 557 origsource = ui.expandpath(source)
549 source, branches = parseurl(origsource, branch) 558 source, branches = parseurl(origsource, branch)
571 if destvfs.lexists(): 580 if destvfs.lexists():
572 if not destvfs.isdir(): 581 if not destvfs.isdir():
573 raise error.Abort(_("destination '%s' already exists") % dest) 582 raise error.Abort(_("destination '%s' already exists") % dest)
574 elif destvfs.listdir(): 583 elif destvfs.listdir():
575 raise error.Abort(_("destination '%s' is not empty") % dest) 584 raise error.Abort(_("destination '%s' is not empty") % dest)
585
586 createopts = {}
587 narrow = False
588
589 if storeincludepats is not None:
590 narrowspec.validatepatterns(storeincludepats)
591 narrow = True
592
593 if storeexcludepats is not None:
594 narrowspec.validatepatterns(storeexcludepats)
595 narrow = True
596
597 if narrow:
598 # Include everything by default if only exclusion patterns defined.
599 if storeexcludepats and not storeincludepats:
600 storeincludepats = {'path:.'}
601
602 createopts['narrowfiles'] = True
576 603
577 shareopts = shareopts or {} 604 shareopts = shareopts or {}
578 sharepool = shareopts.get('pool') 605 sharepool = shareopts.get('pool')
579 sharenamemode = shareopts.get('mode') 606 sharenamemode = shareopts.get('mode')
580 if sharepool and islocal(dest): 607 if sharepool and islocal(dest):
603 sharepool, node.hex(hashlib.sha1(source).digest())) 630 sharepool, node.hex(hashlib.sha1(source).digest()))
604 else: 631 else:
605 raise error.Abort(_('unknown share naming mode: %s') % 632 raise error.Abort(_('unknown share naming mode: %s') %
606 sharenamemode) 633 sharenamemode)
607 634
635 # TODO this is a somewhat arbitrary restriction.
636 if narrow:
637 ui.status(_('(pooled storage not supported for narrow clones)\n'))
638 sharepath = None
639
608 if sharepath: 640 if sharepath:
609 return clonewithshare(ui, peeropts, sharepath, source, srcpeer, 641 return clonewithshare(ui, peeropts, sharepath, source, srcpeer,
610 dest, pull=pull, rev=revs, update=update, 642 dest, pull=pull, rev=revs, update=update,
611 stream=stream) 643 stream=stream)
612 644
622 654
623 copy = False 655 copy = False
624 if (srcrepo and srcrepo.cancopy() and islocal(dest) 656 if (srcrepo and srcrepo.cancopy() and islocal(dest)
625 and not phases.hassecret(srcrepo)): 657 and not phases.hassecret(srcrepo)):
626 copy = not pull and not revs 658 copy = not pull and not revs
659
660 # TODO this is a somewhat arbitrary restriction.
661 if narrow:
662 copy = False
627 663
628 if copy: 664 if copy:
629 try: 665 try:
630 # we use a lock here because if we race with commit, we 666 # we use a lock here because if we race with commit, we
631 # can end up with extra data in the cloned revlogs that's 667 # can end up with extra data in the cloned revlogs that's
669 destpeer = peer(srcrepo, peeropts, dest) 705 destpeer = peer(srcrepo, peeropts, dest)
670 srcrepo.hook('outgoing', source='clone', 706 srcrepo.hook('outgoing', source='clone',
671 node=node.hex(node.nullid)) 707 node=node.hex(node.nullid))
672 else: 708 else:
673 try: 709 try:
674 destpeer = peer(srcrepo or ui, peeropts, dest, create=True) 710 # only pass ui when no srcrepo
675 # only pass ui when no srcrepo 711 destpeer = peer(srcrepo or ui, peeropts, dest, create=True,
712 createopts=createopts)
676 except OSError as inst: 713 except OSError as inst:
677 if inst.errno == errno.EEXIST: 714 if inst.errno == errno.EEXIST:
678 cleandir = None 715 cleandir = None
679 raise error.Abort(_("destination '%s' already exists") 716 raise error.Abort(_("destination '%s' already exists")
680 % dest) 717 % dest)
712 overrides = {('ui', 'quietbookmarkmove'): True} 749 overrides = {('ui', 'quietbookmarkmove'): True}
713 with local.ui.configoverride(overrides, 'clone'): 750 with local.ui.configoverride(overrides, 'clone'):
714 exchange.pull(local, srcpeer, revs, 751 exchange.pull(local, srcpeer, revs,
715 streamclonerequested=stream) 752 streamclonerequested=stream)
716 elif srcrepo: 753 elif srcrepo:
754 # TODO lift restriction once exchange.push() accepts narrow
755 # push.
756 if narrow:
757 raise error.Abort(_('narrow clone not available for '
758 'remote destinations'))
759
717 exchange.push(srcrepo, destpeer, revs=revs, 760 exchange.push(srcrepo, destpeer, revs=revs,
718 bookmarks=srcrepo._bookmarks.keys()) 761 bookmarks=srcrepo._bookmarks.keys())
719 else: 762 else:
720 raise error.Abort(_("clone from remote to remote not supported") 763 raise error.Abort(_("clone from remote to remote not supported")
721 ) 764 )