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:
Adam Simpkins 2020-04-10 13:55:41 -07:00 committed by Facebook GitHub Bot
parent ef8c3435c4
commit c5e6e40888
5 changed files with 45 additions and 270 deletions

View File

@ -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.

View 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

View File

@ -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()

View File

@ -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(),
)

View File

@ -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: