mirror of
https://github.com/facebook/sapling.git
synced 2024-12-29 08:02:24 +03:00
remove support for repo configs from edenfsctl clone
Summary: Update the `edenfsctl clone` command to require that an explicit repository path be passed in. Using the name of a repository listed in an EdenFS config file is no longer supported. Reviewed By: wez Differential Revision: D20876459 fbshipit-source-id: 5e9fef11a2afa4cc48cb8a9bb5b874d2e7923f25
This commit is contained in:
parent
ef8c3435c4
commit
c5e6e40888
@ -293,15 +293,6 @@ class EdenInstance:
|
||||
"""
|
||||
return version.format_eden_version(self.get_running_version_parts())
|
||||
|
||||
def get_repository_list(self) -> List[str]:
|
||||
result = []
|
||||
parser = self._loadConfig()
|
||||
for section in parser.sections():
|
||||
header = section.split(" ")
|
||||
if len(header) == 2 and header[0] == "repository":
|
||||
result.append(header[1])
|
||||
return sorted(result)
|
||||
|
||||
def get_config_value(self, key: str, default: str) -> str:
|
||||
parser = self._loadConfig()
|
||||
section, option = key.split(".", 1)
|
||||
@ -342,42 +333,6 @@ class EdenInstance:
|
||||
data[section] = parser.get_section_str_to_any(section)
|
||||
toml.dump(data, file) # pyre-ignore[T39129461]
|
||||
|
||||
def find_config_for_alias(self, alias: str) -> Optional[CheckoutConfig]:
|
||||
"""Looks through the existing config files and searches for a
|
||||
[repository <alias>] section that defines a config:
|
||||
- If no such section is found, returns None.
|
||||
- If the appropriate section is found, returns a CheckoutConfig if all of
|
||||
the fields for the config data are present and well-formed.
|
||||
- Otherwise, throws an Exception.
|
||||
"""
|
||||
parser = self._loadConfig()
|
||||
repository_header = f"repository {alias}"
|
||||
if not parser.has_section(repository_header):
|
||||
return None
|
||||
|
||||
scm_type = parser.get_str(repository_header, "type", default="")
|
||||
if not scm_type:
|
||||
raise Exception(f'repository "{alias}" missing key "type".')
|
||||
if scm_type not in SUPPORTED_REPOS:
|
||||
raise Exception(f'repository "{alias}" has unsupported type.')
|
||||
|
||||
path = parser.get_str(repository_header, "path", default="")
|
||||
if not path:
|
||||
raise Exception(f'repository "{alias}" missing key "path".')
|
||||
|
||||
default_revision = (
|
||||
parser.get_str(repository_header, "default-revision", default="")
|
||||
or parser.get_str("clone", "default-revision", default="")
|
||||
or DEFAULT_REVISION[scm_type]
|
||||
)
|
||||
|
||||
return CheckoutConfig(
|
||||
backing_repo=Path(path),
|
||||
scm_type=scm_type,
|
||||
default_revision=default_revision,
|
||||
redirections={},
|
||||
)
|
||||
|
||||
def get_mount_paths(self) -> List[str]:
|
||||
"""Return the paths of the set mount points stored in config.json"""
|
||||
return [str(path) for path in self._get_directory_map().keys()]
|
||||
@ -947,10 +902,11 @@ class EdenCheckout:
|
||||
curdir = typing.cast(Path, curdir.parent)
|
||||
|
||||
def get_config(self) -> CheckoutConfig:
|
||||
if self._config is None:
|
||||
self._config = self._read_config()
|
||||
# pyre-fixme[7]: Expected `CheckoutConfig` but got `Optional[CheckoutConfig]`.
|
||||
return self._config
|
||||
config = self._config
|
||||
if config is None:
|
||||
config = self._read_config()
|
||||
self._config = config
|
||||
return config
|
||||
|
||||
def save_config(self, checkout_config: CheckoutConfig) -> None:
|
||||
# Store information about the mount in the config.toml file.
|
||||
|
@ -478,12 +478,10 @@ class RepoError(Exception):
|
||||
@subcmd("clone", "Create a clone of a specific repo and check it out")
|
||||
class CloneCmd(Subcmd):
|
||||
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
||||
parser.add_argument("repo", help="The path to an existing repository to clone")
|
||||
parser.add_argument(
|
||||
"repo",
|
||||
help="The path to an existing repo to clone, or the name of a "
|
||||
"known repository configuration",
|
||||
"path", help="The path where the checkout should be mounted"
|
||||
)
|
||||
parser.add_argument("path", help="Path where the checkout should be mounted")
|
||||
parser.add_argument(
|
||||
"--rev", "-r", type=str, help="The initial revision to check out"
|
||||
)
|
||||
@ -620,55 +618,23 @@ re-run `eden clone` with --allow-empty-repo"""
|
||||
)
|
||||
return repo, None, checkout_config
|
||||
|
||||
# Check to see if repo_arg looks like an existing repository path.
|
||||
# Confirm that repo_arg looks like an existing repository path.
|
||||
repo = util.get_repo(repo_arg)
|
||||
if repo is None:
|
||||
# This is not a valid repository path.
|
||||
# Check to see if this is a repository config name instead.
|
||||
repo_config = instance.find_config_for_alias(repo_arg)
|
||||
if repo_config is None:
|
||||
raise RepoError(
|
||||
f"{repo_arg!r} does not look like a valid "
|
||||
"hg or git repository or a well-known "
|
||||
"repository name"
|
||||
)
|
||||
|
||||
repo = util.get_repo(str(repo_config.backing_repo))
|
||||
if repo is None:
|
||||
raise RepoError(
|
||||
f"cloning {repo_arg} requires an existing "
|
||||
f"repository to be present at "
|
||||
f"{repo_config.backing_repo}"
|
||||
)
|
||||
|
||||
return repo, repo_arg, repo_config
|
||||
raise RepoError(
|
||||
f"{repo_arg!r} does not look like a valid hg or git repository"
|
||||
)
|
||||
|
||||
# This is a valid repository path.
|
||||
# Try to identify what type of repository this is, so we can find
|
||||
# the proper configuration to use.
|
||||
# Prepare a CheckoutConfig object for it.
|
||||
project_id = util.get_project_id(repo, rev)
|
||||
|
||||
project_config = None
|
||||
if project_id is not None:
|
||||
project_config = instance.find_config_for_alias(project_id)
|
||||
repo_type = project_id
|
||||
if project_config is None:
|
||||
repo_config = config_mod.CheckoutConfig(
|
||||
backing_repo=Path(repo.source),
|
||||
scm_type=repo.type,
|
||||
default_revision=config_mod.DEFAULT_REVISION[repo.type],
|
||||
redirections={},
|
||||
)
|
||||
else:
|
||||
# Build our own CheckoutConfig object, using our source repository
|
||||
# path and type, but the bind-mount and revision configuration from the
|
||||
# project configuration.
|
||||
repo_config = config_mod.CheckoutConfig(
|
||||
backing_repo=Path(repo.source),
|
||||
scm_type=repo.type,
|
||||
default_revision=project_config.default_revision,
|
||||
redirections={},
|
||||
)
|
||||
repo_config = config_mod.CheckoutConfig(
|
||||
backing_repo=Path(repo.source),
|
||||
scm_type=repo.type,
|
||||
default_revision=config_mod.DEFAULT_REVISION[repo.type],
|
||||
redirections={},
|
||||
)
|
||||
|
||||
return repo, repo_type, repo_config
|
||||
|
||||
|
@ -44,36 +44,12 @@ reporter = 'arc paste --title "eden rage from $(hostname)" --conduit-uri=https:/
|
||||
return cfg_file
|
||||
|
||||
|
||||
def get_toml_test_file_fbsource_repo():
|
||||
cfg_file = """
|
||||
["repository fbsource"]
|
||||
type = "hg"
|
||||
path = "/data/users/${USER}/fbsource"
|
||||
|
||||
["bindmounts fbsource"]
|
||||
fbcode-buck-out = "fbcode/buck-out"
|
||||
buck-out = "buck-out"
|
||||
"""
|
||||
return cfg_file
|
||||
|
||||
|
||||
def get_toml_test_file_user_rc():
|
||||
cfg_file = """
|
||||
[core]
|
||||
ignoreFile = "/home/${USER}/.gitignore-override"
|
||||
edenDirectory = "/home/${USER}/.eden"
|
||||
|
||||
["repository fbsource"]
|
||||
type = "hg"
|
||||
path = "/data/users/${USER}/fbsource-override"
|
||||
|
||||
["bindmounts fbsource"]
|
||||
fbcode-buck-out = "fbcode/buck-out-override"
|
||||
|
||||
["repository git"]
|
||||
type = "git"
|
||||
path = "/home/${USER}/src/git/.git"
|
||||
|
||||
["telemetry"]
|
||||
scribe-cat = "/usr/local/bin/scribe_cat"
|
||||
"""
|
||||
@ -115,9 +91,6 @@ class TomlConfigTest(
|
||||
path = Path(self._config_d) / "defaults.toml"
|
||||
path.write_text(get_toml_test_file_defaults())
|
||||
|
||||
path = Path(self._config_d) / "fbsource.repo.toml"
|
||||
path.write_text(get_toml_test_file_fbsource_repo())
|
||||
|
||||
path = Path(self._home_dir) / ".edenrc"
|
||||
path.write_text(get_toml_test_file_user_rc())
|
||||
|
||||
@ -142,22 +115,6 @@ class TomlConfigTest(
|
||||
f"/home/{self._user}/.eden",
|
||||
)
|
||||
|
||||
def assert_git_repo_config(self, cfg: EdenInstance) -> None:
|
||||
cc = cfg.find_config_for_alias("git")
|
||||
assert cc is not None
|
||||
self.assertEqual(cc.backing_repo, Path(f"/home/{self._user}/src/git/.git"))
|
||||
self.assertEqual(cc.scm_type, "git")
|
||||
self.assertEqual(cc.default_revision, "master")
|
||||
|
||||
def assert_fbsource_repo_config(self, cfg: EdenInstance) -> None:
|
||||
cc = cfg.find_config_for_alias("fbsource")
|
||||
assert cc is not None
|
||||
self.assertEqual(
|
||||
cc.backing_repo, Path(f"/data/users/{self._user}/fbsource-override")
|
||||
)
|
||||
self.assertEqual(cc.scm_type, "hg")
|
||||
self.assertEqual(cc.default_revision, "master")
|
||||
|
||||
def assert_config_precedence(self, cfg: EdenInstance) -> None:
|
||||
self.assertEqual(
|
||||
cfg.get_config_value("telemetry.scribe-cat", default=""),
|
||||
@ -170,16 +127,11 @@ class TomlConfigTest(
|
||||
|
||||
# Check the various config sections
|
||||
self.assert_core_config(cfg)
|
||||
exp_repos = ["fbsource", "git"]
|
||||
self.assertEqual(cfg.get_repository_list(), exp_repos)
|
||||
self.assert_fbsource_repo_config(cfg)
|
||||
self.assert_git_repo_config(cfg)
|
||||
self.assert_config_precedence(cfg)
|
||||
|
||||
# Check if test is for toml or cfg by cfg._user_toml_cfg
|
||||
exp_rc_files = [
|
||||
Path(self._config_d) / "defaults.toml",
|
||||
Path(self._config_d) / "fbsource.repo.toml",
|
||||
Path(self._etc_eden_dir) / "edenfs.rc",
|
||||
Path(self._home_dir) / ".edenrc",
|
||||
]
|
||||
@ -192,9 +144,6 @@ class TomlConfigTest(
|
||||
cfg = self.get_config()
|
||||
cfg._loadConfig()
|
||||
|
||||
exp_repos = ["fbsource"]
|
||||
self.assertEqual(cfg.get_repository_list(), exp_repos)
|
||||
|
||||
self.assertEqual(
|
||||
cfg.get_config_value("rage.reporter", default=""),
|
||||
'arc paste --title "eden rage from $(hostname)" --conduit-uri=https://phabricator.intern.facebook.com/api/',
|
||||
@ -207,84 +156,6 @@ class TomlConfigTest(
|
||||
cfg.get_config_value("core.systemIgnoreFile", default=""),
|
||||
"/etc/eden/gitignore",
|
||||
)
|
||||
cc = cfg.find_config_for_alias("fbsource")
|
||||
assert cc is not None
|
||||
self.assertEqual(cc.backing_repo, Path(f"/data/users/{self._user}/fbsource"))
|
||||
self.assertEqual(cc.scm_type, "hg")
|
||||
self.assertEqual(cc.default_revision, "master")
|
||||
|
||||
def test_missing_type_option_in_repository_is_an_error(self) -> None:
|
||||
self.write_user_config(
|
||||
"""
|
||||
["repository myrepo"]
|
||||
path = "/tmp/myrepo"
|
||||
"""
|
||||
)
|
||||
with self.assertRaises(Exception) as expectation:
|
||||
cfg = self.get_config()
|
||||
cfg.find_config_for_alias("myrepo")
|
||||
self.assertEqual(
|
||||
str(expectation.exception), 'repository "myrepo" missing key "type".'
|
||||
)
|
||||
|
||||
def test_invalid_type_option_in_repository_is_an_error(self) -> None:
|
||||
self.write_user_config(
|
||||
"""
|
||||
["repository myrepo"]
|
||||
type = "invalidrepotype"
|
||||
path = "/tmp/myrepo"
|
||||
"""
|
||||
)
|
||||
with self.assertRaises(Exception) as expectation:
|
||||
cfg = self.get_config()
|
||||
cfg.find_config_for_alias("myrepo")
|
||||
self.assertEqual(
|
||||
str(expectation.exception), 'repository "myrepo" has unsupported type.'
|
||||
)
|
||||
|
||||
def test_empty_type_option_in_repository_is_an_error(self) -> None:
|
||||
self.write_user_config(
|
||||
"""
|
||||
["repository myrepo"]
|
||||
type = ""
|
||||
path = "/tmp/myrepo"
|
||||
"""
|
||||
)
|
||||
with self.assertRaises(Exception) as expectation:
|
||||
cfg = self.get_config()
|
||||
cfg.find_config_for_alias("myrepo")
|
||||
self.assertEqual(
|
||||
str(expectation.exception), 'repository "myrepo" missing key "type".'
|
||||
)
|
||||
|
||||
def test_missing_path_option_in_repository_is_an_error(self) -> None:
|
||||
self.write_user_config(
|
||||
"""
|
||||
["repository myrepo"]
|
||||
type = "hg"
|
||||
"""
|
||||
)
|
||||
with self.assertRaises(Exception) as expectation:
|
||||
cfg = self.get_config()
|
||||
cfg.find_config_for_alias("myrepo")
|
||||
self.assertEqual(
|
||||
str(expectation.exception), 'repository "myrepo" missing key "path".'
|
||||
)
|
||||
|
||||
def test_empty_path_option_in_repository_is_an_error(self) -> None:
|
||||
self.write_user_config(
|
||||
"""
|
||||
["repository myrepo"]
|
||||
type = "hg"
|
||||
path = ""
|
||||
"""
|
||||
)
|
||||
with self.assertRaises(Exception) as expectation:
|
||||
cfg = self.get_config()
|
||||
cfg.find_config_for_alias("myrepo")
|
||||
self.assertEqual(
|
||||
str(expectation.exception), 'repository "myrepo" missing key "path".'
|
||||
)
|
||||
|
||||
def test_toml_error(self) -> None:
|
||||
self.copy_config_files()
|
||||
|
@ -227,15 +227,14 @@ class CloneTest(testcase.EdenRepoTest):
|
||||
f"error: destination path {non_existent_dir} is not a directory\n", stderr
|
||||
)
|
||||
|
||||
def test_attempt_clone_invalid_repo_name(self) -> None:
|
||||
def test_attempt_clone_invalid_repo_path(self) -> None:
|
||||
tmp = self.make_temporary_directory()
|
||||
repo_name = "repo-name-that-is-not-in-the-config"
|
||||
repo_path = "/this/directory/does/not/exist"
|
||||
|
||||
with self.assertRaises(edenclient.EdenCommandError) as context:
|
||||
self.eden.run_cmd("clone", repo_name, tmp)
|
||||
self.eden.run_cmd("clone", repo_path, tmp)
|
||||
self.assertIn(
|
||||
f"error: {repo_name!r} does not look like a valid hg or git "
|
||||
"repository or a well-known repository name\n",
|
||||
f"error: {repo_path!r} does not look like a valid hg or git repository\n",
|
||||
context.exception.stderr.decode(),
|
||||
)
|
||||
|
||||
|
@ -56,30 +56,6 @@ via-profile = "bind"
|
||||
],
|
||||
)
|
||||
|
||||
def clone_with_legacy_bind_mounts(self) -> str:
|
||||
edenrc = os.path.join(os.environ["HOME"], ".edenrc")
|
||||
with open(edenrc, "w") as f:
|
||||
f.write(
|
||||
"""\
|
||||
["repository {repo_name}"]
|
||||
path = "{repo_path}"
|
||||
type = "{repo_type}"
|
||||
|
||||
["bindmounts {repo_name}"]
|
||||
buck-out = "buck-out"
|
||||
""".format(
|
||||
repo_name=self.repo_name,
|
||||
repo_path=self.repo.get_canonical_root(),
|
||||
repo_type=self.repo.get_type(),
|
||||
)
|
||||
)
|
||||
|
||||
basename = "eden_mount"
|
||||
tmp = os.path.join(self.tmp_dir, basename)
|
||||
|
||||
self.eden.run_cmd("clone", self.repo_name, tmp)
|
||||
return tmp
|
||||
|
||||
def test_disallow_bind_mount_outside_repo(self) -> None:
|
||||
dir_to_mount = os.path.join(self.tmp_dir, "to-mount")
|
||||
os.mkdir(dir_to_mount)
|
||||
@ -120,11 +96,9 @@ buck-out = "buck-out"
|
||||
msg="Can't mount outside the repo via relative path",
|
||||
)
|
||||
|
||||
def test_list_with_legacy_bind_mount(self) -> None:
|
||||
tmp = self.clone_with_legacy_bind_mounts()
|
||||
|
||||
profile_path = scratch_path(tmp, "edenfs/redirections/via-profile")
|
||||
output = self.eden.run_cmd("redirect", "list", "--json", "--mount", tmp)
|
||||
def test_list(self) -> None:
|
||||
profile_path = scratch_path(self.mount, "edenfs/redirections/via-profile")
|
||||
output = self.eden.run_cmd("redirect", "list", "--json", "--mount", self.mount)
|
||||
self.assertEqual(
|
||||
json.loads(output),
|
||||
[
|
||||
@ -140,12 +114,14 @@ buck-out = "buck-out"
|
||||
)
|
||||
|
||||
output = self.eden.run_cmd(
|
||||
"redirect", "add", "--mount", tmp, "a/new-one", "bind"
|
||||
"redirect", "add", "--mount", self.mount, "a/new-one", "bind"
|
||||
)
|
||||
self.assertEqual(output, "", msg="we believe we set up a new bind mount")
|
||||
|
||||
list_output = self.eden.run_cmd("redirect", "list", "--json", "--mount", tmp)
|
||||
target_path = scratch_path(tmp, "edenfs/redirections/a/new-one")
|
||||
list_output = self.eden.run_cmd(
|
||||
"redirect", "list", "--json", "--mount", self.mount
|
||||
)
|
||||
target_path = scratch_path(self.mount, "edenfs/redirections/a/new-one")
|
||||
self.assertEqual(
|
||||
json.loads(list_output),
|
||||
[
|
||||
@ -167,8 +143,8 @@ buck-out = "buck-out"
|
||||
msg="saved config agrees with last output",
|
||||
)
|
||||
|
||||
mount_stat = os.stat(tmp)
|
||||
bind_mount_stat = os.stat(os.path.join(tmp, "a/new-one"))
|
||||
mount_stat = os.stat(self.mount)
|
||||
bind_mount_stat = os.stat(os.path.join(self.mount, "a/new-one"))
|
||||
self.assertNotEqual(
|
||||
mount_stat.st_dev,
|
||||
bind_mount_stat.st_dev,
|
||||
@ -176,11 +152,13 @@ buck-out = "buck-out"
|
||||
)
|
||||
|
||||
output = self.eden.run_cmd(
|
||||
"redirect", "add", "--mount", tmp, "a/new-one", "symlink"
|
||||
"redirect", "add", "--mount", self.mount, "a/new-one", "symlink"
|
||||
)
|
||||
self.assertEqual(output, "", msg="we believe we switched to a symlink")
|
||||
|
||||
list_output = self.eden.run_cmd("redirect", "list", "--json", "--mount", tmp)
|
||||
list_output = self.eden.run_cmd(
|
||||
"redirect", "list", "--json", "--mount", self.mount
|
||||
)
|
||||
self.assertEqual(
|
||||
json.loads(list_output),
|
||||
[
|
||||
@ -203,15 +181,19 @@ buck-out = "buck-out"
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
os.readlink(os.path.join(tmp, "a", "new-one")),
|
||||
os.readlink(os.path.join(self.mount, "a", "new-one")),
|
||||
target_path,
|
||||
msg="symlink points to scratch space",
|
||||
)
|
||||
|
||||
output = self.eden.run_cmd("redirect", "del", "--mount", tmp, "a/new-one")
|
||||
output = self.eden.run_cmd(
|
||||
"redirect", "del", "--mount", self.mount, "a/new-one"
|
||||
)
|
||||
self.assertEqual(output, "", msg="we believe we removed the symlink")
|
||||
|
||||
list_output = self.eden.run_cmd("redirect", "list", "--json", "--mount", tmp)
|
||||
list_output = self.eden.run_cmd(
|
||||
"redirect", "list", "--json", "--mount", self.mount
|
||||
)
|
||||
self.assertEqual(
|
||||
json.loads(list_output),
|
||||
[
|
||||
@ -227,7 +209,8 @@ buck-out = "buck-out"
|
||||
)
|
||||
|
||||
self.assertFalse(
|
||||
os.path.exists(os.path.join(tmp, "a", "new-one")), msg="symlink is gone"
|
||||
os.path.exists(os.path.join(self.mount, "a", "new-one")),
|
||||
msg="symlink is gone",
|
||||
)
|
||||
|
||||
def test_fixup_mounts_things(self) -> None:
|
||||
|
Loading…
Reference in New Issue
Block a user