2017-04-04 01:47:53 +03:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
#
|
2019-06-20 02:58:25 +03:00
|
|
|
# Copyright (c) Facebook, Inc. and its affiliates.
|
2017-04-04 01:47:53 +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.
|
2017-04-04 01:47:53 +03:00
|
|
|
|
|
|
|
import argparse
|
|
|
|
import binascii
|
2017-12-15 00:42:05 +03:00
|
|
|
import collections
|
2018-07-23 22:49:54 +03:00
|
|
|
import json
|
2017-04-04 01:47:53 +03:00
|
|
|
import os
|
2018-05-23 11:27:16 +03:00
|
|
|
import re
|
2018-03-30 11:48:09 +03:00
|
|
|
import shlex
|
2017-04-04 01:47:53 +03:00
|
|
|
import stat
|
|
|
|
import sys
|
2019-07-09 19:10:19 +03:00
|
|
|
import time
|
2018-08-22 21:05:44 +03:00
|
|
|
from pathlib import Path
|
2018-08-16 00:43:28 +03:00
|
|
|
from typing import (
|
|
|
|
IO,
|
|
|
|
Any,
|
2018-08-22 21:05:52 +03:00
|
|
|
BinaryIO,
|
2018-08-16 00:43:28 +03:00
|
|
|
Callable,
|
|
|
|
Dict,
|
|
|
|
Iterator,
|
|
|
|
List,
|
|
|
|
Optional,
|
|
|
|
Pattern,
|
|
|
|
Tuple,
|
2019-10-02 02:05:50 +03:00
|
|
|
Union,
|
2018-08-16 00:43:28 +03:00
|
|
|
cast,
|
|
|
|
)
|
2017-07-08 04:32:42 +03:00
|
|
|
|
Store Hg dirstate data in Hg instead of Eden.
Summary:
This is a major change to how we manage the dirstate in Eden's Hg extension.
Previously, the dirstate information was stored under `$EDEN_CONFIG_DIR`,
which is Eden's private storage. Any time the Mercurial extension wanted to
read or write the dirstate, it had to make a Thrift request to Eden to do so on
its behalf. The upside is that Eden could answer dirstate-related questions
independently of the Python code.
This was sufficiently different than how Mercurial's default dirstate worked
that our subclass, `eden_dirstate`, had to override quite a bit of behavior.
Failing to manage the `.hg/dirstate` file in a way similar to the way Mercurial
does has exposed some "unofficial contracts" that Mercurial has. For example,
tools like Nuclide rely on changes to the `.hg/dirstate` file as a heuristic to
determine when to invalidate its internal caches for Mercurial data.
Today, Mercurial has a well-factored `dirstatemap` abstraction that is primarily
responsible for the transactions with the dirstate's data. With this split, we can
focus on putting most of our customizations in our `eden_dirstate_map` subclass
while our `eden_dirstate` class has to override fewer methods. Because the
data is managed through the `.hg/dirstate` file, transaction logic in Mercurial that
relies on renaming/copying that file will work out-of-the-box. This change
also reduces the number of Thrift calls the Mercurial extension has to make
for operations like `hg status` or `hg add`.
In this revision, we introduce our own binary format for the `.hg/dirstate` file.
The logic to read and write this file is in `eden/py/dirstate.py`. After the first
40 bytes, which are used for the parent hashes, the next four bytes are
reserved for a version number for the file format so we can manage file format
changes going forward.
Admittedly one downside of this change is that it is a breaking change.
Ideally, users should commit all of their local changes in their existing mounts,
shutdown Eden, delete the old mounts, restart Eden, and re-clone.
In the end, this change deletes a number of Mercurial-specific code and Thrift
APIs from Eden. This is a better separation of concerns that makes Eden more
SCM-agnostic. For example, this change removes `Dirstate.cpp` and
`DirstatePersistance.cpp`, replacing them with the much simpler and more
general `Differ.cpp`. The Mercurial-specific logic from `Dirstate.cpp` that turned
a diff into an `hg status` now lives in the Mercurial extension in
`EdenThriftClient.getStatus()`, which is much more appropriate.
Note that this reverts the changes that were recently introduced in D6116105:
we now need to intercept `localrepo.localrepository.dirstate` once again.
Reviewed By: simpkins
Differential Revision: D6179950
fbshipit-source-id: 5b78904909b669c9cc606e2fe1fd118ef6eaab95
2017-11-07 06:44:24 +03:00
|
|
|
import eden.dirstate
|
2018-10-23 21:52:29 +03:00
|
|
|
import facebook.eden.ttypes as eden_ttypes
|
|
|
|
import thrift.util.inspect
|
|
|
|
from facebook.eden import EdenService
|
2018-05-23 11:27:16 +03:00
|
|
|
from facebook.eden.ttypes import (
|
|
|
|
DebugGetRawJournalParams,
|
2018-08-16 00:43:28 +03:00
|
|
|
DebugJournalDelta,
|
2019-06-27 02:34:00 +03:00
|
|
|
EdenError,
|
2018-05-23 11:27:16 +03:00
|
|
|
NoValueForKeyError,
|
|
|
|
TimeSpec,
|
|
|
|
TreeInodeDebugInfo,
|
|
|
|
)
|
2019-09-27 01:14:23 +03:00
|
|
|
from fb303_core import BaseService
|
|
|
|
from thrift.protocol.TSimpleJSONProtocol import TSimpleJSONProtocolFactory
|
|
|
|
from thrift.util import Serializer
|
2017-04-04 01:47:53 +03:00
|
|
|
|
2019-07-23 05:01:54 +03:00
|
|
|
from . import cmd_util, stats_print, subcmd as subcmd_mod, ui as ui_mod
|
2018-08-22 21:05:44 +03:00
|
|
|
from .config import EdenCheckout, EdenInstance
|
2018-04-20 03:36:17 +03:00
|
|
|
from .subcmd import Subcmd
|
2019-08-13 21:31:11 +03:00
|
|
|
from .util import split_inodes_by_operation_type
|
2018-04-20 03:36:17 +03:00
|
|
|
|
2018-05-10 07:33:49 +03:00
|
|
|
|
2018-04-20 03:36:17 +03:00
|
|
|
debug_cmd = subcmd_mod.Decorator()
|
2017-04-04 01:47:53 +03:00
|
|
|
|
|
|
|
|
|
|
|
def escape_path(value: bytes) -> str:
|
2018-05-10 07:33:49 +03:00
|
|
|
"""
|
2017-04-04 01:47:53 +03:00
|
|
|
Take a binary path value, and return a printable string, with special
|
|
|
|
characters escaped.
|
2018-05-10 07:33:49 +03:00
|
|
|
"""
|
2018-05-04 19:04:43 +03:00
|
|
|
|
2018-04-20 03:36:17 +03:00
|
|
|
def human_readable_byte(b: int) -> str:
|
2018-10-01 18:04:14 +03:00
|
|
|
if b < 0x20 or b >= 0x7F:
|
2018-05-10 07:33:49 +03:00
|
|
|
return "\\x{:02x}".format(b)
|
|
|
|
elif b == ord(b"\\"):
|
|
|
|
return "\\\\"
|
2017-04-04 01:47:53 +03:00
|
|
|
return chr(b)
|
2018-05-04 19:04:43 +03:00
|
|
|
|
2018-05-10 07:33:49 +03:00
|
|
|
return "".join(human_readable_byte(b) for b in value)
|
2017-04-04 01:47:53 +03:00
|
|
|
|
|
|
|
|
|
|
|
def hash_str(value: bytes) -> str:
|
2018-05-10 07:33:49 +03:00
|
|
|
"""
|
2017-04-04 01:47:53 +03:00
|
|
|
Take a hash as a binary value, and return it represented as a hexadecimal
|
|
|
|
string.
|
2018-05-10 07:33:49 +03:00
|
|
|
"""
|
|
|
|
return binascii.hexlify(value).decode("utf-8")
|
2017-04-04 01:47:53 +03:00
|
|
|
|
|
|
|
|
|
|
|
def parse_object_id(value: str) -> bytes:
|
2018-05-10 07:33:49 +03:00
|
|
|
"""
|
2017-04-04 01:47:53 +03:00
|
|
|
Parse an object ID as a 40-byte hexadecimal string, and return a 20-byte
|
|
|
|
binary value.
|
2018-05-10 07:33:49 +03:00
|
|
|
"""
|
2017-04-04 01:47:53 +03:00
|
|
|
try:
|
|
|
|
binary = binascii.unhexlify(value)
|
|
|
|
if len(binary) != 20:
|
|
|
|
raise ValueError()
|
|
|
|
except ValueError:
|
2018-05-10 07:33:49 +03:00
|
|
|
raise ValueError("blob ID must be a 40-byte hexadecimal value")
|
2017-04-04 01:47:53 +03:00
|
|
|
return binary
|
|
|
|
|
|
|
|
|
2018-05-10 07:33:49 +03:00
|
|
|
@debug_cmd("tree", "Show eden's data for a source control tree")
|
2018-04-20 03:36:17 +03:00
|
|
|
class TreeCmd(Subcmd):
|
|
|
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
|
|
parser.add_argument(
|
2018-05-10 07:33:49 +03:00
|
|
|
"-L",
|
|
|
|
"--load",
|
|
|
|
action="store_true",
|
2018-05-04 19:04:43 +03:00
|
|
|
default=False,
|
2018-05-10 07:33:49 +03:00
|
|
|
help="Load data from the backing store if necessary",
|
2018-05-04 19:04:43 +03:00
|
|
|
)
|
2018-05-10 07:33:49 +03:00
|
|
|
parser.add_argument("mount", help="The eden mount point path.")
|
|
|
|
parser.add_argument("id", help="The tree ID")
|
2017-04-04 01:47:53 +03:00
|
|
|
|
2018-04-20 03:36:17 +03:00
|
|
|
def run(self, args: argparse.Namespace) -> int:
|
2018-08-22 21:05:44 +03:00
|
|
|
instance, checkout, _rel_path = cmd_util.require_checkout(args, args.mount)
|
2018-04-20 03:36:17 +03:00
|
|
|
tree_id = parse_object_id(args.id)
|
2017-04-04 01:47:53 +03:00
|
|
|
|
2018-04-20 03:36:17 +03:00
|
|
|
local_only = not args.load
|
2018-08-16 07:34:10 +03:00
|
|
|
with instance.get_thrift_client() as client:
|
2018-08-22 21:05:44 +03:00
|
|
|
entries = client.debugGetScmTree(
|
|
|
|
bytes(checkout.path), tree_id, localStoreOnly=local_only
|
|
|
|
)
|
2017-04-04 01:47:53 +03:00
|
|
|
|
2018-04-20 03:36:17 +03:00
|
|
|
for entry in entries:
|
|
|
|
file_type_flags, perms = _parse_mode(entry.mode)
|
2018-05-04 19:04:43 +03:00
|
|
|
print(
|
2018-05-10 07:33:49 +03:00
|
|
|
"{} {:4o} {:40} {}".format(
|
|
|
|
file_type_flags, perms, hash_str(entry.id), escape_path(entry.name)
|
2018-05-04 19:04:43 +03:00
|
|
|
)
|
|
|
|
)
|
2017-04-04 01:47:53 +03:00
|
|
|
|
2018-04-20 03:36:17 +03:00
|
|
|
return 0
|
2017-04-04 01:47:53 +03:00
|
|
|
|
|
|
|
|
2018-05-10 07:33:49 +03:00
|
|
|
@debug_cmd("blob", "Show eden's data for a source control blob")
|
2018-04-20 03:36:17 +03:00
|
|
|
class BlobCmd(Subcmd):
|
|
|
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
|
|
parser.add_argument(
|
2018-05-10 07:33:49 +03:00
|
|
|
"-L",
|
|
|
|
"--load",
|
|
|
|
action="store_true",
|
2018-05-04 19:04:43 +03:00
|
|
|
default=False,
|
2018-05-10 07:33:49 +03:00
|
|
|
help="Load data from the backing store if necessary",
|
2018-05-04 19:04:43 +03:00
|
|
|
)
|
2018-05-10 07:33:49 +03:00
|
|
|
parser.add_argument("mount", help="The eden mount point path.")
|
|
|
|
parser.add_argument("id", help="The blob ID")
|
2017-04-04 01:47:53 +03:00
|
|
|
|
2018-04-20 03:36:17 +03:00
|
|
|
def run(self, args: argparse.Namespace) -> int:
|
2018-08-22 21:05:44 +03:00
|
|
|
instance, checkout, _rel_path = cmd_util.require_checkout(args, args.mount)
|
2018-04-20 03:36:17 +03:00
|
|
|
blob_id = parse_object_id(args.id)
|
2017-04-04 01:47:53 +03:00
|
|
|
|
2018-04-20 03:36:17 +03:00
|
|
|
local_only = not args.load
|
2018-08-16 07:34:10 +03:00
|
|
|
with instance.get_thrift_client() as client:
|
2018-08-22 21:05:44 +03:00
|
|
|
data = client.debugGetScmBlob(
|
|
|
|
bytes(checkout.path), blob_id, localStoreOnly=local_only
|
|
|
|
)
|
2018-04-20 03:36:17 +03:00
|
|
|
|
|
|
|
sys.stdout.buffer.write(data)
|
|
|
|
return 0
|
2017-04-04 01:47:53 +03:00
|
|
|
|
|
|
|
|
2018-05-10 07:33:49 +03:00
|
|
|
@debug_cmd("blobmeta", "Show eden's metadata about a source control blob")
|
2018-04-20 03:36:17 +03:00
|
|
|
class BlobMetaCmd(Subcmd):
|
|
|
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
|
|
parser.add_argument(
|
2018-05-10 07:33:49 +03:00
|
|
|
"-L",
|
|
|
|
"--load",
|
|
|
|
action="store_true",
|
2018-05-04 19:04:43 +03:00
|
|
|
default=False,
|
2018-05-10 07:33:49 +03:00
|
|
|
help="Load data from the backing store if necessary",
|
2018-05-04 19:04:43 +03:00
|
|
|
)
|
2018-05-10 07:33:49 +03:00
|
|
|
parser.add_argument("mount", help="The eden mount point path.")
|
|
|
|
parser.add_argument("id", help="The blob ID")
|
2018-04-20 03:36:17 +03:00
|
|
|
|
|
|
|
def run(self, args: argparse.Namespace) -> int:
|
2018-08-22 21:05:44 +03:00
|
|
|
instance, checkout, _rel_path = cmd_util.require_checkout(args, args.mount)
|
2018-04-20 03:36:17 +03:00
|
|
|
blob_id = parse_object_id(args.id)
|
|
|
|
|
|
|
|
local_only = not args.load
|
2018-08-16 07:34:10 +03:00
|
|
|
with instance.get_thrift_client() as client:
|
2018-05-04 19:04:43 +03:00
|
|
|
info = client.debugGetScmBlobMetadata(
|
2018-08-22 21:05:44 +03:00
|
|
|
bytes(checkout.path), blob_id, localStoreOnly=local_only
|
2018-05-04 19:04:43 +03:00
|
|
|
)
|
2018-04-20 03:36:17 +03:00
|
|
|
|
2018-05-10 07:33:49 +03:00
|
|
|
print("Blob ID: {}".format(args.id))
|
|
|
|
print("Size: {}".format(info.size))
|
|
|
|
print("SHA1: {}".format(hash_str(info.contentsSha1)))
|
2018-04-20 03:36:17 +03:00
|
|
|
return 0
|
2017-04-04 01:47:53 +03:00
|
|
|
|
|
|
|
|
2018-05-10 07:33:49 +03:00
|
|
|
_FILE_TYPE_FLAGS = {stat.S_IFREG: "f", stat.S_IFDIR: "d", stat.S_IFLNK: "l"}
|
2017-04-04 01:47:53 +03:00
|
|
|
|
|
|
|
|
|
|
|
def _parse_mode(mode: int) -> Tuple[str, int]:
|
2018-05-10 07:33:49 +03:00
|
|
|
"""
|
2017-04-04 01:47:53 +03:00
|
|
|
Take a mode value, and return a tuple of (file_type, permissions)
|
|
|
|
where file type is a one-character flag indicating if this is a file,
|
|
|
|
directory, or symbolic link.
|
2018-05-10 07:33:49 +03:00
|
|
|
"""
|
|
|
|
file_type_str = _FILE_TYPE_FLAGS.get(stat.S_IFMT(mode), "?")
|
2018-05-19 09:05:39 +03:00
|
|
|
perms = mode & 0o7777
|
2017-04-04 01:47:53 +03:00
|
|
|
return file_type_str, perms
|
|
|
|
|
|
|
|
|
2018-05-10 07:33:49 +03:00
|
|
|
@debug_cmd("buildinfo", "Show the build info for the Eden server")
|
2018-04-20 03:36:17 +03:00
|
|
|
class BuildInfoCmd(Subcmd):
|
|
|
|
def run(self, args: argparse.Namespace) -> int:
|
2018-08-16 07:34:10 +03:00
|
|
|
instance = cmd_util.get_eden_instance(args)
|
|
|
|
do_buildinfo(instance)
|
2018-04-20 03:36:17 +03:00
|
|
|
return 0
|
|
|
|
|
|
|
|
|
2018-08-16 07:34:10 +03:00
|
|
|
def do_buildinfo(instance: EdenInstance, out: Optional[IO[bytes]] = None) -> None:
|
2017-12-20 02:42:53 +03:00
|
|
|
if out is None:
|
|
|
|
out = sys.stdout.buffer
|
2018-08-16 07:34:10 +03:00
|
|
|
build_info = instance.get_server_build_info()
|
2017-12-15 00:42:05 +03:00
|
|
|
sorted_build_info = collections.OrderedDict(sorted(build_info.items()))
|
|
|
|
for key, value in sorted_build_info.items():
|
2018-05-10 07:33:49 +03:00
|
|
|
out.write(b"%s: %s\n" % (key.encode(), value.encode()))
|
2017-12-15 00:42:05 +03:00
|
|
|
|
|
|
|
|
2018-05-31 20:39:53 +03:00
|
|
|
@debug_cmd("clear_local_caches", "Clears local caches of objects stored in RocksDB")
|
|
|
|
class ClearLocalCachesCmd(Subcmd):
|
|
|
|
def run(self, args: argparse.Namespace) -> int:
|
2018-08-16 07:34:10 +03:00
|
|
|
instance = cmd_util.get_eden_instance(args)
|
|
|
|
with instance.get_thrift_client() as client:
|
2018-05-31 20:39:53 +03:00
|
|
|
client.debugClearLocalStoreCaches()
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
2018-05-31 20:39:58 +03:00
|
|
|
@debug_cmd("compact_local_storage", "Asks RocksDB to compact its storage")
|
|
|
|
class CompactLocalStorageCmd(Subcmd):
|
|
|
|
def run(self, args: argparse.Namespace) -> int:
|
2018-08-16 07:34:10 +03:00
|
|
|
instance = cmd_util.get_eden_instance(args)
|
|
|
|
with instance.get_thrift_client() as client:
|
2018-05-31 20:39:58 +03:00
|
|
|
client.debugCompactLocalStorage()
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
2018-05-10 07:33:49 +03:00
|
|
|
@debug_cmd("uptime", "Check how long edenfs has been running")
|
2018-04-20 03:36:17 +03:00
|
|
|
class UptimeCmd(Subcmd):
|
|
|
|
def run(self, args: argparse.Namespace) -> int:
|
2018-08-16 07:34:10 +03:00
|
|
|
instance = cmd_util.get_eden_instance(args)
|
|
|
|
do_uptime(instance)
|
2018-04-20 03:36:17 +03:00
|
|
|
return 0
|
|
|
|
|
|
|
|
|
2018-08-16 07:34:10 +03:00
|
|
|
def do_uptime(instance: EdenInstance, out: Optional[IO[bytes]] = None) -> None:
|
2017-12-20 02:42:53 +03:00
|
|
|
if out is None:
|
|
|
|
out = sys.stdout.buffer
|
2018-08-16 07:34:10 +03:00
|
|
|
uptime = instance.get_uptime() # Check if uptime is negative?
|
2017-12-15 08:07:22 +03:00
|
|
|
days = uptime.days
|
|
|
|
hours, remainder = divmod(uptime.seconds, 3600)
|
|
|
|
minutes, seconds = divmod(remainder, 60)
|
2018-05-10 07:33:49 +03:00
|
|
|
out.write(b"%dd:%02dh:%02dm:%02ds\n" % (days, hours, minutes, seconds))
|
2017-12-15 08:07:22 +03:00
|
|
|
|
|
|
|
|
2018-05-10 07:33:49 +03:00
|
|
|
@debug_cmd("hg_copy_map_get_all", "Copymap for dirstate")
|
2018-04-20 03:36:17 +03:00
|
|
|
class HgCopyMapGetAllCmd(Subcmd):
|
|
|
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
|
|
parser.add_argument(
|
2018-05-10 07:33:49 +03:00
|
|
|
"path",
|
|
|
|
nargs="?",
|
|
|
|
help="The path to an Eden mount point. Uses `pwd` by default.",
|
2018-05-04 19:04:43 +03:00
|
|
|
)
|
2018-04-20 03:36:17 +03:00
|
|
|
|
|
|
|
def run(self, args: argparse.Namespace) -> int:
|
2018-07-12 03:50:48 +03:00
|
|
|
path = args.path or os.getcwd()
|
2018-08-22 21:05:44 +03:00
|
|
|
_instance, checkout, _rel_path = cmd_util.require_checkout(args, path)
|
|
|
|
_parents, _dirstate_tuples, copymap = _get_dirstate_data(checkout)
|
2018-04-20 03:36:17 +03:00
|
|
|
_print_copymap(copymap)
|
|
|
|
return 0
|
2017-10-26 08:24:46 +03:00
|
|
|
|
|
|
|
|
2018-04-20 03:36:17 +03:00
|
|
|
def _print_copymap(copy_map: Dict[str, str]) -> None:
|
2018-05-10 07:33:49 +03:00
|
|
|
copies = [f"{item[1]} -> {item[0]}" for item in copy_map.items()]
|
2017-10-26 08:24:46 +03:00
|
|
|
copies.sort()
|
|
|
|
for copy in copies:
|
|
|
|
print(copy)
|
|
|
|
|
|
|
|
|
2018-05-10 07:33:49 +03:00
|
|
|
@debug_cmd("hg_dirstate", "Print full dirstate")
|
2018-04-20 03:36:17 +03:00
|
|
|
class HgDirstateCmd(Subcmd):
|
|
|
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
|
|
parser.add_argument(
|
2018-05-10 07:33:49 +03:00
|
|
|
"path",
|
|
|
|
nargs="?",
|
|
|
|
help="The path to an Eden mount point. Uses `pwd` by default.",
|
2018-05-04 19:04:43 +03:00
|
|
|
)
|
2018-04-20 03:36:17 +03:00
|
|
|
|
|
|
|
def run(self, args: argparse.Namespace) -> int:
|
2018-07-12 03:50:48 +03:00
|
|
|
path = args.path or os.getcwd()
|
2018-08-22 21:05:44 +03:00
|
|
|
_instance, checkout, _rel_path = cmd_util.require_checkout(args, path)
|
|
|
|
_parents, dirstate_tuples, copymap = _get_dirstate_data(checkout)
|
2018-07-19 04:30:59 +03:00
|
|
|
out = ui_mod.get_output()
|
2018-04-20 03:36:17 +03:00
|
|
|
entries = list(dirstate_tuples.items())
|
2018-07-19 04:30:59 +03:00
|
|
|
out.writeln(f"Non-normal Files ({len(entries)}):", attr=out.BOLD)
|
2018-04-20 03:36:17 +03:00
|
|
|
entries.sort(key=lambda entry: entry[0]) # Sort by key.
|
|
|
|
for path, dirstate_tuple in entries:
|
2018-08-22 21:05:44 +03:00
|
|
|
_print_hg_nonnormal_file(Path(os.fsdecode(path)), dirstate_tuple, out)
|
2018-04-20 03:36:17 +03:00
|
|
|
|
2018-07-19 04:30:59 +03:00
|
|
|
out.writeln(f"Copymap ({len(copymap)}):", attr=out.BOLD)
|
2018-04-20 03:36:17 +03:00
|
|
|
_print_copymap(copymap)
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
2018-05-10 07:33:49 +03:00
|
|
|
@debug_cmd("hg_get_dirstate_tuple", "Dirstate status for file")
|
2018-04-20 03:36:17 +03:00
|
|
|
class HgGetDirstateTupleCmd(Subcmd):
|
|
|
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
|
|
parser.add_argument(
|
2018-05-10 07:33:49 +03:00
|
|
|
"path", help="The path to the file whose status should be queried."
|
2018-05-04 19:04:43 +03:00
|
|
|
)
|
2018-04-20 03:36:17 +03:00
|
|
|
|
|
|
|
def run(self, args: argparse.Namespace) -> int:
|
2018-08-22 21:05:44 +03:00
|
|
|
instance, checkout, rel_path = cmd_util.require_checkout(args, args.path)
|
|
|
|
_parents, dirstate_tuples, _copymap = _get_dirstate_data(checkout)
|
|
|
|
dirstate_tuple = dirstate_tuples.get(str(rel_path))
|
2018-07-19 04:30:59 +03:00
|
|
|
out = ui_mod.get_output()
|
2018-04-20 03:36:17 +03:00
|
|
|
if dirstate_tuple:
|
2018-07-19 04:30:59 +03:00
|
|
|
_print_hg_nonnormal_file(rel_path, dirstate_tuple, out)
|
2018-04-20 03:36:17 +03:00
|
|
|
else:
|
2018-08-16 07:34:10 +03:00
|
|
|
instance = cmd_util.get_eden_instance(args)
|
|
|
|
with instance.get_thrift_client() as client:
|
2018-04-20 03:36:17 +03:00
|
|
|
try:
|
2018-08-22 21:05:44 +03:00
|
|
|
entry = client.getManifestEntry(
|
|
|
|
bytes(checkout.path), bytes(rel_path)
|
|
|
|
)
|
2018-05-10 07:33:49 +03:00
|
|
|
dirstate_tuple = ("n", entry.mode, 0)
|
2018-07-19 04:30:59 +03:00
|
|
|
_print_hg_nonnormal_file(rel_path, dirstate_tuple, out)
|
2018-04-20 03:36:17 +03:00
|
|
|
except NoValueForKeyError:
|
2018-08-22 21:05:44 +03:00
|
|
|
print(f"No tuple for {rel_path}", file=sys.stderr)
|
2018-04-20 03:36:17 +03:00
|
|
|
return 1
|
2017-08-23 06:57:26 +03:00
|
|
|
|
2018-04-20 03:36:17 +03:00
|
|
|
return 0
|
2017-10-26 08:24:46 +03:00
|
|
|
|
2017-08-19 07:36:34 +03:00
|
|
|
|
2017-10-26 08:24:46 +03:00
|
|
|
def _print_hg_nonnormal_file(
|
2018-08-22 21:05:44 +03:00
|
|
|
rel_path: Path, dirstate_tuple: Tuple[str, Any, int], out: ui_mod.Output
|
2017-10-26 08:24:46 +03:00
|
|
|
) -> None:
|
Store Hg dirstate data in Hg instead of Eden.
Summary:
This is a major change to how we manage the dirstate in Eden's Hg extension.
Previously, the dirstate information was stored under `$EDEN_CONFIG_DIR`,
which is Eden's private storage. Any time the Mercurial extension wanted to
read or write the dirstate, it had to make a Thrift request to Eden to do so on
its behalf. The upside is that Eden could answer dirstate-related questions
independently of the Python code.
This was sufficiently different than how Mercurial's default dirstate worked
that our subclass, `eden_dirstate`, had to override quite a bit of behavior.
Failing to manage the `.hg/dirstate` file in a way similar to the way Mercurial
does has exposed some "unofficial contracts" that Mercurial has. For example,
tools like Nuclide rely on changes to the `.hg/dirstate` file as a heuristic to
determine when to invalidate its internal caches for Mercurial data.
Today, Mercurial has a well-factored `dirstatemap` abstraction that is primarily
responsible for the transactions with the dirstate's data. With this split, we can
focus on putting most of our customizations in our `eden_dirstate_map` subclass
while our `eden_dirstate` class has to override fewer methods. Because the
data is managed through the `.hg/dirstate` file, transaction logic in Mercurial that
relies on renaming/copying that file will work out-of-the-box. This change
also reduces the number of Thrift calls the Mercurial extension has to make
for operations like `hg status` or `hg add`.
In this revision, we introduce our own binary format for the `.hg/dirstate` file.
The logic to read and write this file is in `eden/py/dirstate.py`. After the first
40 bytes, which are used for the parent hashes, the next four bytes are
reserved for a version number for the file format so we can manage file format
changes going forward.
Admittedly one downside of this change is that it is a breaking change.
Ideally, users should commit all of their local changes in their existing mounts,
shutdown Eden, delete the old mounts, restart Eden, and re-clone.
In the end, this change deletes a number of Mercurial-specific code and Thrift
APIs from Eden. This is a better separation of concerns that makes Eden more
SCM-agnostic. For example, this change removes `Dirstate.cpp` and
`DirstatePersistance.cpp`, replacing them with the much simpler and more
general `Differ.cpp`. The Mercurial-specific logic from `Dirstate.cpp` that turned
a diff into an `hg status` now lives in the Mercurial extension in
`EdenThriftClient.getStatus()`, which is much more appropriate.
Note that this reverts the changes that were recently introduced in D6116105:
we now need to intercept `localrepo.localrepository.dirstate` once again.
Reviewed By: simpkins
Differential Revision: D6179950
fbshipit-source-id: 5b78904909b669c9cc606e2fe1fd118ef6eaab95
2017-11-07 06:44:24 +03:00
|
|
|
status = _dirstate_char_to_name(dirstate_tuple[0])
|
|
|
|
merge_state = _dirstate_merge_state_to_name(dirstate_tuple[2])
|
2017-10-26 08:24:46 +03:00
|
|
|
|
2018-08-22 21:05:44 +03:00
|
|
|
out.writeln(f"{rel_path}", fg=out.GREEN)
|
2018-07-19 04:30:59 +03:00
|
|
|
out.writeln(f" status = {status}")
|
|
|
|
out.writeln(f" mode = {oct(dirstate_tuple[1])}")
|
|
|
|
out.writeln(f" mergeState = {merge_state}")
|
2017-08-19 07:36:34 +03:00
|
|
|
|
|
|
|
|
Store Hg dirstate data in Hg instead of Eden.
Summary:
This is a major change to how we manage the dirstate in Eden's Hg extension.
Previously, the dirstate information was stored under `$EDEN_CONFIG_DIR`,
which is Eden's private storage. Any time the Mercurial extension wanted to
read or write the dirstate, it had to make a Thrift request to Eden to do so on
its behalf. The upside is that Eden could answer dirstate-related questions
independently of the Python code.
This was sufficiently different than how Mercurial's default dirstate worked
that our subclass, `eden_dirstate`, had to override quite a bit of behavior.
Failing to manage the `.hg/dirstate` file in a way similar to the way Mercurial
does has exposed some "unofficial contracts" that Mercurial has. For example,
tools like Nuclide rely on changes to the `.hg/dirstate` file as a heuristic to
determine when to invalidate its internal caches for Mercurial data.
Today, Mercurial has a well-factored `dirstatemap` abstraction that is primarily
responsible for the transactions with the dirstate's data. With this split, we can
focus on putting most of our customizations in our `eden_dirstate_map` subclass
while our `eden_dirstate` class has to override fewer methods. Because the
data is managed through the `.hg/dirstate` file, transaction logic in Mercurial that
relies on renaming/copying that file will work out-of-the-box. This change
also reduces the number of Thrift calls the Mercurial extension has to make
for operations like `hg status` or `hg add`.
In this revision, we introduce our own binary format for the `.hg/dirstate` file.
The logic to read and write this file is in `eden/py/dirstate.py`. After the first
40 bytes, which are used for the parent hashes, the next four bytes are
reserved for a version number for the file format so we can manage file format
changes going forward.
Admittedly one downside of this change is that it is a breaking change.
Ideally, users should commit all of their local changes in their existing mounts,
shutdown Eden, delete the old mounts, restart Eden, and re-clone.
In the end, this change deletes a number of Mercurial-specific code and Thrift
APIs from Eden. This is a better separation of concerns that makes Eden more
SCM-agnostic. For example, this change removes `Dirstate.cpp` and
`DirstatePersistance.cpp`, replacing them with the much simpler and more
general `Differ.cpp`. The Mercurial-specific logic from `Dirstate.cpp` that turned
a diff into an `hg status` now lives in the Mercurial extension in
`EdenThriftClient.getStatus()`, which is much more appropriate.
Note that this reverts the changes that were recently introduced in D6116105:
we now need to intercept `localrepo.localrepository.dirstate` once again.
Reviewed By: simpkins
Differential Revision: D6179950
fbshipit-source-id: 5b78904909b669c9cc606e2fe1fd118ef6eaab95
2017-11-07 06:44:24 +03:00
|
|
|
def _dirstate_char_to_name(state: str) -> str:
|
2018-05-10 07:33:49 +03:00
|
|
|
if state == "n":
|
|
|
|
return "Normal"
|
|
|
|
elif state == "m":
|
|
|
|
return "NeedsMerging"
|
|
|
|
elif state == "r":
|
|
|
|
return "MarkedForRemoval"
|
|
|
|
elif state == "a":
|
|
|
|
return "MarkedForAddition"
|
|
|
|
elif state == "?":
|
|
|
|
return "NotTracked"
|
Store Hg dirstate data in Hg instead of Eden.
Summary:
This is a major change to how we manage the dirstate in Eden's Hg extension.
Previously, the dirstate information was stored under `$EDEN_CONFIG_DIR`,
which is Eden's private storage. Any time the Mercurial extension wanted to
read or write the dirstate, it had to make a Thrift request to Eden to do so on
its behalf. The upside is that Eden could answer dirstate-related questions
independently of the Python code.
This was sufficiently different than how Mercurial's default dirstate worked
that our subclass, `eden_dirstate`, had to override quite a bit of behavior.
Failing to manage the `.hg/dirstate` file in a way similar to the way Mercurial
does has exposed some "unofficial contracts" that Mercurial has. For example,
tools like Nuclide rely on changes to the `.hg/dirstate` file as a heuristic to
determine when to invalidate its internal caches for Mercurial data.
Today, Mercurial has a well-factored `dirstatemap` abstraction that is primarily
responsible for the transactions with the dirstate's data. With this split, we can
focus on putting most of our customizations in our `eden_dirstate_map` subclass
while our `eden_dirstate` class has to override fewer methods. Because the
data is managed through the `.hg/dirstate` file, transaction logic in Mercurial that
relies on renaming/copying that file will work out-of-the-box. This change
also reduces the number of Thrift calls the Mercurial extension has to make
for operations like `hg status` or `hg add`.
In this revision, we introduce our own binary format for the `.hg/dirstate` file.
The logic to read and write this file is in `eden/py/dirstate.py`. After the first
40 bytes, which are used for the parent hashes, the next four bytes are
reserved for a version number for the file format so we can manage file format
changes going forward.
Admittedly one downside of this change is that it is a breaking change.
Ideally, users should commit all of their local changes in their existing mounts,
shutdown Eden, delete the old mounts, restart Eden, and re-clone.
In the end, this change deletes a number of Mercurial-specific code and Thrift
APIs from Eden. This is a better separation of concerns that makes Eden more
SCM-agnostic. For example, this change removes `Dirstate.cpp` and
`DirstatePersistance.cpp`, replacing them with the much simpler and more
general `Differ.cpp`. The Mercurial-specific logic from `Dirstate.cpp` that turned
a diff into an `hg status` now lives in the Mercurial extension in
`EdenThriftClient.getStatus()`, which is much more appropriate.
Note that this reverts the changes that were recently introduced in D6116105:
we now need to intercept `localrepo.localrepository.dirstate` once again.
Reviewed By: simpkins
Differential Revision: D6179950
fbshipit-source-id: 5b78904909b669c9cc606e2fe1fd118ef6eaab95
2017-11-07 06:44:24 +03:00
|
|
|
else:
|
2018-05-10 07:33:49 +03:00
|
|
|
raise Exception(f"Unrecognized dirstate char: {state}")
|
Store Hg dirstate data in Hg instead of Eden.
Summary:
This is a major change to how we manage the dirstate in Eden's Hg extension.
Previously, the dirstate information was stored under `$EDEN_CONFIG_DIR`,
which is Eden's private storage. Any time the Mercurial extension wanted to
read or write the dirstate, it had to make a Thrift request to Eden to do so on
its behalf. The upside is that Eden could answer dirstate-related questions
independently of the Python code.
This was sufficiently different than how Mercurial's default dirstate worked
that our subclass, `eden_dirstate`, had to override quite a bit of behavior.
Failing to manage the `.hg/dirstate` file in a way similar to the way Mercurial
does has exposed some "unofficial contracts" that Mercurial has. For example,
tools like Nuclide rely on changes to the `.hg/dirstate` file as a heuristic to
determine when to invalidate its internal caches for Mercurial data.
Today, Mercurial has a well-factored `dirstatemap` abstraction that is primarily
responsible for the transactions with the dirstate's data. With this split, we can
focus on putting most of our customizations in our `eden_dirstate_map` subclass
while our `eden_dirstate` class has to override fewer methods. Because the
data is managed through the `.hg/dirstate` file, transaction logic in Mercurial that
relies on renaming/copying that file will work out-of-the-box. This change
also reduces the number of Thrift calls the Mercurial extension has to make
for operations like `hg status` or `hg add`.
In this revision, we introduce our own binary format for the `.hg/dirstate` file.
The logic to read and write this file is in `eden/py/dirstate.py`. After the first
40 bytes, which are used for the parent hashes, the next four bytes are
reserved for a version number for the file format so we can manage file format
changes going forward.
Admittedly one downside of this change is that it is a breaking change.
Ideally, users should commit all of their local changes in their existing mounts,
shutdown Eden, delete the old mounts, restart Eden, and re-clone.
In the end, this change deletes a number of Mercurial-specific code and Thrift
APIs from Eden. This is a better separation of concerns that makes Eden more
SCM-agnostic. For example, this change removes `Dirstate.cpp` and
`DirstatePersistance.cpp`, replacing them with the much simpler and more
general `Differ.cpp`. The Mercurial-specific logic from `Dirstate.cpp` that turned
a diff into an `hg status` now lives in the Mercurial extension in
`EdenThriftClient.getStatus()`, which is much more appropriate.
Note that this reverts the changes that were recently introduced in D6116105:
we now need to intercept `localrepo.localrepository.dirstate` once again.
Reviewed By: simpkins
Differential Revision: D6179950
fbshipit-source-id: 5b78904909b669c9cc606e2fe1fd118ef6eaab95
2017-11-07 06:44:24 +03:00
|
|
|
|
|
|
|
|
|
|
|
def _dirstate_merge_state_to_name(merge_state: int) -> str:
|
|
|
|
if merge_state == 0:
|
2018-05-10 07:33:49 +03:00
|
|
|
return "NotApplicable"
|
Store Hg dirstate data in Hg instead of Eden.
Summary:
This is a major change to how we manage the dirstate in Eden's Hg extension.
Previously, the dirstate information was stored under `$EDEN_CONFIG_DIR`,
which is Eden's private storage. Any time the Mercurial extension wanted to
read or write the dirstate, it had to make a Thrift request to Eden to do so on
its behalf. The upside is that Eden could answer dirstate-related questions
independently of the Python code.
This was sufficiently different than how Mercurial's default dirstate worked
that our subclass, `eden_dirstate`, had to override quite a bit of behavior.
Failing to manage the `.hg/dirstate` file in a way similar to the way Mercurial
does has exposed some "unofficial contracts" that Mercurial has. For example,
tools like Nuclide rely on changes to the `.hg/dirstate` file as a heuristic to
determine when to invalidate its internal caches for Mercurial data.
Today, Mercurial has a well-factored `dirstatemap` abstraction that is primarily
responsible for the transactions with the dirstate's data. With this split, we can
focus on putting most of our customizations in our `eden_dirstate_map` subclass
while our `eden_dirstate` class has to override fewer methods. Because the
data is managed through the `.hg/dirstate` file, transaction logic in Mercurial that
relies on renaming/copying that file will work out-of-the-box. This change
also reduces the number of Thrift calls the Mercurial extension has to make
for operations like `hg status` or `hg add`.
In this revision, we introduce our own binary format for the `.hg/dirstate` file.
The logic to read and write this file is in `eden/py/dirstate.py`. After the first
40 bytes, which are used for the parent hashes, the next four bytes are
reserved for a version number for the file format so we can manage file format
changes going forward.
Admittedly one downside of this change is that it is a breaking change.
Ideally, users should commit all of their local changes in their existing mounts,
shutdown Eden, delete the old mounts, restart Eden, and re-clone.
In the end, this change deletes a number of Mercurial-specific code and Thrift
APIs from Eden. This is a better separation of concerns that makes Eden more
SCM-agnostic. For example, this change removes `Dirstate.cpp` and
`DirstatePersistance.cpp`, replacing them with the much simpler and more
general `Differ.cpp`. The Mercurial-specific logic from `Dirstate.cpp` that turned
a diff into an `hg status` now lives in the Mercurial extension in
`EdenThriftClient.getStatus()`, which is much more appropriate.
Note that this reverts the changes that were recently introduced in D6116105:
we now need to intercept `localrepo.localrepository.dirstate` once again.
Reviewed By: simpkins
Differential Revision: D6179950
fbshipit-source-id: 5b78904909b669c9cc606e2fe1fd118ef6eaab95
2017-11-07 06:44:24 +03:00
|
|
|
elif merge_state == -1:
|
2018-05-10 07:33:49 +03:00
|
|
|
return "BothParents"
|
Store Hg dirstate data in Hg instead of Eden.
Summary:
This is a major change to how we manage the dirstate in Eden's Hg extension.
Previously, the dirstate information was stored under `$EDEN_CONFIG_DIR`,
which is Eden's private storage. Any time the Mercurial extension wanted to
read or write the dirstate, it had to make a Thrift request to Eden to do so on
its behalf. The upside is that Eden could answer dirstate-related questions
independently of the Python code.
This was sufficiently different than how Mercurial's default dirstate worked
that our subclass, `eden_dirstate`, had to override quite a bit of behavior.
Failing to manage the `.hg/dirstate` file in a way similar to the way Mercurial
does has exposed some "unofficial contracts" that Mercurial has. For example,
tools like Nuclide rely on changes to the `.hg/dirstate` file as a heuristic to
determine when to invalidate its internal caches for Mercurial data.
Today, Mercurial has a well-factored `dirstatemap` abstraction that is primarily
responsible for the transactions with the dirstate's data. With this split, we can
focus on putting most of our customizations in our `eden_dirstate_map` subclass
while our `eden_dirstate` class has to override fewer methods. Because the
data is managed through the `.hg/dirstate` file, transaction logic in Mercurial that
relies on renaming/copying that file will work out-of-the-box. This change
also reduces the number of Thrift calls the Mercurial extension has to make
for operations like `hg status` or `hg add`.
In this revision, we introduce our own binary format for the `.hg/dirstate` file.
The logic to read and write this file is in `eden/py/dirstate.py`. After the first
40 bytes, which are used for the parent hashes, the next four bytes are
reserved for a version number for the file format so we can manage file format
changes going forward.
Admittedly one downside of this change is that it is a breaking change.
Ideally, users should commit all of their local changes in their existing mounts,
shutdown Eden, delete the old mounts, restart Eden, and re-clone.
In the end, this change deletes a number of Mercurial-specific code and Thrift
APIs from Eden. This is a better separation of concerns that makes Eden more
SCM-agnostic. For example, this change removes `Dirstate.cpp` and
`DirstatePersistance.cpp`, replacing them with the much simpler and more
general `Differ.cpp`. The Mercurial-specific logic from `Dirstate.cpp` that turned
a diff into an `hg status` now lives in the Mercurial extension in
`EdenThriftClient.getStatus()`, which is much more appropriate.
Note that this reverts the changes that were recently introduced in D6116105:
we now need to intercept `localrepo.localrepository.dirstate` once again.
Reviewed By: simpkins
Differential Revision: D6179950
fbshipit-source-id: 5b78904909b669c9cc606e2fe1fd118ef6eaab95
2017-11-07 06:44:24 +03:00
|
|
|
elif merge_state == -2:
|
2018-05-10 07:33:49 +03:00
|
|
|
return "OtherParent"
|
Store Hg dirstate data in Hg instead of Eden.
Summary:
This is a major change to how we manage the dirstate in Eden's Hg extension.
Previously, the dirstate information was stored under `$EDEN_CONFIG_DIR`,
which is Eden's private storage. Any time the Mercurial extension wanted to
read or write the dirstate, it had to make a Thrift request to Eden to do so on
its behalf. The upside is that Eden could answer dirstate-related questions
independently of the Python code.
This was sufficiently different than how Mercurial's default dirstate worked
that our subclass, `eden_dirstate`, had to override quite a bit of behavior.
Failing to manage the `.hg/dirstate` file in a way similar to the way Mercurial
does has exposed some "unofficial contracts" that Mercurial has. For example,
tools like Nuclide rely on changes to the `.hg/dirstate` file as a heuristic to
determine when to invalidate its internal caches for Mercurial data.
Today, Mercurial has a well-factored `dirstatemap` abstraction that is primarily
responsible for the transactions with the dirstate's data. With this split, we can
focus on putting most of our customizations in our `eden_dirstate_map` subclass
while our `eden_dirstate` class has to override fewer methods. Because the
data is managed through the `.hg/dirstate` file, transaction logic in Mercurial that
relies on renaming/copying that file will work out-of-the-box. This change
also reduces the number of Thrift calls the Mercurial extension has to make
for operations like `hg status` or `hg add`.
In this revision, we introduce our own binary format for the `.hg/dirstate` file.
The logic to read and write this file is in `eden/py/dirstate.py`. After the first
40 bytes, which are used for the parent hashes, the next four bytes are
reserved for a version number for the file format so we can manage file format
changes going forward.
Admittedly one downside of this change is that it is a breaking change.
Ideally, users should commit all of their local changes in their existing mounts,
shutdown Eden, delete the old mounts, restart Eden, and re-clone.
In the end, this change deletes a number of Mercurial-specific code and Thrift
APIs from Eden. This is a better separation of concerns that makes Eden more
SCM-agnostic. For example, this change removes `Dirstate.cpp` and
`DirstatePersistance.cpp`, replacing them with the much simpler and more
general `Differ.cpp`. The Mercurial-specific logic from `Dirstate.cpp` that turned
a diff into an `hg status` now lives in the Mercurial extension in
`EdenThriftClient.getStatus()`, which is much more appropriate.
Note that this reverts the changes that were recently introduced in D6116105:
we now need to intercept `localrepo.localrepository.dirstate` once again.
Reviewed By: simpkins
Differential Revision: D6179950
fbshipit-source-id: 5b78904909b669c9cc606e2fe1fd118ef6eaab95
2017-11-07 06:44:24 +03:00
|
|
|
else:
|
2018-05-10 07:33:49 +03:00
|
|
|
raise Exception(f"Unrecognized merge_state value: {merge_state}")
|
Store Hg dirstate data in Hg instead of Eden.
Summary:
This is a major change to how we manage the dirstate in Eden's Hg extension.
Previously, the dirstate information was stored under `$EDEN_CONFIG_DIR`,
which is Eden's private storage. Any time the Mercurial extension wanted to
read or write the dirstate, it had to make a Thrift request to Eden to do so on
its behalf. The upside is that Eden could answer dirstate-related questions
independently of the Python code.
This was sufficiently different than how Mercurial's default dirstate worked
that our subclass, `eden_dirstate`, had to override quite a bit of behavior.
Failing to manage the `.hg/dirstate` file in a way similar to the way Mercurial
does has exposed some "unofficial contracts" that Mercurial has. For example,
tools like Nuclide rely on changes to the `.hg/dirstate` file as a heuristic to
determine when to invalidate its internal caches for Mercurial data.
Today, Mercurial has a well-factored `dirstatemap` abstraction that is primarily
responsible for the transactions with the dirstate's data. With this split, we can
focus on putting most of our customizations in our `eden_dirstate_map` subclass
while our `eden_dirstate` class has to override fewer methods. Because the
data is managed through the `.hg/dirstate` file, transaction logic in Mercurial that
relies on renaming/copying that file will work out-of-the-box. This change
also reduces the number of Thrift calls the Mercurial extension has to make
for operations like `hg status` or `hg add`.
In this revision, we introduce our own binary format for the `.hg/dirstate` file.
The logic to read and write this file is in `eden/py/dirstate.py`. After the first
40 bytes, which are used for the parent hashes, the next four bytes are
reserved for a version number for the file format so we can manage file format
changes going forward.
Admittedly one downside of this change is that it is a breaking change.
Ideally, users should commit all of their local changes in their existing mounts,
shutdown Eden, delete the old mounts, restart Eden, and re-clone.
In the end, this change deletes a number of Mercurial-specific code and Thrift
APIs from Eden. This is a better separation of concerns that makes Eden more
SCM-agnostic. For example, this change removes `Dirstate.cpp` and
`DirstatePersistance.cpp`, replacing them with the much simpler and more
general `Differ.cpp`. The Mercurial-specific logic from `Dirstate.cpp` that turned
a diff into an `hg status` now lives in the Mercurial extension in
`EdenThriftClient.getStatus()`, which is much more appropriate.
Note that this reverts the changes that were recently introduced in D6116105:
we now need to intercept `localrepo.localrepository.dirstate` once again.
Reviewed By: simpkins
Differential Revision: D6179950
fbshipit-source-id: 5b78904909b669c9cc606e2fe1fd118ef6eaab95
2017-11-07 06:44:24 +03:00
|
|
|
|
|
|
|
|
2018-04-20 03:36:17 +03:00
|
|
|
def _get_dirstate_data(
|
2018-08-22 21:05:44 +03:00
|
|
|
checkout: EdenCheckout,
|
2018-05-10 07:33:49 +03:00
|
|
|
) -> Tuple[Tuple[bytes, bytes], Dict[str, Tuple[str, Any, int]], Dict[str, str]]:
|
|
|
|
"""Returns a tuple of (parents, dirstate_tuples, copymap).
|
Store Hg dirstate data in Hg instead of Eden.
Summary:
This is a major change to how we manage the dirstate in Eden's Hg extension.
Previously, the dirstate information was stored under `$EDEN_CONFIG_DIR`,
which is Eden's private storage. Any time the Mercurial extension wanted to
read or write the dirstate, it had to make a Thrift request to Eden to do so on
its behalf. The upside is that Eden could answer dirstate-related questions
independently of the Python code.
This was sufficiently different than how Mercurial's default dirstate worked
that our subclass, `eden_dirstate`, had to override quite a bit of behavior.
Failing to manage the `.hg/dirstate` file in a way similar to the way Mercurial
does has exposed some "unofficial contracts" that Mercurial has. For example,
tools like Nuclide rely on changes to the `.hg/dirstate` file as a heuristic to
determine when to invalidate its internal caches for Mercurial data.
Today, Mercurial has a well-factored `dirstatemap` abstraction that is primarily
responsible for the transactions with the dirstate's data. With this split, we can
focus on putting most of our customizations in our `eden_dirstate_map` subclass
while our `eden_dirstate` class has to override fewer methods. Because the
data is managed through the `.hg/dirstate` file, transaction logic in Mercurial that
relies on renaming/copying that file will work out-of-the-box. This change
also reduces the number of Thrift calls the Mercurial extension has to make
for operations like `hg status` or `hg add`.
In this revision, we introduce our own binary format for the `.hg/dirstate` file.
The logic to read and write this file is in `eden/py/dirstate.py`. After the first
40 bytes, which are used for the parent hashes, the next four bytes are
reserved for a version number for the file format so we can manage file format
changes going forward.
Admittedly one downside of this change is that it is a breaking change.
Ideally, users should commit all of their local changes in their existing mounts,
shutdown Eden, delete the old mounts, restart Eden, and re-clone.
In the end, this change deletes a number of Mercurial-specific code and Thrift
APIs from Eden. This is a better separation of concerns that makes Eden more
SCM-agnostic. For example, this change removes `Dirstate.cpp` and
`DirstatePersistance.cpp`, replacing them with the much simpler and more
general `Differ.cpp`. The Mercurial-specific logic from `Dirstate.cpp` that turned
a diff into an `hg status` now lives in the Mercurial extension in
`EdenThriftClient.getStatus()`, which is much more appropriate.
Note that this reverts the changes that were recently introduced in D6116105:
we now need to intercept `localrepo.localrepository.dirstate` once again.
Reviewed By: simpkins
Differential Revision: D6179950
fbshipit-source-id: 5b78904909b669c9cc606e2fe1fd118ef6eaab95
2017-11-07 06:44:24 +03:00
|
|
|
On error, returns None.
|
2018-05-10 07:33:49 +03:00
|
|
|
"""
|
2018-08-22 21:05:44 +03:00
|
|
|
filename = checkout.path.joinpath(".hg", "dirstate")
|
|
|
|
with filename.open("rb") as f:
|
|
|
|
return eden.dirstate.read(f, str(filename))
|
Store Hg dirstate data in Hg instead of Eden.
Summary:
This is a major change to how we manage the dirstate in Eden's Hg extension.
Previously, the dirstate information was stored under `$EDEN_CONFIG_DIR`,
which is Eden's private storage. Any time the Mercurial extension wanted to
read or write the dirstate, it had to make a Thrift request to Eden to do so on
its behalf. The upside is that Eden could answer dirstate-related questions
independently of the Python code.
This was sufficiently different than how Mercurial's default dirstate worked
that our subclass, `eden_dirstate`, had to override quite a bit of behavior.
Failing to manage the `.hg/dirstate` file in a way similar to the way Mercurial
does has exposed some "unofficial contracts" that Mercurial has. For example,
tools like Nuclide rely on changes to the `.hg/dirstate` file as a heuristic to
determine when to invalidate its internal caches for Mercurial data.
Today, Mercurial has a well-factored `dirstatemap` abstraction that is primarily
responsible for the transactions with the dirstate's data. With this split, we can
focus on putting most of our customizations in our `eden_dirstate_map` subclass
while our `eden_dirstate` class has to override fewer methods. Because the
data is managed through the `.hg/dirstate` file, transaction logic in Mercurial that
relies on renaming/copying that file will work out-of-the-box. This change
also reduces the number of Thrift calls the Mercurial extension has to make
for operations like `hg status` or `hg add`.
In this revision, we introduce our own binary format for the `.hg/dirstate` file.
The logic to read and write this file is in `eden/py/dirstate.py`. After the first
40 bytes, which are used for the parent hashes, the next four bytes are
reserved for a version number for the file format so we can manage file format
changes going forward.
Admittedly one downside of this change is that it is a breaking change.
Ideally, users should commit all of their local changes in their existing mounts,
shutdown Eden, delete the old mounts, restart Eden, and re-clone.
In the end, this change deletes a number of Mercurial-specific code and Thrift
APIs from Eden. This is a better separation of concerns that makes Eden more
SCM-agnostic. For example, this change removes `Dirstate.cpp` and
`DirstatePersistance.cpp`, replacing them with the much simpler and more
general `Differ.cpp`. The Mercurial-specific logic from `Dirstate.cpp` that turned
a diff into an `hg status` now lives in the Mercurial extension in
`EdenThriftClient.getStatus()`, which is much more appropriate.
Note that this reverts the changes that were recently introduced in D6116105:
we now need to intercept `localrepo.localrepository.dirstate` once again.
Reviewed By: simpkins
Differential Revision: D6179950
fbshipit-source-id: 5b78904909b669c9cc606e2fe1fd118ef6eaab95
2017-11-07 06:44:24 +03:00
|
|
|
|
|
|
|
|
2018-05-10 07:33:49 +03:00
|
|
|
@debug_cmd("inode", "Show data about loaded inodes")
|
2018-04-20 03:36:17 +03:00
|
|
|
class InodeCmd(Subcmd):
|
|
|
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
|
|
parser.add_argument(
|
2018-05-10 07:33:49 +03:00
|
|
|
"path",
|
|
|
|
help="The path to the eden mount point. If a subdirectory inside "
|
|
|
|
"a mount point is specified, only data about inodes under the "
|
|
|
|
"specified subdirectory will be reported.",
|
2018-05-04 19:04:43 +03:00
|
|
|
)
|
2018-04-20 03:36:17 +03:00
|
|
|
|
|
|
|
def run(self, args: argparse.Namespace) -> int:
|
2017-10-16 21:47:36 +03:00
|
|
|
out = sys.stdout.buffer
|
2018-08-22 21:05:44 +03:00
|
|
|
instance, checkout, rel_path = cmd_util.require_checkout(args, args.path)
|
2018-08-16 07:34:10 +03:00
|
|
|
with instance.get_thrift_client() as client:
|
2018-08-23 21:53:14 +03:00
|
|
|
results = client.debugInodeStatus(bytes(checkout.path), bytes(rel_path))
|
2017-04-04 01:47:53 +03:00
|
|
|
|
2018-05-10 07:33:49 +03:00
|
|
|
out.write(b"%d loaded TreeInodes\n" % len(results))
|
2018-04-20 03:36:17 +03:00
|
|
|
for inode_info in results:
|
|
|
|
_print_inode_info(inode_info, out)
|
|
|
|
return 0
|
2017-10-16 21:47:36 +03:00
|
|
|
|
|
|
|
|
2019-08-13 21:31:11 +03:00
|
|
|
@debug_cmd("file_stats", "Show data about loaded and written files")
|
|
|
|
class FileStatsCMD(Subcmd):
|
|
|
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
|
|
parser.add_argument("path", help="The path to the eden mount point")
|
2019-10-02 02:05:50 +03:00
|
|
|
|
|
|
|
# TODO: remove after updating Sandcastle code to stop using this flag
|
2019-08-16 05:46:21 +03:00
|
|
|
parser.add_argument("--sizes", action="store_true", help="Compute file sizes")
|
|
|
|
|
|
|
|
def make_file_entries(
|
2019-10-02 02:05:50 +03:00
|
|
|
self, paths_and_sizes: List[Tuple[str, int]]
|
|
|
|
) -> List[Dict[str, Union[str, int]]]:
|
|
|
|
return [
|
|
|
|
{"path": path, "size": file_size} for (path, file_size) in paths_and_sizes
|
|
|
|
]
|
2019-08-13 21:31:11 +03:00
|
|
|
|
|
|
|
def run(self, args: argparse.Namespace) -> int:
|
|
|
|
request_root = args.path
|
|
|
|
instance, checkout, rel_path = cmd_util.require_checkout(args, request_root)
|
|
|
|
|
|
|
|
with instance.get_thrift_client() as client:
|
|
|
|
inode_results = client.debugInodeStatus(
|
|
|
|
bytes(checkout.path), bytes(rel_path)
|
|
|
|
)
|
|
|
|
|
|
|
|
read_files, written_files = split_inodes_by_operation_type(inode_results)
|
2019-08-16 05:46:21 +03:00
|
|
|
operations = {
|
2019-10-02 02:05:50 +03:00
|
|
|
"read_files": self.make_file_entries(read_files),
|
|
|
|
"written_files": self.make_file_entries(written_files),
|
2019-08-16 05:46:21 +03:00
|
|
|
}
|
2019-08-16 02:23:27 +03:00
|
|
|
json.dump(operations, fp=sys.stdout, indent=4, separators=(",", ": "))
|
2019-08-13 21:31:11 +03:00
|
|
|
return 0
|
|
|
|
|
|
|
|
|
2018-05-10 07:33:49 +03:00
|
|
|
@debug_cmd("fuse_calls", "Show data about outstanding fuse calls")
|
2018-04-20 03:36:17 +03:00
|
|
|
class FuseCallsCmd(Subcmd):
|
|
|
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
2018-05-10 07:33:49 +03:00
|
|
|
parser.add_argument("path", help="The path to the eden mount point.")
|
2018-04-20 03:36:17 +03:00
|
|
|
|
|
|
|
def run(self, args: argparse.Namespace) -> int:
|
2018-03-20 20:18:29 +03:00
|
|
|
out = sys.stdout.buffer
|
2018-08-22 21:05:44 +03:00
|
|
|
instance, checkout, _rel_path = cmd_util.require_checkout(args, args.path)
|
2018-08-16 07:34:10 +03:00
|
|
|
with instance.get_thrift_client() as client:
|
2018-08-22 21:05:44 +03:00
|
|
|
outstanding_call = client.debugOutstandingFuseCalls(bytes(checkout.path))
|
2018-03-20 20:18:29 +03:00
|
|
|
|
2018-05-10 07:33:49 +03:00
|
|
|
out.write(b"Number of outstanding Calls: %d\n" % len(outstanding_call))
|
2018-04-20 03:36:17 +03:00
|
|
|
for count, call in enumerate(outstanding_call):
|
2018-05-10 07:33:49 +03:00
|
|
|
out.write(b"Call %d\n" % (count + 1))
|
|
|
|
out.write(b"\tlen: %d\n" % call.len)
|
|
|
|
out.write(b"\topcode: %d\n" % call.opcode)
|
|
|
|
out.write(b"\tunique: %d\n" % call.unique)
|
|
|
|
out.write(b"\tnodeid: %d\n" % call.nodeid)
|
|
|
|
out.write(b"\tuid: %d\n" % call.uid)
|
|
|
|
out.write(b"\tgid: %d\n" % call.gid)
|
|
|
|
out.write(b"\tpid: %d\n" % call.pid)
|
2018-03-20 20:18:29 +03:00
|
|
|
|
2018-04-20 03:36:17 +03:00
|
|
|
return 0
|
2018-03-20 20:18:29 +03:00
|
|
|
|
|
|
|
|
2018-04-20 03:36:17 +03:00
|
|
|
def _print_inode_info(inode_info: TreeInodeDebugInfo, out: IO[bytes]) -> None:
|
2018-05-10 07:33:49 +03:00
|
|
|
out.write(inode_info.path + b"\n")
|
|
|
|
out.write(b" Inode number: %d\n" % inode_info.inodeNumber)
|
|
|
|
out.write(b" Ref count: %d\n" % inode_info.refcount)
|
|
|
|
out.write(b" Materialized?: %s\n" % str(inode_info.materialized).encode())
|
|
|
|
out.write(b" Object ID: %s\n" % hash_str(inode_info.treeHash).encode())
|
|
|
|
out.write(b" Entries (%d total):\n" % len(inode_info.entries))
|
2017-10-16 21:47:36 +03:00
|
|
|
for entry in inode_info.entries:
|
|
|
|
if entry.loaded:
|
2018-05-10 07:33:49 +03:00
|
|
|
loaded_flag = "L"
|
2017-10-16 21:47:36 +03:00
|
|
|
else:
|
2018-05-10 07:33:49 +03:00
|
|
|
loaded_flag = "-"
|
2017-10-16 21:47:36 +03:00
|
|
|
|
|
|
|
file_type_str, perms = _parse_mode(entry.mode)
|
2018-05-10 07:33:49 +03:00
|
|
|
line = " {:9} {} {:4o} {} {:40} {}\n".format(
|
|
|
|
entry.inodeNumber,
|
|
|
|
file_type_str,
|
|
|
|
perms,
|
|
|
|
loaded_flag,
|
|
|
|
hash_str(entry.hash),
|
|
|
|
escape_path(entry.name),
|
2018-05-04 19:04:43 +03:00
|
|
|
)
|
2017-10-16 21:47:36 +03:00
|
|
|
out.write(line.encode())
|
2017-04-04 01:47:53 +03:00
|
|
|
|
|
|
|
|
2018-05-10 07:33:49 +03:00
|
|
|
@debug_cmd("getpath", "Get the eden path that corresponds to an inode number")
|
2018-04-20 03:36:17 +03:00
|
|
|
class GetPathCmd(Subcmd):
|
|
|
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
|
|
parser.add_argument(
|
2018-05-10 07:33:49 +03:00
|
|
|
"path",
|
|
|
|
nargs="?",
|
|
|
|
help="The path to an Eden mount point. Uses `pwd` by default.",
|
2018-05-04 19:04:43 +03:00
|
|
|
)
|
2018-04-20 03:36:17 +03:00
|
|
|
parser.add_argument(
|
2018-05-10 07:33:49 +03:00
|
|
|
"number",
|
2018-04-20 03:36:17 +03:00
|
|
|
type=int,
|
2018-05-10 07:33:49 +03:00
|
|
|
help="Display information for the specified inode number.",
|
2018-05-04 19:04:43 +03:00
|
|
|
)
|
2017-08-17 05:56:32 +03:00
|
|
|
|
2018-04-20 03:36:17 +03:00
|
|
|
def run(self, args: argparse.Namespace) -> int:
|
2018-08-22 21:05:44 +03:00
|
|
|
path = args.path or os.getcwd()
|
|
|
|
instance, checkout, _rel_path = cmd_util.require_checkout(args, path)
|
2017-06-23 08:33:01 +03:00
|
|
|
|
2018-08-16 07:34:10 +03:00
|
|
|
with instance.get_thrift_client() as client:
|
2018-08-22 21:05:44 +03:00
|
|
|
inodePathInfo = client.debugGetInodePath(bytes(checkout.path), args.number)
|
|
|
|
|
|
|
|
state = "loaded" if inodePathInfo.loaded else "unloaded"
|
|
|
|
resolved_path = (
|
|
|
|
checkout.path.joinpath(os.fsdecode(inodePathInfo.path))
|
|
|
|
if inodePathInfo.linked
|
|
|
|
else "[unlinked]"
|
2018-05-04 19:04:43 +03:00
|
|
|
)
|
2018-08-22 21:05:44 +03:00
|
|
|
print(f"{state} {resolved_path}")
|
2018-04-20 03:36:17 +03:00
|
|
|
return 0
|
|
|
|
|
|
|
|
|
2018-05-10 07:33:49 +03:00
|
|
|
@debug_cmd("unload", "Unload unused inodes")
|
2018-04-20 03:36:17 +03:00
|
|
|
class UnloadInodesCmd(Subcmd):
|
|
|
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
|
|
parser.add_argument(
|
2018-05-10 07:33:49 +03:00
|
|
|
"path",
|
|
|
|
help="The path to the eden mount point. If a subdirectory inside "
|
|
|
|
"a mount point is specified, only inodes under the "
|
|
|
|
"specified subdirectory will be unloaded.",
|
2018-05-04 19:04:43 +03:00
|
|
|
)
|
2018-04-20 03:36:17 +03:00
|
|
|
parser.add_argument(
|
2018-05-10 07:33:49 +03:00
|
|
|
"age",
|
2018-04-20 03:36:17 +03:00
|
|
|
type=float,
|
2018-08-15 22:52:12 +03:00
|
|
|
nargs="?",
|
|
|
|
default=0,
|
2018-05-10 07:33:49 +03:00
|
|
|
help="Minimum age of the inodes to be unloaded in seconds",
|
2018-04-20 03:36:17 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
def run(self, args: argparse.Namespace) -> int:
|
2018-08-22 21:05:44 +03:00
|
|
|
instance, checkout, rel_path = cmd_util.require_checkout(args, args.path)
|
2017-07-08 04:32:42 +03:00
|
|
|
|
2018-08-16 07:34:10 +03:00
|
|
|
with instance.get_thrift_client() as client:
|
2018-04-20 03:36:17 +03:00
|
|
|
# set the age in nanoSeconds
|
|
|
|
age = TimeSpec()
|
|
|
|
age.seconds = int(args.age)
|
2018-05-10 07:33:49 +03:00
|
|
|
age.nanoSeconds = int((args.age - age.seconds) * 10 ** 9)
|
2018-08-23 21:53:14 +03:00
|
|
|
count = client.unloadInodeForPath(
|
|
|
|
bytes(checkout.path), bytes(rel_path), age
|
|
|
|
)
|
2017-06-17 02:08:19 +03:00
|
|
|
|
2018-08-22 21:05:44 +03:00
|
|
|
unload_path = checkout.path.joinpath(rel_path)
|
2018-08-15 22:52:12 +03:00
|
|
|
print(f"Unloaded {count} inodes under {unload_path}")
|
2017-06-23 08:33:01 +03:00
|
|
|
|
2018-04-20 03:36:17 +03:00
|
|
|
return 0
|
2017-07-08 04:32:42 +03:00
|
|
|
|
2017-06-17 02:08:19 +03:00
|
|
|
|
2018-05-10 07:33:49 +03:00
|
|
|
@debug_cmd("flush_cache", "Flush kernel cache for inode")
|
2018-04-20 03:36:17 +03:00
|
|
|
class FlushCacheCmd(Subcmd):
|
|
|
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
|
|
parser.add_argument(
|
2018-05-10 07:33:49 +03:00
|
|
|
"path", help="Path to a directory/file inside an eden mount."
|
2018-05-04 19:04:43 +03:00
|
|
|
)
|
2017-08-22 01:52:55 +03:00
|
|
|
|
2018-04-20 03:36:17 +03:00
|
|
|
def run(self, args: argparse.Namespace) -> int:
|
2018-08-22 21:05:44 +03:00
|
|
|
instance, checkout, rel_path = cmd_util.require_checkout(args, args.path)
|
2017-08-22 01:52:55 +03:00
|
|
|
|
2018-08-16 07:34:10 +03:00
|
|
|
with instance.get_thrift_client() as client:
|
2018-08-22 21:05:44 +03:00
|
|
|
client.invalidateKernelInodeCache(bytes(checkout.path), bytes(rel_path))
|
2017-08-22 01:52:55 +03:00
|
|
|
|
2018-04-20 03:36:17 +03:00
|
|
|
return 0
|
2018-03-30 11:48:09 +03:00
|
|
|
|
|
|
|
|
2018-05-10 07:33:49 +03:00
|
|
|
@debug_cmd("log", "Display the eden log file")
|
2018-04-20 03:36:17 +03:00
|
|
|
class LogCmd(Subcmd):
|
|
|
|
def run(self, args: argparse.Namespace) -> int:
|
|
|
|
# Display eden's log with the system pager if possible. We could
|
|
|
|
# add a --tail option.
|
2018-08-16 07:34:10 +03:00
|
|
|
instance = cmd_util.get_eden_instance(args)
|
2018-03-30 11:48:09 +03:00
|
|
|
|
2018-08-16 07:34:10 +03:00
|
|
|
eden_log_path = instance.get_log_path()
|
2019-03-09 06:02:42 +03:00
|
|
|
if not eden_log_path.exists():
|
|
|
|
print(f"No log file found at {eden_log_path}", file=sys.stderr)
|
2018-04-20 03:36:17 +03:00
|
|
|
return 1
|
2018-03-30 11:48:09 +03:00
|
|
|
|
2018-05-10 07:33:49 +03:00
|
|
|
pager_env = os.getenv("PAGER")
|
2018-04-20 03:36:17 +03:00
|
|
|
if pager_env:
|
|
|
|
pager_cmd = shlex.split(pager_env)
|
|
|
|
else:
|
2018-05-10 07:33:49 +03:00
|
|
|
pager_cmd = ["less"]
|
2019-03-09 06:02:42 +03:00
|
|
|
pager_cmd.append(str(eden_log_path))
|
2018-04-20 03:36:17 +03:00
|
|
|
|
|
|
|
os.execvp(pager_cmd[0], pager_cmd)
|
2018-05-10 07:33:49 +03:00
|
|
|
raise Exception("we should never reach here")
|
2018-04-20 03:36:17 +03:00
|
|
|
|
|
|
|
|
2018-07-23 22:49:54 +03:00
|
|
|
@debug_cmd("logging", "Display or modify logging configuration for the edenfs daemon")
|
|
|
|
class LoggingCmd(Subcmd):
|
2018-04-20 03:36:17 +03:00
|
|
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
|
|
parser.add_argument(
|
2018-07-23 22:49:54 +03:00
|
|
|
"-a",
|
|
|
|
"--all",
|
|
|
|
action="store_true",
|
|
|
|
help="Show the configuration of all logging categories, even ones "
|
|
|
|
"with default configuration settings",
|
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
"--reset",
|
|
|
|
action="store_true",
|
|
|
|
help="Fully reset the logging config to the specified settings rather "
|
|
|
|
"than updating the current configuration with the new settings. "
|
|
|
|
"(Beware that you need to specify log handlers unless you want them "
|
|
|
|
"all to be deleted.)",
|
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
"config",
|
2018-04-20 03:36:17 +03:00
|
|
|
type=str,
|
2018-07-23 22:49:54 +03:00
|
|
|
nargs="?",
|
|
|
|
help="A log configuration string to use to modify the log settings. See "
|
|
|
|
"folly/logging/docs/Config.md (https://git.io/fNZhr)"
|
|
|
|
" for syntax documentation. The most basic syntax is CATEGORY=LEVEL.",
|
2018-04-20 03:36:17 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
def run(self, args: argparse.Namespace) -> int:
|
2018-08-16 07:34:10 +03:00
|
|
|
instance = cmd_util.get_eden_instance(args)
|
2018-03-30 11:48:09 +03:00
|
|
|
|
2018-07-23 22:49:54 +03:00
|
|
|
if args.reset and args.config is None:
|
|
|
|
# The configuration to use if the caller specifies --reset with no
|
|
|
|
# explicit config argument.
|
2019-07-11 00:27:04 +03:00
|
|
|
# pyre-fixme[16]: `Namespace` has no attribute `config`.
|
2018-07-23 22:49:54 +03:00
|
|
|
args.config = (
|
|
|
|
"WARN:default,eden=DBG2; default=stream:stream=stderr,async=true"
|
|
|
|
)
|
|
|
|
|
2018-08-16 07:34:10 +03:00
|
|
|
with instance.get_thrift_client() as client:
|
2018-07-23 22:49:54 +03:00
|
|
|
if args.config is not None:
|
|
|
|
if args.reset:
|
|
|
|
print(f"Resetting logging configuration to {args.config!r}")
|
2018-11-14 23:13:46 +03:00
|
|
|
client.setOption("logging_full", args.config)
|
2018-07-23 22:49:54 +03:00
|
|
|
else:
|
|
|
|
print(f"Updating logging configuration with {args.config!r}")
|
2018-11-14 23:13:46 +03:00
|
|
|
client.setOption("logging", args.config)
|
2018-07-23 22:49:54 +03:00
|
|
|
print("Updated configuration. New config settings:")
|
|
|
|
else:
|
|
|
|
print("Current logging configuration:")
|
|
|
|
|
|
|
|
if args.all:
|
|
|
|
config_str = client.getOption("logging_full")
|
|
|
|
else:
|
|
|
|
config_str = client.getOption("logging")
|
|
|
|
self.print_config(config_str)
|
2017-10-17 02:22:27 +03:00
|
|
|
|
2018-04-20 03:36:17 +03:00
|
|
|
return 0
|
2017-10-17 02:22:27 +03:00
|
|
|
|
2018-07-23 22:49:54 +03:00
|
|
|
def print_config(self, config_str: str) -> None:
|
|
|
|
config = json.loads(config_str)
|
|
|
|
|
|
|
|
handler_fmt = " {:12} {:12} {}"
|
|
|
|
separator = " " + ("-" * 76)
|
|
|
|
|
|
|
|
print("=== Log Handlers ===")
|
|
|
|
if not config["handlers"]:
|
|
|
|
print(" Warning: no log handlers configured!")
|
|
|
|
else:
|
|
|
|
print(handler_fmt.format("Name", "Type", "Options"))
|
|
|
|
print(separator)
|
|
|
|
for name, handler in sorted(config["handlers"].items()):
|
|
|
|
options_str = ", ".join(
|
|
|
|
sorted("{}={}".format(k, v) for k, v in handler["options"].items())
|
|
|
|
)
|
|
|
|
print(handler_fmt.format(name, handler["type"], options_str))
|
|
|
|
|
|
|
|
print("\n=== Log Categories ===")
|
|
|
|
category_fmt = " {:50} {:12} {}"
|
|
|
|
print(category_fmt.format("Name", "Level", "Handlers"))
|
|
|
|
print(separator)
|
|
|
|
for name, category in sorted(config["categories"].items()):
|
|
|
|
# For categories that do not inherit their parent's level (unusual)
|
|
|
|
# show the level with a trailing '!'
|
|
|
|
# Don't do this for the root category, though--it never inherits it's
|
|
|
|
# parent's level since it has no parent.
|
|
|
|
level_str = category["level"]
|
|
|
|
if not category["inherit"] and name != "":
|
|
|
|
level_str = level_str + "!"
|
|
|
|
|
|
|
|
# Print the root category name as '.' instead of the empty string just
|
|
|
|
# to help make it clear that there is a category name here.
|
|
|
|
# (The logging config parsing code accepts '.' as the root category
|
|
|
|
# name too.)
|
|
|
|
if name == "":
|
|
|
|
name = "."
|
|
|
|
|
|
|
|
handlers_str = ", ".join(category["handlers"])
|
|
|
|
print(category_fmt.format(name, level_str, handlers_str))
|
|
|
|
|
|
|
|
|
|
|
|
# set_log_level is deprecated.
|
|
|
|
# We should delete it in a few weeks (from 2018-07-18).
|
|
|
|
# The debugSetLogLevel() API in eden/fs/service/eden.thrift can also be removed at the
|
|
|
|
# same time.
|
|
|
|
@debug_cmd("set_log_level", help=None)
|
|
|
|
class SetLogLevelCmd(Subcmd):
|
|
|
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
|
|
parser.add_argument("category", type=str, help="Period-separated log category.")
|
|
|
|
parser.add_argument(
|
|
|
|
"level",
|
|
|
|
type=str,
|
|
|
|
help="Log level string as understood by stringToLogLevel.",
|
|
|
|
)
|
|
|
|
|
|
|
|
def run(self, args: argparse.Namespace) -> int:
|
|
|
|
print(
|
|
|
|
"The set_log_level command is deprecated. "
|
|
|
|
"Use `eden debug logging` instead:"
|
|
|
|
)
|
|
|
|
log_arg = shlex.quote(f"{args.category}={args.level}")
|
|
|
|
print(f" eden debug logging {log_arg}")
|
|
|
|
return 1
|
|
|
|
|
2017-10-17 02:22:27 +03:00
|
|
|
|
2019-07-03 04:59:07 +03:00
|
|
|
@debug_cmd("journal_set_memory_limit", "Sets the journal memory limit")
|
|
|
|
class DebugJournalSetMemoryLimitCmd(Subcmd):
|
|
|
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
|
|
parser.add_argument(
|
|
|
|
"limit",
|
|
|
|
type=int,
|
|
|
|
help="The amount of memory (in bytes) that the journal can keep.",
|
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
"path",
|
|
|
|
nargs="?",
|
|
|
|
help="The path to an Eden mount point. Uses `pwd` by default.",
|
|
|
|
)
|
|
|
|
|
|
|
|
def run(self, args: argparse.Namespace) -> int:
|
|
|
|
instance, checkout, _rel_path = cmd_util.require_checkout(args, args.path)
|
|
|
|
|
|
|
|
with instance.get_thrift_client() as client:
|
|
|
|
try:
|
|
|
|
client.setJournalMemoryLimit(bytes(checkout.path), args.limit)
|
|
|
|
except EdenError as err:
|
|
|
|
print(err, file=sys.stderr)
|
|
|
|
return 1
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
@debug_cmd("journal_get_memory_limit", "Gets the journal memory limit")
|
|
|
|
class DebugJournalGetMemoryLimitCmd(Subcmd):
|
|
|
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
|
|
parser.add_argument(
|
|
|
|
"path",
|
|
|
|
nargs="?",
|
|
|
|
help="The path to an Eden mount point. Uses `pwd` by default.",
|
|
|
|
)
|
|
|
|
|
|
|
|
def run(self, args: argparse.Namespace) -> int:
|
|
|
|
instance, checkout, _rel_path = cmd_util.require_checkout(args, args.path)
|
|
|
|
|
|
|
|
with instance.get_thrift_client() as client:
|
|
|
|
try:
|
|
|
|
mem = client.getJournalMemoryLimit(bytes(checkout.path))
|
|
|
|
except EdenError as err:
|
|
|
|
print(err, file=sys.stderr)
|
|
|
|
return 1
|
|
|
|
print("Journal memory limit is " + stats_print.format_size(mem))
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
2019-07-31 08:02:05 +03:00
|
|
|
@debug_cmd(
|
|
|
|
"flush_journal",
|
|
|
|
"Flushes the journal, and causes any subscribers to get a truncated result",
|
|
|
|
)
|
|
|
|
class DebugFlushJournalCmd(Subcmd):
|
|
|
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
|
|
parser.add_argument(
|
|
|
|
"path",
|
|
|
|
nargs="?",
|
|
|
|
help="The path to an Eden mount point. Uses `pwd` by default.",
|
|
|
|
)
|
|
|
|
|
|
|
|
def run(self, args: argparse.Namespace) -> int:
|
|
|
|
instance, checkout, _rel_path = cmd_util.require_checkout(args, args.path)
|
|
|
|
|
|
|
|
with instance.get_thrift_client() as client:
|
|
|
|
try:
|
|
|
|
client.flushJournal(bytes(checkout.path))
|
|
|
|
except EdenError as err:
|
|
|
|
print(err, file=sys.stderr)
|
|
|
|
return 1
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
2019-07-09 19:10:19 +03:00
|
|
|
@debug_cmd("journal", "Prints the most recent entries from the journal")
|
2018-07-12 05:40:33 +03:00
|
|
|
class DebugJournalCmd(Subcmd):
|
2018-05-23 11:27:16 +03:00
|
|
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
|
|
parser.add_argument(
|
|
|
|
"-n",
|
|
|
|
"--limit",
|
|
|
|
type=int,
|
|
|
|
default=1000,
|
|
|
|
help="The number of journal entries to print.",
|
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
"-e",
|
|
|
|
"--pattern",
|
|
|
|
type=str,
|
|
|
|
help="Show only deltas for paths matching this pattern. "
|
|
|
|
"Specify '^((?!^\\.hg/).)*$' to exclude the .hg/ directory.",
|
|
|
|
)
|
2019-07-09 19:10:19 +03:00
|
|
|
parser.add_argument(
|
|
|
|
"-f",
|
|
|
|
"--follow",
|
|
|
|
action="store_true",
|
|
|
|
default=False,
|
|
|
|
help="Output appended data as the journal grows.",
|
|
|
|
)
|
2018-05-23 11:27:16 +03:00
|
|
|
parser.add_argument(
|
|
|
|
"-i",
|
|
|
|
"--ignore-case",
|
|
|
|
action="store_true",
|
|
|
|
default=False,
|
|
|
|
help="Ignore case in the pattern specified by --pattern.",
|
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
"path",
|
|
|
|
nargs="?",
|
|
|
|
help="The path to an Eden mount point. Uses `pwd` by default.",
|
|
|
|
)
|
|
|
|
|
|
|
|
def run(self, args: argparse.Namespace) -> int:
|
2019-07-09 19:10:19 +03:00
|
|
|
pattern: Optional[Pattern[bytes]] = None
|
|
|
|
if args.pattern:
|
|
|
|
pattern_bytes = args.pattern.encode("utf-8")
|
|
|
|
flags = re.IGNORECASE if args.ignore_case else 0
|
|
|
|
pattern = re.compile(pattern_bytes, flags)
|
2018-05-23 11:27:16 +03:00
|
|
|
|
2019-07-09 19:10:19 +03:00
|
|
|
instance, checkout, _ = cmd_util.require_checkout(args, args.path)
|
|
|
|
mount = bytes(checkout.path)
|
|
|
|
|
|
|
|
def refresh(params):
|
|
|
|
with instance.get_thrift_client() as client:
|
|
|
|
journal = client.debugGetRawJournal(params)
|
|
|
|
|
|
|
|
deltas = journal.allDeltas
|
|
|
|
if len(deltas) == 0:
|
|
|
|
seq_num = params.fromSequenceNumber
|
|
|
|
else:
|
|
|
|
seq_num = deltas[0].fromPosition.sequenceNumber + 1
|
|
|
|
_print_raw_journal_deltas(reversed(deltas), pattern)
|
|
|
|
|
|
|
|
return seq_num
|
|
|
|
|
|
|
|
try:
|
2018-05-23 11:27:16 +03:00
|
|
|
params = DebugGetRawJournalParams(
|
2019-07-09 19:10:19 +03:00
|
|
|
mountPoint=mount, fromSequenceNumber=1, limit=args.limit
|
2018-05-23 11:27:16 +03:00
|
|
|
)
|
2019-07-09 19:10:19 +03:00
|
|
|
seq_num = refresh(params)
|
|
|
|
while args.follow:
|
|
|
|
REFRESH_SEC = 2
|
|
|
|
time.sleep(REFRESH_SEC)
|
|
|
|
params = DebugGetRawJournalParams(
|
|
|
|
mountPoint=mount, fromSequenceNumber=seq_num
|
|
|
|
)
|
|
|
|
seq_num = refresh(params)
|
|
|
|
except EdenError as err:
|
|
|
|
print(err, file=sys.stderr)
|
|
|
|
return 1
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
if args.follow:
|
|
|
|
pass
|
2018-05-23 11:27:16 +03:00
|
|
|
else:
|
2019-07-09 19:10:19 +03:00
|
|
|
raise
|
2018-05-23 11:27:16 +03:00
|
|
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
2019-01-11 02:07:04 +03:00
|
|
|
def _print_raw_journal_deltas(
|
2019-01-08 01:40:57 +03:00
|
|
|
deltas: Iterator[DebugJournalDelta], pattern: Optional[Pattern[bytes]]
|
2018-05-23 11:27:16 +03:00
|
|
|
) -> None:
|
2018-08-11 11:34:44 +03:00
|
|
|
matcher: Callable[[bytes], bool] = (lambda x: True) if pattern is None else cast(
|
2018-08-04 00:54:10 +03:00
|
|
|
Any, pattern.match
|
|
|
|
)
|
2018-08-16 00:43:28 +03:00
|
|
|
|
|
|
|
labels = {
|
|
|
|
(False, False): "_",
|
|
|
|
(False, True): "A",
|
|
|
|
(True, False): "R",
|
|
|
|
(True, True): "M",
|
|
|
|
}
|
|
|
|
|
2018-05-23 11:27:16 +03:00
|
|
|
for delta in deltas:
|
2018-08-16 00:43:28 +03:00
|
|
|
entries: List[str] = []
|
|
|
|
|
|
|
|
for path, info in delta.changedPaths.items():
|
|
|
|
if not matcher(path):
|
|
|
|
continue
|
|
|
|
|
|
|
|
label = labels[(info.existedBefore, info.existedAfter)]
|
2018-08-20 23:42:05 +03:00
|
|
|
entries.append(f"{label} {os.fsdecode(path)}")
|
2018-08-16 00:43:28 +03:00
|
|
|
|
|
|
|
for path in delta.uncleanPaths:
|
2018-08-20 23:42:05 +03:00
|
|
|
entries.append(f"X {os.fsdecode(path)}")
|
2018-05-23 11:27:16 +03:00
|
|
|
|
2019-01-11 02:07:04 +03:00
|
|
|
# Only print journal entries if they changed paths that matched the matcher
|
|
|
|
# or if they change the current working directory commit.
|
|
|
|
if entries or delta.fromPosition.snapshotHash != delta.toPosition.snapshotHash:
|
|
|
|
_print_journal_entry(delta, entries)
|
2018-05-23 11:27:16 +03:00
|
|
|
|
2018-08-16 00:43:28 +03:00
|
|
|
|
2019-01-11 02:07:04 +03:00
|
|
|
def _print_journal_entry(delta: DebugJournalDelta, entries: List[str]) -> None:
|
|
|
|
if delta.fromPosition.snapshotHash != delta.toPosition.snapshotHash:
|
|
|
|
from_commit = hash_str(delta.fromPosition.snapshotHash)
|
|
|
|
to_commit = hash_str(delta.toPosition.snapshotHash)
|
|
|
|
commit_ids = f"{from_commit} -> {to_commit}"
|
|
|
|
else:
|
|
|
|
commit_ids = hash_str(delta.toPosition.snapshotHash)
|
|
|
|
|
|
|
|
if delta.fromPosition.sequenceNumber != delta.toPosition.sequenceNumber:
|
|
|
|
print(
|
|
|
|
f"MERGE {delta.fromPosition.sequenceNumber}-"
|
|
|
|
f"{delta.toPosition.sequenceNumber} {commit_ids}"
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
print(f"DELTA {delta.fromPosition.sequenceNumber} {commit_ids}")
|
|
|
|
|
2019-01-11 06:46:21 +03:00
|
|
|
if entries:
|
|
|
|
entries.sort()
|
|
|
|
print(" " + "\n ".join(entries))
|
2018-05-23 11:27:16 +03:00
|
|
|
|
|
|
|
|
2018-10-23 21:52:29 +03:00
|
|
|
@debug_cmd("thrift", "Invoke a thrift function")
|
|
|
|
class DebugThriftCmd(Subcmd):
|
|
|
|
args_suffix = "_args"
|
|
|
|
result_suffix = "_result"
|
|
|
|
|
|
|
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
|
|
parser.add_argument(
|
|
|
|
"-l",
|
|
|
|
"--list",
|
|
|
|
action="store_true",
|
|
|
|
help="List the available thrift functions.",
|
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
"--eval-all-args",
|
|
|
|
action="store_true",
|
|
|
|
help="Always pass all arguments through eval(), even for plain strings.",
|
|
|
|
)
|
2019-09-27 01:14:23 +03:00
|
|
|
parser.add_argument(
|
|
|
|
"--json", action="store_true", help="Attempt to encode the result as JSON."
|
|
|
|
)
|
2018-10-23 21:52:29 +03:00
|
|
|
parser.add_argument(
|
|
|
|
"function_name", nargs="?", help="The thrift function to call."
|
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
"args", nargs="*", help="The arguments to the thrift function."
|
|
|
|
)
|
|
|
|
|
|
|
|
def run(self, args: argparse.Namespace) -> int:
|
|
|
|
if args.list:
|
|
|
|
self._list_functions()
|
|
|
|
return 0
|
|
|
|
|
|
|
|
if not args.function_name:
|
|
|
|
print(f"Error: no function name specified", file=sys.stderr)
|
|
|
|
print(
|
|
|
|
"Use the --list argument to see a list of available functions, or "
|
|
|
|
"specify a function name",
|
|
|
|
file=sys.stderr,
|
|
|
|
)
|
|
|
|
return 1
|
|
|
|
|
|
|
|
# Look up the function information
|
|
|
|
try:
|
|
|
|
fn_info = thrift.util.inspect.get_function_info(
|
|
|
|
EdenService, args.function_name
|
|
|
|
)
|
|
|
|
except thrift.util.inspect.NoSuchFunctionError:
|
|
|
|
print(f"Error: unknown function {args.function_name!r}", file=sys.stderr)
|
|
|
|
print(
|
|
|
|
'Run "eden debug thrift --list" to see a list of available functions',
|
|
|
|
file=sys.stderr,
|
|
|
|
)
|
|
|
|
return 1
|
|
|
|
|
|
|
|
if len(args.args) != len(fn_info.arg_specs):
|
|
|
|
print(
|
|
|
|
f"Error: {args.function_name} requires {len(fn_info.arg_specs)} "
|
|
|
|
f"arguments, but {len(args.args)} were supplied>",
|
|
|
|
file=sys.stderr,
|
|
|
|
)
|
|
|
|
return 1
|
|
|
|
|
|
|
|
python_args = self._eval_args(
|
|
|
|
args.args, fn_info, eval_strings=args.eval_all_args
|
|
|
|
)
|
|
|
|
|
2019-09-27 01:14:23 +03:00
|
|
|
def lookup_module_member(modules, name):
|
|
|
|
for module in modules:
|
|
|
|
try:
|
|
|
|
return getattr(module, name)
|
|
|
|
except AttributeError:
|
|
|
|
continue
|
|
|
|
raise AttributeError(f"Failed to find {name} in {modules}")
|
|
|
|
|
2018-10-23 21:52:29 +03:00
|
|
|
instance = cmd_util.get_eden_instance(args)
|
|
|
|
with instance.get_thrift_client() as client:
|
|
|
|
fn = getattr(client, args.function_name)
|
|
|
|
result = fn(**python_args)
|
2019-09-27 01:14:23 +03:00
|
|
|
if args.json:
|
|
|
|
# The following back-and-forth is required to reliably
|
|
|
|
# convert a Python Thrift client result into its JSON
|
|
|
|
# form. The Python Thrift client returns native Python
|
|
|
|
# lists and dicts for lists and maps, but they cannot
|
|
|
|
# be passed directly to TSimpleJSONProtocol. Instead,
|
|
|
|
# map the result back into a Thrift message, and then
|
|
|
|
# serialize that as JSON. Finally, strip the message
|
|
|
|
# container.
|
|
|
|
#
|
|
|
|
# NOTE: Stripping the root object means the output may
|
|
|
|
# not have a root dict or array, which is required by
|
|
|
|
# most JSON specs. But Python's json module and jq are
|
|
|
|
# both fine with this deviation.
|
|
|
|
result_type = lookup_module_member(
|
|
|
|
[EdenService, BaseService], args.function_name + "_result"
|
|
|
|
)
|
|
|
|
json_data = Serializer.serialize(
|
|
|
|
TSimpleJSONProtocolFactory(), result_type(result)
|
|
|
|
)
|
|
|
|
json.dump(
|
|
|
|
# If the method returns void, json_data will not
|
|
|
|
# have a "success" field. Print `null` in that
|
|
|
|
# case.
|
|
|
|
json.loads(json_data).get("success"),
|
|
|
|
sys.stdout,
|
|
|
|
sort_keys=True,
|
|
|
|
indent=2,
|
|
|
|
)
|
|
|
|
sys.stdout.write("\n")
|
|
|
|
else:
|
|
|
|
print(result)
|
2018-10-23 21:52:29 +03:00
|
|
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
def _eval_args(
|
|
|
|
self, args: List[str], fn_info: thrift.util.inspect.Function, eval_strings: bool
|
|
|
|
) -> Dict[str, Any]:
|
|
|
|
from thrift.Thrift import TType
|
|
|
|
|
|
|
|
code_globals = {key: getattr(eden_ttypes, key) for key in dir(eden_ttypes)}
|
|
|
|
parsed_args = {}
|
|
|
|
for arg, arg_spec in zip(args, fn_info.arg_specs):
|
|
|
|
(
|
|
|
|
_field_id,
|
|
|
|
thrift_type,
|
|
|
|
arg_name,
|
|
|
|
_extra_spec,
|
|
|
|
_default,
|
|
|
|
_required,
|
|
|
|
) = arg_spec
|
|
|
|
# If the argument is a string type, don't pass it through eval.
|
|
|
|
# This is purely to make it easier for humans to input strings.
|
|
|
|
if not eval_strings and thrift_type == TType.STRING:
|
|
|
|
parsed_arg = arg
|
|
|
|
else:
|
|
|
|
code = compile(arg, "<command_line>", "eval", 0, 1)
|
|
|
|
parsed_arg = eval(code, code_globals.copy())
|
|
|
|
parsed_args[arg_name] = parsed_arg
|
|
|
|
|
|
|
|
return parsed_args
|
|
|
|
|
|
|
|
def _list_functions(self) -> None:
|
|
|
|
# Report functions by module, from parent service downwards
|
|
|
|
modules = thrift.util.inspect.get_service_module_hierarchy(EdenService)
|
|
|
|
for module in reversed(modules):
|
|
|
|
module_functions = thrift.util.inspect.list_service_functions(module)
|
|
|
|
print(f"From {module.__name__}:")
|
|
|
|
|
|
|
|
for _fn_name, fn_info in sorted(module_functions.items()):
|
|
|
|
print(f" {fn_info}")
|
|
|
|
|
|
|
|
|
2018-06-21 21:14:16 +03:00
|
|
|
@subcmd_mod.subcmd("debug", "Internal commands for examining eden state")
|
2018-04-20 03:36:17 +03:00
|
|
|
class DebugCmd(Subcmd):
|
2019-08-30 23:28:14 +03:00
|
|
|
# pyre-fixme[13]: Attribute `parser` is never initialized.
|
2018-11-13 07:23:19 +03:00
|
|
|
parser: argparse.ArgumentParser
|
|
|
|
|
2018-04-20 03:36:17 +03:00
|
|
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
2019-07-23 05:01:54 +03:00
|
|
|
# The "debug_posix" module contains other debug subcommands that are
|
|
|
|
# supported on POSIX platforms (basically, not Windows). Import this module
|
|
|
|
# if we aren't running on Windows. This will make sure it has registered all of
|
|
|
|
# its subcommands in our debug_cmd.commands list.
|
|
|
|
if os.name != "nt":
|
|
|
|
from . import debug_posix # noqa: F401
|
|
|
|
|
2018-04-20 03:36:17 +03:00
|
|
|
# Save the parser so we can use it to print help in run() if we are
|
|
|
|
# called with no arguments.
|
|
|
|
self.parser = parser
|
|
|
|
self.add_subcommands(parser, debug_cmd.commands)
|
2017-12-15 08:07:22 +03:00
|
|
|
|
2018-04-20 03:36:17 +03:00
|
|
|
def run(self, args: argparse.Namespace) -> int:
|
|
|
|
self.parser.print_help()
|
|
|
|
return 0
|