Page Menu
Home
Sealhub
Search
Configure Global Search
Log In
Files
F995682
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
32 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/repository/api/ArcanistRepositoryAPI.php b/src/repository/api/ArcanistRepositoryAPI.php
index 1c58fdb4..c9ace486 100644
--- a/src/repository/api/ArcanistRepositoryAPI.php
+++ b/src/repository/api/ArcanistRepositoryAPI.php
@@ -1,674 +1,638 @@
<?php
/**
* Interfaces with the VCS in the working copy.
*
* @task status Path Status
* @group workingcopy
*/
abstract class ArcanistRepositoryAPI {
const FLAG_MODIFIED = 1;
const FLAG_ADDED = 2;
const FLAG_DELETED = 4;
const FLAG_UNTRACKED = 8;
const FLAG_CONFLICT = 16;
const FLAG_MISSING = 32;
const FLAG_UNSTAGED = 64;
const FLAG_UNCOMMITTED = 128;
const FLAG_EXTERNALS = 256;
// Occurs in SVN when you replace a file with a directory without telling
// SVN about it.
const FLAG_OBSTRUCTED = 512;
// Occurs in SVN when an update was interrupted or failed, e.g. you ^C'd it.
const FLAG_INCOMPLETE = 1024;
protected $path;
protected $diffLinesOfContext = 0x7FFF;
private $baseCommitExplanation = '???';
private $configurationManager;
private $baseCommitArgumentRules;
private $uncommittedStatusCache;
private $commitRangeStatusCache;
private $symbolicBaseCommit;
private $resolvedBaseCommit;
abstract public function getSourceControlSystemName();
public function getDiffLinesOfContext() {
return $this->diffLinesOfContext;
}
public function setDiffLinesOfContext($lines) {
$this->diffLinesOfContext = $lines;
return $this;
}
public function getWorkingCopyIdentity() {
return $this->configurationManager->getWorkingCopyIdentity();
}
public function getConfigurationManager() {
return $this->configurationManager;
}
public static function newAPIFromConfigurationManager(
ArcanistConfigurationManager $configuration_manager) {
$working_copy = $configuration_manager->getWorkingCopyIdentity();
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) {
$this->path = $path;
}
public function getPath($to_file = null) {
if ($to_file !== null) {
return $this->path.DIRECTORY_SEPARATOR.
ltrim($to_file, DIRECTORY_SEPARATOR);
} else {
return $this->path.DIRECTORY_SEPARATOR;
}
}
/* -( Path Status )-------------------------------------------------------- */
abstract protected function buildUncommittedStatus();
abstract protected function buildCommitRangeStatus();
/**
* Get a list of uncommitted paths in the working copy that have been changed
* or are affected by other status effects, like conflicts or untracked
* files.
*
* Convenience methods @{method:getUntrackedChanges},
* @{method:getUnstagedChanges}, @{method:getUncommittedChanges},
* @{method:getMergeConflicts}, and @{method:getIncompleteChanges} allow
* simpler selection of paths in a specific state.
*
* This method returns a map of paths to bitmasks with status, using
* `FLAG_` constants. For example:
*
* array(
* 'some/uncommitted/file.txt' => ArcanistRepositoryAPI::FLAG_UNSTAGED,
* );
*
* A file may be in several states. Not all states are possible with all
* version control systems.
*
* @return map<string, bitmask> Map of paths, see above.
* @task status
*/
final public function getUncommittedStatus() {
if ($this->uncommittedStatusCache === null) {
$status = $this->buildUncommittedStatus();
ksort($status);
$this->uncommittedStatusCache = $status;
}
return $this->uncommittedStatusCache;
}
/**
* @task status
*/
final public function getUntrackedChanges() {
return $this->getUncommittedPathsWithMask(self::FLAG_UNTRACKED);
}
/**
* @task status
*/
final public function getUnstagedChanges() {
return $this->getUncommittedPathsWithMask(self::FLAG_UNSTAGED);
}
/**
* @task status
*/
final public function getUncommittedChanges() {
return $this->getUncommittedPathsWithMask(self::FLAG_UNCOMMITTED);
}
/**
* @task status
*/
final public function getMergeConflicts() {
return $this->getUncommittedPathsWithMask(self::FLAG_CONFLICT);
}
/**
* @task status
*/
final public function getIncompleteChanges() {
return $this->getUncommittedPathsWithMask(self::FLAG_INCOMPLETE);
}
/**
* @task status
*/
private function getUncommittedPathsWithMask($mask) {
$match = array();
foreach ($this->getUncommittedStatus() as $path => $flags) {
if ($flags & $mask) {
$match[] = $path;
}
}
return $match;
}
/**
* Get a list of paths affected by the commits in the current commit range.
*
* See @{method:getUncommittedStatus} for a description of the return value.
*
* @return map<string, bitmask> Map from paths to status.
* @task status
*/
final public function getCommitRangeStatus() {
if ($this->commitRangeStatusCache === null) {
$status = $this->buildCommitRangeStatus();
ksort($status);
$this->commitRangeStatusCache = $status;
}
return $this->commitRangeStatusCache;
}
/**
* Get a list of paths affected by commits in the current commit range, or
* uncommitted changes in the working copy. See @{method:getUncommittedStatus}
* or @{method:getCommitRangeStatus} to retreive smaller parts of the status.
*
* See @{method:getUncommittedStatus} for a description of the return value.
*
* @return map<string, bitmask> Map from paths to status.
* @task status
*/
final public function getWorkingCopyStatus() {
$range_status = $this->getCommitRangeStatus();
$uncommitted_status = $this->getUncommittedStatus();
$result = new PhutilArrayWithDefaultValue($range_status);
foreach ($uncommitted_status as $path => $mask) {
$result[$path] |= $mask;
}
$result = $result->toArray();
ksort($result);
return $result;
}
/**
* Drops caches after changes to the working copy. By default, some queries
* against the working copy are cached. They
*
* @return this
* @task status
*/
final public function reloadWorkingCopy() {
$this->uncommittedStatusCache = null;
$this->commitRangeStatusCache = null;
$this->didReloadWorkingCopy();
$this->reloadCommitRange();
return $this;
}
/**
* Hook for implementations to dirty working copy caches after the working
* copy has been updated.
*
* @return this
* @task status
*/
protected function didReloadWorkingCopy() {
return;
}
-
- 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.
*
* @return map<string, string> Map from path to file data.
*/
public function getBulkOriginalFileData($paths) {
$filedata = array();
foreach ($paths as $path) {
$filedata[$path] = $this->getOriginalFileData($path);
}
return $filedata;
}
/**
* Fetches the current file data for each path provided.
*
* @return map<string, string> Map from path to file data.
*/
public function getBulkCurrentFileData($paths) {
$filedata = array();
foreach ($paths as $path) {
$filedata[$path] = $this->getCurrentFileData($path);
}
return $filedata;
}
/**
* @return Traversable
*/
abstract public function getAllFiles();
abstract public function getBlame($path);
abstract public function getRawDiffText($path);
abstract public function getOriginalFileData($path);
abstract public function getCurrentFileData($path);
abstract public function getLocalCommitInformation();
abstract public function getSourceControlBaseRevision();
abstract public function getCanonicalRevisionName($string);
abstract public function getBranchName();
abstract public function getSourceControlPath();
abstract public function isHistoryDefaultImmutable();
abstract public function supportsAmend();
abstract public function getWorkingCopyRevision();
abstract public function updateWorkingCopy();
abstract public function getMetadataPath();
abstract public function loadWorkingCopyDifferentialRevisions(
ConduitClient $conduit,
array $query);
public function getUnderlyingWorkingCopyRevision() {
return $this->getWorkingCopyRevision();
}
public function getChangedFiles($since_commit) {
throw new ArcanistCapabilityNotSupportedException($this);
}
public function getAuthor() {
throw new ArcanistCapabilityNotSupportedException($this);
}
public function addToCommit(array $paths) {
throw new ArcanistCapabilityNotSupportedException($this);
}
abstract public function supportsLocalCommits();
public function doCommit($message) {
throw new ArcanistCapabilityNotSupportedException($this);
}
public function amendCommit($message = null) {
throw new ArcanistCapabilityNotSupportedException($this);
}
public function getAllBranches() {
// TODO: Implement for Mercurial/SVN and make abstract.
return array();
}
public function hasLocalCommit($commit) {
throw new ArcanistCapabilityNotSupportedException($this);
}
public function getCommitMessage($commit) {
throw new ArcanistCapabilityNotSupportedException($this);
}
public function getCommitSummary($commit) {
throw new ArcanistCapabilityNotSupportedException($this);
}
public function getAllLocalChanges() {
throw new ArcanistCapabilityNotSupportedException($this);
}
abstract public function supportsLocalBranchMerge();
public function performLocalBranchMerge($branch, $message) {
throw new ArcanistCapabilityNotSupportedException($this);
}
public function getFinalizedRevisionMessage() {
throw new ArcanistCapabilityNotSupportedException($this);
}
public function execxLocal($pattern /* , ... */) {
$args = func_get_args();
return $this->buildLocalFuture($args)->resolvex();
}
public function execManualLocal($pattern /* , ... */) {
$args = func_get_args();
return $this->buildLocalFuture($args)->resolve();
}
public function execFutureLocal($pattern /* , ... */) {
$args = func_get_args();
return $this->buildLocalFuture($args);
}
abstract protected function buildLocalFuture(array $argv);
public function canStashChanges() {
return false;
}
public function stashChanges() {
throw new ArcanistCapabilityNotSupportedException($this);
}
public function unstashChanges() {
throw new ArcanistCapabilityNotSupportedException($this);
}
/* -( Scratch Files )------------------------------------------------------ */
/**
* Try to read a scratch file, if it exists and is readable.
*
* @param string Scratch file name.
* @return mixed String for file contents, or false for failure.
* @task scratch
*/
public function readScratchFile($path) {
$full_path = $this->getScratchFilePath($path);
if (!$full_path) {
return false;
}
if (!Filesystem::pathExists($full_path)) {
return false;
}
try {
$result = Filesystem::readFile($full_path);
} catch (FilesystemException $ex) {
return false;
}
return $result;
}
/**
* Try to write a scratch file, if there's somewhere to put it and we can
* write there.
*
* @param string Scratch file name to write.
* @param string Data to write.
* @return bool True on success, false on failure.
* @task scratch
*/
public function writeScratchFile($path, $data) {
$dir = $this->getScratchFilePath('');
if (!$dir) {
return false;
}
if (!Filesystem::pathExists($dir)) {
try {
Filesystem::createDirectory($dir);
} catch (Exception $ex) {
return false;
}
}
try {
Filesystem::writeFile($this->getScratchFilePath($path), $data);
} catch (FilesystemException $ex) {
return false;
}
return true;
}
/**
* Try to remove a scratch file.
*
* @param string Scratch file name to remove.
* @return bool True if the file was removed successfully.
* @task scratch
*/
public function removeScratchFile($path) {
$full_path = $this->getScratchFilePath($path);
if (!$full_path) {
return false;
}
try {
Filesystem::remove($full_path);
} catch (FilesystemException $ex) {
return false;
}
return true;
}
/**
* Get a human-readable description of the scratch file location.
*
* @param string Scratch file name.
* @return mixed String, or false on failure.
* @task scratch
*/
public function getReadableScratchFilePath($path) {
$full_path = $this->getScratchFilePath($path);
if ($full_path) {
return Filesystem::readablePath(
$full_path,
$this->getPath());
} else {
return false;
}
}
/**
* Get the path to a scratch file, if possible.
*
* @param string Scratch file name.
* @return mixed File path, or false on failure.
* @task scratch
*/
public function getScratchFilePath($path) {
$new_scratch_path = Filesystem::resolvePath(
'arc',
$this->getMetadataPath());
static $checked = false;
if (!$checked) {
$checked = true;
$old_scratch_path = $this->getPath('.arc');
// we only want to do the migration once
// unfortunately, people have checked in .arc directories which
// means that the old one may get recreated after we delete it
if (Filesystem::pathExists($old_scratch_path) &&
!Filesystem::pathExists($new_scratch_path)) {
Filesystem::createDirectory($new_scratch_path);
$existing_files = Filesystem::listDirectory($old_scratch_path, true);
foreach ($existing_files as $file) {
$new_path = Filesystem::resolvePath($file, $new_scratch_path);
$old_path = Filesystem::resolvePath($file, $old_scratch_path);
Filesystem::writeFile(
$new_path,
Filesystem::readFile($old_path));
}
Filesystem::remove($old_scratch_path);
}
}
return Filesystem::resolvePath($path, $new_scratch_path);
}
/* -( Base Commits )------------------------------------------------------- */
abstract public function supportsCommitRanges();
final public function setBaseCommit($symbolic_commit) {
if (!$this->supportsCommitRanges()) {
throw new ArcanistCapabilityNotSupportedException($this);
}
$this->symbolicBaseCommit = $symbolic_commit;
$this->reloadCommitRange();
return $this;
}
final public function getBaseCommit() {
if (!$this->supportsCommitRanges()) {
throw new ArcanistCapabilityNotSupportedException($this);
}
if ($this->resolvedBaseCommit === null) {
$commit = $this->buildBaseCommit($this->symbolicBaseCommit);
$this->resolvedBaseCommit = $commit;
}
return $this->resolvedBaseCommit;
}
final public function reloadCommitRange() {
$this->resolvedBaseCommit = null;
$this->baseCommitExplanation = null;
$this->didReloadCommitRange();
return $this;
}
protected function didReloadCommitRange() {
return;
}
protected function buildBaseCommit($symbolic_commit) {
throw new ArcanistCapabilityNotSupportedException($this);
}
public function getBaseCommitExplanation() {
return $this->baseCommitExplanation;
}
public function setBaseCommitExplanation($explanation) {
$this->baseCommitExplanation = $explanation;
return $this;
}
public function resolveBaseCommitRule($rule, $source) {
return null;
}
public function setBaseCommitArgumentRules($base_commit_argument_rules) {
$this->baseCommitArgumentRules = $base_commit_argument_rules;
return $this;
}
public function getBaseCommitArgumentRules() {
return $this->baseCommitArgumentRules;
}
public function resolveBaseCommit() {
$base_commit_rules = array(
'runtime' => $this->getBaseCommitArgumentRules(),
'local' => '',
'project' => '',
'user' => '',
'system' => '',
);
$all_sources = $this->configurationManager->getConfigFromAllSources('base');
$base_commit_rules = $all_sources + $base_commit_rules;
$parser = new ArcanistBaseCommitParser($this);
$commit = $parser->resolveBaseCommit($base_commit_rules);
return $commit;
}
}
diff --git a/src/workingcopyidentity/ArcanistWorkingCopyIdentity.php b/src/workingcopyidentity/ArcanistWorkingCopyIdentity.php
index c86f1def..7c617153 100644
--- a/src/workingcopyidentity/ArcanistWorkingCopyIdentity.php
+++ b/src/workingcopyidentity/ArcanistWorkingCopyIdentity.php
@@ -1,255 +1,341 @@
<?php
/**
* Interfaces with basic information about the working copy.
*
*
* @task config
*
* @group workingcopy
*/
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(
$root,
$config_raw,
$from_where) {
if ($config_raw === null) {
$config = array();
} else {
$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) {
$proj = json_decode($raw_config, true);
if (!is_array($proj)) {
throw new Exception(
"Unable to parse '.arcconfig' file '{$from_where}'. The file contents ".
"should be valid JSON.\n\n".
"FILE CONTENTS\n".
substr($raw_config, 0, 2048));
}
$required_keys = array(
'project_id',
);
foreach ($required_keys as $key) {
if (!array_key_exists($key, $proj)) {
throw new Exception(
"Required key '{$key}' is missing from '.arcconfig' file ".
"'{$from_where}'.");
}
}
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() {
return $this->getProjectConfig('project_id');
}
public function getProjectRoot() {
return $this->projectRoot;
}
public function getProjectPath($to_file) {
return $this->projectRoot.'/'.$to_file;
}
+ public function getVCSType() {
+ return $this->vcsType;
+ }
+
+ public function getVCSRoot() {
+ return $this->vcsRoot;
+ }
+
/* -( Config )------------------------------------------------------------- */
public function readProjectConfig() {
return $this->projectConfig;
}
/**
* Deprecated; use @{method:getProjectConfig}.
*/
public function getConfig($key, $default = null) {
return $this->getProjectConfig($key, $default);
}
/**
* Read a configuration directive from project configuration. This reads ONLY
* permanent project configuration (i.e., ".arcconfig"), not other
* configuration sources. See @{method:getConfigFromAnySource} to read from
* user configuration.
*
* @param key Key to read.
* @param wild Default value if key is not found.
* @return wild Value, or default value if not found.
*
* @task config
*/
public function getProjectConfig($key, $default = null) {
$settings = new ArcanistSettings();
$pval = idx($this->projectConfig, $key);
// Test for older names in the per-project config only, since
// they've only been used there.
if ($pval === null) {
$legacy = $settings->getLegacyName($key);
if ($legacy) {
$pval = $this->getProjectConfig($legacy);
}
}
if ($pval === null) {
$pval = $default;
} else {
$pval = $settings->willReadValue($key, $pval);
}
return $pval;
}
/**
* Read a configuration directive from local configuration. This
* reads ONLY the per-working copy configuration,
* i.e. .(git|hg|svn)/arc/config, and not other configuration
* sources. See @{method:getConfigFromAnySource} to read from any
* config source or @{method:getProjectConfig} to read permanent
* project-level config.
*
* @task config
*/
public function getLocalConfig($key, $default=null) {
return idx($this->localConfig, $key, $default);
}
public function readLocalArcConfig() {
if (strlen($this->localMetaDir)) {
$local_path = Filesystem::resolvePath(
'arc/config',
$this->localMetaDir);
if (Filesystem::pathExists($local_path)) {
$file = Filesystem::readFile($local_path);
if ($file) {
return json_decode($file, true);
}
}
}
return array();
}
public function writeLocalArcConfig(array $config) {
$dir = $this->localMetaDir;
if (!strlen($dir)) {
return false;
}
if (!Filesystem::pathExists($dir)) {
try {
Filesystem::createDirectory($dir);
} catch (Exception $ex) {
return false;
}
}
$json_encoder = new PhutilJSON();
$json = $json_encoder->encodeFormatted($config);
$config_file = Filesystem::resolvePath('arc/config', $dir);
try {
Filesystem::writeFile($config_file, $json);
} catch (FilesystemException $ex) {
return false;
}
return true;
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Dec 23, 13:07 (1 d, 19 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
556913
Default Alt Text
(32 KB)
Attached To
Mode
R118 Arcanist - fork
Attached
Detach File
Event Timeline
Log In to Comment