1# Copyright (c) Facebook, Inc. and its affiliates.
2#
3# This source code is licensed under the MIT license found in the
4# LICENSE file in the root directory of this source tree.
5
6from __future__ import absolute_import, division, print_function, unicode_literals
7
8import os
9import shutil
10import subprocess
11
12from .platform import is_windows
13
14
15PREFETCHED_DIRS = set()
16
17
18def containing_repo_type(path):
19    while True:
20        if os.path.exists(os.path.join(path, ".git")):
21            return ("git", path)
22        if os.path.exists(os.path.join(path, ".hg")):
23            return ("hg", path)
24
25        parent = os.path.dirname(path)
26        if parent == path:
27            return None, None
28        path = parent
29
30
31def find_eden_root(dirpath):
32    """If the specified directory is inside an EdenFS checkout, returns
33    the canonical absolute path to the root of that checkout.
34
35    Returns None if the specified directory is not in an EdenFS checkout.
36    """
37    if is_windows():
38        repo_type, repo_root = containing_repo_type(dirpath)
39        if repo_root is not None:
40            if os.path.exists(os.path.join(repo_root, ".eden", "config")):
41                return os.path.realpath(repo_root)
42        return None
43
44    try:
45        return os.readlink(os.path.join(dirpath, ".eden", "root"))
46    except OSError:
47        return None
48
49
50def prefetch_dir_if_eden(dirpath):
51    """After an amend/rebase, Eden may need to fetch a large number
52    of trees from the servers.  The simplistic single threaded walk
53    performed by copytree makes this more expensive than is desirable
54    so we help accelerate things by performing a prefetch on the
55    source directory"""
56    global PREFETCHED_DIRS
57    if dirpath in PREFETCHED_DIRS:
58        return
59    root = find_eden_root(dirpath)
60    if root is None:
61        return
62    glob = f"{os.path.relpath(dirpath, root).replace(os.sep, '/')}/**"
63    print(f"Prefetching {glob}")
64    subprocess.call(["edenfsctl", "prefetch", "--repo", root, "--silent", glob])
65    PREFETCHED_DIRS.add(dirpath)
66
67
68def copytree(src_dir, dest_dir, ignore=None):
69    """Recursively copy the src_dir to the dest_dir, filtering
70    out entries using the ignore lambda.  The behavior of the
71    ignore lambda must match that described by `shutil.copytree`.
72    This `copytree` function knows how to prefetch data when
73    running in an eden repo.
74    TODO: I'd like to either extend this or add a variant that
75    uses watchman to mirror src_dir into dest_dir.
76    """
77    prefetch_dir_if_eden(src_dir)
78    return shutil.copytree(src_dir, dest_dir, ignore=ignore)
79