2017-04-04 01:47:53 +03:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
#
|
|
|
|
# Copyright (c) 2004-present, 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.
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
import binascii
|
2017-12-15 00:42:05 +03:00
|
|
|
import collections
|
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
|
2018-05-23 11:27:16 +03:00
|
|
|
from typing import IO, Any, Dict, Iterator, List, Optional, Pattern, Tuple
|
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-05-10 07:33:49 +03:00
|
|
|
from facebook.eden.overlay.ttypes import OverlayDir
|
2018-05-23 11:27:16 +03:00
|
|
|
from facebook.eden.ttypes import (
|
|
|
|
DebugGetRawJournalParams,
|
|
|
|
FileDelta,
|
|
|
|
JournalPosition,
|
|
|
|
NoValueForKeyError,
|
|
|
|
TimeSpec,
|
|
|
|
TreeInodeDebugInfo,
|
|
|
|
)
|
2017-04-04 01:47:53 +03:00
|
|
|
|
2018-07-12 05:40:34 +03:00
|
|
|
from . import (
|
|
|
|
cmd_util,
|
|
|
|
config as config_mod,
|
|
|
|
overlay as overlay_mod,
|
|
|
|
subcmd as subcmd_mod,
|
|
|
|
util,
|
|
|
|
)
|
2018-05-07 21:17:06 +03:00
|
|
|
from .stdout_printer import StdoutPrinter
|
2018-04-20 03:36:17 +03:00
|
|
|
from .subcmd import Subcmd
|
|
|
|
|
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 get_mount_path(path: str) -> Tuple[str, str]:
|
2018-05-10 07:33:49 +03:00
|
|
|
"""
|
2017-04-04 01:47:53 +03:00
|
|
|
Given a path inside an eden mount, find the path to the eden root.
|
|
|
|
|
|
|
|
Returns a tuple of (eden_mount_path, relative_path)
|
|
|
|
where relative_path is the path such that
|
|
|
|
os.path.join(eden_mount_path, relative_path) refers to the same file as the
|
|
|
|
original input path.
|
2018-05-10 07:33:49 +03:00
|
|
|
"""
|
2017-04-04 01:47:53 +03:00
|
|
|
# TODO: This will probably be easier to do using the special .eden
|
|
|
|
# directory, once the diff adding .eden lands.
|
|
|
|
current_path = os.path.realpath(path)
|
2018-05-10 07:33:49 +03:00
|
|
|
rel_path = ""
|
2017-04-04 01:47:53 +03:00
|
|
|
while True:
|
|
|
|
# For now we simply assume that the first mount point we come across is
|
|
|
|
# the eden mount point. This doesn't handle bind mounts inside the
|
|
|
|
# eden mount, but that's fine for now.
|
|
|
|
if os.path.ismount(current_path):
|
2017-06-15 20:53:15 +03:00
|
|
|
rel_path = os.path.normpath(rel_path)
|
2018-05-10 07:33:49 +03:00
|
|
|
if rel_path == ".":
|
|
|
|
rel_path = ""
|
2017-06-15 20:53:15 +03:00
|
|
|
return (current_path, rel_path)
|
2017-04-04 01:47:53 +03:00
|
|
|
|
|
|
|
parent, basename = os.path.split(current_path)
|
|
|
|
if parent == current_path:
|
2018-05-10 07:33:49 +03:00
|
|
|
raise Exception("eden mount point not found")
|
2017-04-04 01:47:53 +03:00
|
|
|
|
|
|
|
current_path = parent
|
|
|
|
rel_path = os.path.join(basename, rel_path)
|
|
|
|
|
|
|
|
|
|
|
|
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:
|
2017-04-04 01:47:53 +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:
|
|
|
|
config = cmd_util.create_config(args)
|
|
|
|
mount, rel_path = get_mount_path(args.mount)
|
|
|
|
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
|
|
|
|
with config.get_thrift_client() as client:
|
2018-05-10 07:33:49 +03:00
|
|
|
entries = client.debugGetScmTree(mount, 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:
|
|
|
|
config = cmd_util.create_config(args)
|
|
|
|
mount, rel_path = get_mount_path(args.mount)
|
|
|
|
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
|
|
|
|
with config.get_thrift_client() as client:
|
2018-05-10 07:33:49 +03:00
|
|
|
data = client.debugGetScmBlob(mount, 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:
|
|
|
|
config = cmd_util.create_config(args)
|
|
|
|
mount, rel_path = get_mount_path(args.mount)
|
|
|
|
blob_id = parse_object_id(args.id)
|
|
|
|
|
|
|
|
local_only = not args.load
|
|
|
|
with config.get_thrift_client() as client:
|
2018-05-04 19:04:43 +03:00
|
|
|
info = client.debugGetScmBlobMetadata(
|
|
|
|
mount, blob_id, localStoreOnly=local_only
|
|
|
|
)
|
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:
|
|
|
|
config = cmd_util.create_config(args)
|
|
|
|
do_buildinfo(config)
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
2018-05-10 07:33:49 +03:00
|
|
|
def do_buildinfo(config: config_mod.Config, out: Optional[IO[bytes]] = None) -> None:
|
2017-12-20 02:42:53 +03:00
|
|
|
if out is None:
|
|
|
|
out = sys.stdout.buffer
|
2017-12-15 00:42:05 +03:00
|
|
|
build_info = config.get_server_build_info()
|
|
|
|
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:
|
|
|
|
config = cmd_util.create_config(args)
|
|
|
|
with config.get_thrift_client() as client:
|
|
|
|
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:
|
|
|
|
config = cmd_util.create_config(args)
|
|
|
|
with config.get_thrift_client() as client:
|
|
|
|
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:
|
|
|
|
config = cmd_util.create_config(args)
|
|
|
|
do_uptime(config)
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
2018-05-10 07:33:49 +03:00
|
|
|
def do_uptime(config: config_mod.Config, out: Optional[IO[bytes]] = None) -> None:
|
2017-12-20 02:42:53 +03:00
|
|
|
if out is None:
|
|
|
|
out = sys.stdout.buffer
|
2017-12-15 08:07:22 +03:00
|
|
|
uptime = config.get_uptime() # Check if uptime is negative?
|
|
|
|
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()
|
|
|
|
mount, _ = get_mount_path(path)
|
2018-04-20 03:36:17 +03:00
|
|
|
_parents, _dirstate_tuples, copymap = _get_dirstate_data(mount)
|
|
|
|
_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()
|
|
|
|
mount, _ = get_mount_path(path)
|
2018-04-20 03:36:17 +03:00
|
|
|
_parents, dirstate_tuples, copymap = _get_dirstate_data(mount)
|
|
|
|
printer = StdoutPrinter()
|
|
|
|
entries = list(dirstate_tuples.items())
|
2018-05-10 07:33:49 +03:00
|
|
|
print(printer.bold("Non-normal Files (%d):" % len(entries)))
|
2018-04-20 03:36:17 +03:00
|
|
|
entries.sort(key=lambda entry: entry[0]) # Sort by key.
|
|
|
|
for path, dirstate_tuple in entries:
|
|
|
|
_print_hg_nonnormal_file(path, dirstate_tuple, printer)
|
|
|
|
|
2018-05-10 07:33:49 +03:00
|
|
|
print(printer.bold("Copymap (%d):" % len(copymap)))
|
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:
|
|
|
|
mount, rel_path = get_mount_path(args.path)
|
|
|
|
_parents, dirstate_tuples, _copymap = _get_dirstate_data(mount)
|
|
|
|
dirstate_tuple = dirstate_tuples.get(rel_path)
|
|
|
|
printer = StdoutPrinter()
|
|
|
|
if dirstate_tuple:
|
|
|
|
_print_hg_nonnormal_file(rel_path, dirstate_tuple, printer)
|
|
|
|
else:
|
|
|
|
config = cmd_util.create_config(args)
|
|
|
|
with config.get_thrift_client() as client:
|
|
|
|
try:
|
|
|
|
entry = client.getManifestEntry(mount, rel_path)
|
2018-05-10 07:33:49 +03:00
|
|
|
dirstate_tuple = ("n", entry.mode, 0)
|
2018-04-20 03:36:17 +03:00
|
|
|
_print_hg_nonnormal_file(rel_path, dirstate_tuple, printer)
|
|
|
|
except NoValueForKeyError:
|
2018-05-10 07:33:49 +03:00
|
|
|
print("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-05-10 07:33:49 +03:00
|
|
|
rel_path: str, dirstate_tuple: Tuple[str, Any, int], printer: "StdoutPrinter"
|
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
|
|
|
|
|
|
|
print(
|
2018-05-10 07:33:49 +03:00
|
|
|
f"""\
|
2017-10-26 08:24:46 +03:00
|
|
|
{printer.green(rel_path)}
|
2017-08-19 07:36:34 +03:00
|
|
|
status = {status}
|
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
|
|
|
mode = {oct(dirstate_tuple[1])}
|
2017-08-19 07:36:34 +03:00
|
|
|
mergeState = {merge_state}\
|
2018-05-10 07:33:49 +03:00
|
|
|
"""
|
2017-10-26 08:24:46 +03:00
|
|
|
)
|
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(
|
|
|
|
mount: str
|
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
|
|
|
"""
|
|
|
|
filename = os.path.join(mount, ".hg", "dirstate")
|
|
|
|
with open(filename, "rb") as f:
|
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
|
|
|
return eden.dirstate.read(f, filename)
|
|
|
|
|
|
|
|
|
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-04-20 03:36:17 +03:00
|
|
|
config = cmd_util.create_config(args)
|
|
|
|
mount, rel_path = get_mount_path(args.path)
|
|
|
|
with config.get_thrift_client() as client:
|
|
|
|
results = client.debugInodeStatus(mount, 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
|
|
|
|
|
|
|
|
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-04-20 03:36:17 +03:00
|
|
|
config = cmd_util.create_config(args)
|
|
|
|
mount, rel_path = get_mount_path(args.path)
|
|
|
|
with config.get_thrift_client() as client:
|
|
|
|
outstanding_call = client.debugOutstandingFuseCalls(mount)
|
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("overlay", "Show data about the overlay")
|
2018-04-20 03:36:17 +03:00
|
|
|
class OverlayCmd(Subcmd):
|
|
|
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
|
|
parser.add_argument(
|
2018-05-10 07:33:49 +03:00
|
|
|
"-n",
|
|
|
|
"--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
|
|
|
)
|
2018-04-20 03:36:17 +03:00
|
|
|
parser.add_argument(
|
2018-05-10 07:33:49 +03:00
|
|
|
"-d", "--depth", type=int, default=0, help="Recurse to the specified depth."
|
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
|
|
|
"-r",
|
|
|
|
"--recurse",
|
|
|
|
action="store_const",
|
2018-05-04 19:04:43 +03:00
|
|
|
const=-1,
|
2018-05-10 07:33:49 +03:00
|
|
|
dest="depth",
|
2018-05-04 19:04:43 +03:00
|
|
|
default=0,
|
2018-05-10 07:33:49 +03:00
|
|
|
help="Recursively print child entries.",
|
2018-05-04 19:04:43 +03:00
|
|
|
)
|
2018-05-10 07:33:49 +03:00
|
|
|
parser.add_argument("path", nargs="?", help="The path to the eden mount point.")
|
2018-04-20 03:36:17 +03:00
|
|
|
|
|
|
|
def run(self, args: argparse.Namespace) -> int:
|
2018-07-12 05:40:34 +03:00
|
|
|
self.args = args
|
2018-04-20 03:36:17 +03:00
|
|
|
config = cmd_util.create_config(args)
|
|
|
|
mount, rel_path = get_mount_path(args.path or os.getcwd())
|
2017-07-08 04:32:42 +03:00
|
|
|
|
2018-04-20 03:36:17 +03:00
|
|
|
# Get the path to the overlay directory for this mount point
|
|
|
|
client_dir = config._get_client_dir_for_mount_point(mount)
|
2018-07-12 05:40:34 +03:00
|
|
|
self.overlay = overlay_mod.Overlay(os.path.join(client_dir, "local"))
|
2017-07-08 04:32:42 +03:00
|
|
|
|
2018-04-20 03:36:17 +03:00
|
|
|
if args.number is not None:
|
2018-07-12 05:40:34 +03:00
|
|
|
self._display_overlay(args.number, "")
|
2018-04-20 03:36:17 +03:00
|
|
|
elif rel_path:
|
|
|
|
rel_path = os.path.normpath(rel_path)
|
2018-07-12 05:40:34 +03:00
|
|
|
inode_number = self.overlay.lookup_path(rel_path)
|
|
|
|
if inode_number is None:
|
|
|
|
print(f"{rel_path} is not materialized", file=sys.stderr)
|
|
|
|
return 1
|
|
|
|
self._display_overlay(inode_number, rel_path)
|
2018-04-20 03:36:17 +03:00
|
|
|
else:
|
2018-07-12 05:40:34 +03:00
|
|
|
self._display_overlay(1, "/")
|
2017-07-08 04:32:42 +03:00
|
|
|
|
2018-04-20 03:36:17 +03:00
|
|
|
return 0
|
2017-08-17 05:56:32 +03:00
|
|
|
|
2018-07-12 05:40:34 +03:00
|
|
|
def _display_overlay(self, inode_number: int, path: str, level: int = 0) -> None:
|
|
|
|
data = self.overlay.read_dir_inode(inode_number)
|
|
|
|
self._print_overlay_tree(inode_number, path, data)
|
|
|
|
|
|
|
|
# If self.args.depth is negative, recurse forever.
|
|
|
|
# Stop if self.args.depth is non-negative, and level reaches the maximum
|
|
|
|
# requested recursion depth.
|
|
|
|
if self.args.depth >= 0 and level >= self.args.depth:
|
|
|
|
return
|
|
|
|
|
|
|
|
entries = {} if data.entries is None else data.entries
|
|
|
|
for name, entry in entries.items():
|
|
|
|
if entry.hash or entry.inodeNumber is None or entry.inodeNumber == 0:
|
|
|
|
# This entry is not materialized
|
|
|
|
continue
|
|
|
|
if entry.mode is None or stat.S_IFMT(entry.mode) != stat.S_IFDIR:
|
|
|
|
# Only display data for directories
|
|
|
|
continue
|
|
|
|
print()
|
|
|
|
entry_path = os.path.join(path, name)
|
|
|
|
self._display_overlay(entry.inodeNumber, entry_path, level + 1)
|
|
|
|
|
|
|
|
def _print_overlay_tree(
|
|
|
|
self, inode_number: int, path: str, tree_data: OverlayDir
|
|
|
|
) -> None:
|
|
|
|
def hex(binhash) -> str:
|
|
|
|
if binhash is None:
|
|
|
|
return "None"
|
|
|
|
else:
|
|
|
|
return binascii.hexlify(binhash).decode("utf-8")
|
|
|
|
|
|
|
|
print("Inode {}: {}".format(inode_number, path))
|
|
|
|
if not tree_data.entries:
|
|
|
|
return
|
|
|
|
name_width = max(len(name) for name in tree_data.entries)
|
|
|
|
for name, entry in tree_data.entries.items():
|
|
|
|
assert entry.mode is not None
|
|
|
|
perms = entry.mode & 0o7777
|
|
|
|
file_type = stat.S_IFMT(entry.mode)
|
|
|
|
if file_type == stat.S_IFREG:
|
|
|
|
file_type_flag = "f"
|
|
|
|
elif file_type == stat.S_IFDIR:
|
|
|
|
file_type_flag = "d"
|
|
|
|
elif file_type == stat.S_IFLNK:
|
|
|
|
file_type_flag = "l"
|
|
|
|
else:
|
|
|
|
file_type_flag = "?"
|
|
|
|
|
|
|
|
print(
|
|
|
|
" {:{name_width}s} : {:12d} {} {:04o} {}".format(
|
|
|
|
name,
|
|
|
|
entry.inodeNumber,
|
|
|
|
file_type_flag,
|
|
|
|
perms,
|
|
|
|
hex(entry.hash),
|
|
|
|
name_width=name_width,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2017-08-17 05:56:32 +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:
|
|
|
|
config = cmd_util.create_config(args)
|
|
|
|
mount, _ = get_mount_path(args.path or os.getcwd())
|
2017-06-23 08:33:01 +03:00
|
|
|
|
2018-04-20 03:36:17 +03:00
|
|
|
with config.get_thrift_client() as client:
|
|
|
|
inodePathInfo = client.debugGetInodePath(mount, args.number)
|
2018-05-04 19:04:43 +03:00
|
|
|
print(
|
2018-05-10 07:33:49 +03:00
|
|
|
"%s %s"
|
|
|
|
% (
|
|
|
|
"loaded" if inodePathInfo.loaded else "unloaded",
|
2018-05-04 19:04:43 +03:00
|
|
|
os.path.normpath(os.path.join(mount, inodePathInfo.path))
|
2018-05-10 07:33:49 +03:00
|
|
|
if inodePathInfo.linked
|
|
|
|
else "unlinked",
|
2018-05-04 19:04:43 +03:00
|
|
|
)
|
|
|
|
)
|
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-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:
|
|
|
|
config = cmd_util.create_config(args)
|
|
|
|
mount, rel_path = get_mount_path(args.path)
|
2017-07-08 04:32:42 +03:00
|
|
|
|
2018-04-20 03:36:17 +03:00
|
|
|
with config.get_thrift_client() as client:
|
|
|
|
# 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-04-20 03:36:17 +03:00
|
|
|
count = client.unloadInodeForPath(mount, rel_path, age)
|
2017-06-17 02:08:19 +03:00
|
|
|
|
2018-05-10 07:33:49 +03:00
|
|
|
print(f"Unloaded {count} inodes under {mount}/{rel_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:
|
|
|
|
config = cmd_util.create_config(args)
|
|
|
|
mount, rel_path = get_mount_path(args.path)
|
2017-08-22 01:52:55 +03:00
|
|
|
|
2018-04-20 03:36:17 +03:00
|
|
|
with config.get_thrift_client() as client:
|
|
|
|
client.invalidateKernelInodeCache(mount, 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.
|
|
|
|
config = cmd_util.create_config(args)
|
2018-03-30 11:48:09 +03:00
|
|
|
|
2018-04-20 03:36:17 +03:00
|
|
|
eden_log_path = config.get_log_path()
|
|
|
|
if not os.path.exists(eden_log_path):
|
2018-05-10 07:33:49 +03:00
|
|
|
print("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"]
|
2018-04-20 03:36:17 +03:00
|
|
|
pager_cmd.append(eden_log_path)
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
@debug_cmd(
|
2018-05-10 07:33:49 +03:00
|
|
|
"set_log_level", "Set the log level for a given category in the edenfs daemon"
|
2018-04-20 03:36:17 +03:00
|
|
|
)
|
|
|
|
class SetLogLevelCmd(Subcmd):
|
|
|
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
2018-05-10 07:33:49 +03:00
|
|
|
parser.add_argument("category", type=str, help="Period-separated log category.")
|
2018-04-20 03:36:17 +03:00
|
|
|
parser.add_argument(
|
2018-05-10 07:33:49 +03:00
|
|
|
"level",
|
2018-04-20 03:36:17 +03:00
|
|
|
type=str,
|
2018-05-10 07:33:49 +03:00
|
|
|
help="Log level string as understood by stringToLogLevel.",
|
2018-04-20 03:36:17 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
def run(self, args: argparse.Namespace) -> int:
|
|
|
|
config = cmd_util.create_config(args)
|
2018-03-30 11:48:09 +03:00
|
|
|
|
2018-04-20 03:36:17 +03:00
|
|
|
with config.get_thrift_client() as client:
|
|
|
|
result = client.debugSetLogLevel(args.category, args.level)
|
|
|
|
if result.categoryCreated:
|
|
|
|
util.print_stderr(
|
|
|
|
"Warning: New category '{}' created. Did you mistype?",
|
|
|
|
args.category,
|
|
|
|
)
|
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-05-23 11:27:16 +03:00
|
|
|
@debug_cmd("journal", "Prints the most recent N 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.",
|
|
|
|
)
|
|
|
|
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:
|
|
|
|
config = cmd_util.create_config(args)
|
|
|
|
mount, _ = get_mount_path(args.path or os.getcwd())
|
|
|
|
|
|
|
|
with config.get_thrift_client() as client:
|
|
|
|
to_position = client.getCurrentJournalPosition(mount)
|
|
|
|
from_sequence = max(to_position.sequenceNumber - args.limit, 0)
|
|
|
|
from_position = JournalPosition(
|
|
|
|
mountGeneration=to_position.mountGeneration,
|
|
|
|
sequenceNumber=from_sequence,
|
|
|
|
snapshotHash="",
|
|
|
|
)
|
|
|
|
|
|
|
|
params = DebugGetRawJournalParams(
|
|
|
|
mountPoint=mount, fromPosition=from_position, toPosition=to_position
|
|
|
|
)
|
|
|
|
raw_journal = client.debugGetRawJournal(params)
|
|
|
|
if args.pattern:
|
|
|
|
flags = re.IGNORECASE if args.ignore_case else 0
|
|
|
|
pattern = re.compile(args.pattern, flags)
|
|
|
|
else:
|
|
|
|
pattern = None
|
|
|
|
# debugGetRawJournal() returns the most recent entries first, but
|
|
|
|
# we want to display the oldest entries first, so we pass a reversed
|
|
|
|
# iterator along.
|
|
|
|
print_raw_journal_deltas(reversed(raw_journal.deltas), pattern)
|
|
|
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
def print_raw_journal_deltas(
|
|
|
|
deltas: Iterator[FileDelta], pattern: Optional[Pattern]
|
|
|
|
) -> None:
|
|
|
|
matcher = (lambda x: True) if pattern is None else pattern.match
|
|
|
|
for delta in deltas:
|
|
|
|
# Note that filter() returns an Iterator not a List.
|
|
|
|
changed_paths = filter(matcher, delta.changedPaths)
|
|
|
|
created_paths = filter(matcher, delta.createdPaths)
|
|
|
|
removed_paths = filter(matcher, delta.removedPaths)
|
|
|
|
unclean_paths = filter(matcher, delta.uncleanPaths)
|
|
|
|
|
|
|
|
# Because we have Iterators, iterate everything and see whether entries
|
|
|
|
# is non-empty before writing to stdout.
|
|
|
|
entries = ""
|
|
|
|
for label, paths in [
|
|
|
|
("M", changed_paths),
|
|
|
|
("A", created_paths),
|
|
|
|
("R", removed_paths),
|
|
|
|
("X", unclean_paths),
|
|
|
|
]:
|
|
|
|
for path in paths:
|
|
|
|
entries += f"{label} {path}\n"
|
|
|
|
|
|
|
|
if not entries:
|
|
|
|
continue
|
|
|
|
|
|
|
|
if delta.fromPosition.sequenceNumber != delta.toPosition.sequenceNumber:
|
|
|
|
print(
|
|
|
|
f"MERGE {delta.fromPosition.sequenceNumber}-"
|
|
|
|
f"{delta.toPosition.sequenceNumber}"
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
print(f"DELTA {delta.fromPosition.sequenceNumber}")
|
|
|
|
print(entries, end="") # entries already contains a trailing newline.
|
|
|
|
|
|
|
|
|
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):
|
|
|
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
|
|
# 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
|