Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F87669
D7686.diff
All Users
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
12 KB
Referenced Files
None
Subscribers
None
D7686.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D7686: Fix working copy binding powers in weird edge cases
Attached
Detach File
Event Timeline
Log In to Comment