git: support simple clone

Summary: Support cloning a git repo using `git+` URLs.

Reviewed By: DurhamG

Differential Revision: D33351383

fbshipit-source-id: 684ac78111b201e44256b0dfebe2aa52d46f7693
This commit is contained in:
Jun Wu 2022-01-19 17:37:36 -08:00 committed by Facebook GitHub Bot
parent cf612fcd26
commit 80845a4fb9
5 changed files with 155 additions and 22 deletions

View File

@ -145,6 +145,7 @@ from edenscm.mercurial import (
error,
exchange,
extensions,
git,
hg,
localrepo,
match,
@ -290,8 +291,10 @@ def wrappackers():
packermap["03"] = (shallowbundle.shallowcg3packer, packermap03[1])
def cloneshallow(orig, ui, repo, *args, **opts):
if opts.get("shallow"):
def cloneshallow(orig, ui, source, *args, **opts):
# skip for (full) git repos
giturl = git.maybegiturl(source)
if opts.get("shallow") and giturl is None:
repos = []
def pull_shallow(orig, self, *args, **kwargs):
@ -333,7 +336,7 @@ def cloneshallow(orig, ui, repo, *args, **opts):
wrapfunction(localrepo, "newreporequirements", newreporequirements)
orig(ui, repo, *args, **opts)
orig(ui, source, *args, **opts)
def debugdatashallow(orig, *args, **kwds):

View File

@ -1618,18 +1618,25 @@ def clone(ui, source, dest=None, **opts):
"""
if opts.get("noupdate") and opts.get("updaterev"):
raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
r = hg.clone(
ui,
opts,
source,
dest,
pull=opts.get("pull"),
stream=opts.get("stream") or opts.get("uncompressed"),
rev=opts.get("rev"),
update=opts.get("updaterev") or not opts.get("noupdate"),
shallow=opts.get("shallow"),
)
giturl = git.maybegiturl(source)
if giturl is not None:
if opts.get("noupdate"):
update = False
else:
update = opts.get("updaterev") or True
r = git.clone(ui, giturl, dest, update)
else:
r = hg.clone(
ui,
opts,
source,
dest,
pull=opts.get("pull"),
stream=opts.get("stream") or opts.get("uncompressed"),
rev=opts.get("rev"),
update=opts.get("updaterev") or not opts.get("noupdate"),
shallow=opts.get("shallow"),
)
return r is None

View File

@ -1557,12 +1557,7 @@ def debuginitgit(ui, destpath, **opts):
if not os.path.exists(os.path.join(gitdir, "refs")):
raise error.Abort(_("invalid --git-dir: %s") % gitdir)
repo = hg.repository(ui, ui.expandpath(destpath), create=True).local()
with repo.lock(), repo.transaction("initgit"):
repo.svfs.writeutf8(git.GIT_DIR_FILE, gitdir)
repo.storerequirements.add(git.GIT_REQUIREMENT)
repo._writestorerequirements()
repo.invalidatechangelog()
visibility.add(repo, repo.changelog.dageval(lambda: heads(all())))
git.initgit(repo, gitdir)
@command("debuginstall", [] + cmdutil.formatteropts, "", norepo=True)

View File

@ -8,6 +8,8 @@ utilities for git support
"""
import hashlib
import os
import shutil
import subprocess
import bindings
@ -39,11 +41,107 @@ def isgit(repo):
return GIT_REQUIREMENT in repo.storerequirements
def clone(ui, url, destpath=None, update=True):
"""Clone a git repo, then create a repo at dest backed by the git repo.
update can be False, or True, or a node to update to.
- False: do not update, leave an empty working copy.
- True: upate to git HEAD.
- other: update to `other` (node, or name).
"""
from . import hg
if destpath is None:
# use basename as fallback, but strip ".git" or "/.git".
basename = os.path.basename(url)
if basename == ".git":
basename = os.path.basename(os.path.dirname(url))
elif basename.endswith(".git"):
basename = basename[:-4]
destpath = os.path.realpath(basename)
repo = hg.repository(ui, ui.expandpath(destpath), create=True).local()
try:
ret = clonegitbare(ui, url, repo.svfs.join("git"))
if ret != 0:
raise error.Abort(_("git clone was not successful"))
initgit(repo, "git")
except Exception:
repo = None
shutil.rmtree(destpath, ignore_errors=True)
raise
if update is not False:
if update is True:
update = None
postpullupdate(repo, update)
return repo
def initgit(repo, gitdir):
"""Change a repo to be backed by a bare git repo in `gitdir`.
This should only be called for newly created repos.
"""
from . import visibility
hgrc = "%include builtin:git.rc\n"
with repo.lock(), repo.transaction("initgit"):
repo.svfs.writeutf8(GIT_DIR_FILE, gitdir)
repo.storerequirements.add(GIT_REQUIREMENT)
repo._writestorerequirements()
repo.invalidatechangelog()
visibility.add(repo, repo.changelog.dageval(lambda: heads(all())))
repo.sharedvfs.writeutf8("hgrc", hgrc)
repo.ui.reloadconfigs(repo.root)
def maybegiturl(url):
"""Return normalized url if url is a git url, or None otherwise.
For now url schemes "git", "git+file", "git+ftp", "git+http", "git+https",
"git+ssh" are considered git urls. The "git+" part will be stripped.
"""
parsed = util.url(url)
if parsed.scheme == "git":
return url
if parsed.scheme in {
"git+file",
"git+ftp",
"git+ftps",
"git+http",
"git+https",
"git+ssh",
}:
if url.startswith("git+"):
return url[4:]
return None
def clonegitbare(ui, giturl, destpath):
"""Clone a git repo into local path `dest` as a git bare repo.
This does not prepare working copy or `.hg`.
"""
# not using 'git clone --bare' because it writes refs to refs/heads/,
# not in desirable refs/remotes/origin/heads/.
for gitdir, cmd in [
(None, ["init", "-q", "-b", "default", "--bare", destpath]),
(destpath, ["remote", "add", "origin", giturl]),
(destpath, ["fetch", "origin"]),
]:
ret = rungitnorepo(ui, cmd, gitdir=gitdir)
if ret != 0:
return ret
return 0
@cached
def readgitdir(repo):
"""Return the path of the GIT_DIR, if the repo is backed by git"""
if isgit(repo):
return repo.svfs.readutf8(GIT_DIR_FILE)
path = repo.svfs.readutf8(GIT_DIR_FILE)
if os.path.isabs(path):
return path
else:
return repo.svfs.join(path)
else:
return None

View File

@ -176,3 +176,33 @@ Test pull:
- infinitepush compatibility
$ hg pull --config extensions.infinitepush=
Test clone with flags (--noupdate, --updaterev):
$ mkdir $TESTTMP/clonetest
$ cd $TESTTMP/clonetest
$ hg clone -q --noupdate "git+file://$TESTTMP/gitrepo"
$ cd gitrepo
$ hg log -r . -T '{node|short}\n'
000000000000
$ cd ..
$ hg clone "git+file://$TESTTMP/gitrepo" cloned1
From file:/*/$TESTTMP/gitrepo (glob)
* [new branch] foo -> origin/foo
* [new branch] master -> origin/master
2 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ hg --cwd cloned1 log -r . -T '{node|short} {remotenames} {desc}\n'
5c9a5ee451a8 origin/foo alpha3
$ cd ..
$ hg clone --updaterev origin/foo "git+file://$TESTTMP/gitrepo" cloned2
From file:/*/$TESTTMP/gitrepo (glob)
* [new branch] foo -> origin/foo
* [new branch] master -> origin/master
2 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ hg --cwd cloned2 log -r . -T '{node|short} {remotenames} {desc}\n'
5c9a5ee451a8 origin/foo alpha3
$ cd ..