/** * Locate all the information we need about a directory which we presume * to be a working copy. Particularly, we want to discover: * * - Is the directory inside a working copy (hg, git, svn)? * - If so, what is the root of the working copy? * - Is there a `.arcconfig` file? * * This is complicated, mostly because Subversion has special rules. In * particular: * * - Until 1.7, Subversion put a `.svn/` directory inside //every// * directory in a working copy. After 1.7, it //only// puts one at the * root. * - We allow `.arcconfig` to appear anywhere in a Subversion working copy, * and use the one closest to the directory. * - Although we may use a `.arcconfig` from a subdirectory, we store * metadata in the root's `.svn/`, because it's the only one guaranteed * to exist. * * Users also do these kinds of things in the wild: * * - Put working copies inside other working copies. * - Put working copies inside `.git/` directories. * - Create `.arcconfig` files at `/.arcconfig`, `/home/.arcconfig`, etc. * * This method attempts to be robust against all sorts of possible * misconfiguration. * * @param string Path to load information for, usually the current working * directory (unless running unit tests). * @param map|null Pass `null` to locate and load a `.arcconfig` file if one * exists. Pass a map to use it to set configuration. * @return ArcanistWorkingCopyIdentity Constructed working copy identity. */ private static function newFromPathWithConfig($path, $config) { $project_root = null; $vcs_root = null; $vcs_type = null; // First, find the outermost directory which is a Git, Mercurial or // Subversion repository, if one exists. We go from the top because this // makes it easier to identify the root of old SVN working copies (which // have a ".svn/" directory inside every directory in the working copy) and // gives us the right result if you have a Git repository inside a // Subversion repository or something equally ridiculous. $paths = Filesystem::walkToRoot($path); $config_paths = array(); $paths = array_reverse($paths); foreach ($paths as $path_key => $parent_path) { $try = array('git' => $parent_path . '/.git', 'hg' => $parent_path . '/.hg', 'svn' => $parent_path . '/.svn'); foreach ($try as $vcs => $try_dir) { if (!Filesystem::pathExists($try_dir)) { continue; } // NOTE: We're distinguishing between the `$project_root` and the // `$vcs_root` because they may not be the same in Subversion. Normally, // they are identical. However, in Subversion, the `$vcs_root` is the // base directory of the working copy (the directory which has the // `.svn/` directory, after SVN 1.7), while the `$project_root` might // be any subdirectory of the `$vcs_root`: it's the the directory // closest to the current directory which contains a `.arcconfig`. $project_root = $parent_path; $vcs_root = $parent_path; $vcs_type = $vcs; if ($vcs == 'svn') { // For Subversion, we'll look for a ".arcconfig" file here or in // any subdirectory, starting at the deepest subdirectory. $config_paths = array_slice($paths, $path_key); $config_paths = array_reverse($config_paths); } else { // For Git and Mercurial, we'll only look for ".arcconfig" right here. $config_paths = array($parent_path); } break; } } $console = PhutilConsole::getConsole(); $looked_in = array(); foreach ($config_paths as $config_path) { $config_file = $config_path . '/.arcconfig'; $looked_in[] = $config_file; if (Filesystem::pathExists($config_file)) { // We always need to examine the filesystem to look for `.arcconfig` // so we can set the project root correctly. We might or might not // actually read the file: if the caller passed in configuration data, // we'll ignore the actual file contents. $project_root = $config_path; if ($config === null) { $console->writeLog("%s\n", pht('Working Copy: Reading .arcconfig from "%s".', $config_file)); $config_data = Filesystem::readFile($config_file); $config = self::parseRawConfigFile($config_data, $config_file); } break; } } if ($config === null) { if ($looked_in) { $console->writeLog("%s\n", pht('Working Copy: Unable to find .arcconfig in any of these ' . 'locations: %s.', implode(', ', $looked_in))); } else { $console->writeLog("%s\n", pht('Working Copy: No candidate locations for .arcconfig from ' . 'this working directory.')); } $config = array(); } if ($project_root === null) { // We aren't in a working directory at all. This is fine if we're // running a command like "arc help". If we're running something that // requires a working directory, an exception will be raised a little // later on. $console->writeLog("%s\n", pht('Working Copy: Path "%s" is not in any working copy.', $path)); return new ArcanistWorkingCopyIdentity($path, $config); } $console->writeLog("%s\n", pht('Working Copy: Path "%s" is part of `%s` working copy "%s".', $path, $vcs_type, $vcs_root)); $console->writeLog("%s\n", pht('Working Copy: Project root is at "%s".', $project_root)); $identity = new ArcanistWorkingCopyIdentity($project_root, $config); $identity->localMetaDir = $vcs_root . '/.' . $vcs_type; $identity->localConfig = $identity->readLocalArcConfig(); $identity->vcsType = $vcs_type; $identity->vcsRoot = $vcs_root; return $identity; }