Mercurial > public > mercurial-scm > hg
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 ) |