mirror of
https://github.com/facebook/sapling.git
synced 2024-10-06 23:07:18 +03:00
346a4cdc4e
Summary: The _get_hg_dir() function had an incorrect return statement caused "eden init" to fail with mercurial repositories, unless the path pointed to a shared working directory. Also update the code to store the path to the mercurial repository, rather than the path to the .hg directory. Unlike git, mercurial requires the repository path itself, and refuses to work if given a path to the .hg directory. Reviewed By: bolinfest Differential Revision: D3409808 fbshipit-source-id: 22ee7748422b614f5af57aff07fcfa517af846d9
395 lines
12 KiB
Python
395 lines
12 KiB
Python
# Copyright (c) 2016, Facebook, Inc.
|
|
# All rights reserved.
|
|
#
|
|
# This source code is licensed under the BSD-style license found in the
|
|
# LICENSE file in the root directory of this source tree. An additional grant
|
|
# of patent rights can be found in the PATENTS file in the same directory.
|
|
|
|
from __future__ import (absolute_import, division,
|
|
print_function, unicode_literals)
|
|
|
|
import argparse
|
|
from eden.thrift import EdenNotRunningError
|
|
import errno
|
|
import glue
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import thrift
|
|
|
|
from . import config as config_mod
|
|
from fb303.ttypes import fb_status
|
|
|
|
# Relative to the user's $HOME/%USERPROFILE% directory.
|
|
# TODO: This value should be .eden outside of Facebook devservers.
|
|
DEFAULT_CONFIG_DIR = 'local/.eden'
|
|
|
|
# Environment variable that can be used instead of specifying --config-dir.
|
|
CONFIG_DIR_ENVIRONMENT_VARIABLE = 'EDEN_CONFIG_DIR'
|
|
|
|
|
|
def infer_client_from_cwd(config, clientname):
|
|
if clientname:
|
|
return clientname
|
|
|
|
all_clients = config.get_all_client_config_info()
|
|
path = normalize_path_arg(os.getcwd())
|
|
|
|
# Keep going while we're not in the root, as dirname(/) is /
|
|
# and we can keep iterating forever.
|
|
while len(path) > 1:
|
|
for client, info in all_clients.iteritems():
|
|
if info['mount'] == path:
|
|
return client
|
|
path = os.path.dirname(path)
|
|
|
|
print_stderr(
|
|
'cwd is not an eden mount point, and no client name was specified.')
|
|
sys.exit(2)
|
|
|
|
|
|
def do_help(args, parser, subparsers):
|
|
help_args = args.args
|
|
num_help_args = len(help_args)
|
|
if num_help_args == 1:
|
|
name = args.args[0]
|
|
subparser = subparsers.choices.get(name, None)
|
|
if subparser:
|
|
subparser.parse_args(['--help'])
|
|
else:
|
|
print_stderr('No manual entry for %s' % name)
|
|
sys.exit(2)
|
|
elif num_help_args == 0:
|
|
parser.parse_args(['--help'])
|
|
else:
|
|
print_stderr('Too many args passed to help: %s' % help_args)
|
|
sys.exit(2)
|
|
|
|
|
|
def do_info(args):
|
|
config = create_config(args)
|
|
info = config.get_client_info(infer_client_from_cwd(config, args.client))
|
|
json.dump(info, sys.stdout, indent=2)
|
|
sys.stdout.write('\n')
|
|
|
|
|
|
def _is_git_dir(path):
|
|
return (os.path.isdir(os.path.join(path, 'objects')) and
|
|
os.path.isdir(os.path.join(path, 'refs')) and
|
|
os.path.exists(os.path.join(path, 'HEAD')))
|
|
|
|
|
|
def _get_git_dir(path):
|
|
'''
|
|
If path points to a git repository, return the path to the repository .git
|
|
directory. Otherwise, if the path is not a git repository, return None.
|
|
'''
|
|
path = os.path.realpath(path)
|
|
if path.endswith('.git') and _is_git_dir(path):
|
|
return path
|
|
|
|
git_subdir = os.path.join(path, '.git')
|
|
if _is_git_dir(git_subdir):
|
|
return git_subdir
|
|
|
|
return None
|
|
|
|
|
|
def _get_hg_repo(path):
|
|
'''
|
|
If path points to a mercurial repository, return a normalized path to the
|
|
repository root. Otherwise, if path is not a mercurial repository, return
|
|
None.
|
|
'''
|
|
repo_path = os.path.realpath(path)
|
|
hg_dir = os.path.join(repo_path, '.hg')
|
|
if not os.path.exists(os.path.join(hg_dir, 'hgrc')):
|
|
return None
|
|
|
|
# Check to see if this is a shared working directory from another
|
|
# repository
|
|
try:
|
|
with open(os.path.join(hg_dir, 'sharedpath'), 'r') as f:
|
|
hg_dir = f.readline().rstrip('\n')
|
|
hg_dir = os.path.realpath(hg_dir)
|
|
repo_path = os.path.dirname(hg_dir)
|
|
except EnvironmentError as ex:
|
|
if ex.errno != errno.ENOENT:
|
|
raise
|
|
|
|
if not os.path.isdir(os.path.join(hg_dir, 'store')):
|
|
return None
|
|
|
|
return repo_path
|
|
|
|
|
|
def _get_hg_commit(repo):
|
|
env = os.environ.copy()
|
|
env['HGPLAIN'] = '1'
|
|
cmd = ['hg', '--cwd', repo, 'log', '-T{node}\\n', '-r.']
|
|
out = subprocess.check_output(cmd, env=env)
|
|
return out.strip()
|
|
|
|
|
|
def do_health(args):
|
|
config = create_config(args)
|
|
try:
|
|
client = config.get_thrift_client()
|
|
status = client.getStatus()
|
|
except (EdenNotRunningError, thrift.Thrift.TException) as e:
|
|
print(e, file=sys.stderr)
|
|
return 1
|
|
|
|
if status == fb_status.ALIVE:
|
|
print('eden health check passes')
|
|
return 0
|
|
else:
|
|
msg = fb_status._VALUES_TO_NAMES(status)
|
|
print('eden health check fails. thrift server status: ' + msg,
|
|
file=sys.stderr)
|
|
return 1
|
|
|
|
|
|
def do_init(args):
|
|
args.mount = normalize_path_arg(args.mount)
|
|
args.repo = normalize_path_arg(args.repo)
|
|
|
|
config = create_config(args)
|
|
db = _ensure_dot_eden_folder_exists(config)
|
|
|
|
# Check to see if we can figure out the repository type
|
|
snapshot_id = None
|
|
git_dir = _get_git_dir(args.repo)
|
|
if git_dir is not None:
|
|
snapshot_id = glue.do_git_import(git_dir, db)
|
|
config.create_client(args.name, args.mount, snapshot_id,
|
|
repo_type='git',
|
|
repo_source=git_dir,
|
|
with_buck=args.with_buck)
|
|
return
|
|
|
|
hg_repo = _get_hg_repo(args.repo)
|
|
if hg_repo is not None:
|
|
snapshot_id = _get_hg_commit(args.repo)
|
|
config.create_client(args.name, args.mount, snapshot_id,
|
|
repo_type='hg',
|
|
repo_source=hg_repo,
|
|
with_buck=args.with_buck)
|
|
return
|
|
|
|
raise Exception('{} does not look like a git or hg repository'.format(
|
|
args.repo))
|
|
|
|
|
|
def do_list(args):
|
|
config = create_config(args)
|
|
for name in config.get_client_names():
|
|
print(name)
|
|
|
|
|
|
def do_mount(args):
|
|
config = create_config(args)
|
|
try:
|
|
return config.mount(args.name)
|
|
except EdenNotRunningError as ex:
|
|
# TODO: Eventually it would be nice to automatically start the edenfs
|
|
# daemon for the user, and run it in the background.
|
|
print_stderr('error: {}', ex)
|
|
progname = os.path.basename(sys.argv[0])
|
|
print_stderr('Try starting edenfs first with "{} daemon"', progname)
|
|
return 1
|
|
|
|
|
|
def do_unmount(args):
|
|
config = create_config(args)
|
|
try:
|
|
return config.unmount(args.name)
|
|
except Exception as ex:
|
|
print_stderr('error: {}', ex)
|
|
return 1
|
|
|
|
|
|
def do_checkout(args):
|
|
config = create_config(args)
|
|
try:
|
|
config.checkout(infer_client_from_cwd(config, args.client),
|
|
args.snapshot)
|
|
except Exception as ex:
|
|
print_stderr('checkout of %s failed for client %s: %s' % (
|
|
args.snapshot,
|
|
args.client,
|
|
str(ex)))
|
|
sys.exit(1)
|
|
|
|
|
|
def do_daemon(args):
|
|
config = create_config(args)
|
|
# If this is the first time running the daemon, the ~/.eden directory
|
|
# structure needs to be set up.
|
|
_ensure_dot_eden_folder_exists(config)
|
|
return config.spawn(debug=args.debug, gdb=args.gdb,
|
|
preserve_environment=args.preserve_environment)
|
|
|
|
|
|
def _ensure_dot_eden_folder_exists(config):
|
|
'''Creates the ~/.eden folder as specified by --config-dir/$EDEN_CONFIG_DIR.
|
|
If the ~/.eden folder already exists, it will be left alone.
|
|
|
|
Returns the path to the RocksDB.
|
|
'''
|
|
db = config.get_or_create_path_to_rocks_db()
|
|
return db
|
|
|
|
|
|
def do_shutdown(args):
|
|
config = create_config(args)
|
|
client = None
|
|
try:
|
|
client = config.get_thrift_client()
|
|
except EdenNotRunningError:
|
|
return 1
|
|
client.shutdown()
|
|
|
|
|
|
def create_parser():
|
|
'''Returns a parser and its immediate subparsers.'''
|
|
parser = argparse.ArgumentParser(description='Manage Eden clients.')
|
|
parser.add_argument(
|
|
'--config-dir',
|
|
help='Path to directory where client data is stored.',
|
|
default=find_default_config_dir())
|
|
subparsers = parser.add_subparsers(dest='subparser_name')
|
|
|
|
# Please add the subparsers in alphabetical order because that is the order
|
|
# in which they are displayed when the user runs --help.
|
|
checkout_parser = subparsers.add_parser(
|
|
'checkout', help='Check out an alternative snapshot hash.')
|
|
checkout_parser.add_argument('--client', '-c',
|
|
default=None,
|
|
help='Name of the mounted client')
|
|
checkout_parser.add_argument('snapshot', help='Snapshot hash to check out')
|
|
checkout_parser.set_defaults(func=do_checkout)
|
|
|
|
daemon_parser = subparsers.add_parser(
|
|
'daemon', help='Run the edenfs daemon')
|
|
daemon_parser.add_argument(
|
|
'--debug', '-d', action='store_true', help='Enable fuse debugging.')
|
|
daemon_parser.add_argument(
|
|
'--preserve-environment', '-E', action='store_true',
|
|
help='Preserve environment for underlying sudo calls')
|
|
daemon_parser.add_argument(
|
|
'--gdb', '-g', action='store_true', help='Run under gdb')
|
|
daemon_parser.set_defaults(func=do_daemon)
|
|
|
|
health_parser = subparsers.add_parser(
|
|
'health', help='Check the health of the Eden service')
|
|
health_parser.set_defaults(func=do_health)
|
|
|
|
help_parser = subparsers.add_parser(
|
|
'help', help='Display help information about Eden.')
|
|
help_parser.set_defaults(func=do_help)
|
|
help_parser.add_argument('args', nargs='*')
|
|
|
|
info_parser = subparsers.add_parser(
|
|
'info', help='Get details about a client.')
|
|
info_parser.add_argument(
|
|
'client',
|
|
default=None,
|
|
nargs='?',
|
|
help='Name of the client')
|
|
info_parser.set_defaults(func=do_info)
|
|
|
|
init_parser = subparsers.add_parser(
|
|
'init', help='Create a new Eden client.')
|
|
init_parser.add_argument(
|
|
'--repo', help='Path to the repository to import.',
|
|
required=True)
|
|
init_parser.add_argument(
|
|
'--mount', '-m', help='Path where the client should be mounted.',
|
|
required=True)
|
|
init_parser.add_argument(
|
|
'--with-buck', '-b', action='store_true',
|
|
help='Client should create a bind mount for buck-out/.')
|
|
init_parser.add_argument(
|
|
'name', help='Name of the new client')
|
|
init_parser.set_defaults(func=do_init)
|
|
|
|
list_parser = subparsers.add_parser(
|
|
'list', help='List available clients')
|
|
list_parser.set_defaults(func=do_list)
|
|
|
|
mount_parser = subparsers.add_parser(
|
|
'mount', help='Mount a specific client')
|
|
mount_parser.add_argument(
|
|
'name', help='Name of the client to mount')
|
|
mount_parser.set_defaults(func=do_mount)
|
|
|
|
shutdown_parser = subparsers.add_parser(
|
|
'shutdown', help='Shutdown the daemon')
|
|
shutdown_parser.set_defaults(func=do_shutdown)
|
|
|
|
unmount_parser = subparsers.add_parser(
|
|
'unmount', help='Unmount a specific client')
|
|
unmount_parser.add_argument(
|
|
'name', help='Name of client to unmount')
|
|
unmount_parser.set_defaults(func=do_unmount)
|
|
|
|
return parser, subparsers
|
|
|
|
|
|
def find_default_config_dir():
|
|
'''Returns the path to default Eden config directory.
|
|
|
|
If the environment variable $EDEN_CONFIG_DIR is set, it takes precedence
|
|
over the default, which is "$HOME/.eden".
|
|
|
|
Note that the path is not guaranteed to correspond to an existing directory.
|
|
'''
|
|
config_dir = os.getenv(CONFIG_DIR_ENVIRONMENT_VARIABLE)
|
|
if config_dir:
|
|
return config_dir
|
|
|
|
if os.name == 'nt':
|
|
home_dir = os.getenv('USERPROFILE')
|
|
else:
|
|
home_dir = os.getenv('HOME')
|
|
return os.path.join(home_dir, DEFAULT_CONFIG_DIR)
|
|
|
|
|
|
def create_config(args):
|
|
return config_mod.Config(args.config_dir)
|
|
|
|
|
|
def main():
|
|
parser, subparsers = create_parser()
|
|
args = parser.parse_args()
|
|
if args.subparser_name == 'help':
|
|
retcode = args.func(args, parser, subparsers)
|
|
else:
|
|
retcode = args.func(args)
|
|
return retcode
|
|
|
|
|
|
def print_stderr(message, *args, **kwargs):
|
|
'''Prints the message to stderr.'''
|
|
if args or kwargs:
|
|
message = message.format(*args, **kwargs)
|
|
print(message, file=sys.stderr)
|
|
|
|
|
|
def normalize_path_arg(path_arg):
|
|
'''
|
|
This ensures that path expansions such as ~ are handled properly and that
|
|
relative paths are made absolute.
|
|
'''
|
|
if path_arg:
|
|
return os.path.abspath(os.path.normpath(os.path.expanduser(path_arg)))
|
|
else:
|
|
return path_arg
|
|
|
|
|
|
if __name__ == '__main__':
|
|
retcode = main()
|
|
sys.exit(retcode)
|