2016-06-27 21:58:55 +03:00
|
|
|
#!/usr/bin/env python3
|
2019-06-20 02:58:25 +03:00
|
|
|
# Copyright (c) Facebook, Inc. and its affiliates.
|
2016-05-12 23:43:17 +03:00
|
|
|
#
|
2019-06-20 02:58:25 +03:00
|
|
|
# This software may be used and distributed according to the terms of the
|
|
|
|
# GNU General Public License version 2.
|
2016-05-12 23:43:17 +03:00
|
|
|
|
2017-08-31 01:07:46 +03:00
|
|
|
import binascii
|
2016-05-12 23:43:17 +03:00
|
|
|
import collections
|
2017-12-15 08:07:22 +03:00
|
|
|
import datetime
|
2016-07-12 04:27:29 +03:00
|
|
|
import errno
|
2016-05-12 23:43:17 +03:00
|
|
|
import json
|
2020-02-18 19:03:48 +03:00
|
|
|
import logging
|
2016-05-12 23:43:17 +03:00
|
|
|
import os
|
2016-07-07 02:14:30 +03:00
|
|
|
import shutil
|
2019-09-17 17:26:09 +03:00
|
|
|
import subprocess
|
2019-10-01 04:55:45 +03:00
|
|
|
import sys
|
2020-02-18 19:03:48 +03:00
|
|
|
import time
|
2018-03-29 08:10:41 +03:00
|
|
|
import typing
|
2020-12-15 19:03:23 +03:00
|
|
|
import uuid
|
2018-08-22 21:05:42 +03:00
|
|
|
from pathlib import Path
|
2021-03-10 23:51:03 +03:00
|
|
|
from typing import (
|
|
|
|
KeysView,
|
|
|
|
IO,
|
|
|
|
Any,
|
|
|
|
Dict,
|
|
|
|
List,
|
|
|
|
Mapping,
|
|
|
|
Optional,
|
|
|
|
Set,
|
|
|
|
Tuple,
|
|
|
|
Union,
|
|
|
|
)
|
2016-06-18 01:11:04 +03:00
|
|
|
|
2016-05-12 23:43:17 +03:00
|
|
|
import facebook.eden.ttypes as eden_ttypes
|
2018-05-10 07:33:49 +03:00
|
|
|
import toml
|
2020-09-17 23:23:24 +03:00
|
|
|
from eden.thrift import legacy
|
2018-05-10 07:33:49 +03:00
|
|
|
|
2020-02-27 08:15:16 +03:00
|
|
|
from . import configinterpolator, configutil, telemetry, util, version
|
2019-09-24 02:35:01 +03:00
|
|
|
from .util import (
|
|
|
|
EdenStartError,
|
|
|
|
HealthStatus,
|
|
|
|
print_stderr,
|
|
|
|
readlink_retry_estale,
|
|
|
|
write_file_atomically,
|
|
|
|
)
|
2018-05-10 07:33:49 +03:00
|
|
|
|
2016-05-12 23:43:17 +03:00
|
|
|
|
2020-10-22 21:37:27 +03:00
|
|
|
try:
|
|
|
|
from eden.thrift import client
|
|
|
|
except ImportError:
|
|
|
|
# Thrift-py3 is not supported in the CMake build yet.
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2021-03-10 23:51:03 +03:00
|
|
|
log: logging.Logger = logging.getLogger(__name__)
|
2020-02-18 19:03:48 +03:00
|
|
|
|
2019-07-02 01:41:03 +03:00
|
|
|
# On Linux we import fcntl for flock. The Windows LockFileEx is not semantically
|
|
|
|
# same as flock. We will need to make some changes for LockFileEx to work.
|
2020-04-04 02:48:29 +03:00
|
|
|
if sys.platform != "win32":
|
2019-07-02 01:41:03 +03:00
|
|
|
import fcntl
|
|
|
|
|
|
|
|
|
2019-06-26 04:39:31 +03:00
|
|
|
if typing.TYPE_CHECKING:
|
2020-03-25 21:44:05 +03:00
|
|
|
from eden.fs.cli.redirect import RedirectionType # noqa: F401
|
2019-06-26 04:39:31 +03:00
|
|
|
|
2017-01-24 10:52:46 +03:00
|
|
|
# Use --etcEdenDir to change the value used for a given invocation
|
|
|
|
# of the eden cli.
|
2020-04-04 02:48:29 +03:00
|
|
|
if sys.platform == "win32":
|
2020-09-04 03:16:53 +03:00
|
|
|
DEFAULT_ETC_EDEN_DIR = "C:\\ProgramData\\facebook\\eden"
|
2019-06-22 19:05:36 +03:00
|
|
|
else:
|
|
|
|
DEFAULT_ETC_EDEN_DIR = "/etc/eden"
|
|
|
|
|
2016-07-07 02:14:18 +03:00
|
|
|
# These are INI files that hold config data.
|
2017-01-24 10:52:46 +03:00
|
|
|
# CONFIG_DOT_D is relative to DEFAULT_ETC_EDEN_DIR, or whatever the
|
|
|
|
# effective value is for that path
|
2018-05-10 07:33:49 +03:00
|
|
|
CONFIG_DOT_D = "config.d"
|
2017-01-24 10:52:46 +03:00
|
|
|
# USER_CONFIG is relative to the HOME dir for the user
|
2018-05-10 07:33:49 +03:00
|
|
|
USER_CONFIG = ".edenrc"
|
2020-02-18 19:03:48 +03:00
|
|
|
# SYSTEM_CONFIG is relative to the etc eden dir
|
|
|
|
SYSTEM_CONFIG = "edenfs.rc"
|
2016-07-07 02:14:18 +03:00
|
|
|
|
2016-05-12 23:43:17 +03:00
|
|
|
# These paths are relative to the user's client directory.
|
2018-05-10 07:33:49 +03:00
|
|
|
CLIENTS_DIR = "clients"
|
|
|
|
CONFIG_JSON = "config.json"
|
2016-05-12 23:43:17 +03:00
|
|
|
|
|
|
|
# These are files in a client directory.
|
2018-05-10 07:33:49 +03:00
|
|
|
CLONE_SUCCEEDED = "clone-succeeded"
|
|
|
|
MOUNT_CONFIG = "config.toml"
|
|
|
|
SNAPSHOT = "SNAPSHOT"
|
|
|
|
SNAPSHOT_MAGIC = b"eden\x00\x00\x00\x01"
|
2016-05-12 23:43:17 +03:00
|
|
|
|
2018-01-14 01:20:10 +03:00
|
|
|
DEFAULT_REVISION = { # supported repo name -> default bookmark
|
2018-05-19 09:05:39 +03:00
|
|
|
"git": "refs/heads/master",
|
2020-04-09 10:15:57 +03:00
|
|
|
"hg": "first(present(master) + .)",
|
2018-01-14 01:20:10 +03:00
|
|
|
}
|
|
|
|
|
2021-03-10 23:51:03 +03:00
|
|
|
SUPPORTED_REPOS: KeysView[str] = DEFAULT_REVISION.keys()
|
2018-01-14 01:20:10 +03:00
|
|
|
|
2018-05-10 07:33:49 +03:00
|
|
|
REPO_FOR_EXTENSION = {".git": "git", ".hg": "hg"}
|
2018-01-14 01:20:10 +03:00
|
|
|
|
2021-02-05 07:08:38 +03:00
|
|
|
SUPPORTED_MOUNT_PROTOCOLS = {"fuse", "nfs", "prjfs"}
|
|
|
|
|
2018-06-27 06:53:22 +03:00
|
|
|
# Create a readme file with this name in the mount point directory.
|
|
|
|
# The intention is for this to contain instructions telling users what to do if their
|
|
|
|
# Eden mount is not currently mounted.
|
|
|
|
NOT_MOUNTED_README_PATH = "README_EDEN.txt"
|
|
|
|
# The path under /etc/eden where site-specific contents for the not-mounted README can
|
|
|
|
# be found.
|
|
|
|
NOT_MOUNTED_SITE_SPECIFIC_README_PATH = "NOT_MOUNTED_README.txt"
|
|
|
|
# The default contents for the not-mounted README if a site-specific template
|
|
|
|
# is not found.
|
|
|
|
NOT_MOUNTED_DEFAULT_TEXT = """\
|
|
|
|
This directory is the mount point for a virtual checkout managed by Eden.
|
|
|
|
|
|
|
|
If you are seeing this file that means that your repository checkout is not
|
|
|
|
currently mounted. This could either be because the edenfs daemon is not
|
|
|
|
currently running, or it simply does not have this checkout mounted yet.
|
|
|
|
|
|
|
|
You can run "eden doctor" to check for problems with Eden and try to have it
|
|
|
|
automatically remount your checkouts.
|
|
|
|
"""
|
|
|
|
|
2018-01-14 01:20:10 +03:00
|
|
|
assert sorted(REPO_FOR_EXTENSION.values()) == sorted(SUPPORTED_REPOS)
|
2016-05-12 23:43:17 +03:00
|
|
|
|
2018-05-10 07:33:49 +03:00
|
|
|
|
2016-07-23 03:31:17 +03:00
|
|
|
class UsageError(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2019-02-07 07:04:29 +03:00
|
|
|
class CheckoutConfig(typing.NamedTuple):
|
2018-12-13 03:38:20 +03:00
|
|
|
"""Configuration for an Eden checkout. A checkout stores its config in config.toml
|
|
|
|
it its state directory (.eden/clients/<checkout_name>/config.toml)
|
2017-11-15 01:01:59 +03:00
|
|
|
|
2019-02-07 07:04:29 +03:00
|
|
|
- backing_repo: The path where the true repo resides on disk. For mercurial backing
|
|
|
|
repositories this does not include the final ".hg" directory component.
|
|
|
|
- scm_type: "hg" or "git"
|
2021-02-05 07:08:38 +03:00
|
|
|
- mount_protocol: "fuse", "nfs" or "prjfs"
|
2021-02-11 07:09:33 +03:00
|
|
|
- case_sensitive: whether the mount point is case sensitive. Default to
|
|
|
|
false on Windows and macOS.
|
2021-02-23 22:33:07 +03:00
|
|
|
- require_utf8_path: whether the mount point will disallow non-utf8 paths
|
|
|
|
to be written to it.
|
2020-12-15 19:03:23 +03:00
|
|
|
- guid: Used on Windows by ProjectedFS to identify this checkout.
|
2019-06-26 04:39:31 +03:00
|
|
|
- redirections: dict where keys are relative pathnames in the EdenFS mount
|
|
|
|
and the values are RedirectionType enum values that describe the type of
|
2020-04-10 03:28:20 +03:00
|
|
|
the redirection.
|
2018-05-10 07:33:49 +03:00
|
|
|
"""
|
2018-05-30 10:33:03 +03:00
|
|
|
|
2019-02-07 07:04:29 +03:00
|
|
|
backing_repo: Path
|
|
|
|
scm_type: str
|
2020-12-15 19:03:23 +03:00
|
|
|
guid: str
|
2021-02-05 07:08:38 +03:00
|
|
|
mount_protocol: str
|
2021-02-11 07:09:33 +03:00
|
|
|
case_sensitive: bool
|
2021-02-23 22:33:07 +03:00
|
|
|
require_utf8_path: bool
|
2019-02-07 07:04:29 +03:00
|
|
|
default_revision: str
|
2019-06-26 04:39:31 +03:00
|
|
|
redirections: Dict[str, "RedirectionType"]
|
2020-09-24 19:52:59 +03:00
|
|
|
active_prefetch_profiles: List[str]
|
2018-01-14 01:20:10 +03:00
|
|
|
|
2017-11-17 00:19:57 +03:00
|
|
|
|
2018-08-16 07:34:10 +03:00
|
|
|
class EdenInstance:
|
|
|
|
"""This class contains information about a particular edenfs instance.
|
|
|
|
|
|
|
|
It provides APIs for communicating with edenfs over thrift and for examining and
|
|
|
|
modifying the list of checkouts managed by this edenfs instance.
|
|
|
|
"""
|
|
|
|
|
2020-02-27 08:15:16 +03:00
|
|
|
_telemetry_logger: Optional[telemetry.TelemetryLogger] = None
|
|
|
|
|
2018-08-04 02:56:34 +03:00
|
|
|
def __init__(
|
|
|
|
self,
|
2019-03-09 06:02:42 +03:00
|
|
|
config_dir: Union[Path, str, None],
|
|
|
|
etc_eden_dir: Union[Path, str, None],
|
|
|
|
home_dir: Union[Path, str, None],
|
2018-08-04 02:56:34 +03:00
|
|
|
interpolate_dict: Optional[Dict[str, str]] = None,
|
|
|
|
) -> None:
|
2019-03-09 06:02:42 +03:00
|
|
|
self._etc_eden_dir = Path(etc_eden_dir or DEFAULT_ETC_EDEN_DIR)
|
|
|
|
self._home_dir = Path(home_dir) if home_dir is not None else util.get_home_dir()
|
|
|
|
self._user_config_path = self._home_dir / USER_CONFIG
|
2020-02-18 19:03:48 +03:00
|
|
|
self._system_config_path = self._etc_eden_dir / SYSTEM_CONFIG
|
2018-08-04 02:56:34 +03:00
|
|
|
self._interpolate_dict = interpolate_dict
|
2016-05-12 23:43:17 +03:00
|
|
|
|
2018-08-22 21:05:42 +03:00
|
|
|
# TODO: We should eventually read the default config_dir path from the config
|
|
|
|
# files rather than always using ~/local/.eden
|
2019-01-12 06:40:22 +03:00
|
|
|
#
|
2019-03-09 06:02:42 +03:00
|
|
|
# We call resolve() to resolve any symlinks in the config directory location.
|
2019-01-12 06:40:22 +03:00
|
|
|
# This is particularly important when starting edenfs, since edenfs in some
|
|
|
|
# cases will try to access this path as root (e.g., when creating bind mounts).
|
|
|
|
# In some cases this path may traverse symlinks that are readable as the
|
|
|
|
# original user but not as root: this can happen if the user has a home
|
|
|
|
# directory on NFS, which may not be readable as root.
|
2019-05-30 21:27:43 +03:00
|
|
|
if config_dir:
|
|
|
|
self._config_dir = Path(config_dir)
|
2020-04-04 02:48:29 +03:00
|
|
|
elif sys.platform == "win32":
|
2019-05-30 21:27:43 +03:00
|
|
|
self._config_dir = self._home_dir / ".eden"
|
|
|
|
else:
|
|
|
|
self._config_dir = self._home_dir / "local" / ".eden"
|
|
|
|
|
2020-09-19 02:37:56 +03:00
|
|
|
self._config_dir = self._config_dir.resolve(strict=False)
|
2019-01-12 06:40:22 +03:00
|
|
|
|
2018-08-22 21:05:42 +03:00
|
|
|
def __repr__(self) -> str:
|
|
|
|
return f"EdenInstance({self._config_dir!r})"
|
|
|
|
|
|
|
|
@property
|
|
|
|
def state_dir(self) -> Path:
|
2019-03-09 06:02:42 +03:00
|
|
|
return self._config_dir
|
2018-08-22 21:05:42 +03:00
|
|
|
|
2020-04-16 01:44:15 +03:00
|
|
|
@property
|
|
|
|
def etc_eden_dir(self) -> Path:
|
|
|
|
return self._etc_eden_dir
|
|
|
|
|
2020-09-03 06:18:08 +03:00
|
|
|
@property
|
|
|
|
def home_dir(self) -> Path:
|
|
|
|
return self._home_dir
|
|
|
|
|
2020-04-16 01:44:15 +03:00
|
|
|
@property
|
|
|
|
def user_config_path(self) -> Path:
|
|
|
|
return self._user_config_path
|
|
|
|
|
2021-03-12 20:30:20 +03:00
|
|
|
def read_configs(self, paths: List[Path]) -> configutil.EdenConfigParser:
|
|
|
|
"""
|
|
|
|
reads all files specified in paths and parses the configs,
|
|
|
|
skips any files which are not found
|
2018-05-10 07:33:49 +03:00
|
|
|
"""
|
2019-01-29 03:34:47 +03:00
|
|
|
parser = configutil.EdenConfigParser(
|
2019-03-15 04:13:29 +03:00
|
|
|
interpolation=configinterpolator.EdenConfigInterpolator(
|
|
|
|
self._config_variables
|
|
|
|
)
|
2018-05-10 07:33:49 +03:00
|
|
|
)
|
2021-03-12 20:30:20 +03:00
|
|
|
for path in paths:
|
2018-08-04 02:56:34 +03:00
|
|
|
try:
|
2019-06-26 04:39:31 +03:00
|
|
|
toml_cfg = load_toml_config(path)
|
2019-02-05 05:25:47 +03:00
|
|
|
except FileNotFoundError:
|
2018-08-04 02:56:34 +03:00
|
|
|
# Ignore missing config files. Eg. user_config_path is optional
|
|
|
|
continue
|
|
|
|
parser.read_dict(toml_cfg)
|
2017-01-24 10:52:37 +03:00
|
|
|
return parser
|
|
|
|
|
2021-03-12 20:30:20 +03:00
|
|
|
def _loadConfig(self) -> configutil.EdenConfigParser:
|
|
|
|
"""to facilitate templatizing a centrally deployed config, we
|
|
|
|
allow a limited set of env vars to be expanded.
|
|
|
|
${HOME} will be replaced by the user's home dir,
|
|
|
|
${USER} will be replaced by the user's login name.
|
|
|
|
These are coupled with the equivalent code in
|
|
|
|
eden/fs/config/CheckoutConfig.cpp and must be kept in sync.
|
|
|
|
"""
|
|
|
|
return self.read_configs(self.get_rc_files())
|
|
|
|
|
2019-03-15 04:13:29 +03:00
|
|
|
@property
|
|
|
|
def _config_variables(self) -> Dict[str, str]:
|
2020-04-04 02:48:29 +03:00
|
|
|
if sys.platform == "win32":
|
2019-07-02 01:41:03 +03:00
|
|
|
# We don't have user ids on Windows right now.
|
|
|
|
# We should update this code if and when we add user id support.
|
|
|
|
user_id = 0
|
|
|
|
user_name = "USERNAME"
|
|
|
|
else:
|
|
|
|
user_id = os.getuid()
|
|
|
|
user_name = "USER"
|
2020-05-22 05:31:01 +03:00
|
|
|
# pyre-fixme[7]: Expected `Dict[str, str]` but got `Optional[Dict[str, str]]`.
|
2019-03-15 04:13:29 +03:00
|
|
|
return (
|
|
|
|
self._interpolate_dict
|
|
|
|
if self._interpolate_dict is not None
|
|
|
|
else {
|
2019-07-02 01:41:03 +03:00
|
|
|
"USER": os.environ.get(user_name, ""),
|
|
|
|
"USER_ID": str(user_id),
|
2019-03-15 04:13:29 +03:00
|
|
|
"HOME": str(self._home_dir),
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2019-03-09 06:02:42 +03:00
|
|
|
def get_rc_files(self) -> List[Path]:
|
|
|
|
result: List[Path] = []
|
|
|
|
config_d = self._etc_eden_dir / CONFIG_DOT_D
|
|
|
|
try:
|
|
|
|
rc_entries = os.listdir(config_d)
|
|
|
|
except OSError as ex:
|
|
|
|
if ex.errno != errno.ENOENT:
|
|
|
|
raise
|
|
|
|
rc_entries = []
|
|
|
|
|
|
|
|
for name in rc_entries:
|
2020-10-17 06:48:58 +03:00
|
|
|
if not name.startswith(".") and name.endswith(".toml"):
|
2019-03-09 06:02:42 +03:00
|
|
|
result.append(config_d / name)
|
2018-08-04 02:56:34 +03:00
|
|
|
result.sort()
|
2020-02-18 19:03:48 +03:00
|
|
|
result.append(self._system_config_path)
|
2016-08-05 22:49:21 +03:00
|
|
|
result.append(self._user_config_path)
|
|
|
|
return result
|
2016-07-07 02:14:18 +03:00
|
|
|
|
2020-02-27 08:15:16 +03:00
|
|
|
def get_telemetry_logger(self) -> telemetry.TelemetryLogger:
|
|
|
|
logger = self._telemetry_logger
|
|
|
|
if logger is None:
|
2020-02-27 08:15:16 +03:00
|
|
|
logger = self._create_telemetry_logger()
|
2020-02-27 08:15:16 +03:00
|
|
|
self._telemetry_logger = logger
|
|
|
|
return logger
|
2020-02-18 19:03:48 +03:00
|
|
|
|
2020-02-27 08:15:16 +03:00
|
|
|
def _create_telemetry_logger(self) -> telemetry.TelemetryLogger:
|
2020-03-04 01:53:38 +03:00
|
|
|
if "INTEGRATION_TEST" in os.environ:
|
|
|
|
return telemetry.NullTelemetryLogger()
|
|
|
|
|
2020-02-27 08:15:16 +03:00
|
|
|
try:
|
2020-03-25 21:44:05 +03:00
|
|
|
from eden.fs.cli.facebook import scuba_telemetry
|
2020-02-27 08:15:16 +03:00
|
|
|
|
|
|
|
return scuba_telemetry.ScubaTelemetryLogger()
|
2020-06-20 01:12:42 +03:00
|
|
|
except (ImportError, NotImplementedError):
|
2020-02-27 08:15:16 +03:00
|
|
|
pass
|
|
|
|
|
|
|
|
scribe_cat = self.get_config_value("telemetry.scribe-cat", default="")
|
|
|
|
scribe_category = self.get_config_value("telemetry.scribe-category", default="")
|
|
|
|
if scribe_cat == "" or scribe_category == "":
|
|
|
|
return telemetry.NullTelemetryLogger()
|
|
|
|
return telemetry.ExternalTelemetryLogger([scribe_cat, scribe_category])
|
|
|
|
|
2020-02-27 08:15:16 +03:00
|
|
|
def build_sample(
|
2020-03-03 19:01:07 +03:00
|
|
|
self, log_type: str, **kwargs: Union[bool, int, str, float, Set[str]]
|
2020-02-27 08:15:16 +03:00
|
|
|
) -> telemetry.TelemetrySample:
|
|
|
|
return self.get_telemetry_logger().new_sample(log_type, **kwargs)
|
2020-02-18 19:03:48 +03:00
|
|
|
|
2020-03-03 19:01:07 +03:00
|
|
|
def log_sample(
|
|
|
|
self, log_type: str, **kwargs: Union[bool, int, str, float, Set[str]]
|
|
|
|
) -> None:
|
2020-02-27 08:15:16 +03:00
|
|
|
self.get_telemetry_logger().log(log_type, **kwargs)
|
2020-02-18 19:03:48 +03:00
|
|
|
|
2020-02-26 05:56:44 +03:00
|
|
|
def get_running_version_parts(self) -> Tuple[str, str]:
|
|
|
|
"""Get a tuple containing (version, release) of the currently running EdenFS
|
|
|
|
daemon.
|
|
|
|
|
|
|
|
The version and release strings will both be the empty string if a development
|
|
|
|
build of EdenFS is being used.
|
|
|
|
|
|
|
|
Throws an EdenNotRunningError if EdenFS does not currently appear to be running.
|
|
|
|
"""
|
|
|
|
bi = self.get_server_build_info()
|
|
|
|
return (
|
|
|
|
bi.get("build_package_version", ""),
|
|
|
|
bi.get("build_package_release", ""),
|
|
|
|
)
|
|
|
|
|
2020-09-03 06:18:08 +03:00
|
|
|
def get_current_and_running_versions(self) -> Tuple[str, Optional[str]]:
|
|
|
|
try:
|
|
|
|
running = self.get_running_version()
|
2020-09-17 23:23:24 +03:00
|
|
|
except legacy.EdenNotRunningError:
|
2020-09-03 06:18:08 +03:00
|
|
|
# return None if EdenFS does not currently appear to be running
|
|
|
|
running = None
|
|
|
|
return version.get_current_version(), running
|
|
|
|
|
2020-02-26 05:56:44 +03:00
|
|
|
def get_running_version(self) -> str:
|
|
|
|
"""Get a human-readable string representation of the currently running EdenFS
|
|
|
|
version.
|
|
|
|
|
|
|
|
Will return the string "-" if a dev build of EdenFS is being used.
|
|
|
|
|
|
|
|
Throws an EdenNotRunningError if EdenFS does not currently appear to be running.
|
|
|
|
"""
|
|
|
|
return version.format_eden_version(self.get_running_version_parts())
|
|
|
|
|
2019-01-26 05:35:43 +03:00
|
|
|
def get_config_value(self, key: str, default: str) -> str:
|
2017-01-24 10:52:37 +03:00
|
|
|
parser = self._loadConfig()
|
2018-05-10 07:33:49 +03:00
|
|
|
section, option = key.split(".", 1)
|
2019-01-29 03:34:47 +03:00
|
|
|
return parser.get_str(section, option, default=default)
|
2017-01-24 10:52:35 +03:00
|
|
|
|
2020-09-18 04:03:14 +03:00
|
|
|
def get_config_bool(self, key: str, default: bool) -> bool:
|
|
|
|
parser = self._loadConfig()
|
|
|
|
section, option = key.split(".", 1)
|
|
|
|
return parser.get_bool(section, option, default=default)
|
|
|
|
|
2018-12-08 05:48:35 +03:00
|
|
|
def should_use_experimental_systemd_mode(self) -> bool:
|
2020-04-09 03:36:42 +03:00
|
|
|
if not sys.platform.startswith("linux"):
|
|
|
|
return False
|
|
|
|
|
2018-12-08 05:48:35 +03:00
|
|
|
# TODO(T33122320): Delete this environment variable when systemd is properly
|
|
|
|
# integrated.
|
2018-12-11 03:27:59 +03:00
|
|
|
env_var_value = os.getenv("EDEN_EXPERIMENTAL_SYSTEMD")
|
|
|
|
if env_var_value == "1":
|
|
|
|
return True
|
|
|
|
if env_var_value == "0":
|
|
|
|
return False
|
|
|
|
|
2019-01-29 06:25:58 +03:00
|
|
|
if self._loadConfig().get_bool(
|
|
|
|
"service", "experimental_systemd", default=False
|
2019-01-26 05:35:43 +03:00
|
|
|
):
|
|
|
|
return True
|
|
|
|
|
2018-12-11 03:27:59 +03:00
|
|
|
return False
|
2018-12-08 05:48:35 +03:00
|
|
|
|
2019-03-15 04:13:29 +03:00
|
|
|
def get_fallback_systemd_xdg_runtime_dir(self) -> str:
|
|
|
|
xdg_runtime_dir = self.get_config_value(
|
|
|
|
"service.fallback_systemd_xdg_runtime_dir", default=""
|
|
|
|
)
|
|
|
|
if xdg_runtime_dir == "":
|
|
|
|
user_id = self._config_variables["USER_ID"]
|
|
|
|
xdg_runtime_dir = f"/run/user/{user_id}"
|
|
|
|
return xdg_runtime_dir
|
|
|
|
|
2019-01-18 22:54:13 +03:00
|
|
|
def print_full_config(self, file: typing.TextIO) -> None:
|
2017-01-24 10:52:37 +03:00
|
|
|
parser = self._loadConfig()
|
2019-01-29 03:34:47 +03:00
|
|
|
data: Dict[str, Mapping[str, str]] = {}
|
2017-01-24 10:52:35 +03:00
|
|
|
for section in parser.sections():
|
2019-01-29 06:25:58 +03:00
|
|
|
data[section] = parser.get_section_str_to_any(section)
|
2020-07-20 23:26:26 +03:00
|
|
|
toml.dump(data, file)
|
2017-01-24 10:52:35 +03:00
|
|
|
|
2019-03-09 06:02:42 +03:00
|
|
|
def get_mount_paths(self) -> List[str]:
|
2018-05-10 07:33:49 +03:00
|
|
|
"""Return the paths of the set mount points stored in config.json"""
|
2019-03-09 06:02:42 +03:00
|
|
|
return [str(path) for path in self._get_directory_map().keys()]
|
2016-05-12 23:43:17 +03:00
|
|
|
|
2021-03-10 23:51:03 +03:00
|
|
|
async def get_thrift_client(
|
|
|
|
self, timeout: Optional[float] = None
|
|
|
|
) -> "client.EdenClient":
|
2020-10-22 21:37:27 +03:00
|
|
|
return client.create_thrift_client(
|
2021-03-10 23:51:03 +03:00
|
|
|
eden_dir=str(self._config_dir),
|
|
|
|
timeout=timeout,
|
2020-10-22 21:37:27 +03:00
|
|
|
)
|
|
|
|
|
2021-03-10 23:51:03 +03:00
|
|
|
def get_thrift_client_legacy(
|
|
|
|
self, timeout: Optional[float] = None
|
|
|
|
) -> legacy.EdenClient:
|
2020-09-17 23:23:24 +03:00
|
|
|
return legacy.create_thrift_client(
|
2021-03-10 23:51:03 +03:00
|
|
|
eden_dir=str(self._config_dir),
|
|
|
|
timeout=timeout,
|
2020-09-17 23:23:24 +03:00
|
|
|
)
|
2016-05-12 23:43:17 +03:00
|
|
|
|
2020-05-08 08:06:36 +03:00
|
|
|
def get_checkout_info(self, path: Union[Path, str]) -> collections.OrderedDict:
|
|
|
|
"""
|
|
|
|
Given a path to a checkout, return a dictionary containing diagnostic
|
|
|
|
information about it.
|
|
|
|
"""
|
2020-09-19 02:37:56 +03:00
|
|
|
path = Path(path).resolve(strict=False)
|
2016-07-07 02:14:30 +03:00
|
|
|
client_dir = self._get_client_dir_for_mount_point(path)
|
2019-03-09 06:02:42 +03:00
|
|
|
checkout = EdenCheckout(self, path, client_dir)
|
2020-05-08 08:06:36 +03:00
|
|
|
return self.get_checkout_info_from_checkout(checkout)
|
2019-09-20 04:15:27 +03:00
|
|
|
|
2020-05-08 08:06:36 +03:00
|
|
|
def get_checkout_info_from_checkout(
|
2019-09-20 04:15:27 +03:00
|
|
|
self, checkout: "EdenCheckout"
|
|
|
|
) -> collections.OrderedDict:
|
2019-02-05 05:25:47 +03:00
|
|
|
checkout_config = checkout.get_config()
|
|
|
|
snapshot = checkout.get_snapshot()
|
2018-05-10 07:33:49 +03:00
|
|
|
return collections.OrderedDict(
|
|
|
|
[
|
2019-09-20 04:15:27 +03:00
|
|
|
("mount", str(checkout.path)),
|
2018-12-13 03:38:20 +03:00
|
|
|
("scm_type", checkout_config.scm_type),
|
2018-05-10 07:33:49 +03:00
|
|
|
("snapshot", snapshot),
|
2020-05-08 08:06:36 +03:00
|
|
|
("state_dir", str(checkout.state_dir)),
|
2018-05-10 07:33:49 +03:00
|
|
|
]
|
|
|
|
)
|
2016-05-12 23:43:17 +03:00
|
|
|
|
2018-12-13 03:38:20 +03:00
|
|
|
def clone(
|
|
|
|
self, checkout_config: CheckoutConfig, path: str, snapshot_id: str
|
|
|
|
) -> None:
|
2016-07-07 02:14:30 +03:00
|
|
|
if path in self._get_directory_map():
|
2018-05-10 07:33:49 +03:00
|
|
|
raise Exception(
|
|
|
|
"""\
|
2017-11-16 06:25:26 +03:00
|
|
|
mount path %s is already configured (see `eden list`). \
|
2018-05-10 07:33:49 +03:00
|
|
|
Do you want to run `eden mount %s` instead?"""
|
|
|
|
% (path, path)
|
|
|
|
)
|
2016-07-07 02:14:30 +03:00
|
|
|
|
2018-06-27 06:53:22 +03:00
|
|
|
# Create the mount point directory
|
|
|
|
self._create_mount_point_dir(path)
|
2016-07-12 04:27:29 +03:00
|
|
|
|
2016-07-07 02:14:30 +03:00
|
|
|
# Create client directory
|
Change the contents and format for the edenrc file under ~/local/.eden.
Summary:
The headline changes of this revision are:
- Changes the format of the config file from INI to TOML
(the `edenrc` file under `~/local/.eden` has been replaced
with `config.toml`). This revision includes logic for automatically
performing the migration when Eden is restarted.
- Inlines data from `/etc/eden/config.d` into the TOML file.
Historically, the `edenrc` file for a client would contain the
name of the "configuration alias" defined in a config file like
`~/.edenrc` or `/etc/eden/config.d/00-defaults`. When Eden
loaded a client, it would have to first read the `edenrc` and
then reconstitute the rest of the client configuration by
looking up the alias in the set of config files that were used to
create the client in the first place.
This changes things so that all of the data that was being
cross-referenced is now inlined in the client's config file.
This makes loading a config considerably simpler at the cost
of no longer being able to change the config for multiple clients
that were cloned from the same configuration alias in one place.
It was questionable whether being able to modify a client from
a foreign config after it was created was a safe thing to do, anyway.
Eliminating the need for a historic link to the configuration alias
will make it easier to support running `eden clone` on an arbitrary
local Hg or Git repo. So long as `eden clone` can extract enough
information from the local repo to create an appropriate config file
for the new Eden client, there is no need for a configuration alias
to exist a priori.
Since we were already changing the data in the config file, this
seemed like an appropriate time to make the switch from INI to
TOML, as this was something we wanted to do, anyway.
In testing, I discovered a discrepancy between how boost's
`boost::property_tree::ptree` and Python's `ConfigParser` handled
the following section heading:
```
[repository ZtmpZsillyZeden-clone.LIkh32]
```
Apparently `hasSection("repository ZtmpZsillyZeden-clone.LIkh32")`
in boost would fail to find this section. Because
[[https://stackoverflow.com/questions/13109506/are-hyphens-allowed-in-section-definitions-in-ini-files | there is no spec for INI]],
it is not that surprising that boost and `ConfigParser` do not 100% agree
on what they accept. Moving to TOML means we have a configuration
language with the following desirable properties:
- It has a formal spec, unlike INI. This is important because there are parsers
in a wide range of programming languages that, in theory, accept a consistent
input language.
- It is reasonable for humans to write, as it supports comments, unlike JSON.
- It supports nested structures, like maps and arrays, without going crazy
on the input language it supports, unlike YAML.
Eden now depends on the following third-party TOML parsers:
* C++ https://github.com/skystrife/cpptoml
* Python https://github.com/uiri/toml
This revision also changes the organization of `~/local/.eden` slightly. For now,
there is still a `config.json` file, but the values are no longer hashes of the realpath
of the mount. Instead, we take the basename of the realpath and use that as the
name of the directory under `~/local/.eden/clients`. If there is a naming collision, we
add the first available integral suffix. Using the basename makes it easier to
navigate the `~/local/.eden/clients` directory.
Although the `edenrc` file under `~/local/.eden/clients` has been switched from INI
to TOML, the other Eden config files (`~/.edenrc` and `/etc/eden/config.d/*`) still use
INI. Migrating those to TOML will be done in a future revision.
Note this revision allowed us to eliminate `facebook::eden::InterpolatedPropertyTree`
as well as a number of uses of boost due to the elimination of
`ClientConfig::loadConfigData()` in the C++ code. Because `ClientConfig`
no longer does interpolation, a bit of `ClientConfigTest` was deleted as part of
this revision because it is no longer relevant.
Reviewed By: wez
Differential Revision: D6310325
fbshipit-source-id: 2548149c064cdf8e78a3b3ce6fe667ff70f94f84
2017-11-17 00:19:54 +03:00
|
|
|
clients_dir = self._get_clients_dir()
|
2019-03-09 06:02:42 +03:00
|
|
|
clients_dir.mkdir(parents=True, exist_ok=True)
|
Change the contents and format for the edenrc file under ~/local/.eden.
Summary:
The headline changes of this revision are:
- Changes the format of the config file from INI to TOML
(the `edenrc` file under `~/local/.eden` has been replaced
with `config.toml`). This revision includes logic for automatically
performing the migration when Eden is restarted.
- Inlines data from `/etc/eden/config.d` into the TOML file.
Historically, the `edenrc` file for a client would contain the
name of the "configuration alias" defined in a config file like
`~/.edenrc` or `/etc/eden/config.d/00-defaults`. When Eden
loaded a client, it would have to first read the `edenrc` and
then reconstitute the rest of the client configuration by
looking up the alias in the set of config files that were used to
create the client in the first place.
This changes things so that all of the data that was being
cross-referenced is now inlined in the client's config file.
This makes loading a config considerably simpler at the cost
of no longer being able to change the config for multiple clients
that were cloned from the same configuration alias in one place.
It was questionable whether being able to modify a client from
a foreign config after it was created was a safe thing to do, anyway.
Eliminating the need for a historic link to the configuration alias
will make it easier to support running `eden clone` on an arbitrary
local Hg or Git repo. So long as `eden clone` can extract enough
information from the local repo to create an appropriate config file
for the new Eden client, there is no need for a configuration alias
to exist a priori.
Since we were already changing the data in the config file, this
seemed like an appropriate time to make the switch from INI to
TOML, as this was something we wanted to do, anyway.
In testing, I discovered a discrepancy between how boost's
`boost::property_tree::ptree` and Python's `ConfigParser` handled
the following section heading:
```
[repository ZtmpZsillyZeden-clone.LIkh32]
```
Apparently `hasSection("repository ZtmpZsillyZeden-clone.LIkh32")`
in boost would fail to find this section. Because
[[https://stackoverflow.com/questions/13109506/are-hyphens-allowed-in-section-definitions-in-ini-files | there is no spec for INI]],
it is not that surprising that boost and `ConfigParser` do not 100% agree
on what they accept. Moving to TOML means we have a configuration
language with the following desirable properties:
- It has a formal spec, unlike INI. This is important because there are parsers
in a wide range of programming languages that, in theory, accept a consistent
input language.
- It is reasonable for humans to write, as it supports comments, unlike JSON.
- It supports nested structures, like maps and arrays, without going crazy
on the input language it supports, unlike YAML.
Eden now depends on the following third-party TOML parsers:
* C++ https://github.com/skystrife/cpptoml
* Python https://github.com/uiri/toml
This revision also changes the organization of `~/local/.eden` slightly. For now,
there is still a `config.json` file, but the values are no longer hashes of the realpath
of the mount. Instead, we take the basename of the realpath and use that as the
name of the directory under `~/local/.eden/clients`. If there is a naming collision, we
add the first available integral suffix. Using the basename makes it easier to
navigate the `~/local/.eden/clients` directory.
Although the `edenrc` file under `~/local/.eden/clients` has been switched from INI
to TOML, the other Eden config files (`~/.edenrc` and `/etc/eden/config.d/*`) still use
INI. Migrating those to TOML will be done in a future revision.
Note this revision allowed us to eliminate `facebook::eden::InterpolatedPropertyTree`
as well as a number of uses of boost due to the elimination of
`ClientConfig::loadConfigData()` in the C++ code. Because `ClientConfig`
no longer does interpolation, a bit of `ClientConfigTest` was deleted as part of
this revision because it is no longer relevant.
Reviewed By: wez
Differential Revision: D6310325
fbshipit-source-id: 2548149c064cdf8e78a3b3ce6fe667ff70f94f84
2017-11-17 00:19:54 +03:00
|
|
|
client_dir = self._create_client_dir_for_path(clients_dir, path)
|
2016-07-07 02:14:30 +03:00
|
|
|
|
|
|
|
# Store snapshot ID
|
2019-02-05 05:25:47 +03:00
|
|
|
checkout = EdenCheckout(self, Path(path), Path(client_dir))
|
2016-07-07 02:14:27 +03:00
|
|
|
if snapshot_id:
|
2019-02-05 05:25:47 +03:00
|
|
|
checkout.save_snapshot(snapshot_id)
|
2016-07-07 02:14:27 +03:00
|
|
|
else:
|
2018-05-10 07:33:49 +03:00
|
|
|
raise Exception("snapshot id not provided")
|
2016-07-07 02:14:27 +03:00
|
|
|
|
2019-02-05 05:25:47 +03:00
|
|
|
checkout.save_config(checkout_config)
|
Change the contents and format for the edenrc file under ~/local/.eden.
Summary:
The headline changes of this revision are:
- Changes the format of the config file from INI to TOML
(the `edenrc` file under `~/local/.eden` has been replaced
with `config.toml`). This revision includes logic for automatically
performing the migration when Eden is restarted.
- Inlines data from `/etc/eden/config.d` into the TOML file.
Historically, the `edenrc` file for a client would contain the
name of the "configuration alias" defined in a config file like
`~/.edenrc` or `/etc/eden/config.d/00-defaults`. When Eden
loaded a client, it would have to first read the `edenrc` and
then reconstitute the rest of the client configuration by
looking up the alias in the set of config files that were used to
create the client in the first place.
This changes things so that all of the data that was being
cross-referenced is now inlined in the client's config file.
This makes loading a config considerably simpler at the cost
of no longer being able to change the config for multiple clients
that were cloned from the same configuration alias in one place.
It was questionable whether being able to modify a client from
a foreign config after it was created was a safe thing to do, anyway.
Eliminating the need for a historic link to the configuration alias
will make it easier to support running `eden clone` on an arbitrary
local Hg or Git repo. So long as `eden clone` can extract enough
information from the local repo to create an appropriate config file
for the new Eden client, there is no need for a configuration alias
to exist a priori.
Since we were already changing the data in the config file, this
seemed like an appropriate time to make the switch from INI to
TOML, as this was something we wanted to do, anyway.
In testing, I discovered a discrepancy between how boost's
`boost::property_tree::ptree` and Python's `ConfigParser` handled
the following section heading:
```
[repository ZtmpZsillyZeden-clone.LIkh32]
```
Apparently `hasSection("repository ZtmpZsillyZeden-clone.LIkh32")`
in boost would fail to find this section. Because
[[https://stackoverflow.com/questions/13109506/are-hyphens-allowed-in-section-definitions-in-ini-files | there is no spec for INI]],
it is not that surprising that boost and `ConfigParser` do not 100% agree
on what they accept. Moving to TOML means we have a configuration
language with the following desirable properties:
- It has a formal spec, unlike INI. This is important because there are parsers
in a wide range of programming languages that, in theory, accept a consistent
input language.
- It is reasonable for humans to write, as it supports comments, unlike JSON.
- It supports nested structures, like maps and arrays, without going crazy
on the input language it supports, unlike YAML.
Eden now depends on the following third-party TOML parsers:
* C++ https://github.com/skystrife/cpptoml
* Python https://github.com/uiri/toml
This revision also changes the organization of `~/local/.eden` slightly. For now,
there is still a `config.json` file, but the values are no longer hashes of the realpath
of the mount. Instead, we take the basename of the realpath and use that as the
name of the directory under `~/local/.eden/clients`. If there is a naming collision, we
add the first available integral suffix. Using the basename makes it easier to
navigate the `~/local/.eden/clients` directory.
Although the `edenrc` file under `~/local/.eden/clients` has been switched from INI
to TOML, the other Eden config files (`~/.edenrc` and `/etc/eden/config.d/*`) still use
INI. Migrating those to TOML will be done in a future revision.
Note this revision allowed us to eliminate `facebook::eden::InterpolatedPropertyTree`
as well as a number of uses of boost due to the elimination of
`ClientConfig::loadConfigData()` in the C++ code. Because `ClientConfig`
no longer does interpolation, a bit of `ClientConfigTest` was deleted as part of
this revision because it is no longer relevant.
Reviewed By: wez
Differential Revision: D6310325
fbshipit-source-id: 2548149c064cdf8e78a3b3ce6fe667ff70f94f84
2017-11-17 00:19:54 +03:00
|
|
|
|
2016-07-07 02:14:30 +03:00
|
|
|
# Prepare to mount
|
2019-01-02 23:55:02 +03:00
|
|
|
mount_info = eden_ttypes.MountArgument(
|
2020-03-12 07:36:11 +03:00
|
|
|
mountPoint=os.fsencode(path),
|
|
|
|
edenClientPath=os.fsencode(client_dir),
|
|
|
|
readOnly=False,
|
2018-08-11 11:34:44 +03:00
|
|
|
)
|
2020-09-17 23:23:24 +03:00
|
|
|
with self.get_thrift_client_legacy() as client:
|
2016-07-07 02:14:24 +03:00
|
|
|
client.mount(mount_info)
|
|
|
|
|
2019-02-07 07:04:29 +03:00
|
|
|
self._post_clone_checkout_setup(checkout, snapshot_id)
|
2017-11-15 01:02:00 +03:00
|
|
|
|
2016-07-11 23:13:48 +03:00
|
|
|
# Add mapping of mount path to client directory in config.json
|
2019-03-09 06:02:42 +03:00
|
|
|
self._add_path_to_directory_map(Path(path), os.path.basename(client_dir))
|
Change the contents and format for the edenrc file under ~/local/.eden.
Summary:
The headline changes of this revision are:
- Changes the format of the config file from INI to TOML
(the `edenrc` file under `~/local/.eden` has been replaced
with `config.toml`). This revision includes logic for automatically
performing the migration when Eden is restarted.
- Inlines data from `/etc/eden/config.d` into the TOML file.
Historically, the `edenrc` file for a client would contain the
name of the "configuration alias" defined in a config file like
`~/.edenrc` or `/etc/eden/config.d/00-defaults`. When Eden
loaded a client, it would have to first read the `edenrc` and
then reconstitute the rest of the client configuration by
looking up the alias in the set of config files that were used to
create the client in the first place.
This changes things so that all of the data that was being
cross-referenced is now inlined in the client's config file.
This makes loading a config considerably simpler at the cost
of no longer being able to change the config for multiple clients
that were cloned from the same configuration alias in one place.
It was questionable whether being able to modify a client from
a foreign config after it was created was a safe thing to do, anyway.
Eliminating the need for a historic link to the configuration alias
will make it easier to support running `eden clone` on an arbitrary
local Hg or Git repo. So long as `eden clone` can extract enough
information from the local repo to create an appropriate config file
for the new Eden client, there is no need for a configuration alias
to exist a priori.
Since we were already changing the data in the config file, this
seemed like an appropriate time to make the switch from INI to
TOML, as this was something we wanted to do, anyway.
In testing, I discovered a discrepancy between how boost's
`boost::property_tree::ptree` and Python's `ConfigParser` handled
the following section heading:
```
[repository ZtmpZsillyZeden-clone.LIkh32]
```
Apparently `hasSection("repository ZtmpZsillyZeden-clone.LIkh32")`
in boost would fail to find this section. Because
[[https://stackoverflow.com/questions/13109506/are-hyphens-allowed-in-section-definitions-in-ini-files | there is no spec for INI]],
it is not that surprising that boost and `ConfigParser` do not 100% agree
on what they accept. Moving to TOML means we have a configuration
language with the following desirable properties:
- It has a formal spec, unlike INI. This is important because there are parsers
in a wide range of programming languages that, in theory, accept a consistent
input language.
- It is reasonable for humans to write, as it supports comments, unlike JSON.
- It supports nested structures, like maps and arrays, without going crazy
on the input language it supports, unlike YAML.
Eden now depends on the following third-party TOML parsers:
* C++ https://github.com/skystrife/cpptoml
* Python https://github.com/uiri/toml
This revision also changes the organization of `~/local/.eden` slightly. For now,
there is still a `config.json` file, but the values are no longer hashes of the realpath
of the mount. Instead, we take the basename of the realpath and use that as the
name of the directory under `~/local/.eden/clients`. If there is a naming collision, we
add the first available integral suffix. Using the basename makes it easier to
navigate the `~/local/.eden/clients` directory.
Although the `edenrc` file under `~/local/.eden/clients` has been switched from INI
to TOML, the other Eden config files (`~/.edenrc` and `/etc/eden/config.d/*`) still use
INI. Migrating those to TOML will be done in a future revision.
Note this revision allowed us to eliminate `facebook::eden::InterpolatedPropertyTree`
as well as a number of uses of boost due to the elimination of
`ClientConfig::loadConfigData()` in the C++ code. Because `ClientConfig`
no longer does interpolation, a bit of `ClientConfigTest` was deleted as part of
this revision because it is no longer relevant.
Reviewed By: wez
Differential Revision: D6310325
fbshipit-source-id: 2548149c064cdf8e78a3b3ce6fe667ff70f94f84
2017-11-17 00:19:54 +03:00
|
|
|
|
2018-06-27 06:53:22 +03:00
|
|
|
def _create_mount_point_dir(self, path: str) -> None:
|
|
|
|
# Create the directory
|
|
|
|
try:
|
|
|
|
os.makedirs(path)
|
|
|
|
except OSError as e:
|
|
|
|
if e.errno != errno.EEXIST:
|
|
|
|
raise
|
|
|
|
# If the path already exists, make sure it is an empty directory.
|
|
|
|
# listdir() will throw its own error if the path is not a directory.
|
|
|
|
if len(os.listdir(path)) > 0:
|
|
|
|
raise OSError(errno.ENOTEMPTY, os.strerror(errno.ENOTEMPTY), path)
|
|
|
|
|
2020-04-09 06:47:30 +03:00
|
|
|
# On non-Windows platforms, put a README file in the mount point directory.
|
|
|
|
# This will be visible to users when the EdenFS checkout is not mounted,
|
|
|
|
# and will contain instructions for how to get the checkout re-mounted.
|
|
|
|
#
|
|
|
|
# On Windows anything we put in this directory will be visible in the checkout
|
|
|
|
# itself, so we don't want to put a README file here.
|
|
|
|
if sys.platform != "win32":
|
|
|
|
self._create_checkout_readme_file(path)
|
|
|
|
|
|
|
|
def _create_checkout_readme_file(self, path: str) -> None:
|
2019-03-09 06:02:42 +03:00
|
|
|
help_path = Path(path) / NOT_MOUNTED_README_PATH
|
|
|
|
site_readme_path = self._etc_eden_dir / NOT_MOUNTED_SITE_SPECIFIC_README_PATH
|
2018-08-04 00:54:10 +03:00
|
|
|
help_contents: Optional[str] = NOT_MOUNTED_DEFAULT_TEXT
|
2018-06-27 06:53:22 +03:00
|
|
|
try:
|
|
|
|
# Create a symlink to the site-specific readme file. This helps ensure that
|
|
|
|
# users will see up-to-date contents if the site-specific file is updated
|
|
|
|
# later.
|
2019-03-09 06:02:42 +03:00
|
|
|
with site_readme_path.open("r") as f:
|
2018-06-27 06:53:22 +03:00
|
|
|
try:
|
2019-03-09 06:02:42 +03:00
|
|
|
help_path.symlink_to(site_readme_path)
|
2018-06-27 06:53:22 +03:00
|
|
|
help_contents = None
|
|
|
|
except OSError as ex:
|
|
|
|
# EPERM can indicate that the underlying filesystem does not support
|
|
|
|
# symlinks. Read the contents from the site-specific file in this
|
|
|
|
# case. We will copy them into the file instead of making a
|
|
|
|
# symlink.
|
|
|
|
if ex.errno == errno.EPERM:
|
|
|
|
help_contents = f.read()
|
|
|
|
else:
|
|
|
|
raise
|
|
|
|
except OSError as ex:
|
|
|
|
if ex.errno == errno.ENOENT:
|
|
|
|
# If the site-specific readme file does not exist use default contents
|
|
|
|
help_contents = NOT_MOUNTED_DEFAULT_TEXT
|
|
|
|
else:
|
|
|
|
raise
|
|
|
|
|
|
|
|
if help_contents is not None:
|
2019-03-09 06:02:42 +03:00
|
|
|
with help_path.open("w") as f:
|
2018-06-27 06:53:22 +03:00
|
|
|
f.write(help_contents)
|
2020-04-04 02:48:29 +03:00
|
|
|
if sys.platform != "win32":
|
2019-07-02 01:41:03 +03:00
|
|
|
os.fchmod(f.fileno(), 0o444)
|
2018-06-27 06:53:22 +03:00
|
|
|
|
2019-03-09 06:02:42 +03:00
|
|
|
def _create_client_dir_for_path(self, clients_dir: Path, path: str) -> Path:
|
2018-05-10 07:33:49 +03:00
|
|
|
"""Tries to create a new subdirectory of clients_dir based on the
|
Change the contents and format for the edenrc file under ~/local/.eden.
Summary:
The headline changes of this revision are:
- Changes the format of the config file from INI to TOML
(the `edenrc` file under `~/local/.eden` has been replaced
with `config.toml`). This revision includes logic for automatically
performing the migration when Eden is restarted.
- Inlines data from `/etc/eden/config.d` into the TOML file.
Historically, the `edenrc` file for a client would contain the
name of the "configuration alias" defined in a config file like
`~/.edenrc` or `/etc/eden/config.d/00-defaults`. When Eden
loaded a client, it would have to first read the `edenrc` and
then reconstitute the rest of the client configuration by
looking up the alias in the set of config files that were used to
create the client in the first place.
This changes things so that all of the data that was being
cross-referenced is now inlined in the client's config file.
This makes loading a config considerably simpler at the cost
of no longer being able to change the config for multiple clients
that were cloned from the same configuration alias in one place.
It was questionable whether being able to modify a client from
a foreign config after it was created was a safe thing to do, anyway.
Eliminating the need for a historic link to the configuration alias
will make it easier to support running `eden clone` on an arbitrary
local Hg or Git repo. So long as `eden clone` can extract enough
information from the local repo to create an appropriate config file
for the new Eden client, there is no need for a configuration alias
to exist a priori.
Since we were already changing the data in the config file, this
seemed like an appropriate time to make the switch from INI to
TOML, as this was something we wanted to do, anyway.
In testing, I discovered a discrepancy between how boost's
`boost::property_tree::ptree` and Python's `ConfigParser` handled
the following section heading:
```
[repository ZtmpZsillyZeden-clone.LIkh32]
```
Apparently `hasSection("repository ZtmpZsillyZeden-clone.LIkh32")`
in boost would fail to find this section. Because
[[https://stackoverflow.com/questions/13109506/are-hyphens-allowed-in-section-definitions-in-ini-files | there is no spec for INI]],
it is not that surprising that boost and `ConfigParser` do not 100% agree
on what they accept. Moving to TOML means we have a configuration
language with the following desirable properties:
- It has a formal spec, unlike INI. This is important because there are parsers
in a wide range of programming languages that, in theory, accept a consistent
input language.
- It is reasonable for humans to write, as it supports comments, unlike JSON.
- It supports nested structures, like maps and arrays, without going crazy
on the input language it supports, unlike YAML.
Eden now depends on the following third-party TOML parsers:
* C++ https://github.com/skystrife/cpptoml
* Python https://github.com/uiri/toml
This revision also changes the organization of `~/local/.eden` slightly. For now,
there is still a `config.json` file, but the values are no longer hashes of the realpath
of the mount. Instead, we take the basename of the realpath and use that as the
name of the directory under `~/local/.eden/clients`. If there is a naming collision, we
add the first available integral suffix. Using the basename makes it easier to
navigate the `~/local/.eden/clients` directory.
Although the `edenrc` file under `~/local/.eden/clients` has been switched from INI
to TOML, the other Eden config files (`~/.edenrc` and `/etc/eden/config.d/*`) still use
INI. Migrating those to TOML will be done in a future revision.
Note this revision allowed us to eliminate `facebook::eden::InterpolatedPropertyTree`
as well as a number of uses of boost due to the elimination of
`ClientConfig::loadConfigData()` in the C++ code. Because `ClientConfig`
no longer does interpolation, a bit of `ClientConfigTest` was deleted as part of
this revision because it is no longer relevant.
Reviewed By: wez
Differential Revision: D6310325
fbshipit-source-id: 2548149c064cdf8e78a3b3ce6fe667ff70f94f84
2017-11-17 00:19:54 +03:00
|
|
|
basename of the specified path. Tries appending an increasing sequence
|
|
|
|
of integers to the basename if there is a collision until it finds an
|
|
|
|
available directory name.
|
2018-05-10 07:33:49 +03:00
|
|
|
"""
|
Change the contents and format for the edenrc file under ~/local/.eden.
Summary:
The headline changes of this revision are:
- Changes the format of the config file from INI to TOML
(the `edenrc` file under `~/local/.eden` has been replaced
with `config.toml`). This revision includes logic for automatically
performing the migration when Eden is restarted.
- Inlines data from `/etc/eden/config.d` into the TOML file.
Historically, the `edenrc` file for a client would contain the
name of the "configuration alias" defined in a config file like
`~/.edenrc` or `/etc/eden/config.d/00-defaults`. When Eden
loaded a client, it would have to first read the `edenrc` and
then reconstitute the rest of the client configuration by
looking up the alias in the set of config files that were used to
create the client in the first place.
This changes things so that all of the data that was being
cross-referenced is now inlined in the client's config file.
This makes loading a config considerably simpler at the cost
of no longer being able to change the config for multiple clients
that were cloned from the same configuration alias in one place.
It was questionable whether being able to modify a client from
a foreign config after it was created was a safe thing to do, anyway.
Eliminating the need for a historic link to the configuration alias
will make it easier to support running `eden clone` on an arbitrary
local Hg or Git repo. So long as `eden clone` can extract enough
information from the local repo to create an appropriate config file
for the new Eden client, there is no need for a configuration alias
to exist a priori.
Since we were already changing the data in the config file, this
seemed like an appropriate time to make the switch from INI to
TOML, as this was something we wanted to do, anyway.
In testing, I discovered a discrepancy between how boost's
`boost::property_tree::ptree` and Python's `ConfigParser` handled
the following section heading:
```
[repository ZtmpZsillyZeden-clone.LIkh32]
```
Apparently `hasSection("repository ZtmpZsillyZeden-clone.LIkh32")`
in boost would fail to find this section. Because
[[https://stackoverflow.com/questions/13109506/are-hyphens-allowed-in-section-definitions-in-ini-files | there is no spec for INI]],
it is not that surprising that boost and `ConfigParser` do not 100% agree
on what they accept. Moving to TOML means we have a configuration
language with the following desirable properties:
- It has a formal spec, unlike INI. This is important because there are parsers
in a wide range of programming languages that, in theory, accept a consistent
input language.
- It is reasonable for humans to write, as it supports comments, unlike JSON.
- It supports nested structures, like maps and arrays, without going crazy
on the input language it supports, unlike YAML.
Eden now depends on the following third-party TOML parsers:
* C++ https://github.com/skystrife/cpptoml
* Python https://github.com/uiri/toml
This revision also changes the organization of `~/local/.eden` slightly. For now,
there is still a `config.json` file, but the values are no longer hashes of the realpath
of the mount. Instead, we take the basename of the realpath and use that as the
name of the directory under `~/local/.eden/clients`. If there is a naming collision, we
add the first available integral suffix. Using the basename makes it easier to
navigate the `~/local/.eden/clients` directory.
Although the `edenrc` file under `~/local/.eden/clients` has been switched from INI
to TOML, the other Eden config files (`~/.edenrc` and `/etc/eden/config.d/*`) still use
INI. Migrating those to TOML will be done in a future revision.
Note this revision allowed us to eliminate `facebook::eden::InterpolatedPropertyTree`
as well as a number of uses of boost due to the elimination of
`ClientConfig::loadConfigData()` in the C++ code. Because `ClientConfig`
no longer does interpolation, a bit of `ClientConfigTest` was deleted as part of
this revision because it is no longer relevant.
Reviewed By: wez
Differential Revision: D6310325
fbshipit-source-id: 2548149c064cdf8e78a3b3ce6fe667ff70f94f84
2017-11-17 00:19:54 +03:00
|
|
|
basename = os.path.basename(path)
|
2018-05-10 07:33:49 +03:00
|
|
|
if basename == "":
|
|
|
|
raise Exception("Suspicious attempt to clone into: %s" % path)
|
2018-01-08 23:03:46 +03:00
|
|
|
|
|
|
|
i = 0
|
|
|
|
while True:
|
Change the contents and format for the edenrc file under ~/local/.eden.
Summary:
The headline changes of this revision are:
- Changes the format of the config file from INI to TOML
(the `edenrc` file under `~/local/.eden` has been replaced
with `config.toml`). This revision includes logic for automatically
performing the migration when Eden is restarted.
- Inlines data from `/etc/eden/config.d` into the TOML file.
Historically, the `edenrc` file for a client would contain the
name of the "configuration alias" defined in a config file like
`~/.edenrc` or `/etc/eden/config.d/00-defaults`. When Eden
loaded a client, it would have to first read the `edenrc` and
then reconstitute the rest of the client configuration by
looking up the alias in the set of config files that were used to
create the client in the first place.
This changes things so that all of the data that was being
cross-referenced is now inlined in the client's config file.
This makes loading a config considerably simpler at the cost
of no longer being able to change the config for multiple clients
that were cloned from the same configuration alias in one place.
It was questionable whether being able to modify a client from
a foreign config after it was created was a safe thing to do, anyway.
Eliminating the need for a historic link to the configuration alias
will make it easier to support running `eden clone` on an arbitrary
local Hg or Git repo. So long as `eden clone` can extract enough
information from the local repo to create an appropriate config file
for the new Eden client, there is no need for a configuration alias
to exist a priori.
Since we were already changing the data in the config file, this
seemed like an appropriate time to make the switch from INI to
TOML, as this was something we wanted to do, anyway.
In testing, I discovered a discrepancy between how boost's
`boost::property_tree::ptree` and Python's `ConfigParser` handled
the following section heading:
```
[repository ZtmpZsillyZeden-clone.LIkh32]
```
Apparently `hasSection("repository ZtmpZsillyZeden-clone.LIkh32")`
in boost would fail to find this section. Because
[[https://stackoverflow.com/questions/13109506/are-hyphens-allowed-in-section-definitions-in-ini-files | there is no spec for INI]],
it is not that surprising that boost and `ConfigParser` do not 100% agree
on what they accept. Moving to TOML means we have a configuration
language with the following desirable properties:
- It has a formal spec, unlike INI. This is important because there are parsers
in a wide range of programming languages that, in theory, accept a consistent
input language.
- It is reasonable for humans to write, as it supports comments, unlike JSON.
- It supports nested structures, like maps and arrays, without going crazy
on the input language it supports, unlike YAML.
Eden now depends on the following third-party TOML parsers:
* C++ https://github.com/skystrife/cpptoml
* Python https://github.com/uiri/toml
This revision also changes the organization of `~/local/.eden` slightly. For now,
there is still a `config.json` file, but the values are no longer hashes of the realpath
of the mount. Instead, we take the basename of the realpath and use that as the
name of the directory under `~/local/.eden/clients`. If there is a naming collision, we
add the first available integral suffix. Using the basename makes it easier to
navigate the `~/local/.eden/clients` directory.
Although the `edenrc` file under `~/local/.eden/clients` has been switched from INI
to TOML, the other Eden config files (`~/.edenrc` and `/etc/eden/config.d/*`) still use
INI. Migrating those to TOML will be done in a future revision.
Note this revision allowed us to eliminate `facebook::eden::InterpolatedPropertyTree`
as well as a number of uses of boost due to the elimination of
`ClientConfig::loadConfigData()` in the C++ code. Because `ClientConfig`
no longer does interpolation, a bit of `ClientConfigTest` was deleted as part of
this revision because it is no longer relevant.
Reviewed By: wez
Differential Revision: D6310325
fbshipit-source-id: 2548149c064cdf8e78a3b3ce6fe667ff70f94f84
2017-11-17 00:19:54 +03:00
|
|
|
if i == 0:
|
|
|
|
dir_name = basename
|
|
|
|
else:
|
2018-05-10 07:33:49 +03:00
|
|
|
dir_name = f"{basename}-{i}"
|
Change the contents and format for the edenrc file under ~/local/.eden.
Summary:
The headline changes of this revision are:
- Changes the format of the config file from INI to TOML
(the `edenrc` file under `~/local/.eden` has been replaced
with `config.toml`). This revision includes logic for automatically
performing the migration when Eden is restarted.
- Inlines data from `/etc/eden/config.d` into the TOML file.
Historically, the `edenrc` file for a client would contain the
name of the "configuration alias" defined in a config file like
`~/.edenrc` or `/etc/eden/config.d/00-defaults`. When Eden
loaded a client, it would have to first read the `edenrc` and
then reconstitute the rest of the client configuration by
looking up the alias in the set of config files that were used to
create the client in the first place.
This changes things so that all of the data that was being
cross-referenced is now inlined in the client's config file.
This makes loading a config considerably simpler at the cost
of no longer being able to change the config for multiple clients
that were cloned from the same configuration alias in one place.
It was questionable whether being able to modify a client from
a foreign config after it was created was a safe thing to do, anyway.
Eliminating the need for a historic link to the configuration alias
will make it easier to support running `eden clone` on an arbitrary
local Hg or Git repo. So long as `eden clone` can extract enough
information from the local repo to create an appropriate config file
for the new Eden client, there is no need for a configuration alias
to exist a priori.
Since we were already changing the data in the config file, this
seemed like an appropriate time to make the switch from INI to
TOML, as this was something we wanted to do, anyway.
In testing, I discovered a discrepancy between how boost's
`boost::property_tree::ptree` and Python's `ConfigParser` handled
the following section heading:
```
[repository ZtmpZsillyZeden-clone.LIkh32]
```
Apparently `hasSection("repository ZtmpZsillyZeden-clone.LIkh32")`
in boost would fail to find this section. Because
[[https://stackoverflow.com/questions/13109506/are-hyphens-allowed-in-section-definitions-in-ini-files | there is no spec for INI]],
it is not that surprising that boost and `ConfigParser` do not 100% agree
on what they accept. Moving to TOML means we have a configuration
language with the following desirable properties:
- It has a formal spec, unlike INI. This is important because there are parsers
in a wide range of programming languages that, in theory, accept a consistent
input language.
- It is reasonable for humans to write, as it supports comments, unlike JSON.
- It supports nested structures, like maps and arrays, without going crazy
on the input language it supports, unlike YAML.
Eden now depends on the following third-party TOML parsers:
* C++ https://github.com/skystrife/cpptoml
* Python https://github.com/uiri/toml
This revision also changes the organization of `~/local/.eden` slightly. For now,
there is still a `config.json` file, but the values are no longer hashes of the realpath
of the mount. Instead, we take the basename of the realpath and use that as the
name of the directory under `~/local/.eden/clients`. If there is a naming collision, we
add the first available integral suffix. Using the basename makes it easier to
navigate the `~/local/.eden/clients` directory.
Although the `edenrc` file under `~/local/.eden/clients` has been switched from INI
to TOML, the other Eden config files (`~/.edenrc` and `/etc/eden/config.d/*`) still use
INI. Migrating those to TOML will be done in a future revision.
Note this revision allowed us to eliminate `facebook::eden::InterpolatedPropertyTree`
as well as a number of uses of boost due to the elimination of
`ClientConfig::loadConfigData()` in the C++ code. Because `ClientConfig`
no longer does interpolation, a bit of `ClientConfigTest` was deleted as part of
this revision because it is no longer relevant.
Reviewed By: wez
Differential Revision: D6310325
fbshipit-source-id: 2548149c064cdf8e78a3b3ce6fe667ff70f94f84
2017-11-17 00:19:54 +03:00
|
|
|
|
2019-03-09 06:02:42 +03:00
|
|
|
client_dir = clients_dir / dir_name
|
Change the contents and format for the edenrc file under ~/local/.eden.
Summary:
The headline changes of this revision are:
- Changes the format of the config file from INI to TOML
(the `edenrc` file under `~/local/.eden` has been replaced
with `config.toml`). This revision includes logic for automatically
performing the migration when Eden is restarted.
- Inlines data from `/etc/eden/config.d` into the TOML file.
Historically, the `edenrc` file for a client would contain the
name of the "configuration alias" defined in a config file like
`~/.edenrc` or `/etc/eden/config.d/00-defaults`. When Eden
loaded a client, it would have to first read the `edenrc` and
then reconstitute the rest of the client configuration by
looking up the alias in the set of config files that were used to
create the client in the first place.
This changes things so that all of the data that was being
cross-referenced is now inlined in the client's config file.
This makes loading a config considerably simpler at the cost
of no longer being able to change the config for multiple clients
that were cloned from the same configuration alias in one place.
It was questionable whether being able to modify a client from
a foreign config after it was created was a safe thing to do, anyway.
Eliminating the need for a historic link to the configuration alias
will make it easier to support running `eden clone` on an arbitrary
local Hg or Git repo. So long as `eden clone` can extract enough
information from the local repo to create an appropriate config file
for the new Eden client, there is no need for a configuration alias
to exist a priori.
Since we were already changing the data in the config file, this
seemed like an appropriate time to make the switch from INI to
TOML, as this was something we wanted to do, anyway.
In testing, I discovered a discrepancy between how boost's
`boost::property_tree::ptree` and Python's `ConfigParser` handled
the following section heading:
```
[repository ZtmpZsillyZeden-clone.LIkh32]
```
Apparently `hasSection("repository ZtmpZsillyZeden-clone.LIkh32")`
in boost would fail to find this section. Because
[[https://stackoverflow.com/questions/13109506/are-hyphens-allowed-in-section-definitions-in-ini-files | there is no spec for INI]],
it is not that surprising that boost and `ConfigParser` do not 100% agree
on what they accept. Moving to TOML means we have a configuration
language with the following desirable properties:
- It has a formal spec, unlike INI. This is important because there are parsers
in a wide range of programming languages that, in theory, accept a consistent
input language.
- It is reasonable for humans to write, as it supports comments, unlike JSON.
- It supports nested structures, like maps and arrays, without going crazy
on the input language it supports, unlike YAML.
Eden now depends on the following third-party TOML parsers:
* C++ https://github.com/skystrife/cpptoml
* Python https://github.com/uiri/toml
This revision also changes the organization of `~/local/.eden` slightly. For now,
there is still a `config.json` file, but the values are no longer hashes of the realpath
of the mount. Instead, we take the basename of the realpath and use that as the
name of the directory under `~/local/.eden/clients`. If there is a naming collision, we
add the first available integral suffix. Using the basename makes it easier to
navigate the `~/local/.eden/clients` directory.
Although the `edenrc` file under `~/local/.eden/clients` has been switched from INI
to TOML, the other Eden config files (`~/.edenrc` and `/etc/eden/config.d/*`) still use
INI. Migrating those to TOML will be done in a future revision.
Note this revision allowed us to eliminate `facebook::eden::InterpolatedPropertyTree`
as well as a number of uses of boost due to the elimination of
`ClientConfig::loadConfigData()` in the C++ code. Because `ClientConfig`
no longer does interpolation, a bit of `ClientConfigTest` was deleted as part of
this revision because it is no longer relevant.
Reviewed By: wez
Differential Revision: D6310325
fbshipit-source-id: 2548149c064cdf8e78a3b3ce6fe667ff70f94f84
2017-11-17 00:19:54 +03:00
|
|
|
try:
|
2019-03-09 06:02:42 +03:00
|
|
|
client_dir.mkdir()
|
Change the contents and format for the edenrc file under ~/local/.eden.
Summary:
The headline changes of this revision are:
- Changes the format of the config file from INI to TOML
(the `edenrc` file under `~/local/.eden` has been replaced
with `config.toml`). This revision includes logic for automatically
performing the migration when Eden is restarted.
- Inlines data from `/etc/eden/config.d` into the TOML file.
Historically, the `edenrc` file for a client would contain the
name of the "configuration alias" defined in a config file like
`~/.edenrc` or `/etc/eden/config.d/00-defaults`. When Eden
loaded a client, it would have to first read the `edenrc` and
then reconstitute the rest of the client configuration by
looking up the alias in the set of config files that were used to
create the client in the first place.
This changes things so that all of the data that was being
cross-referenced is now inlined in the client's config file.
This makes loading a config considerably simpler at the cost
of no longer being able to change the config for multiple clients
that were cloned from the same configuration alias in one place.
It was questionable whether being able to modify a client from
a foreign config after it was created was a safe thing to do, anyway.
Eliminating the need for a historic link to the configuration alias
will make it easier to support running `eden clone` on an arbitrary
local Hg or Git repo. So long as `eden clone` can extract enough
information from the local repo to create an appropriate config file
for the new Eden client, there is no need for a configuration alias
to exist a priori.
Since we were already changing the data in the config file, this
seemed like an appropriate time to make the switch from INI to
TOML, as this was something we wanted to do, anyway.
In testing, I discovered a discrepancy between how boost's
`boost::property_tree::ptree` and Python's `ConfigParser` handled
the following section heading:
```
[repository ZtmpZsillyZeden-clone.LIkh32]
```
Apparently `hasSection("repository ZtmpZsillyZeden-clone.LIkh32")`
in boost would fail to find this section. Because
[[https://stackoverflow.com/questions/13109506/are-hyphens-allowed-in-section-definitions-in-ini-files | there is no spec for INI]],
it is not that surprising that boost and `ConfigParser` do not 100% agree
on what they accept. Moving to TOML means we have a configuration
language with the following desirable properties:
- It has a formal spec, unlike INI. This is important because there are parsers
in a wide range of programming languages that, in theory, accept a consistent
input language.
- It is reasonable for humans to write, as it supports comments, unlike JSON.
- It supports nested structures, like maps and arrays, without going crazy
on the input language it supports, unlike YAML.
Eden now depends on the following third-party TOML parsers:
* C++ https://github.com/skystrife/cpptoml
* Python https://github.com/uiri/toml
This revision also changes the organization of `~/local/.eden` slightly. For now,
there is still a `config.json` file, but the values are no longer hashes of the realpath
of the mount. Instead, we take the basename of the realpath and use that as the
name of the directory under `~/local/.eden/clients`. If there is a naming collision, we
add the first available integral suffix. Using the basename makes it easier to
navigate the `~/local/.eden/clients` directory.
Although the `edenrc` file under `~/local/.eden/clients` has been switched from INI
to TOML, the other Eden config files (`~/.edenrc` and `/etc/eden/config.d/*`) still use
INI. Migrating those to TOML will be done in a future revision.
Note this revision allowed us to eliminate `facebook::eden::InterpolatedPropertyTree`
as well as a number of uses of boost due to the elimination of
`ClientConfig::loadConfigData()` in the C++ code. Because `ClientConfig`
no longer does interpolation, a bit of `ClientConfigTest` was deleted as part of
this revision because it is no longer relevant.
Reviewed By: wez
Differential Revision: D6310325
fbshipit-source-id: 2548149c064cdf8e78a3b3ce6fe667ff70f94f84
2017-11-17 00:19:54 +03:00
|
|
|
return client_dir
|
|
|
|
except OSError as e:
|
|
|
|
if e.errno == errno.EEXIST:
|
|
|
|
# A directory with the specified name already exists: try
|
|
|
|
# again with the next candidate name.
|
2018-01-08 23:03:46 +03:00
|
|
|
i += 1
|
Change the contents and format for the edenrc file under ~/local/.eden.
Summary:
The headline changes of this revision are:
- Changes the format of the config file from INI to TOML
(the `edenrc` file under `~/local/.eden` has been replaced
with `config.toml`). This revision includes logic for automatically
performing the migration when Eden is restarted.
- Inlines data from `/etc/eden/config.d` into the TOML file.
Historically, the `edenrc` file for a client would contain the
name of the "configuration alias" defined in a config file like
`~/.edenrc` or `/etc/eden/config.d/00-defaults`. When Eden
loaded a client, it would have to first read the `edenrc` and
then reconstitute the rest of the client configuration by
looking up the alias in the set of config files that were used to
create the client in the first place.
This changes things so that all of the data that was being
cross-referenced is now inlined in the client's config file.
This makes loading a config considerably simpler at the cost
of no longer being able to change the config for multiple clients
that were cloned from the same configuration alias in one place.
It was questionable whether being able to modify a client from
a foreign config after it was created was a safe thing to do, anyway.
Eliminating the need for a historic link to the configuration alias
will make it easier to support running `eden clone` on an arbitrary
local Hg or Git repo. So long as `eden clone` can extract enough
information from the local repo to create an appropriate config file
for the new Eden client, there is no need for a configuration alias
to exist a priori.
Since we were already changing the data in the config file, this
seemed like an appropriate time to make the switch from INI to
TOML, as this was something we wanted to do, anyway.
In testing, I discovered a discrepancy between how boost's
`boost::property_tree::ptree` and Python's `ConfigParser` handled
the following section heading:
```
[repository ZtmpZsillyZeden-clone.LIkh32]
```
Apparently `hasSection("repository ZtmpZsillyZeden-clone.LIkh32")`
in boost would fail to find this section. Because
[[https://stackoverflow.com/questions/13109506/are-hyphens-allowed-in-section-definitions-in-ini-files | there is no spec for INI]],
it is not that surprising that boost and `ConfigParser` do not 100% agree
on what they accept. Moving to TOML means we have a configuration
language with the following desirable properties:
- It has a formal spec, unlike INI. This is important because there are parsers
in a wide range of programming languages that, in theory, accept a consistent
input language.
- It is reasonable for humans to write, as it supports comments, unlike JSON.
- It supports nested structures, like maps and arrays, without going crazy
on the input language it supports, unlike YAML.
Eden now depends on the following third-party TOML parsers:
* C++ https://github.com/skystrife/cpptoml
* Python https://github.com/uiri/toml
This revision also changes the organization of `~/local/.eden` slightly. For now,
there is still a `config.json` file, but the values are no longer hashes of the realpath
of the mount. Instead, we take the basename of the realpath and use that as the
name of the directory under `~/local/.eden/clients`. If there is a naming collision, we
add the first available integral suffix. Using the basename makes it easier to
navigate the `~/local/.eden/clients` directory.
Although the `edenrc` file under `~/local/.eden/clients` has been switched from INI
to TOML, the other Eden config files (`~/.edenrc` and `/etc/eden/config.d/*`) still use
INI. Migrating those to TOML will be done in a future revision.
Note this revision allowed us to eliminate `facebook::eden::InterpolatedPropertyTree`
as well as a number of uses of boost due to the elimination of
`ClientConfig::loadConfigData()` in the C++ code. Because `ClientConfig`
no longer does interpolation, a bit of `ClientConfigTest` was deleted as part of
this revision because it is no longer relevant.
Reviewed By: wez
Differential Revision: D6310325
fbshipit-source-id: 2548149c064cdf8e78a3b3ce6fe667ff70f94f84
2017-11-17 00:19:54 +03:00
|
|
|
continue
|
|
|
|
raise
|
2016-07-11 23:13:48 +03:00
|
|
|
|
2018-12-20 02:31:10 +03:00
|
|
|
def _post_clone_checkout_setup(
|
2019-02-07 07:04:29 +03:00
|
|
|
self, checkout: "EdenCheckout", commit_id: str
|
2018-05-10 07:33:49 +03:00
|
|
|
) -> None:
|
2019-03-08 22:06:47 +03:00
|
|
|
# First, check to see if the post-clone setup has been run successfully
|
2017-11-15 01:02:00 +03:00
|
|
|
# before.
|
2019-02-05 05:25:47 +03:00
|
|
|
clone_success_path = checkout.state_dir / CLONE_SUCCEEDED
|
2018-12-20 02:31:10 +03:00
|
|
|
is_initial_mount = not clone_success_path.is_file()
|
2019-02-07 07:04:29 +03:00
|
|
|
if is_initial_mount and checkout.get_config().scm_type == "hg":
|
2018-12-20 02:31:10 +03:00
|
|
|
from . import hg_util
|
|
|
|
|
2019-02-07 07:04:28 +03:00
|
|
|
hg_util.setup_hg_dir(checkout, commit_id)
|
2017-11-15 01:02:00 +03:00
|
|
|
|
2018-12-20 02:31:10 +03:00
|
|
|
clone_success_path.touch()
|
2017-11-15 01:02:00 +03:00
|
|
|
|
2019-09-17 17:26:09 +03:00
|
|
|
if checkout.get_config().scm_type == "hg":
|
2021-01-06 01:04:12 +03:00
|
|
|
env = os.environ.copy()
|
|
|
|
# These are set by the par machinery and interfere with Mercurial's
|
|
|
|
# own dynamic library loading.
|
|
|
|
env.pop("DYLD_INSERT_LIBRARIES", None)
|
|
|
|
env.pop("DYLD_LIBRARY_PATH", None)
|
|
|
|
|
2019-09-17 17:26:09 +03:00
|
|
|
subprocess.check_call(
|
|
|
|
[
|
|
|
|
os.environ.get("EDEN_HG_BINARY", "hg"),
|
|
|
|
"debugedenrunpostupdatehook",
|
|
|
|
"-R",
|
2020-01-22 09:07:14 +03:00
|
|
|
str(checkout.path),
|
2021-01-06 01:04:12 +03:00
|
|
|
],
|
|
|
|
env=env,
|
2019-09-17 17:26:09 +03:00
|
|
|
)
|
|
|
|
|
2020-03-12 07:36:11 +03:00
|
|
|
def mount(self, path: Union[Path, str], read_only: bool) -> int:
|
2016-08-04 02:18:27 +03:00
|
|
|
# Load the config info for this client, to make sure we
|
|
|
|
# know about the client.
|
2020-09-19 02:37:56 +03:00
|
|
|
path = Path(path).resolve(strict=False)
|
2016-08-04 02:18:27 +03:00
|
|
|
client_dir = self._get_client_dir_for_mount_point(path)
|
2019-03-09 06:02:42 +03:00
|
|
|
checkout = EdenCheckout(self, path, client_dir)
|
2017-11-17 00:19:57 +03:00
|
|
|
|
2019-02-05 05:25:47 +03:00
|
|
|
# Call checkout.get_config() for the side-effect of it raising an
|
2017-11-17 00:19:57 +03:00
|
|
|
# Exception if the config is in an invalid state.
|
2019-02-05 05:25:47 +03:00
|
|
|
checkout.get_config()
|
2016-08-04 02:18:27 +03:00
|
|
|
|
|
|
|
# Make sure the mount path exists
|
2019-03-09 06:02:42 +03:00
|
|
|
path.mkdir(parents=True, exist_ok=True)
|
2016-08-04 02:18:27 +03:00
|
|
|
|
2017-11-01 03:04:30 +03:00
|
|
|
# Check if it is already mounted.
|
|
|
|
try:
|
2019-03-09 06:02:42 +03:00
|
|
|
root = path / ".eden" / "root"
|
2018-10-27 03:18:01 +03:00
|
|
|
target = readlink_retry_estale(root)
|
2019-03-09 06:02:42 +03:00
|
|
|
if Path(target) == path:
|
2018-05-10 07:33:49 +03:00
|
|
|
print_stderr(
|
2019-03-09 06:02:42 +03:00
|
|
|
f"ERROR: Mount point in use! {path} is already mounted by Eden."
|
2018-05-10 07:33:49 +03:00
|
|
|
)
|
2017-11-01 03:04:30 +03:00
|
|
|
return 1
|
|
|
|
else:
|
|
|
|
# If we are here, MOUNT/.eden/root is a symlink, but it does not
|
|
|
|
# point to MOUNT. This suggests `path` is a subdirectory of an
|
|
|
|
# existing mount, though we should never reach this point
|
|
|
|
# because _get_client_dir_for_mount_point() above should have
|
|
|
|
# already thrown an exception. We return non-zero here just in
|
|
|
|
# case.
|
2018-05-10 07:33:49 +03:00
|
|
|
print_stderr(
|
2019-03-09 06:02:42 +03:00
|
|
|
f"ERROR: Mount point in use! "
|
|
|
|
f"{path} is already mounted by Eden as part of {root}."
|
2018-05-10 07:33:49 +03:00
|
|
|
)
|
2017-11-01 03:04:30 +03:00
|
|
|
return 1
|
|
|
|
except OSError as ex:
|
2020-03-07 01:06:44 +03:00
|
|
|
# - ENOENT is expected if the mount is not mounted.
|
|
|
|
# - We'll get ENOTCONN if the directory was not properly unmounted from a
|
|
|
|
# previous EdenFS instance. Remounting over this directory is okay (even
|
|
|
|
# though ideally we would unmount the old stale mount point to clean it
|
|
|
|
# up).
|
|
|
|
# - EINVAL can happen if .eden/root isn't a symlink. This isn't expected
|
|
|
|
# in most circumstances, but it does mean that the directory isn't
|
|
|
|
# currently an EdenFS checkout.
|
2017-11-01 03:04:30 +03:00
|
|
|
err = ex.errno
|
2020-03-07 01:06:44 +03:00
|
|
|
if err not in (errno.ENOENT, errno.ENOTCONN, errno.EINVAL):
|
2017-11-01 03:04:30 +03:00
|
|
|
raise
|
|
|
|
|
2016-08-04 02:18:27 +03:00
|
|
|
# Ask eden to mount the path
|
2019-01-02 23:55:02 +03:00
|
|
|
mount_info = eden_ttypes.MountArgument(
|
2020-03-12 07:36:11 +03:00
|
|
|
mountPoint=bytes(path), edenClientPath=bytes(client_dir), readOnly=read_only
|
2018-08-11 11:34:44 +03:00
|
|
|
)
|
2020-06-20 04:02:42 +03:00
|
|
|
|
|
|
|
try:
|
2020-09-17 23:23:24 +03:00
|
|
|
with self.get_thrift_client_legacy() as client:
|
2020-06-20 04:02:42 +03:00
|
|
|
client.mount(mount_info)
|
|
|
|
except eden_ttypes.EdenError as ex:
|
|
|
|
if "already mounted" in str(ex):
|
|
|
|
print_stderr(
|
|
|
|
f"ERROR: Mount point in use! {path} is already mounted by Eden."
|
|
|
|
)
|
|
|
|
return 1
|
2016-08-04 02:18:27 +03:00
|
|
|
|
2018-01-29 22:10:07 +03:00
|
|
|
return 0
|
|
|
|
|
2018-06-23 22:15:55 +03:00
|
|
|
def unmount(self, path: str) -> None:
|
|
|
|
"""Ask edenfs to unmount the specified checkout."""
|
2020-06-05 22:37:01 +03:00
|
|
|
# In some cases edenfs can take a long time unmounting while it waits for
|
|
|
|
# inodes to become unreferenced. Ideally we should have edenfs timeout and
|
|
|
|
# forcibly clean up the mount point in this situation.
|
|
|
|
#
|
|
|
|
# For now at least time out here so the CLI commands do not hang in this
|
|
|
|
# case.
|
2020-09-17 23:23:24 +03:00
|
|
|
with self.get_thrift_client_legacy(timeout=15) as client:
|
2018-11-14 23:13:46 +03:00
|
|
|
client.unmount(os.fsencode(path))
|
2016-08-04 02:18:27 +03:00
|
|
|
|
2021-01-26 22:09:12 +03:00
|
|
|
def destroy_mount(
|
|
|
|
self, path: Union[Path, str], preserve_mount_point: bool = False
|
|
|
|
) -> None:
|
2018-06-23 22:15:55 +03:00
|
|
|
"""Delete the specified mount point from the configuration file and remove
|
|
|
|
the mount directory, if it exists.
|
|
|
|
|
|
|
|
This should normally be called after unmounting the mount point.
|
|
|
|
"""
|
2019-03-09 06:02:42 +03:00
|
|
|
path = Path(path)
|
2018-06-23 22:15:55 +03:00
|
|
|
shutil.rmtree(self._get_client_dir_for_mount_point(path))
|
|
|
|
self._remove_path_from_directory_map(path)
|
2016-08-04 02:18:27 +03:00
|
|
|
|
2020-08-13 18:24:56 +03:00
|
|
|
if sys.platform != "win32":
|
|
|
|
# Delete the mount point
|
|
|
|
# It should normally contain the readme file that we put there, but nothing
|
|
|
|
# else. We only delete these specific files for now rather than using
|
|
|
|
# shutil.rmtree() to avoid deleting files we did not create.
|
|
|
|
#
|
|
|
|
# Previous versions of Eden made the mount point directory read-only
|
|
|
|
# as part of "eden clone". Make sure it is writable now so we can clean it up.
|
|
|
|
path.chmod(0o755)
|
|
|
|
try:
|
|
|
|
(path / NOT_MOUNTED_README_PATH).unlink()
|
|
|
|
except OSError as ex:
|
|
|
|
if ex.errno != errno.ENOENT:
|
|
|
|
raise
|
2021-01-26 22:09:12 +03:00
|
|
|
if not preserve_mount_point:
|
|
|
|
path.rmdir()
|
2020-08-13 18:24:56 +03:00
|
|
|
else:
|
|
|
|
# On Windows, the mount point contains ProjectedFS placeholder and
|
|
|
|
# files, remove all of them.
|
|
|
|
|
|
|
|
shutil.rmtree(path, ignore_errors=True)
|
|
|
|
if not path.exists():
|
|
|
|
return
|
|
|
|
|
|
|
|
# Somehow, we couldn't remove some of the files, sleep a bit and retry
|
|
|
|
time.sleep(0.5)
|
|
|
|
|
|
|
|
errors = []
|
|
|
|
|
|
|
|
def collect_errors(_f, path, ex):
|
2021-01-08 02:58:49 +03:00
|
|
|
errors.append((path, ex[1]))
|
2020-08-13 18:24:56 +03:00
|
|
|
|
|
|
|
shutil.rmtree(path, onerror=collect_errors)
|
|
|
|
if not path.exists():
|
|
|
|
return
|
|
|
|
|
|
|
|
print(f"Removing {path} failed, the following files couldn't be removed:")
|
|
|
|
for f in errors:
|
|
|
|
print(f"{f[0]}")
|
|
|
|
|
|
|
|
raise errors[0][1]
|
2016-05-28 03:40:10 +03:00
|
|
|
|
2018-10-09 23:55:16 +03:00
|
|
|
def check_health(self, timeout: Optional[float] = None) -> HealthStatus:
|
2018-05-10 07:33:49 +03:00
|
|
|
"""
|
2016-06-18 01:11:04 +03:00
|
|
|
Get the status of the edenfs daemon.
|
|
|
|
|
|
|
|
Returns a HealthStatus object containing health information.
|
2018-05-10 07:33:49 +03:00
|
|
|
"""
|
2018-10-09 23:55:16 +03:00
|
|
|
return util.check_health(
|
2020-09-17 23:23:24 +03:00
|
|
|
self.get_thrift_client_legacy, self._config_dir, timeout=timeout
|
2018-10-09 23:55:16 +03:00
|
|
|
)
|
Write the PID to the lockfile and update `eden health` to use it.
Summary:
We have encountered cases where `eden health` reported
`"edenfs not healthy: edenfs not running"` even though the `edenfs` process is
still running. Because the existing implementation of `eden health` bases its
health check on the output of a `getStatus()` Thrift call, it will erroneously
report `"edenfs not running"` even if Eden is running but its Thrift server is
not running. This type of false negative could occur if `edenfs` has shutdown
the Thrift server, but not the rest of the process (quite possibly, its
shutdown is blocked on calls to `umount2()`).
This is further problematic because `eden daemon` checks `eden health`
before attempting to start the daemon. If it gets a false negative, then
`eden daemon` will forge ahead, trying to launch a new instance of the daemon,
but it will fail with a nasty error like the following:
```
I1017 11:59:25.188414 3064499 main.cpp:81] Starting edenfs. UID=5256, GID=100, PID=3064499
terminate called after throwing an instance of 'std::runtime_error'
what(): another instance of Eden appears to be running for /home/mbolin/local/.eden
*** Aborted at 1508266765 (Unix time, try 'date -d 1508266765') ***
*** Signal 6 (SIGABRT) (0x1488002ec2b3) received by PID 3064499 (pthread TID 0x7fd0d3787d40) (linux TID 3064499) (maybe from PID 30644
99, UID 5256), stack trace: ***
@ 000000000290d3cd folly::symbolizer::(anonymous namespace)::signalHandler(int, siginfo_t*, void*)
@ 00007fd0d133cacf (unknown)
@ 00007fd0d093e7c8 __GI_raise
@ 00007fd0d0940590 __GI_abort
@ 00007fd0d1dfeecc __gnu_cxx::__verbose_terminate_handler()
@ 00007fd0d1dfcdc5 __cxxabiv1::__terminate(void (*)())
@ 00007fd0d1dfce10 std::terminate()
@ 00007fd0d1dfd090 __cxa_throw
@ 00000000015fe8ca facebook::eden::EdenServer::acquireEdenLock()
@ 000000000160f27b facebook::eden::EdenServer::prepare()
@ 00000000016107d5 facebook::eden::EdenServer::run()
@ 000000000042c4ee main
@ 00007fd0d0929857 __libc_start_main
@ 0000000000548ad8 _start
Aborted
```
By providing more accurate information to `eden daemon`, if the user tries to
run it while the daemon is already running, they will get a more polite error
like the following:
```
error: edenfs is already running (pid 274205)
```
This revision addresses this issue by writing the PID of `edenfs` in the
lockfile. It updated the implementation of `eden health` to use the PID in the
lockfile to assess the health of Eden if the call to `getStatus()` fails. It
does this by running:
```
ps -p PID -o comm=
```
and applying some heuristics on the output to assess whether the command
associated with that process is the `edenfs` command. If it is, then
`eden health` reports the status as `STOPPED` whereas previously it would report
it as `DEAD`.
Reviewed By: wez
Differential Revision: D6086473
fbshipit-source-id: 825421a6818b56ddd7deea257a92c070c2232bdd
2017-10-18 21:18:43 +03:00
|
|
|
|
2019-03-09 06:02:42 +03:00
|
|
|
def get_log_path(self) -> Path:
|
|
|
|
return self._config_dir / "logs" / "edenfs.log"
|
2016-06-16 22:43:25 +03:00
|
|
|
|
2018-12-13 03:38:20 +03:00
|
|
|
def get_checkout_config_for_path(self, path: str) -> Optional[CheckoutConfig]:
|
2018-05-10 07:33:49 +03:00
|
|
|
client_link = os.path.join(path, ".eden", "client")
|
2018-04-24 23:17:19 +03:00
|
|
|
try:
|
2018-10-27 03:18:01 +03:00
|
|
|
client_dir = readlink_retry_estale(client_link)
|
2018-04-24 23:17:19 +03:00
|
|
|
except OSError:
|
2017-11-17 00:20:01 +03:00
|
|
|
return None
|
2018-04-24 23:17:19 +03:00
|
|
|
|
2019-02-05 05:25:47 +03:00
|
|
|
checkout = EdenCheckout(self, Path(path), Path(client_dir))
|
|
|
|
return checkout.get_config()
|
2017-11-17 00:20:01 +03:00
|
|
|
|
2019-01-26 03:24:06 +03:00
|
|
|
def get_checkouts(self) -> List["EdenCheckout"]:
|
|
|
|
"""Return information about all configured checkouts defined in Eden's
|
|
|
|
configuration file."""
|
|
|
|
dir_map = self._get_directory_map()
|
|
|
|
checkouts: List[EdenCheckout] = []
|
|
|
|
clients_dir = Path(self._get_clients_dir())
|
|
|
|
for mount_path, client_name in dir_map.items():
|
|
|
|
checkout_data_dir = clients_dir / client_name
|
2019-03-09 06:02:42 +03:00
|
|
|
checkouts.append(EdenCheckout(self, mount_path, checkout_data_dir))
|
2019-01-26 03:24:06 +03:00
|
|
|
|
|
|
|
return checkouts
|
|
|
|
|
2020-01-30 22:13:33 +03:00
|
|
|
def get_hg_repo(self, path: Path) -> util.HgRepo:
|
|
|
|
return util.HgRepo(str(path))
|
2019-12-19 21:46:11 +03:00
|
|
|
|
2019-03-09 06:02:42 +03:00
|
|
|
def _get_directory_map(self) -> Dict[Path, str]:
|
2018-05-10 07:33:49 +03:00
|
|
|
"""
|
2016-07-07 02:14:30 +03:00
|
|
|
Parse config.json which holds a mapping of mount paths to their
|
|
|
|
respective client directory and return contents in a dictionary.
|
2018-05-10 07:33:49 +03:00
|
|
|
"""
|
2019-03-09 06:02:42 +03:00
|
|
|
directory_map = self._config_dir / CONFIG_JSON
|
|
|
|
try:
|
|
|
|
with directory_map.open() as f:
|
2018-03-29 08:10:41 +03:00
|
|
|
data = json.load(f)
|
2019-03-09 06:02:42 +03:00
|
|
|
except OSError as ex:
|
|
|
|
if ex.errno != errno.ENOENT:
|
|
|
|
raise
|
|
|
|
data = {}
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
raise Exception(f"invalid JSON data found in {directory_map}")
|
|
|
|
|
|
|
|
if not isinstance(data, dict):
|
|
|
|
raise Exception(f"invalid data found in {directory_map}")
|
|
|
|
|
|
|
|
result: Dict[Path, str] = {}
|
|
|
|
for k, v in data.items():
|
|
|
|
if not isinstance(k, str) or not isinstance(v, str):
|
|
|
|
raise Exception(f"invalid data found in {directory_map}")
|
|
|
|
result[Path(k)] = v
|
|
|
|
|
|
|
|
return result
|
2016-07-07 02:14:30 +03:00
|
|
|
|
2019-03-09 06:02:42 +03:00
|
|
|
def _add_path_to_directory_map(self, path: Path, dir_name: str) -> None:
|
2016-07-07 02:14:30 +03:00
|
|
|
config_data = self._get_directory_map()
|
|
|
|
if path in config_data:
|
2018-05-10 07:33:49 +03:00
|
|
|
raise Exception("mount path %s already exists." % path)
|
2016-07-07 02:14:30 +03:00
|
|
|
config_data[path] = dir_name
|
|
|
|
self._write_directory_map(config_data)
|
|
|
|
|
2019-03-09 06:02:42 +03:00
|
|
|
def _remove_path_from_directory_map(self, path: Path) -> None:
|
2016-07-07 02:14:30 +03:00
|
|
|
config_data = self._get_directory_map()
|
|
|
|
if path in config_data:
|
|
|
|
del config_data[path]
|
|
|
|
self._write_directory_map(config_data)
|
|
|
|
|
2019-03-09 06:02:42 +03:00
|
|
|
def _write_directory_map(self, config_data: Dict[Path, str]) -> None:
|
|
|
|
json_data = {str(path): name for path, name in config_data.items()}
|
2019-09-24 02:35:01 +03:00
|
|
|
contents = json.dumps(json_data, indent=2, sort_keys=True) + "\n"
|
|
|
|
write_file_atomically(self._config_dir / CONFIG_JSON, contents.encode())
|
2016-07-07 02:14:30 +03:00
|
|
|
|
2019-03-09 06:02:42 +03:00
|
|
|
def _get_client_dir_for_mount_point(self, path: Path) -> Path:
|
2016-10-01 05:05:46 +03:00
|
|
|
# The caller is responsible for making sure the path is already
|
|
|
|
# a normalized, absolute path.
|
2019-03-09 06:02:42 +03:00
|
|
|
assert path.is_absolute()
|
2016-10-01 05:05:46 +03:00
|
|
|
|
2016-07-07 02:14:30 +03:00
|
|
|
config_data = self._get_directory_map()
|
|
|
|
if path not in config_data:
|
2019-03-09 06:02:42 +03:00
|
|
|
raise Exception(f"could not find mount path {path}")
|
|
|
|
return self._get_clients_dir() / config_data[path]
|
2016-07-07 02:14:30 +03:00
|
|
|
|
2019-03-09 06:02:42 +03:00
|
|
|
def _get_clients_dir(self) -> Path:
|
|
|
|
return self._config_dir / CLIENTS_DIR
|
2016-05-12 23:43:17 +03:00
|
|
|
|
2017-12-15 00:42:05 +03:00
|
|
|
def get_server_build_info(self) -> Dict[str, str]:
|
2020-09-17 23:23:24 +03:00
|
|
|
with self.get_thrift_client_legacy() as client:
|
2020-09-17 03:46:15 +03:00
|
|
|
return client.getRegexExportedValues("^build_.*")
|
2017-12-15 00:42:05 +03:00
|
|
|
|
2017-12-15 08:07:22 +03:00
|
|
|
def get_uptime(self) -> datetime.timedelta:
|
|
|
|
now = datetime.datetime.now()
|
2020-09-17 23:23:24 +03:00
|
|
|
with self.get_thrift_client_legacy() as client:
|
2017-12-15 08:07:22 +03:00
|
|
|
since_in_seconds = client.aliveSince()
|
|
|
|
since = datetime.datetime.fromtimestamp(since_in_seconds)
|
|
|
|
return now - since
|
|
|
|
|
2020-10-31 02:38:26 +03:00
|
|
|
def do_uptime(self, pretty: bool, out: Optional[IO[bytes]] = None) -> None:
|
|
|
|
if out is None:
|
|
|
|
out = sys.stdout.buffer
|
|
|
|
|
|
|
|
health_info = self.check_health()
|
|
|
|
edenfs_pid = health_info.pid
|
|
|
|
if edenfs_pid is None:
|
|
|
|
running_details = f"{health_info.detail}\n"
|
|
|
|
out.write(running_details.encode())
|
|
|
|
return
|
|
|
|
|
|
|
|
uptime = self.get_uptime() # Check if uptime is negative?
|
|
|
|
days = uptime.days
|
|
|
|
hours, remainder = divmod(uptime.seconds, 3600)
|
|
|
|
minutes, seconds = divmod(remainder, 60)
|
|
|
|
|
|
|
|
if pretty:
|
|
|
|
if not health_info.is_healthy():
|
|
|
|
not_healthy = f"edenfs (pid: {edenfs_pid}) is not healthy\n"
|
|
|
|
out.write(not_healthy.encode())
|
|
|
|
|
|
|
|
pretty_uptime = f"edenfs uptime (pid {edenfs_pid}): {datetime.timedelta(days=days, hours=hours, minutes=minutes, seconds=seconds)}\n"
|
|
|
|
out.write(pretty_uptime.encode())
|
|
|
|
|
|
|
|
else:
|
|
|
|
out.write(b"%dd:%02dh:%02dm:%02ds\n" % (days, hours, minutes, seconds))
|
|
|
|
|
2021-03-12 20:30:20 +03:00
|
|
|
def read_local_config(self) -> configutil.EdenConfigParser:
|
|
|
|
return self.read_configs([self.user_config_path])
|
|
|
|
|
|
|
|
def write_local_config(self, config: configutil.EdenConfigParser) -> None:
|
|
|
|
"""
|
|
|
|
Writes TOML config to the local config path.
|
|
|
|
NOTE: this method will write an empty file if the config is empty
|
|
|
|
"""
|
|
|
|
write_file_atomically(
|
|
|
|
self.user_config_path, toml.dumps(config.to_raw_dict()).encode()
|
|
|
|
)
|
|
|
|
|
2016-05-12 23:43:17 +03:00
|
|
|
|
2018-08-22 21:05:42 +03:00
|
|
|
class EdenCheckout:
|
|
|
|
"""Information about a particular Eden checkout."""
|
|
|
|
|
|
|
|
def __init__(self, instance: EdenInstance, path: Path, state_dir: Path) -> None:
|
|
|
|
self.instance = instance
|
|
|
|
self.path = path
|
|
|
|
self.state_dir = state_dir
|
2019-01-29 03:43:25 +03:00
|
|
|
self._config: Optional[CheckoutConfig] = None
|
2018-08-22 21:05:42 +03:00
|
|
|
|
|
|
|
def __repr__(self) -> str:
|
|
|
|
return f"EdenCheckout({self.instance!r}, {self.path!r}, {self.state_dir!r})"
|
|
|
|
|
|
|
|
def get_relative_path(self, path: Path, already_resolved: bool = False) -> Path:
|
|
|
|
"""Compute the relative path to a given location inside an eden checkout.
|
|
|
|
|
|
|
|
If the checkout is currently mounted this function is able to correctly resolve
|
|
|
|
paths that refer into the checkout via alternative bind mount locations.
|
|
|
|
e.g. if the checkout is located at "/home/user/foo/eden_checkout" but
|
|
|
|
"/home/user" is also bind-mounted to "/data/user" this will still be able to
|
|
|
|
correctly resolve an input path of "/data/user/foo/eden_checkout/test"
|
|
|
|
"""
|
|
|
|
if not already_resolved:
|
2020-09-19 02:37:56 +03:00
|
|
|
path = path.resolve(strict=False)
|
2018-08-22 21:05:42 +03:00
|
|
|
|
|
|
|
# First try using path.relative_to()
|
|
|
|
# This should work in the common case
|
|
|
|
try:
|
|
|
|
return path.relative_to(self.path)
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
# path.relative_to() may fail if the checkout is bind-mounted to an alternate
|
|
|
|
# location, and the input path points into it using the bind mount location.
|
|
|
|
# In this case search upwards from the input path looking for the checkout root.
|
|
|
|
try:
|
|
|
|
path_stat = path.lstat()
|
|
|
|
except OSError as ex:
|
|
|
|
raise Exception(
|
|
|
|
f"unable to stat {path} to find relative location inside "
|
|
|
|
f"checkout {self.path}: {ex}"
|
|
|
|
)
|
|
|
|
|
|
|
|
try:
|
|
|
|
root_stat = self.path.lstat()
|
|
|
|
except OSError as ex:
|
|
|
|
raise Exception(f"unable to stat checkout at {self.path}: {ex}")
|
|
|
|
|
|
|
|
if (path_stat.st_dev, path_stat.st_ino) == (root_stat.st_dev, root_stat.st_ino):
|
|
|
|
# This is the checkout root
|
|
|
|
return Path()
|
|
|
|
|
2020-04-14 05:34:31 +03:00
|
|
|
curdir = path.parent
|
2018-08-22 21:05:42 +03:00
|
|
|
path_parts = [path.name]
|
|
|
|
while True:
|
|
|
|
stat = curdir.lstat()
|
|
|
|
if (stat.st_dev, stat.st_ino) == (root_stat.st_dev, root_stat.st_ino):
|
2018-11-13 07:23:19 +03:00
|
|
|
path_parts.reverse()
|
|
|
|
return Path(*path_parts)
|
2018-08-22 21:05:42 +03:00
|
|
|
|
|
|
|
if curdir.parent == curdir:
|
|
|
|
raise Exception(
|
|
|
|
f"unable to determine relative location of {path} "
|
|
|
|
f"inside {self.path}"
|
|
|
|
)
|
|
|
|
|
|
|
|
path_parts.append(curdir.name)
|
2020-04-14 05:34:31 +03:00
|
|
|
curdir = curdir.parent
|
2018-08-22 21:05:42 +03:00
|
|
|
|
2021-01-21 02:54:20 +03:00
|
|
|
# only for use in unit tests, in production the config should always be read
|
|
|
|
# from disk
|
|
|
|
def set_config(self, config: CheckoutConfig) -> None:
|
|
|
|
self._config = config
|
|
|
|
|
2019-01-29 03:43:25 +03:00
|
|
|
def get_config(self) -> CheckoutConfig:
|
2020-04-10 23:55:41 +03:00
|
|
|
config = self._config
|
|
|
|
if config is None:
|
|
|
|
config = self._read_config()
|
|
|
|
self._config = config
|
|
|
|
return config
|
2019-01-29 03:43:25 +03:00
|
|
|
|
2019-02-05 05:25:47 +03:00
|
|
|
def save_config(self, checkout_config: CheckoutConfig) -> None:
|
|
|
|
# Store information about the mount in the config.toml file.
|
2019-06-26 04:39:31 +03:00
|
|
|
|
|
|
|
# This is a little gross, but only needs to live long enough
|
|
|
|
# to swing through migrating folks away from the legacy
|
|
|
|
# configuration.
|
|
|
|
|
2019-12-04 21:42:07 +03:00
|
|
|
redirections = {k: str(v) for k, v in checkout_config.redirections.items()}
|
2019-02-05 05:25:47 +03:00
|
|
|
config_data = {
|
|
|
|
"repository": {
|
2020-04-21 07:01:47 +03:00
|
|
|
# TODO: replace is needed to workaround a bug in toml
|
|
|
|
"path": str(checkout_config.backing_repo).replace("\\", "/"),
|
2019-02-05 05:25:47 +03:00
|
|
|
"type": checkout_config.scm_type,
|
2020-12-15 19:03:23 +03:00
|
|
|
"guid": checkout_config.guid,
|
2021-02-05 07:08:38 +03:00
|
|
|
"protocol": checkout_config.mount_protocol,
|
2021-02-11 07:09:33 +03:00
|
|
|
"case-sensitive": checkout_config.case_sensitive,
|
2021-02-23 22:33:07 +03:00
|
|
|
"require-utf8-path": checkout_config.require_utf8_path,
|
2019-02-05 05:25:47 +03:00
|
|
|
},
|
2019-06-26 04:39:31 +03:00
|
|
|
"redirections": redirections,
|
2020-09-24 19:52:59 +03:00
|
|
|
"profiles": {"active": checkout_config.active_prefetch_profiles},
|
2019-02-05 05:25:47 +03:00
|
|
|
}
|
2019-06-26 04:39:31 +03:00
|
|
|
|
2019-10-01 00:12:57 +03:00
|
|
|
util.write_file_atomically(
|
|
|
|
self._config_path(), toml.dumps(config_data).encode()
|
|
|
|
)
|
2019-02-05 05:25:47 +03:00
|
|
|
|
|
|
|
# Update our local config cache
|
|
|
|
self._config = checkout_config
|
|
|
|
|
|
|
|
def _config_path(self) -> Path:
|
|
|
|
return self.state_dir / MOUNT_CONFIG
|
|
|
|
|
|
|
|
def _read_config(self) -> CheckoutConfig:
|
|
|
|
"""Returns CheckoutConfig or raises an Exception if the config.toml
|
|
|
|
under self.state_dir is not properly formatted or does not exist.
|
|
|
|
"""
|
|
|
|
config_path = self._config_path()
|
2019-06-26 04:39:31 +03:00
|
|
|
config = load_toml_config(config_path)
|
2019-02-05 05:25:47 +03:00
|
|
|
repo_field = config.get("repository")
|
|
|
|
if isinstance(repo_field, dict):
|
|
|
|
repository = repo_field
|
|
|
|
else:
|
|
|
|
raise Exception(f"{config_path} is missing [repository]")
|
|
|
|
|
|
|
|
def get_field(key: str) -> str:
|
|
|
|
value = repository.get(key)
|
|
|
|
if isinstance(value, str):
|
|
|
|
return value
|
|
|
|
raise Exception(f"{config_path} is missing {key} in " "[repository]")
|
|
|
|
|
|
|
|
scm_type = get_field("type")
|
|
|
|
if scm_type not in SUPPORTED_REPOS:
|
|
|
|
raise Exception(
|
|
|
|
f'repository "{config_path}" has unsupported type ' f'"{scm_type}"'
|
|
|
|
)
|
|
|
|
|
2021-02-05 07:08:38 +03:00
|
|
|
mount_protocol = repository.get("protocol")
|
|
|
|
if not isinstance(mount_protocol, str):
|
|
|
|
mount_protocol = "prjfs" if sys.platform == "win32" else "fuse"
|
|
|
|
if mount_protocol not in SUPPORTED_MOUNT_PROTOCOLS:
|
|
|
|
raise Exception(
|
|
|
|
f'repository "{config_path}" has unsupported mount protocol '
|
|
|
|
f'"{mount_protocol}"'
|
|
|
|
)
|
|
|
|
|
2020-12-15 19:03:23 +03:00
|
|
|
guid = repository.get("guid")
|
|
|
|
if not isinstance(guid, str):
|
|
|
|
guid = str(uuid.uuid4())
|
|
|
|
|
2021-02-11 07:09:33 +03:00
|
|
|
case_sensitive = repository.get("case-sensitive")
|
|
|
|
if not isinstance(case_sensitive, bool):
|
|
|
|
# For existing repositories, keep it case sensitive
|
|
|
|
case_sensitive = sys.platform != "win32"
|
|
|
|
|
2021-02-23 22:33:07 +03:00
|
|
|
require_utf8_path = repository.get("require-utf8-path")
|
|
|
|
if not isinstance(require_utf8_path, bool):
|
|
|
|
# Existing repositories may have non-utf8 files, thus allow them.
|
|
|
|
require_utf8_path = True
|
|
|
|
|
2019-06-26 04:39:31 +03:00
|
|
|
redirections = {}
|
|
|
|
redirections_dict = config.get("redirections")
|
|
|
|
|
|
|
|
if redirections_dict is not None:
|
2020-03-25 21:44:05 +03:00
|
|
|
from eden.fs.cli.redirect import RedirectionType # noqa: F811
|
2019-06-26 04:39:31 +03:00
|
|
|
|
|
|
|
if not isinstance(redirections_dict, dict):
|
|
|
|
raise Exception(f"{config_path} has an invalid [redirections] section")
|
|
|
|
for key, value in redirections_dict.items():
|
|
|
|
if not isinstance(value, str):
|
|
|
|
raise Exception(
|
|
|
|
f"{config_path} has invalid value in "
|
|
|
|
f"[redirections] for {key}: {value} "
|
|
|
|
"(string expected)"
|
|
|
|
)
|
|
|
|
try:
|
|
|
|
redirections[key] = RedirectionType.from_arg_str(value)
|
|
|
|
except ValueError as exc:
|
|
|
|
raise Exception(
|
|
|
|
f"{config_path} has invalid value in "
|
|
|
|
f"[redirections] for {key}: {value} "
|
|
|
|
f"{str(exc)}"
|
|
|
|
)
|
|
|
|
|
2020-09-24 19:52:59 +03:00
|
|
|
prefetch_profiles = []
|
|
|
|
prefetch_profiles_list = config.get("profiles")
|
|
|
|
|
|
|
|
if prefetch_profiles_list is not None:
|
|
|
|
prefetch_profiles_list = prefetch_profiles_list.get("active")
|
|
|
|
if prefetch_profiles_list is not None:
|
|
|
|
if not isinstance(prefetch_profiles_list, list):
|
|
|
|
raise Exception(f"{config_path} has an invalid [profiles] section")
|
|
|
|
for profile in prefetch_profiles_list:
|
|
|
|
if not isinstance(profile, str):
|
|
|
|
raise Exception(
|
|
|
|
f"{config_path} has invalid value in "
|
|
|
|
f"[profiles] {profile} (string expected)"
|
|
|
|
)
|
|
|
|
|
|
|
|
prefetch_profiles.append(profile)
|
|
|
|
|
2019-02-05 05:25:47 +03:00
|
|
|
return CheckoutConfig(
|
2019-02-07 07:04:29 +03:00
|
|
|
backing_repo=Path(get_field("path")),
|
2019-02-05 05:25:47 +03:00
|
|
|
scm_type=scm_type,
|
2020-12-15 19:03:23 +03:00
|
|
|
guid=guid,
|
2021-02-11 07:09:33 +03:00
|
|
|
case_sensitive=case_sensitive,
|
2021-02-23 22:33:07 +03:00
|
|
|
require_utf8_path=require_utf8_path,
|
2021-02-05 07:08:38 +03:00
|
|
|
mount_protocol=mount_protocol,
|
2019-06-26 04:39:31 +03:00
|
|
|
redirections=redirections,
|
2019-02-05 05:25:47 +03:00
|
|
|
default_revision=(
|
|
|
|
repository.get("default-revision") or DEFAULT_REVISION[scm_type]
|
|
|
|
),
|
2020-09-24 19:52:59 +03:00
|
|
|
active_prefetch_profiles=prefetch_profiles,
|
2019-02-05 05:25:47 +03:00
|
|
|
)
|
|
|
|
|
2019-01-29 03:43:25 +03:00
|
|
|
def get_snapshot(self) -> str:
|
|
|
|
"""Return the hex version of the parent hash in the SNAPSHOT file."""
|
2019-02-05 05:25:47 +03:00
|
|
|
snapshot_path = self.state_dir / SNAPSHOT
|
|
|
|
with snapshot_path.open("rb") as f:
|
|
|
|
assert f.read(8) == SNAPSHOT_MAGIC
|
|
|
|
return binascii.hexlify(f.read(20)).decode("utf-8")
|
|
|
|
|
|
|
|
def save_snapshot(self, commid_id: str) -> None:
|
|
|
|
"""Write a new parent commit ID into the SNAPSOHT file."""
|
|
|
|
snapshot_path = self.state_dir / SNAPSHOT
|
|
|
|
assert len(commid_id) == 40
|
|
|
|
commit_bin = binascii.unhexlify(commid_id)
|
2019-11-08 04:23:37 +03:00
|
|
|
write_file_atomically(snapshot_path, SNAPSHOT_MAGIC + commit_bin)
|
2019-01-29 03:43:25 +03:00
|
|
|
|
2020-01-30 22:13:33 +03:00
|
|
|
def get_backing_repo(self) -> util.HgRepo:
|
|
|
|
repo_path = self.get_config().backing_repo
|
|
|
|
return self.instance.get_hg_repo(repo_path)
|
|
|
|
|
2020-12-09 19:46:39 +03:00
|
|
|
def activate_profile(
|
|
|
|
self, profile: str, telemetry_sample: telemetry.TelemetrySample
|
|
|
|
) -> int:
|
2020-09-24 19:52:59 +03:00
|
|
|
"""Add a profile to the config (read the config file and write it back
|
|
|
|
with profile added). Returns 0 on sucess and anything else on failure.
|
|
|
|
Note this should print information on why it failed if this is not
|
|
|
|
returning 0."""
|
|
|
|
|
|
|
|
old_config = self.get_config()
|
|
|
|
old_active_profiles = old_config.active_prefetch_profiles
|
|
|
|
if profile in old_active_profiles:
|
|
|
|
print(f"Profile {profile} already activated.")
|
2020-12-09 19:46:39 +03:00
|
|
|
telemetry_sample.fail(f"Profile {profile} already activated.")
|
2020-09-24 19:52:59 +03:00
|
|
|
return 1
|
|
|
|
|
|
|
|
new_active_profiles = old_active_profiles.copy()
|
|
|
|
new_active_profiles.append(profile)
|
|
|
|
|
|
|
|
new_config = CheckoutConfig(
|
|
|
|
backing_repo=old_config.backing_repo,
|
|
|
|
scm_type=old_config.scm_type,
|
2020-12-15 19:03:23 +03:00
|
|
|
guid=old_config.guid,
|
2021-02-11 07:09:33 +03:00
|
|
|
case_sensitive=old_config.case_sensitive,
|
2021-02-23 22:33:07 +03:00
|
|
|
require_utf8_path=old_config.require_utf8_path,
|
2021-02-05 07:08:38 +03:00
|
|
|
mount_protocol=old_config.mount_protocol,
|
2020-09-24 19:52:59 +03:00
|
|
|
redirections=old_config.redirections,
|
|
|
|
default_revision=old_config.default_revision,
|
|
|
|
active_prefetch_profiles=new_active_profiles,
|
|
|
|
)
|
|
|
|
|
|
|
|
self.save_config(new_config)
|
|
|
|
return 0
|
|
|
|
|
2020-12-09 19:46:39 +03:00
|
|
|
def deactivate_profile(
|
|
|
|
self, profile: str, telemetry_sample: telemetry.TelemetrySample
|
|
|
|
) -> int:
|
2020-09-24 19:52:59 +03:00
|
|
|
"""Remove a profile to the config (read the config file and write it back
|
|
|
|
with profile added). Returns 0 on sucess and anything else on failure.
|
|
|
|
Note this should print information on why it failed if this is not
|
|
|
|
returning 0."""
|
|
|
|
|
|
|
|
old_config = self.get_config()
|
|
|
|
old_active_profiles = old_config.active_prefetch_profiles
|
|
|
|
if profile not in old_active_profiles:
|
2020-12-09 19:46:39 +03:00
|
|
|
print(f"Profile {profile} was not active.")
|
|
|
|
telemetry_sample.fail(f"Profile {profile} was not active.")
|
2020-09-24 19:52:59 +03:00
|
|
|
return 1
|
|
|
|
|
|
|
|
new_active_profiles = old_active_profiles.copy()
|
|
|
|
new_active_profiles.remove(profile)
|
|
|
|
|
|
|
|
new_config = CheckoutConfig(
|
|
|
|
backing_repo=old_config.backing_repo,
|
|
|
|
scm_type=old_config.scm_type,
|
2020-12-15 19:03:23 +03:00
|
|
|
guid=old_config.guid,
|
2021-02-11 07:09:33 +03:00
|
|
|
case_sensitive=old_config.case_sensitive,
|
2021-02-23 22:33:07 +03:00
|
|
|
require_utf8_path=old_config.require_utf8_path,
|
2021-02-05 07:08:38 +03:00
|
|
|
mount_protocol=old_config.mount_protocol,
|
2020-09-24 19:52:59 +03:00
|
|
|
redirections=old_config.redirections,
|
|
|
|
default_revision=old_config.default_revision,
|
|
|
|
active_prefetch_profiles=new_active_profiles,
|
|
|
|
)
|
|
|
|
|
|
|
|
self.save_config(new_config)
|
|
|
|
return 0
|
|
|
|
|
2018-08-22 21:05:42 +03:00
|
|
|
|
|
|
|
def find_eden(
|
|
|
|
path: Union[str, Path],
|
|
|
|
etc_eden_dir: Optional[str] = None,
|
|
|
|
home_dir: Optional[str] = None,
|
|
|
|
state_dir: Optional[str] = None,
|
|
|
|
) -> Tuple[EdenInstance, Optional[EdenCheckout], Optional[Path]]:
|
|
|
|
"""Look up the EdenInstance and EdenCheckout for a path.
|
|
|
|
|
|
|
|
If the input path points into an Eden checkout, this returns a tuple of
|
|
|
|
(EdenInstance, EdenCheckout, rel_path), where EdenInstance contains information for
|
|
|
|
the edenfs instance serving this checkout, EdenCheckout contains information about
|
|
|
|
the checkout, and rel_path contains the relative location of the input path inside
|
|
|
|
the checkout. The checkout does not need to be currently mounted for this to work.
|
|
|
|
|
|
|
|
If the input path does not point inside a known Eden checkout, this returns
|
|
|
|
(EdenInstance, None, None)
|
|
|
|
"""
|
|
|
|
if isinstance(path, str):
|
|
|
|
path = Path(path)
|
|
|
|
|
2020-09-19 02:37:56 +03:00
|
|
|
path = path.resolve(strict=False)
|
2018-08-22 21:05:42 +03:00
|
|
|
|
|
|
|
# First check to see if this looks like a mounted checkout
|
|
|
|
eden_state_dir = None
|
|
|
|
checkout_root = None
|
|
|
|
checkout_state_dir = None
|
|
|
|
try:
|
2020-04-04 02:48:29 +03:00
|
|
|
if sys.platform != "win32":
|
2020-03-04 00:24:20 +03:00
|
|
|
eden_socket_path = readlink_retry_estale(
|
|
|
|
path.joinpath(path, ".eden", "socket")
|
|
|
|
)
|
|
|
|
eden_state_dir = os.path.dirname(eden_socket_path)
|
|
|
|
|
|
|
|
checkout_root = Path(readlink_retry_estale(path.joinpath(".eden", "root")))
|
|
|
|
checkout_state_dir = Path(
|
|
|
|
readlink_retry_estale(path.joinpath(".eden", "client"))
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
# On Windows, walk the path backwards until both parent and dir
|
|
|
|
# point to "C:\"
|
|
|
|
curdir = path
|
|
|
|
while curdir != curdir.parent:
|
|
|
|
try:
|
|
|
|
tomlconfig = toml.load(curdir / ".eden" / "config")
|
|
|
|
except FileNotFoundError:
|
|
|
|
curdir = curdir.parent
|
|
|
|
continue
|
|
|
|
|
|
|
|
eden_socket_path = tomlconfig["Config"]["socket"]
|
|
|
|
eden_state_dir = os.path.dirname(eden_socket_path)
|
|
|
|
checkout_root = Path(tomlconfig["Config"]["root"])
|
|
|
|
checkout_state_dir = Path(tomlconfig["Config"]["client"])
|
|
|
|
break
|
2018-08-22 21:05:42 +03:00
|
|
|
|
|
|
|
except OSError:
|
|
|
|
# We will get an OSError if any of these symlinks do not exist
|
|
|
|
# Fall through and we will handle this below.
|
|
|
|
pass
|
|
|
|
|
|
|
|
if eden_state_dir is None:
|
|
|
|
# Use the state directory argument supplied by the caller.
|
|
|
|
# If this is None the EdenInstance constructor will pick the correct location.
|
|
|
|
eden_state_dir = state_dir
|
|
|
|
elif state_dir is not None:
|
|
|
|
# We found a state directory from the checkout and the user also specified an
|
|
|
|
# explicit state directory. Make sure they match.
|
|
|
|
_check_same_eden_directory(Path(eden_state_dir), Path(state_dir))
|
|
|
|
|
|
|
|
instance = EdenInstance(
|
|
|
|
eden_state_dir, etc_eden_dir=etc_eden_dir, home_dir=home_dir
|
|
|
|
)
|
|
|
|
checkout: Optional[EdenCheckout] = None
|
|
|
|
rel_path: Optional[Path] = None
|
|
|
|
if checkout_root is None:
|
|
|
|
all_checkouts = instance._get_directory_map()
|
|
|
|
for checkout_path_str, checkout_name in all_checkouts.items():
|
|
|
|
checkout_path = Path(checkout_path_str)
|
|
|
|
try:
|
|
|
|
rel_path = path.relative_to(checkout_path)
|
|
|
|
except ValueError:
|
|
|
|
continue
|
|
|
|
|
2018-11-10 01:25:36 +03:00
|
|
|
checkout_state_dir = instance.state_dir.joinpath(CLIENTS_DIR, checkout_name)
|
2018-08-22 21:05:42 +03:00
|
|
|
checkout = EdenCheckout(instance, checkout_path, checkout_state_dir)
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
# This path does not appear to be inside a known checkout
|
|
|
|
checkout = None
|
|
|
|
rel_path = None
|
|
|
|
elif checkout_state_dir is None:
|
|
|
|
all_checkouts = instance._get_directory_map()
|
2019-03-09 06:02:42 +03:00
|
|
|
checkout_name_value = all_checkouts.get(checkout_root)
|
2018-08-22 21:05:42 +03:00
|
|
|
if checkout_name_value is None:
|
|
|
|
raise Exception(f"unknown checkout {checkout_root}")
|
2018-11-10 01:25:36 +03:00
|
|
|
checkout_state_dir = instance.state_dir.joinpath(
|
|
|
|
CLIENTS_DIR, checkout_name_value
|
|
|
|
)
|
2018-08-22 21:05:42 +03:00
|
|
|
checkout = EdenCheckout(instance, checkout_root, checkout_state_dir)
|
|
|
|
rel_path = checkout.get_relative_path(path, already_resolved=True)
|
|
|
|
else:
|
|
|
|
checkout = EdenCheckout(instance, checkout_root, checkout_state_dir)
|
|
|
|
rel_path = checkout.get_relative_path(path, already_resolved=True)
|
|
|
|
|
|
|
|
return (instance, checkout, rel_path)
|
|
|
|
|
|
|
|
|
2020-09-03 06:18:08 +03:00
|
|
|
def eden_instance_from_cmdline(cmdline: List[bytes]) -> EdenInstance:
|
|
|
|
try:
|
|
|
|
eden_dir_idx = cmdline.index(b"--edenDir") + 1
|
|
|
|
eden_dir = Path(cmdline[eden_dir_idx].decode("utf-8"))
|
|
|
|
except ValueError:
|
|
|
|
eden_dir = None
|
|
|
|
|
|
|
|
try:
|
|
|
|
etc_eden_dir_idx = cmdline.index(b"--etcEdenDir") + 1
|
|
|
|
etc_eden_dir = Path(cmdline[etc_eden_dir_idx].decode("utf-8"))
|
|
|
|
except ValueError:
|
|
|
|
etc_eden_dir = None
|
|
|
|
try:
|
|
|
|
config_path_idx = cmdline.index(b"--configPath") + 1
|
|
|
|
config_path = Path(cmdline[config_path_idx].decode("utf-8")).parent
|
|
|
|
except ValueError:
|
|
|
|
config_path = None
|
|
|
|
|
|
|
|
return EdenInstance(eden_dir, etc_eden_dir, config_path)
|
|
|
|
|
|
|
|
|
2018-08-22 21:05:42 +03:00
|
|
|
def _check_same_eden_directory(found_path: Path, path_arg: Path) -> None:
|
|
|
|
s1 = found_path.lstat()
|
|
|
|
s2 = path_arg.lstat()
|
|
|
|
if (s1.st_dev, s1.st_ino) != (s2.st_dev, s2.st_ino):
|
|
|
|
raise Exception(
|
|
|
|
f"the specified directory is managed by the edenfs instance at "
|
|
|
|
f"{found_path}, which is different from the explicitly requested "
|
|
|
|
f"instance at {path_arg}"
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2018-03-29 08:10:41 +03:00
|
|
|
def _verify_mount_point(mount_point: str) -> None:
|
2016-05-12 23:43:17 +03:00
|
|
|
if os.path.isdir(mount_point):
|
|
|
|
return
|
|
|
|
parent_dir = os.path.dirname(mount_point)
|
|
|
|
if os.path.isdir(parent_dir):
|
|
|
|
os.mkdir(mount_point)
|
|
|
|
else:
|
|
|
|
raise Exception(
|
2018-05-10 07:33:49 +03:00
|
|
|
(
|
|
|
|
"%s must be a directory in order to mount a client at %s. "
|
|
|
|
+ "If this is the correct location, run `mkdir -p %s` to create "
|
|
|
|
+ "the directory."
|
|
|
|
)
|
|
|
|
% (parent_dir, mount_point, parent_dir)
|
|
|
|
)
|
2018-10-17 04:39:13 +03:00
|
|
|
|
|
|
|
|
2019-06-26 04:39:31 +03:00
|
|
|
TomlConfigDict = Mapping[str, Mapping[str, Any]]
|
2018-11-13 07:23:19 +03:00
|
|
|
|
|
|
|
|
2019-06-26 04:39:31 +03:00
|
|
|
def load_toml_config(path: Path) -> TomlConfigDict:
|
|
|
|
return typing.cast(TomlConfigDict, toml.load(str(path)))
|