diff -r 648323f41a89 -r 0d37b9b21467 mercurial/hg.py --- a/mercurial/hg.py Wed Jul 08 16:43:49 2015 -0500 +++ b/mercurial/hg.py Wed Jul 08 16:19:09 2015 -0700 @@ -284,8 +284,50 @@ release(destlock) raise +def clonewithshare(ui, peeropts, sharepath, source, srcpeer, dest, pull=False, + rev=None, update=True, stream=False): + """Perform a clone using a shared repo. + + The store for the repository will be located at /.hg. The + specified revisions will be cloned or pulled from "source". A shared repo + will be created at "dest" and a working copy will be created if "update" is + True. + """ + revs = None + if rev: + if not srcpeer.capable('lookup'): + raise util.Abort(_("src repository does not support " + "revision lookup and so doesn't " + "support clone by revision")) + revs = [srcpeer.lookup(r) for r in rev] + + basename = os.path.basename(sharepath) + + if os.path.exists(sharepath): + ui.status(_('(sharing from existing pooled repository %s)\n') % + basename) + else: + ui.status(_('(sharing from new pooled repository %s)\n') % basename) + # Always use pull mode because hardlinks in share mode don't work well. + # Never update because working copies aren't necessary in share mode. + clone(ui, peeropts, source, dest=sharepath, pull=True, + rev=rev, update=False, stream=stream) + + sharerepo = repository(ui, path=sharepath) + share(ui, sharerepo, dest=dest, update=update, bookmarks=False) + + # We need to perform a pull against the dest repo to fetch bookmarks + # and other non-store data that isn't shared by default. In the case of + # non-existing shared repo, this means we pull from the remote twice. This + # is a bit weird. But at the time it was implemented, there wasn't an easy + # way to pull just non-changegroup data. + destrepo = repository(ui, path=dest) + exchange.pull(destrepo, srcpeer, heads=revs) + + return srcpeer, peer(ui, peeropts, dest) + def clone(ui, peeropts, source, dest=None, pull=False, rev=None, - update=True, stream=False, branch=None): + update=True, stream=False, branch=None, shareopts=None): """Make a copy of an existing repository. Create a copy of an existing repository in a new directory. The @@ -320,6 +362,13 @@ anything else is treated as a revision) branch: branches to clone + + shareopts: dict of options to control auto sharing behavior. The "pool" key + activates auto sharing mode and defines the directory for stores. The + "mode" key determines how to construct the directory name of the shared + repository. "identity" means the name is derived from the node of the first + changeset in the repository. "remote" means the name is derived from the + remote's path/URL. Defaults to "identity." """ if isinstance(source, str): @@ -352,6 +401,36 @@ elif destvfs.listdir(): raise util.Abort(_("destination '%s' is not empty") % dest) + shareopts = shareopts or {} + sharepool = shareopts.get('pool') + sharenamemode = shareopts.get('mode') + if sharepool: + sharepath = None + if sharenamemode == 'identity': + # Resolve the name from the initial changeset in the remote + # repository. This returns nullid when the remote is empty. It + # raises RepoLookupError if revision 0 is filtered or otherwise + # not available. If we fail to resolve, sharing is not enabled. + try: + rootnode = srcpeer.lookup('0') + if rootnode != node.nullid: + sharepath = os.path.join(sharepool, node.hex(rootnode)) + else: + ui.status(_('(not using pooled storage: ' + 'remote appears to be empty)\n')) + except error.RepoLookupError: + ui.status(_('(not using pooled storage: ' + 'unable to resolve identity of remote)\n')) + elif sharenamemode == 'remote': + sharepath = os.path.join(sharepool, util.sha1(source).hexdigest()) + else: + raise util.Abort('unknown share naming mode: %s' % sharenamemode) + + if sharepath: + return clonewithshare(ui, peeropts, sharepath, source, srcpeer, + dest, pull=pull, rev=rev, update=update, + stream=stream) + srclock = destlock = cleandir = None srcrepo = srcpeer.local() try: