Page MenuHomePhabricator

D7686.diff

diff --git a/src/repository/api/ArcanistRepositoryAPI.php b/src/repository/api/ArcanistRepositoryAPI.php
--- a/src/repository/api/ArcanistRepositoryAPI.php
+++ b/src/repository/api/ArcanistRepositoryAPI.php
@@ -63,48 +63,31 @@
if (!$working_copy) {
throw new Exception(
- "Trying to create a RepositoryApi without a working copy");
+ pht(
+ "Trying to create a RepositoryAPI without a working copy!"));
}
$root = $working_copy->getProjectRoot();
-
- if (!$root) {
- throw new ArcanistUsageException(
- "There is no readable '.arcconfig' file in the working directory or ".
- "any parent directory. Create an '.arcconfig' file to configure arc.");
- }
-
- if (Filesystem::pathExists($root.'/.hg')) {
- $api = new ArcanistMercurialAPI($root);
- $api->configurationManager = $configuration_manager;
- return $api;
- }
-
- $git_root = self::discoverGitBaseDirectory($root);
- if ($git_root) {
- if (!Filesystem::pathsAreEquivalent($root, $git_root)) {
- throw new ArcanistUsageException(
- "'.arcconfig' file is located at '{$root}', but working copy root ".
- "is '{$git_root}'. Move '.arcconfig' file to the working copy root.");
- }
-
- $api = new ArcanistGitAPI($root);
- $api->configurationManager = $configuration_manager;
- return $api;
- }
-
- // check if we're in an svn working copy
- foreach (Filesystem::walkToRoot($root) as $dir) {
- if (Filesystem::pathExists($dir . '/.svn')) {
+ switch ($working_copy->getVCSType()) {
+ case 'svn':
$api = new ArcanistSubversionAPI($root);
- $api->configurationManager = $configuration_manager;
- return $api;
- }
+ break;
+ case 'hg':
+ $api = new ArcanistMercurialAPI($root);
+ break;
+ case 'git':
+ $api = new ArcanistGitAPI($root);
+ break;
+ default:
+ throw new Exception(
+ pht(
+ "The current working directory is not part of a working copy for ".
+ "a supported version control system (Git, Subversion or ".
+ "Mercurial)."));
}
- throw new ArcanistUsageException(
- "The current working directory is not part of a working copy for a ".
- "supported version control system (svn, git or mercurial).");
+ $api->configurationManager = $configuration_manager;
+ return $api;
}
public function __construct($path) {
@@ -288,25 +271,6 @@
}
-
- private static function discoverGitBaseDirectory($root) {
- try {
-
- // NOTE: This awkward construction is to make sure things work on Windows.
- $future = new ExecFuture('git rev-parse --show-cdup');
- $future->setCWD($root);
- list($stdout) = $future->resolvex();
-
- return Filesystem::resolvePath(rtrim($stdout, "\n"), $root);
- } catch (CommandException $ex) {
- // This might be because the $root isn't a Git working copy, or the user
- // might not have Git installed at all so the `git` command fails. Assume
- // that users trying to work with git working copies will have a working
- // `git` binary.
- return null;
- }
- }
-
/**
* Fetches the original file data for each path provided.
*
diff --git a/src/workingcopyidentity/ArcanistWorkingCopyIdentity.php b/src/workingcopyidentity/ArcanistWorkingCopyIdentity.php
--- a/src/workingcopyidentity/ArcanistWorkingCopyIdentity.php
+++ b/src/workingcopyidentity/ArcanistWorkingCopyIdentity.php
@@ -10,47 +10,165 @@
*/
final class ArcanistWorkingCopyIdentity {
- protected $localConfig;
- protected $projectConfig;
- protected $projectRoot;
- protected $localMetaDir;
+ private $projectConfig;
+ private $projectRoot;
+ private $localConfig = array();
+ private $localMetaDir;
+ private $vcsType;
+ private $vcsRoot;
public static function newDummyWorkingCopy() {
- return new ArcanistWorkingCopyIdentity('/', array());
+ return self::newFromPathWithConfig('/', array());
}
public static function newFromPath($path) {
- $project_id = null;
+ return self::newFromPathWithConfig($path, null);
+ }
+
+ /**
+ * 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;
- $config = array();
- foreach (Filesystem::walkToRoot($path) as $dir) {
- $config_file = $dir.'/.arcconfig';
- if (!Filesystem::pathExists($config_file)) {
- continue;
+ $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;
}
- $proj_raw = Filesystem::readFile($config_file);
- $config = self::parseRawConfigFile($proj_raw, $config_file);
- $project_root = $dir;
- break;
}
- if (!$project_root) {
- foreach (Filesystem::walkToRoot($path) as $dir) {
- $try = array(
- $dir.'/.svn',
- $dir.'/.hg',
- $dir.'/.git',
- );
- foreach ($try as $trydir) {
- if (Filesystem::pathExists($trydir)) {
- $project_root = $dir;
- break 2;
- }
+ $console = PhutilConsole::getConsole();
+
+ foreach ($config_paths as $config_path) {
+ $config_file = $config_path.'/.arcconfig';
+ 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;
}
}
- return new ArcanistWorkingCopyIdentity($project_root, $config);
+ if ($config === null) {
+ // We didn't find a ".arcconfig" anywhere, so just use an empty array.
+ $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;
}
public static function newFromRootAndConfigFile(
@@ -64,7 +182,7 @@
$config = self::parseRawConfigFile($config_raw, $from_where);
}
- return new ArcanistWorkingCopyIdentity($root, $config);
+ return self::newFromPathWithConfig($root, $config);
}
private static function parseRawConfigFile($raw_config, $from_where) {
@@ -89,49 +207,9 @@
return $proj;
}
- protected function __construct($root, array $config) {
- $this->projectRoot = $root;
- $this->projectConfig = $config;
- $this->localConfig = array();
- $this->localMetaDir = null;
-
- $vc_dirs = array(
- '.git',
- '.hg',
- '.svn',
- );
- $found_meta_dir = false;
- foreach ($vc_dirs as $dir) {
- $meta_path = Filesystem::resolvePath(
- $dir,
- $this->projectRoot);
- if (Filesystem::pathExists($meta_path)) {
- $found_meta_dir = true;
- $this->localMetaDir = $meta_path;
- $local_path = Filesystem::resolvePath(
- 'arc/config',
- $meta_path);
- $this->localConfig = $this->readLocalArcConfig();
- break;
- }
- }
-
- if (!$found_meta_dir) {
- // Try for a single higher-level .svn directory as used by svn 1.7+
- foreach (Filesystem::walkToRoot($this->projectRoot) as $parent_path) {
- $meta_path = Filesystem::resolvePath(
- '.svn',
- $parent_path);
- $local_path = Filesystem::resolvePath(
- '.svn/arc/config',
- $parent_path);
- if (Filesystem::pathExists($local_path)) {
- $this->localMetaDir = $meta_path;
- $this->localConfig = $this->readLocalArcConfig();
- }
- }
- }
-
+ private function __construct($root, array $config) {
+ $this->projectRoot = $root;
+ $this->projectConfig = $config;
}
public function getProjectID() {
@@ -146,6 +224,14 @@
return $this->projectRoot.'/'.$to_file;
}
+ public function getVCSType() {
+ return $this->vcsType;
+ }
+
+ public function getVCSRoot() {
+ return $this->vcsRoot;
+ }
+
/* -( Config )------------------------------------------------------------- */

File Metadata

Mime Type
text/x-diff
Storage Engine
amazon-s3
Storage Format
Raw Data
Storage Handle
phabricator/kb/g2/r4ttsf6eluvjd3fk
Default Alt Text
D7686.diff (12 KB)

Event Timeline