sapling/eden/scm/edenscm/mercurial/clone.py
Jun Wu 219554305e clone: add a new clone function with less tech-debt
Summary:
The current `clone --shallow` command has some issues:
- It fetches *all* remote bookmarks, since selectivepull does not work with
  streamclone, then remove most remote bookmarks in a second transaction.
- It goes through remotenames, which is racy, and D20703268 does not fix the
  clone case. Possible cause of T65349853.
- Too many wrappers (ex. in remotefilelog, remotenames, fastdiscovery) wtih
  many configurations (ex. narrow-heads on/off) makes it hard to reason about.

Instead of bandaidding the clone function, this diff adds a new clone implementation
that aims to solve the issues:
- Use streamclone, but do not pull all remote names.
- Pull selectivepull names explicitly with a working "discovery" strategy
  (repo heads should be non-empty with narrow-heads on or off).
- Do clone in one transaction. Outside world won't see an incomplete state.
- Use `repo.pull` API, which is not subject to race conditions.
- Eventually, this might be the only supported "clone" after Mononoke becoming
  the single source of truth.

Note: the code path still goes through bookmarks.py and remotenames.py.
They will be cleaned up in upcoming diffs.

Reviewed By: DurhamG

Differential Revision: D21011401

fbshipit-source-id: d8751ac9bd643e9661e58c87b683be285f0dc925
2020-05-12 10:23:23 -07:00

62 lines
2.1 KiB
Python

# Copyright (c) Facebook, Inc. and its affiliates.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2.
"""clone utilities that aims for Mononoke compatibility"""
from . import bookmarks as bookmod, error, streamclone
from .i18n import _
def shallowclone(source, repo):
"""clone from source into an empty shallow repo"""
repo = repo.unfiltered()
with repo.wlock(), repo.lock(), repo.transaction("clone"):
if any(
repo.svfs.tryread(name)
for name in ["00changelog.i", "bookmarks", "remotenames"]
):
raise error.Abort(_("clone: repo %s is not empty") % repo.root)
repo.requirements.add("remotefilelog")
repo._writerequirements()
repo.ui.status(_("fetching changelog\n"))
with repo.conn(source) as conn:
# Assume the remote server supports streamclone.
peer = conn.peer
fp = peer.stream_out(shallow=True)
l = fp.readline()
if l.strip() != b"0":
raise error.ResponseError(
_("unexpected response from remote server:"), l
)
l = fp.readline()
try:
filecount, bytecount = list(map(int, l.split(b" ", 1)))
except (ValueError, TypeError):
raise error.ResponseError(
_("unexpected response from remote server:"), l
)
# Get 00changelog.{i,d}. This does not write bookmarks or remotenames.
streamclone.consumev1(repo, fp, filecount, bytecount)
# repo.changelog needs to be reloaded.
repo.invalidate()
# Fetch selected remote bookmarks.
repo.ui.status(_("fetching selected remote bookmarks\n"))
remote = repo.ui.paths.getname(repo.ui.paths.getpath(source).rawloc)
assert remote is not None
repo.pull(
source, bookmarknames=bookmod.selectivepullbookmarknames(repo, remote)
)
# Data migration.
if "zstorecommitdata" in repo.storerequirements:
repo._syncrevlogtozstore()