Page MenuHomeSealhub

No OneTemporary

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

Mime Type
text/x-diff
Expires
Mon, Dec 23, 13:07 (1 d, 8 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
556913
Default Alt Text
(32 KB)

Event Timeline