#!/usr/bin/env python3 # Copyright (c) Facebook, Inc. and its affiliates. # # This software may be used and distributed according to the terms of the # GNU General Public License version 2. # pyre-strict import binascii import typing from pathlib import Path from typing import BinaryIO, Dict, Tuple import eden.dirstate from .config import EdenCheckout _DEFAULT_EXTRA_HGRC_CONTENTS: str = """\ [extensions] eden = share = [ui] portablefilenames = ignore """ def setup_hg_dir(checkout: EdenCheckout, commit_id: str) -> None: checkout_hg_dir = checkout.path / ".hg" try: checkout_hg_dir.mkdir() except FileExistsError: raise Exception(f"{checkout_hg_dir} directory already exists") # hgrc hgrc_data = get_hgrc_data(checkout) (checkout_hg_dir / "hgrc").write_text(hgrc_data) # requires requires_data = get_requires_data(checkout) (checkout_hg_dir / "requires").write_text(requires_data) # Create the shared and sharedpath files, which tell mercurial where it should # really look for most of the mercurial state, and that bookmarks should also be # shared. # # Note that the sharedpath file intentionally does not include a trailing newline. # This matches how it is generated by mercurial. backing_hg_dir = get_backing_hg_dir(checkout) (checkout_hg_dir / "sharedpath").write_bytes(bytes(backing_hg_dir)) (checkout_hg_dir / "shared").write_text("bookmarks\n") # Create an empty bookmarks file (checkout_hg_dir / "bookmarks").touch() # Create a branch file with the contents "default\n". Even though we do not # use branches, we have seen some users with a function in their .bashrc # that categorically reads the .hg/branch file to include in their prompt. (checkout_hg_dir / "branch").write_text("default\n") # Write the parents to the dirstate file. with typing.cast(BinaryIO, (checkout_hg_dir / "dirstate").open("wb")) as f: parents = (binascii.unhexlify(commit_id), b"\x00" * 20) tuples_dict: Dict[str, Tuple[str, int, int]] = {} copymap: Dict[str, str] = {} eden.dirstate.write(f, parents, tuples_dict, copymap) def get_backing_hg_dir(checkout: EdenCheckout) -> Path: """Given an EdenCheckout object, return the path to the actual .hg/ directory that contains the mercurial data store. This is the path that .hg/sharedpath should point to. """ return checkout.get_config().backing_repo / ".hg" def get_hgrc_data(checkout: EdenCheckout) -> str: backing_hg_dir = get_backing_hg_dir(checkout) extra_hgrc = checkout.instance.get_config_value( "hg.extra_hgrc", default=_DEFAULT_EXTRA_HGRC_CONTENTS ) if extra_hgrc and extra_hgrc[-1] != "\n": extra_hgrc += "\n" try: orig_hgrc = (backing_hg_dir / "hgrc").read_text() except FileNotFoundError: # Repositories aren't required to have an .hgrc file # Make sure the .hg directory itself exists though, to confirm # we weren't called with incorrect arguments. if not backing_hg_dir.is_dir(): raise Exception(f"backing repository does not exist: {backing_hg_dir}") orig_hgrc = "" return orig_hgrc + "\n" + extra_hgrc def get_requires_data(checkout: EdenCheckout) -> str: backing_hg_dir = get_backing_hg_dir(checkout) try: orig_requires_data = (backing_hg_dir / "requires").read_text() requires = set(orig_requires_data.splitlines()) except FileNotFoundError: requires = set() # Add eden as a requirement requires.add("eden") # Drop other dirstate-related requirements that are specific to # the backing repository's dirstate. requires.discard("sqldirstate") requires.discard("treedirstate") return "\n".join(sorted(requires)) + "\n"