Page MenuHomePhorge

No OneTemporary

diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditBranchesController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditBranchesController.php
index 84cc7938ff..7d185d90f2 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryEditBranchesController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryEditBranchesController.php
@@ -1,159 +1,251 @@
<?php
final class DiffusionRepositoryEditBranchesController
extends DiffusionRepositoryEditController {
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$drequest = $this->diffusionRequest;
$repository = $drequest->getRepository();
$repository = id(new PhabricatorRepositoryQuery())
->setViewer($viewer)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->withIDs(array($repository->getID()))
->executeOne();
if (!$repository) {
return new Aphront404Response();
}
$is_git = false;
$is_hg = false;
switch ($repository->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$is_git = true;
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$is_hg = true;
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
throw new Exception(
pht('Subversion does not support branches!'));
default:
throw new Exception(
pht('Repository has unknown version control system!'));
}
$edit_uri = $this->getRepositoryControllerURI($repository, 'edit/');
$v_default = $repository->getHumanReadableDetail('default-branch');
- $v_track = $repository->getHumanReadableDetail(
+ $v_track = $repository->getDetail(
'branch-filter',
array());
- $v_autoclose = $repository->getHumanReadableDetail(
+ $v_track = array_keys($v_track);
+ $v_autoclose = $repository->getDetail(
'close-commits-filter',
array());
+ $v_autoclose = array_keys($v_autoclose);
+ $e_track = null;
+ $e_autoclose = null;
+
+ $validation_exception = null;
if ($request->isFormPost()) {
$v_default = $request->getStr('default');
- $v_track = $request->getStrList('track');
- $v_autoclose = $request->getStrList('autoclose');
+
+ $v_track = $this->processBranches($request->getStr('track'));
+ if (!$is_hg) {
+ $v_autoclose = $this->processBranches($request->getStr('autoclose'));
+ }
$xactions = array();
$template = id(new PhabricatorRepositoryTransaction());
$type_default = PhabricatorRepositoryTransaction::TYPE_DEFAULT_BRANCH;
$type_track = PhabricatorRepositoryTransaction::TYPE_TRACK_ONLY;
$type_autoclose = PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE_ONLY;
$xactions[] = id(clone $template)
->setTransactionType($type_default)
->setNewValue($v_default);
$xactions[] = id(clone $template)
->setTransactionType($type_track)
->setNewValue($v_track);
if (!$is_hg) {
$xactions[] = id(clone $template)
->setTransactionType($type_autoclose)
->setNewValue($v_autoclose);
}
- id(new PhabricatorRepositoryEditor())
+ $editor = id(new PhabricatorRepositoryEditor())
->setContinueOnNoEffect(true)
->setContentSourceFromRequest($request)
- ->setActor($viewer)
- ->applyTransactions($repository, $xactions);
+ ->setActor($viewer);
+
+ try {
+ $editor->applyTransactions($repository, $xactions);
+ return id(new AphrontRedirectResponse())->setURI($edit_uri);
+ } catch (PhabricatorApplicationTransactionValidationException $ex) {
+ $validation_exception = $ex;
- return id(new AphrontRedirectResponse())->setURI($edit_uri);
+ $e_track = $validation_exception->getShortMessage($type_track);
+ $e_autoclose = $validation_exception->getShortMessage($type_autoclose);
+ }
}
$content = array();
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Edit Branches'));
$title = pht('Edit Branches (%s)', $repository->getName());
$policies = id(new PhabricatorPolicyQuery())
->setViewer($viewer)
->setObject($repository)
->execute();
+ $rows = array();
+ $rows[] = array(
+ array(
+ 'master',
+ ),
+ pht('Select only master.'),
+ );
+ $rows[] = array(
+ array(
+ 'master',
+ 'develop',
+ 'release',
+ ),
+ pht('Select master, develop, and release.'),
+ );
+ $rows[] = array(
+ array(
+ 'master',
+ 'regexp(/^release-/)',
+ ),
+ pht('Select master, and all branches which start with "release-".'),
+ );
+ $rows[] = array(
+ array(
+ 'regexp(/^(?!temp-)/)',
+ ),
+ pht('Select all branches which do not start with "temp-".'),
+ );
+
+ foreach ($rows as $k => $row) {
+ $rows[$k][0] = phutil_tag(
+ 'pre',
+ array(),
+ implode("\n", $row[0]));
+ }
+
+ $example_table = id(new AphrontTableView($rows))
+ ->setHeaders(
+ array(
+ pht('Example'),
+ pht('Effect'),
+ ))
+ ->setColumnClasses(
+ array(
+ '',
+ 'wide',
+ ));
+
+ $v_track = implode("\n", $v_track);
+ $v_autoclose = implode("\n", $v_autoclose);
+
$form = id(new AphrontFormView())
->setUser($viewer)
->appendRemarkupInstructions(
pht(
- 'You can choose a **Default Branch** for viewing this repository.'.
- "\n\n".
- 'If you want to import only some branches into Diffusion, you can '.
- 'list them in **Track Only**. Other branches will be ignored. If '.
- 'you do not specify any branches, all branches are tracked.'.
- "\n\n".
- 'If you have **Autoclose** enabled, Phabricator can close tasks and '.
- 'revisions when corresponding commits are pushed to the repository. '.
- 'If you want to autoclose objects only when commits appear on '.
- 'specific branches, you can list those branches in **Autoclose '.
- 'Only**. By default, all branches autoclose objects.'))
+ 'You can choose a **Default Branch** for viewing this repository.'))
->appendChild(
id(new AphrontFormTextControl())
->setName('default')
->setLabel(pht('Default Branch'))
- ->setValue($v_default)
- ->setCaption(
- pht('Example: %s', phutil_tag('tt', array(), 'develop'))))
+ ->setValue($v_default))
+ ->appendRemarkupInstructions(
+ pht(
+ 'If you want to import only some branches into Diffusion, you can '.
+ 'list them in **Track Only**. Other branches will be ignored. If '.
+ 'you do not specify any branches, all branches are tracked.'));
+
+ if (!$is_hg) {
+ $form->appendRemarkupInstructions(
+ pht(
+ 'If you have **Autoclose** enabled for this repository, Phabricator '.
+ 'can close tasks and revisions when corresponding commits are '.
+ 'pushed to the repository. If you want to autoclose objects only '.
+ 'when commits appear on specific branches, you can list those '.
+ 'branches in **Autoclose Only**. By default, all tracked branches '.
+ 'will autoclose objects.'));
+ }
+
+ $form
+ ->appendRemarkupInstructions(
+ pht(
+ 'When specifying branches, you should enter one branch name per '.
+ 'line. You can use regular expressions to match branches by '.
+ 'wrapping an expression in `regexp(...)`. For example:'))
->appendChild(
- id(new AphrontFormTextControl())
+ id(new AphrontFormMarkupControl())
+ ->setValue($example_table))
+ ->appendChild(
+ id(new AphrontFormTextAreaControl())
->setName('track')
->setLabel(pht('Track Only'))
- ->setValue($v_track)
- ->setCaption(
- pht('Example: %s', phutil_tag('tt', array(), 'master, develop'))));
+ ->setError($e_track)
+ ->setValue($v_track));
if (!$is_hg) {
$form->appendChild(
- id(new AphrontFormTextControl())
+ id(new AphrontFormTextAreaControl())
->setName('autoclose')
->setLabel(pht('Autoclose Only'))
- ->setValue($v_autoclose)
- ->setCaption(
- pht('Example: %s', phutil_tag('tt', array(), 'master, release'))));
+ ->setError($e_autoclose)
+ ->setValue($v_autoclose));
}
$form->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Save Branches'))
->addCancelButton($edit_uri));
$form_box = id(new PHUIObjectBoxView())
+ ->setValidationException($validation_exception)
->setHeaderText($title)
->setForm($form);
return $this->buildApplicationPage(
array(
$crumbs,
$form_box,
),
array(
'title' => $title,
));
}
+ private function processBranches($string) {
+ $lines = phutil_split_lines($string, $retain_endings = false);
+ foreach ($lines as $key => $line) {
+ $lines[$key] = trim($line);
+ if (!strlen($lines[$key])) {
+ unset($lines[$key]);
+ }
+ }
+
+ return array_values($lines);
+ }
+
}
diff --git a/src/applications/repository/editor/PhabricatorRepositoryEditor.php b/src/applications/repository/editor/PhabricatorRepositoryEditor.php
index 0116527267..5505f9c6c1 100644
--- a/src/applications/repository/editor/PhabricatorRepositoryEditor.php
+++ b/src/applications/repository/editor/PhabricatorRepositoryEditor.php
@@ -1,364 +1,410 @@
<?php
final class PhabricatorRepositoryEditor
extends PhabricatorApplicationTransactionEditor {
public function getEditorApplicationClass() {
return 'PhabricatorDiffusionApplication';
}
public function getEditorObjectsDescription() {
return pht('Repositories');
}
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
$types[] = PhabricatorRepositoryTransaction::TYPE_VCS;
$types[] = PhabricatorRepositoryTransaction::TYPE_ACTIVATE;
$types[] = PhabricatorRepositoryTransaction::TYPE_NAME;
$types[] = PhabricatorRepositoryTransaction::TYPE_DESCRIPTION;
$types[] = PhabricatorRepositoryTransaction::TYPE_ENCODING;
$types[] = PhabricatorRepositoryTransaction::TYPE_DEFAULT_BRANCH;
$types[] = PhabricatorRepositoryTransaction::TYPE_TRACK_ONLY;
$types[] = PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE_ONLY;
$types[] = PhabricatorRepositoryTransaction::TYPE_UUID;
$types[] = PhabricatorRepositoryTransaction::TYPE_SVN_SUBPATH;
$types[] = PhabricatorRepositoryTransaction::TYPE_NOTIFY;
$types[] = PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE;
$types[] = PhabricatorRepositoryTransaction::TYPE_REMOTE_URI;
$types[] = PhabricatorRepositoryTransaction::TYPE_SSH_LOGIN;
$types[] = PhabricatorRepositoryTransaction::TYPE_SSH_KEY;
$types[] = PhabricatorRepositoryTransaction::TYPE_SSH_KEYFILE;
$types[] = PhabricatorRepositoryTransaction::TYPE_HTTP_LOGIN;
$types[] = PhabricatorRepositoryTransaction::TYPE_HTTP_PASS;
$types[] = PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH;
$types[] = PhabricatorRepositoryTransaction::TYPE_HOSTING;
$types[] = PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP;
$types[] = PhabricatorRepositoryTransaction::TYPE_PROTOCOL_SSH;
$types[] = PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY;
$types[] = PhabricatorRepositoryTransaction::TYPE_CREDENTIAL;
$types[] = PhabricatorRepositoryTransaction::TYPE_DANGEROUS;
$types[] = PhabricatorRepositoryTransaction::TYPE_CLONE_NAME;
$types[] = PhabricatorTransactions::TYPE_EDGE;
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
return $types;
}
protected function getCustomTransactionOldValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorRepositoryTransaction::TYPE_VCS:
return $object->getVersionControlSystem();
case PhabricatorRepositoryTransaction::TYPE_ACTIVATE:
return $object->isTracked();
case PhabricatorRepositoryTransaction::TYPE_NAME:
return $object->getName();
case PhabricatorRepositoryTransaction::TYPE_DESCRIPTION:
return $object->getDetail('description');
case PhabricatorRepositoryTransaction::TYPE_ENCODING:
return $object->getDetail('encoding');
case PhabricatorRepositoryTransaction::TYPE_DEFAULT_BRANCH:
return $object->getDetail('default-branch');
case PhabricatorRepositoryTransaction::TYPE_TRACK_ONLY:
return array_keys($object->getDetail('branch-filter', array()));
case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE_ONLY:
return array_keys($object->getDetail('close-commits-filter', array()));
case PhabricatorRepositoryTransaction::TYPE_UUID:
return $object->getUUID();
case PhabricatorRepositoryTransaction::TYPE_SVN_SUBPATH:
return $object->getDetail('svn-subpath');
case PhabricatorRepositoryTransaction::TYPE_NOTIFY:
return (int)!$object->getDetail('herald-disabled');
case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE:
return (int)!$object->getDetail('disable-autoclose');
case PhabricatorRepositoryTransaction::TYPE_REMOTE_URI:
return $object->getDetail('remote-uri');
case PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH:
return $object->getDetail('local-path');
case PhabricatorRepositoryTransaction::TYPE_HOSTING:
return $object->isHosted();
case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP:
return $object->getServeOverHTTP();
case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_SSH:
return $object->getServeOverSSH();
case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY:
return $object->getPushPolicy();
case PhabricatorRepositoryTransaction::TYPE_CREDENTIAL:
return $object->getCredentialPHID();
case PhabricatorRepositoryTransaction::TYPE_DANGEROUS:
return $object->shouldAllowDangerousChanges();
case PhabricatorRepositoryTransaction::TYPE_CLONE_NAME:
return $object->getDetail('clone-name');
}
}
protected function getCustomTransactionNewValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorRepositoryTransaction::TYPE_ACTIVATE:
case PhabricatorRepositoryTransaction::TYPE_NAME:
case PhabricatorRepositoryTransaction::TYPE_DESCRIPTION:
case PhabricatorRepositoryTransaction::TYPE_ENCODING:
case PhabricatorRepositoryTransaction::TYPE_DEFAULT_BRANCH:
case PhabricatorRepositoryTransaction::TYPE_TRACK_ONLY:
case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE_ONLY:
case PhabricatorRepositoryTransaction::TYPE_UUID:
case PhabricatorRepositoryTransaction::TYPE_SVN_SUBPATH:
case PhabricatorRepositoryTransaction::TYPE_REMOTE_URI:
case PhabricatorRepositoryTransaction::TYPE_SSH_LOGIN:
case PhabricatorRepositoryTransaction::TYPE_SSH_KEY:
case PhabricatorRepositoryTransaction::TYPE_SSH_KEYFILE:
case PhabricatorRepositoryTransaction::TYPE_HTTP_LOGIN:
case PhabricatorRepositoryTransaction::TYPE_HTTP_PASS:
case PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH:
case PhabricatorRepositoryTransaction::TYPE_VCS:
case PhabricatorRepositoryTransaction::TYPE_HOSTING:
case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP:
case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_SSH:
case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY:
case PhabricatorRepositoryTransaction::TYPE_CREDENTIAL:
case PhabricatorRepositoryTransaction::TYPE_DANGEROUS:
case PhabricatorRepositoryTransaction::TYPE_CLONE_NAME:
return $xaction->getNewValue();
case PhabricatorRepositoryTransaction::TYPE_NOTIFY:
case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE:
return (int)$xaction->getNewValue();
}
}
protected function applyCustomInternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorRepositoryTransaction::TYPE_VCS:
$object->setVersionControlSystem($xaction->getNewValue());
break;
case PhabricatorRepositoryTransaction::TYPE_ACTIVATE:
$object->setDetail('tracking-enabled', $xaction->getNewValue());
break;
case PhabricatorRepositoryTransaction::TYPE_NAME:
$object->setName($xaction->getNewValue());
break;
case PhabricatorRepositoryTransaction::TYPE_DESCRIPTION:
$object->setDetail('description', $xaction->getNewValue());
break;
case PhabricatorRepositoryTransaction::TYPE_DEFAULT_BRANCH:
$object->setDetail('default-branch', $xaction->getNewValue());
break;
case PhabricatorRepositoryTransaction::TYPE_TRACK_ONLY:
$object->setDetail(
'branch-filter',
array_fill_keys($xaction->getNewValue(), true));
break;
case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE_ONLY:
$object->setDetail(
'close-commits-filter',
array_fill_keys($xaction->getNewValue(), true));
break;
case PhabricatorRepositoryTransaction::TYPE_UUID:
$object->setUUID($xaction->getNewValue());
break;
case PhabricatorRepositoryTransaction::TYPE_SVN_SUBPATH:
$object->setDetail('svn-subpath', $xaction->getNewValue());
break;
case PhabricatorRepositoryTransaction::TYPE_NOTIFY:
$object->setDetail('herald-disabled', (int)!$xaction->getNewValue());
break;
case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE:
$object->setDetail('disable-autoclose', (int)!$xaction->getNewValue());
break;
case PhabricatorRepositoryTransaction::TYPE_REMOTE_URI:
$object->setDetail('remote-uri', $xaction->getNewValue());
break;
case PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH:
$object->setDetail('local-path', $xaction->getNewValue());
break;
case PhabricatorRepositoryTransaction::TYPE_HOSTING:
return $object->setHosted($xaction->getNewValue());
case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP:
return $object->setServeOverHTTP($xaction->getNewValue());
case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_SSH:
return $object->setServeOverSSH($xaction->getNewValue());
case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY:
return $object->setPushPolicy($xaction->getNewValue());
case PhabricatorRepositoryTransaction::TYPE_CREDENTIAL:
return $object->setCredentialPHID($xaction->getNewValue());
case PhabricatorRepositoryTransaction::TYPE_DANGEROUS:
$object->setDetail('allow-dangerous-changes', $xaction->getNewValue());
return;
case PhabricatorRepositoryTransaction::TYPE_CLONE_NAME:
$object->setDetail('clone-name', $xaction->getNewValue());
return;
case PhabricatorRepositoryTransaction::TYPE_ENCODING:
// Make sure the encoding is valid by converting to UTF-8. This tests
// that the user has mbstring installed, and also that they didn't type
// a garbage encoding name. Note that we're converting from UTF-8 to
// the target encoding, because mbstring is fine with converting from
// a nonsense encoding.
$encoding = $xaction->getNewValue();
if (strlen($encoding)) {
try {
phutil_utf8_convert('.', $encoding, 'UTF-8');
} catch (Exception $ex) {
throw new PhutilProxyException(
pht(
"Error setting repository encoding '%s': %s'",
$encoding,
$ex->getMessage()),
$ex);
}
}
$object->setDetail('encoding', $encoding);
break;
}
}
protected function applyCustomExternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorRepositoryTransaction::TYPE_CREDENTIAL:
// Adjust the object <-> credential edge for this repository.
$old_phid = $xaction->getOldValue();
$new_phid = $xaction->getNewValue();
$editor = new PhabricatorEdgeEditor();
$edge_type = PhabricatorEdgeConfig::TYPE_OBJECT_USES_CREDENTIAL;
$src_phid = $object->getPHID();
if ($old_phid) {
$editor->removeEdge($src_phid, $edge_type, $old_phid);
}
if ($new_phid) {
$editor->addEdge($src_phid, $edge_type, $new_phid);
}
$editor->save();
break;
}
}
protected function mergeTransactions(
PhabricatorApplicationTransaction $u,
PhabricatorApplicationTransaction $v) {
$type = $u->getTransactionType();
switch ($type) {
}
return parent::mergeTransactions($u, $v);
}
protected function transactionHasEffect(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
$type = $xaction->getTransactionType();
switch ($type) {
}
return parent::transactionHasEffect($object, $xaction);
}
protected function requireCapabilities(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorRepositoryTransaction::TYPE_ACTIVATE:
case PhabricatorRepositoryTransaction::TYPE_NAME:
case PhabricatorRepositoryTransaction::TYPE_DESCRIPTION:
case PhabricatorRepositoryTransaction::TYPE_ENCODING:
case PhabricatorRepositoryTransaction::TYPE_DEFAULT_BRANCH:
case PhabricatorRepositoryTransaction::TYPE_TRACK_ONLY:
case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE_ONLY:
case PhabricatorRepositoryTransaction::TYPE_UUID:
case PhabricatorRepositoryTransaction::TYPE_SVN_SUBPATH:
case PhabricatorRepositoryTransaction::TYPE_REMOTE_URI:
case PhabricatorRepositoryTransaction::TYPE_SSH_LOGIN:
case PhabricatorRepositoryTransaction::TYPE_SSH_KEY:
case PhabricatorRepositoryTransaction::TYPE_SSH_KEYFILE:
case PhabricatorRepositoryTransaction::TYPE_HTTP_LOGIN:
case PhabricatorRepositoryTransaction::TYPE_HTTP_PASS:
case PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH:
case PhabricatorRepositoryTransaction::TYPE_VCS:
case PhabricatorRepositoryTransaction::TYPE_NOTIFY:
case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE:
case PhabricatorRepositoryTransaction::TYPE_HOSTING:
case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP:
case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_SSH:
case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY:
case PhabricatorRepositoryTransaction::TYPE_CREDENTIAL:
case PhabricatorRepositoryTransaction::TYPE_DANGEROUS:
case PhabricatorRepositoryTransaction::TYPE_CLONE_NAME:
PhabricatorPolicyFilter::requireCapability(
$this->requireActor(),
$object,
PhabricatorPolicyCapability::CAN_EDIT);
break;
}
}
protected function validateTransaction(
PhabricatorLiskDAO $object,
$type,
array $xactions) {
$errors = parent::validateTransaction($object, $type, $xactions);
switch ($type) {
+ case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE:
+ case PhabricatorRepositoryTransaction::TYPE_TRACK_ONLY:
+ foreach ($xactions as $xaction) {
+ foreach ($xaction->getNewValue() as $pattern) {
+ // Check for invalid regular expressions.
+ $regexp = PhabricatorRepository::extractBranchRegexp($pattern);
+ if ($regexp !== null) {
+ $ok = @preg_match($regexp, '');
+ if ($ok === false) {
+ $error = new PhabricatorApplicationTransactionValidationError(
+ $type,
+ pht('Invalid'),
+ pht(
+ 'Expression "%s" is not a valid regular expression. Note '.
+ 'that you must include delimiters.',
+ $regexp),
+ $xaction);
+ $errors[] = $error;
+ continue;
+ }
+ }
+
+ // Check for formatting mistakes like `regex(...)` instead of
+ // `regexp(...)`.
+ $matches = null;
+ if (preg_match('/^([^(]+)\\(.*\\)\z/', $pattern, $matches)) {
+ switch ($matches[1]) {
+ case 'regexp':
+ break;
+ default:
+ $error = new PhabricatorApplicationTransactionValidationError(
+ $type,
+ pht('Invalid'),
+ pht(
+ 'Matching function "%s(...)" is not recognized. Valid '.
+ 'functions are: regexp(...).',
+ $matches[1]),
+ $xaction);
+ $errors[] = $error;
+ break;
+ }
+ }
+ }
+ }
+ break;
+
case PhabricatorRepositoryTransaction::TYPE_REMOTE_URI:
foreach ($xactions as $xaction) {
$new_uri = $xaction->getNewValue();
try {
PhabricatorRepository::assertValidRemoteURI($new_uri);
} catch (Exception $ex) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
$ex->getMessage(),
$xaction);
}
}
break;
case PhabricatorRepositoryTransaction::TYPE_CREDENTIAL:
$ok = PassphraseCredentialControl::validateTransactions(
$this->getActor(),
$xactions);
if (!$ok) {
foreach ($xactions as $xaction) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht(
'The selected credential does not exist, or you do not have '.
'permission to use it.'),
$xaction);
}
}
break;
}
return $errors;
}
}
diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php
index 41e387313f..10f225173f 100644
--- a/src/applications/repository/storage/PhabricatorRepository.php
+++ b/src/applications/repository/storage/PhabricatorRepository.php
@@ -1,1534 +1,1565 @@
<?php
/**
* @task uri Repository URI Management
* @task autoclose Autoclose
*/
final class PhabricatorRepository extends PhabricatorRepositoryDAO
implements
PhabricatorPolicyInterface,
PhabricatorFlaggableInterface,
PhabricatorMarkupInterface,
PhabricatorDestructibleInterface,
PhabricatorProjectInterface {
/**
* Shortest hash we'll recognize in raw "a829f32" form.
*/
const MINIMUM_UNQUALIFIED_HASH = 7;
/**
* Shortest hash we'll recognize in qualified "rXab7ef2f8" form.
*/
const MINIMUM_QUALIFIED_HASH = 5;
const TABLE_PATH = 'repository_path';
const TABLE_PATHCHANGE = 'repository_pathchange';
const TABLE_FILESYSTEM = 'repository_filesystem';
const TABLE_SUMMARY = 'repository_summary';
const TABLE_BADCOMMIT = 'repository_badcommit';
const TABLE_LINTMESSAGE = 'repository_lintmessage';
const TABLE_PARENTS = 'repository_parents';
const SERVE_OFF = 'off';
const SERVE_READONLY = 'readonly';
const SERVE_READWRITE = 'readwrite';
const BECAUSE_REPOSITORY_IMPORTING = 'auto/importing';
const BECAUSE_AUTOCLOSE_DISABLED = 'auto/disabled';
const BECAUSE_NOT_ON_AUTOCLOSE_BRANCH = 'auto/nobranch';
const BECAUSE_BRANCH_UNTRACKED = 'auto/notrack';
const BECAUSE_BRANCH_NOT_AUTOCLOSE = 'auto/noclose';
protected $name;
protected $callsign;
protected $uuid;
protected $viewPolicy;
protected $editPolicy;
protected $pushPolicy;
protected $versionControlSystem;
protected $details = array();
protected $credentialPHID;
private $commitCount = self::ATTACHABLE;
private $mostRecentCommit = self::ATTACHABLE;
private $projectPHIDs = self::ATTACHABLE;
public static function initializeNewRepository(PhabricatorUser $actor) {
$app = id(new PhabricatorApplicationQuery())
->setViewer($actor)
->withClasses(array('PhabricatorDiffusionApplication'))
->executeOne();
$view_policy = $app->getPolicy(DiffusionDefaultViewCapability::CAPABILITY);
$edit_policy = $app->getPolicy(DiffusionDefaultEditCapability::CAPABILITY);
$push_policy = $app->getPolicy(DiffusionDefaultPushCapability::CAPABILITY);
return id(new PhabricatorRepository())
->setViewPolicy($view_policy)
->setEditPolicy($edit_policy)
->setPushPolicy($push_policy);
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'details' => self::SERIALIZATION_JSON,
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorRepositoryRepositoryPHIDType::TYPECONST);
}
public function toDictionary() {
return array(
'id' => $this->getID(),
'name' => $this->getName(),
'phid' => $this->getPHID(),
'callsign' => $this->getCallsign(),
'monogram' => $this->getMonogram(),
'vcs' => $this->getVersionControlSystem(),
'uri' => PhabricatorEnv::getProductionURI($this->getURI()),
'remoteURI' => (string)$this->getRemoteURI(),
'description' => $this->getDetail('description'),
'isActive' => $this->isTracked(),
'isHosted' => $this->isHosted(),
'isImporting' => $this->isImporting(),
);
}
public function getMonogram() {
return 'r'.$this->getCallsign();
}
public function getDetail($key, $default = null) {
return idx($this->details, $key, $default);
}
public function getHumanReadableDetail($key, $default = null) {
$value = $this->getDetail($key, $default);
switch ($key) {
case 'branch-filter':
case 'close-commits-filter':
$value = array_keys($value);
$value = implode(', ', $value);
break;
}
return $value;
}
public function setDetail($key, $value) {
$this->details[$key] = $value;
return $this;
}
public function attachCommitCount($count) {
$this->commitCount = $count;
return $this;
}
public function getCommitCount() {
return $this->assertAttached($this->commitCount);
}
public function attachMostRecentCommit(
PhabricatorRepositoryCommit $commit = null) {
$this->mostRecentCommit = $commit;
return $this;
}
public function getMostRecentCommit() {
return $this->assertAttached($this->mostRecentCommit);
}
public function getDiffusionBrowseURIForPath(
PhabricatorUser $user,
$path,
$line = null,
$branch = null) {
$drequest = DiffusionRequest::newFromDictionary(
array(
'user' => $user,
'repository' => $this,
'path' => $path,
'branch' => $branch,
));
return $drequest->generateURI(
array(
'action' => 'browse',
'line' => $line,
));
}
public function getLocalPath() {
return $this->getDetail('local-path');
}
public function getSubversionBaseURI($commit = null) {
$subpath = $this->getDetail('svn-subpath');
if (!strlen($subpath)) {
$subpath = null;
}
return $this->getSubversionPathURI($subpath, $commit);
}
public function getSubversionPathURI($path = null, $commit = null) {
$vcs = $this->getVersionControlSystem();
if ($vcs != PhabricatorRepositoryType::REPOSITORY_TYPE_SVN) {
throw new Exception('Not a subversion repository!');
}
if ($this->isHosted()) {
$uri = 'file://'.$this->getLocalPath();
} else {
$uri = $this->getDetail('remote-uri');
}
$uri = rtrim($uri, '/');
if (strlen($path)) {
$path = rawurlencode($path);
$path = str_replace('%2F', '/', $path);
$uri = $uri.'/'.ltrim($path, '/');
}
if ($path !== null || $commit !== null) {
$uri .= '@';
}
if ($commit !== null) {
$uri .= $commit;
}
return $uri;
}
public function attachProjectPHIDs(array $project_phids) {
$this->projectPHIDs = $project_phids;
return $this;
}
public function getProjectPHIDs() {
return $this->assertAttached($this->projectPHIDs);
}
/**
* Get the name of the directory this repository should clone or checkout
* into. For example, if the repository name is "Example Repository", a
* reasonable name might be "example-repository". This is used to help users
* get reasonable results when cloning repositories, since they generally do
* not want to clone into directories called "X/" or "Example Repository/".
*
* @return string
*/
public function getCloneName() {
$name = $this->getDetail('clone-name');
// Make some reasonable effort to produce reasonable default directory
// names from repository names.
if (!strlen($name)) {
$name = $this->getName();
$name = phutil_utf8_strtolower($name);
$name = preg_replace('@[/ -:]+@', '-', $name);
$name = trim($name, '-');
if (!strlen($name)) {
$name = $this->getCallsign();
}
}
return $name;
}
/* -( Remote Command Execution )------------------------------------------- */
public function execRemoteCommand($pattern /* , $arg, ... */) {
$args = func_get_args();
return $this->newRemoteCommandFuture($args)->resolve();
}
public function execxRemoteCommand($pattern /* , $arg, ... */) {
$args = func_get_args();
return $this->newRemoteCommandFuture($args)->resolvex();
}
public function getRemoteCommandFuture($pattern /* , $arg, ... */) {
$args = func_get_args();
return $this->newRemoteCommandFuture($args);
}
public function passthruRemoteCommand($pattern /* , $arg, ... */) {
$args = func_get_args();
return $this->newRemoteCommandPassthru($args)->execute();
}
private function newRemoteCommandFuture(array $argv) {
$argv = $this->formatRemoteCommand($argv);
$future = newv('ExecFuture', $argv);
$future->setEnv($this->getRemoteCommandEnvironment());
return $future;
}
private function newRemoteCommandPassthru(array $argv) {
$argv = $this->formatRemoteCommand($argv);
$passthru = newv('PhutilExecPassthru', $argv);
$passthru->setEnv($this->getRemoteCommandEnvironment());
return $passthru;
}
/* -( Local Command Execution )-------------------------------------------- */
public function execLocalCommand($pattern /* , $arg, ... */) {
$args = func_get_args();
return $this->newLocalCommandFuture($args)->resolve();
}
public function execxLocalCommand($pattern /* , $arg, ... */) {
$args = func_get_args();
return $this->newLocalCommandFuture($args)->resolvex();
}
public function getLocalCommandFuture($pattern /* , $arg, ... */) {
$args = func_get_args();
return $this->newLocalCommandFuture($args);
}
public function passthruLocalCommand($pattern /* , $arg, ... */) {
$args = func_get_args();
return $this->newLocalCommandPassthru($args)->execute();
}
private function newLocalCommandFuture(array $argv) {
$this->assertLocalExists();
$argv = $this->formatLocalCommand($argv);
$future = newv('ExecFuture', $argv);
$future->setEnv($this->getLocalCommandEnvironment());
if ($this->usesLocalWorkingCopy()) {
$future->setCWD($this->getLocalPath());
}
return $future;
}
private function newLocalCommandPassthru(array $argv) {
$this->assertLocalExists();
$argv = $this->formatLocalCommand($argv);
$future = newv('PhutilExecPassthru', $argv);
$future->setEnv($this->getLocalCommandEnvironment());
if ($this->usesLocalWorkingCopy()) {
$future->setCWD($this->getLocalPath());
}
return $future;
}
/* -( Command Infrastructure )--------------------------------------------- */
private function getSSHWrapper() {
$root = dirname(phutil_get_library_root('phabricator'));
return $root.'/bin/ssh-connect';
}
private function getCommonCommandEnvironment() {
$env = array(
// NOTE: Force the language to "en_US.UTF-8", which overrides locale
// settings. This makes stuff print in English instead of, e.g., French,
// so we can parse the output of some commands, error messages, etc.
'LANG' => 'en_US.UTF-8',
// Propagate PHABRICATOR_ENV explicitly. For discussion, see T4155.
'PHABRICATOR_ENV' => PhabricatorEnv::getSelectedEnvironmentName(),
);
switch ($this->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
// NOTE: See T2965. Some time after Git 1.7.5.4, Git started fataling if
// it can not read $HOME. For many users, $HOME points at /root (this
// seems to be a default result of Apache setup). Instead, explicitly
// point $HOME at a readable, empty directory so that Git looks for the
// config file it's after, fails to locate it, and moves on. This is
// really silly, but seems like the least damaging approach to
// mitigating the issue.
$root = dirname(phutil_get_library_root('phabricator'));
$env['HOME'] = $root.'/support/empty/';
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
// NOTE: This overrides certain configuration, extensions, and settings
// which make Mercurial commands do random unusual things.
$env['HGPLAIN'] = 1;
break;
default:
throw new Exception('Unrecognized version control system.');
}
return $env;
}
private function getLocalCommandEnvironment() {
return $this->getCommonCommandEnvironment();
}
private function getRemoteCommandEnvironment() {
$env = $this->getCommonCommandEnvironment();
if ($this->shouldUseSSH()) {
// NOTE: This is read by `bin/ssh-connect`, and tells it which credentials
// to use.
$env['PHABRICATOR_CREDENTIAL'] = $this->getCredentialPHID();
switch ($this->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
// Force SVN to use `bin/ssh-connect`.
$env['SVN_SSH'] = $this->getSSHWrapper();
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
// Force Git to use `bin/ssh-connect`.
$env['GIT_SSH'] = $this->getSSHWrapper();
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
// We force Mercurial through `bin/ssh-connect` too, but it uses a
// command-line flag instead of an environmental variable.
break;
default:
throw new Exception('Unrecognized version control system.');
}
}
return $env;
}
private function formatRemoteCommand(array $args) {
$pattern = $args[0];
$args = array_slice($args, 1);
switch ($this->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
if ($this->shouldUseHTTP() || $this->shouldUseSVNProtocol()) {
$flags = array();
$flag_args = array();
$flags[] = '--non-interactive';
$flags[] = '--no-auth-cache';
if ($this->shouldUseHTTP()) {
$flags[] = '--trust-server-cert';
}
$credential_phid = $this->getCredentialPHID();
if ($credential_phid) {
$key = PassphrasePasswordKey::loadFromPHID(
$credential_phid,
PhabricatorUser::getOmnipotentUser());
$flags[] = '--username %P';
$flags[] = '--password %P';
$flag_args[] = $key->getUsernameEnvelope();
$flag_args[] = $key->getPasswordEnvelope();
}
$flags = implode(' ', $flags);
$pattern = "svn {$flags} {$pattern}";
$args = array_mergev(array($flag_args, $args));
} else {
$pattern = "svn --non-interactive {$pattern}";
}
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$pattern = "git {$pattern}";
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
if ($this->shouldUseSSH()) {
$pattern = "hg --config ui.ssh=%s {$pattern}";
array_unshift(
$args,
$this->getSSHWrapper());
} else {
$pattern = "hg {$pattern}";
}
break;
default:
throw new Exception('Unrecognized version control system.');
}
array_unshift($args, $pattern);
return $args;
}
private function formatLocalCommand(array $args) {
$pattern = $args[0];
$args = array_slice($args, 1);
switch ($this->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$pattern = "svn --non-interactive {$pattern}";
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$pattern = "git {$pattern}";
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$pattern = "hg {$pattern}";
break;
default:
throw new Exception('Unrecognized version control system.');
}
array_unshift($args, $pattern);
return $args;
}
/**
* Sanitize output of an `hg` command invoked with the `--debug` flag to make
* it usable.
*
* @param string Output from `hg --debug ...`
* @return string Usable output.
*/
public static function filterMercurialDebugOutput($stdout) {
// When hg commands are run with `--debug` and some config file isn't
// trusted, Mercurial prints out a warning to stdout, twice, after Feb 2011.
//
// http://selenic.com/pipermail/mercurial-devel/2011-February/028541.html
$lines = preg_split('/(?<=\n)/', $stdout);
$regex = '/ignoring untrusted configuration option .*\n$/';
foreach ($lines as $key => $line) {
$lines[$key] = preg_replace($regex, '', $line);
}
return implode('', $lines);
}
public function getURI() {
return '/diffusion/'.$this->getCallsign().'/';
}
public function getNormalizedPath() {
$uri = (string)$this->getCloneURIObject();
switch ($this->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$normalized_uri = new PhabricatorRepositoryURINormalizer(
PhabricatorRepositoryURINormalizer::TYPE_GIT,
$uri);
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$normalized_uri = new PhabricatorRepositoryURINormalizer(
PhabricatorRepositoryURINormalizer::TYPE_SVN,
$uri);
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$normalized_uri = new PhabricatorRepositoryURINormalizer(
PhabricatorRepositoryURINormalizer::TYPE_MERCURIAL,
$uri);
break;
default:
throw new Exception('Unrecognized version control system.');
}
return $normalized_uri->getNormalizedPath();
}
public function isTracked() {
return $this->getDetail('tracking-enabled', false);
}
public function getDefaultBranch() {
$default = $this->getDetail('default-branch');
if (strlen($default)) {
return $default;
}
$default_branches = array(
PhabricatorRepositoryType::REPOSITORY_TYPE_GIT => 'master',
PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL => 'default',
);
return idx($default_branches, $this->getVersionControlSystem());
}
public function getDefaultArcanistBranch() {
return coalesce($this->getDefaultBranch(), 'svn');
}
private function isBranchInFilter($branch, $filter_key) {
$vcs = $this->getVersionControlSystem();
$is_git = ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT);
$use_filter = ($is_git);
+ if (!$use_filter) {
+ // If this VCS doesn't use filters, pass everything through.
+ return true;
+ }
- if ($use_filter) {
- $filter = $this->getDetail($filter_key, array());
- if ($filter && empty($filter[$branch])) {
- return false;
+
+ $filter = $this->getDetail($filter_key, array());
+
+ // If there's no filter set, let everything through.
+ if (!$filter) {
+ return true;
+ }
+
+ // If this branch isn't literally named `regexp(...)`, and it's in the
+ // filter list, let it through.
+ if (isset($filter[$branch])) {
+ if (self::extractBranchRegexp($branch) === null) {
+ return true;
}
}
- // By default, all branches pass.
- return true;
+ // If the branch matches a regexp, let it through.
+ foreach ($filter as $pattern => $ignored) {
+ $regexp = self::extractBranchRegexp($pattern);
+ if ($regexp !== null) {
+ if (preg_match($regexp, $branch)) {
+ return true;
+ }
+ }
+ }
+
+ // Nothing matched, so filter this branch out.
+ return false;
+ }
+
+ public static function extractBranchRegexp($pattern) {
+ $matches = null;
+ if (preg_match('/^regexp\\((.*)\\)\z/', $pattern, $matches)) {
+ return $matches[1];
+ }
+ return null;
}
public function shouldTrackBranch($branch) {
return $this->isBranchInFilter($branch, 'branch-filter');
}
public function formatCommitName($commit_identifier) {
$vcs = $this->getVersionControlSystem();
$type_git = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
$type_hg = PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL;
$is_git = ($vcs == $type_git);
$is_hg = ($vcs == $type_hg);
if ($is_git || $is_hg) {
$short_identifier = substr($commit_identifier, 0, 12);
} else {
$short_identifier = $commit_identifier;
}
return 'r'.$this->getCallsign().$short_identifier;
}
public function isImporting() {
return (bool)$this->getDetail('importing', false);
}
/* -( Autoclose )---------------------------------------------------------- */
/**
* Determine if autoclose is active for a branch.
*
* For more details about why, use @{method:shouldSkipAutocloseBranch}.
*
* @param string Branch name to check.
* @return bool True if autoclose is active for the branch.
* @task autoclose
*/
public function shouldAutocloseBranch($branch) {
return ($this->shouldSkipAutocloseBranch($branch) === null);
}
/**
* Determine if autoclose is active for a commit.
*
* For more details about why, use @{method:shouldSkipAutocloseCommit}.
*
* @param PhabricatorRepositoryCommit Commit to check.
* @return bool True if autoclose is active for the commit.
* @task autoclose
*/
public function shouldAutocloseCommit(PhabricatorRepositoryCommit $commit) {
return ($this->shouldSkipAutocloseCommit($commit) === null);
}
/**
* Determine why autoclose should be skipped for a branch.
*
* This method gives a detailed reason why autoclose will be skipped. To
* perform a simple test, use @{method:shouldAutocloseBranch}.
*
* @param string Branch name to check.
* @return const|null Constant identifying reason to skip this branch, or null
* if autoclose is active.
* @task autoclose
*/
public function shouldSkipAutocloseBranch($branch) {
$all_reason = $this->shouldSkipAllAutoclose();
if ($all_reason) {
return $all_reason;
}
if (!$this->shouldTrackBranch($branch)) {
return self::BECAUSE_BRANCH_UNTRACKED;
}
if (!$this->isBranchInFilter($branch, 'close-commits-filter')) {
return self::BECAUSE_BRANCH_NOT_AUTOCLOSE;
}
return null;
}
/**
* Determine why autoclose should be skipped for a commit.
*
* This method gives a detailed reason why autoclose will be skipped. To
* perform a simple test, use @{method:shouldAutocloseCommit}.
*
* @param PhabricatorRepositoryCommit Commit to check.
* @return const|null Constant identifying reason to skip this commit, or null
* if autoclose is active.
* @task autoclose
*/
public function shouldSkipAutocloseCommit(
PhabricatorRepositoryCommit $commit) {
$all_reason = $this->shouldSkipAllAutoclose();
if ($all_reason) {
return $all_reason;
}
switch ($this->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
return null;
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
break;
default:
throw new Exception('Unrecognized version control system.');
}
$closeable_flag = PhabricatorRepositoryCommit::IMPORTED_CLOSEABLE;
if (!$commit->isPartiallyImported($closeable_flag)) {
return self::BECAUSE_NOT_ON_AUTOCLOSE_BRANCH;
}
return null;
}
/**
* Determine why all autoclose operations should be skipped for this
* repository.
*
* @return const|null Constant identifying reason to skip all autoclose
* operations, or null if autoclose operations are not blocked at the
* repository level.
* @task autoclose
*/
private function shouldSkipAllAutoclose() {
if ($this->isImporting()) {
return self::BECAUSE_REPOSITORY_IMPORTING;
}
if ($this->getDetail('disable-autoclose', false)) {
return self::BECAUSE_AUTOCLOSE_DISABLED;
}
return null;
}
/* -( Repository URI Management )------------------------------------------ */
/**
* Get the remote URI for this repository.
*
* @return string
* @task uri
*/
public function getRemoteURI() {
return (string)$this->getRemoteURIObject();
}
/**
* Get the remote URI for this repository, including credentials if they're
* used by this repository.
*
* @return PhutilOpaqueEnvelope URI, possibly including credentials.
* @task uri
*/
public function getRemoteURIEnvelope() {
$uri = $this->getRemoteURIObject();
$remote_protocol = $this->getRemoteProtocol();
if ($remote_protocol == 'http' || $remote_protocol == 'https') {
// For SVN, we use `--username` and `--password` flags separately, so
// don't add any credentials here.
if (!$this->isSVN()) {
$credential_phid = $this->getCredentialPHID();
if ($credential_phid) {
$key = PassphrasePasswordKey::loadFromPHID(
$credential_phid,
PhabricatorUser::getOmnipotentUser());
$uri->setUser($key->getUsernameEnvelope()->openEnvelope());
$uri->setPass($key->getPasswordEnvelope()->openEnvelope());
}
}
}
return new PhutilOpaqueEnvelope((string)$uri);
}
/**
* Get the clone (or checkout) URI for this repository, without authentication
* information.
*
* @return string Repository URI.
* @task uri
*/
public function getPublicCloneURI() {
$uri = $this->getCloneURIObject();
// Make sure we don't leak anything if this repo is using HTTP Basic Auth
// with the credentials in the URI or something zany like that.
// If repository is not accessed over SSH we remove both username and
// password.
if (!$this->isHosted()) {
if (!$this->shouldUseSSH()) {
$uri->setUser(null);
// This might be a Git URI or a normal URI. If it's Git, there's no
// password support.
if ($uri instanceof PhutilURI) {
$uri->setPass(null);
}
}
}
return (string)$uri;
}
/**
* Get the protocol for the repository's remote.
*
* @return string Protocol, like "ssh" or "git".
* @task uri
*/
public function getRemoteProtocol() {
$uri = $this->getRemoteURIObject();
if ($uri instanceof PhutilGitURI) {
return 'ssh';
} else {
return $uri->getProtocol();
}
}
/**
* Get a parsed object representation of the repository's remote URI. This
* may be a normal URI (returned as a @{class@libphutil:PhutilURI}) or a git
* URI (returned as a @{class@libphutil:PhutilGitURI}).
*
* @return wild A @{class@libphutil:PhutilURI} or
* @{class@libphutil:PhutilGitURI}.
* @task uri
*/
public function getRemoteURIObject() {
$raw_uri = $this->getDetail('remote-uri');
if (!$raw_uri) {
return new PhutilURI('');
}
if (!strncmp($raw_uri, '/', 1)) {
return new PhutilURI('file://'.$raw_uri);
}
$uri = new PhutilURI($raw_uri);
if ($uri->getProtocol()) {
return $uri;
}
$uri = new PhutilGitURI($raw_uri);
if ($uri->getDomain()) {
return $uri;
}
throw new Exception("Remote URI '{$raw_uri}' could not be parsed!");
}
/**
* Get the "best" clone/checkout URI for this repository, on any protocol.
*/
public function getCloneURIObject() {
if (!$this->isHosted()) {
if ($this->isSVN()) {
// Make sure we pick up the "Import Only" path for Subversion, so
// the user clones the repository starting at the correct path, not
// from the root.
$base_uri = $this->getSubversionBaseURI();
$base_uri = new PhutilURI($base_uri);
$path = $base_uri->getPath();
if (!$path) {
$path = '/';
}
// If the trailing "@" is not required to escape the URI, strip it for
// readability.
if (!preg_match('/@.*@/', $path)) {
$path = rtrim($path, '@');
}
$base_uri->setPath($path);
return $base_uri;
} else {
return $this->getRemoteURIObject();
}
}
// Choose the best URI: pick a read/write URI over a URI which is not
// read/write, and SSH over HTTP.
$serve_ssh = $this->getServeOverSSH();
$serve_http = $this->getServeOverHTTP();
if ($serve_ssh === self::SERVE_READWRITE) {
return $this->getSSHCloneURIObject();
} else if ($serve_http === self::SERVE_READWRITE) {
return $this->getHTTPCloneURIObject();
} else if ($serve_ssh !== self::SERVE_OFF) {
return $this->getSSHCloneURIObject();
} else if ($serve_http !== self::SERVE_OFF) {
return $this->getHTTPCloneURIObject();
} else {
return null;
}
}
/**
* Get the repository's SSH clone/checkout URI, if one exists.
*/
public function getSSHCloneURIObject() {
if (!$this->isHosted()) {
if ($this->shouldUseSSH()) {
return $this->getRemoteURIObject();
} else {
return null;
}
}
$serve_ssh = $this->getServeOverSSH();
if ($serve_ssh === self::SERVE_OFF) {
return null;
}
$uri = new PhutilURI(PhabricatorEnv::getProductionURI($this->getURI()));
if ($this->isSVN()) {
$uri->setProtocol('svn+ssh');
} else {
$uri->setProtocol('ssh');
}
if ($this->isGit()) {
$uri->setPath($uri->getPath().$this->getCloneName().'.git');
} else if ($this->isHg()) {
$uri->setPath($uri->getPath().$this->getCloneName().'/');
}
$ssh_user = PhabricatorEnv::getEnvConfig('diffusion.ssh-user');
if ($ssh_user) {
$uri->setUser($ssh_user);
}
$uri->setPort(PhabricatorEnv::getEnvConfig('diffusion.ssh-port'));
return $uri;
}
/**
* Get the repository's HTTP clone/checkout URI, if one exists.
*/
public function getHTTPCloneURIObject() {
if (!$this->isHosted()) {
if ($this->shouldUseHTTP()) {
return $this->getRemoteURIObject();
} else {
return null;
}
}
$serve_http = $this->getServeOverHTTP();
if ($serve_http === self::SERVE_OFF) {
return null;
}
$uri = PhabricatorEnv::getProductionURI($this->getURI());
$uri = new PhutilURI($uri);
if ($this->isGit()) {
$uri->setPath($uri->getPath().$this->getCloneName().'.git');
} else if ($this->isHg()) {
$uri->setPath($uri->getPath().$this->getCloneName().'/');
}
return $uri;
}
/**
* Determine if we should connect to the remote using SSH flags and
* credentials.
*
* @return bool True to use the SSH protocol.
* @task uri
*/
private function shouldUseSSH() {
if ($this->isHosted()) {
return false;
}
$protocol = $this->getRemoteProtocol();
if ($this->isSSHProtocol($protocol)) {
return true;
}
return false;
}
/**
* Determine if we should connect to the remote using HTTP flags and
* credentials.
*
* @return bool True to use the HTTP protocol.
* @task uri
*/
private function shouldUseHTTP() {
if ($this->isHosted()) {
return false;
}
$protocol = $this->getRemoteProtocol();
return ($protocol == 'http' || $protocol == 'https');
}
/**
* Determine if we should connect to the remote using SVN flags and
* credentials.
*
* @return bool True to use the SVN protocol.
* @task uri
*/
private function shouldUseSVNProtocol() {
if ($this->isHosted()) {
return false;
}
$protocol = $this->getRemoteProtocol();
return ($protocol == 'svn');
}
/**
* Determine if a protocol is SSH or SSH-like.
*
* @param string A protocol string, like "http" or "ssh".
* @return bool True if the protocol is SSH-like.
* @task uri
*/
private function isSSHProtocol($protocol) {
return ($protocol == 'ssh' || $protocol == 'svn+ssh');
}
public function delete() {
$this->openTransaction();
$paths = id(new PhabricatorOwnersPath())
->loadAllWhere('repositoryPHID = %s', $this->getPHID());
foreach ($paths as $path) {
$path->delete();
}
$projects = id(new PhabricatorRepositoryArcanistProject())
->loadAllWhere('repositoryID = %d', $this->getID());
foreach ($projects as $project) {
// note each project deletes its PhabricatorRepositorySymbols
$project->delete();
}
$commits = id(new PhabricatorRepositoryCommit())
->loadAllWhere('repositoryID = %d', $this->getID());
foreach ($commits as $commit) {
// note PhabricatorRepositoryAuditRequests and
// PhabricatorRepositoryCommitData are deleted here too.
$commit->delete();
}
$mirrors = id(new PhabricatorRepositoryMirror())
->loadAllWhere('repositoryPHID = %s', $this->getPHID());
foreach ($mirrors as $mirror) {
$mirror->delete();
}
$ref_cursors = id(new PhabricatorRepositoryRefCursor())
->loadAllWhere('repositoryPHID = %s', $this->getPHID());
foreach ($ref_cursors as $cursor) {
$cursor->delete();
}
$conn_w = $this->establishConnection('w');
queryfx(
$conn_w,
'DELETE FROM %T WHERE repositoryID = %d',
self::TABLE_FILESYSTEM,
$this->getID());
queryfx(
$conn_w,
'DELETE FROM %T WHERE repositoryID = %d',
self::TABLE_PATHCHANGE,
$this->getID());
queryfx(
$conn_w,
'DELETE FROM %T WHERE repositoryID = %d',
self::TABLE_SUMMARY,
$this->getID());
$result = parent::delete();
$this->saveTransaction();
return $result;
}
public function isGit() {
$vcs = $this->getVersionControlSystem();
return ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT);
}
public function isSVN() {
$vcs = $this->getVersionControlSystem();
return ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_SVN);
}
public function isHg() {
$vcs = $this->getVersionControlSystem();
return ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL);
}
public function isHosted() {
return (bool)$this->getDetail('hosting-enabled', false);
}
public function setHosted($enabled) {
return $this->setDetail('hosting-enabled', $enabled);
}
public function getServeOverHTTP() {
if ($this->isSVN()) {
return self::SERVE_OFF;
}
$serve = $this->getDetail('serve-over-http', self::SERVE_OFF);
return $this->normalizeServeConfigSetting($serve);
}
public function setServeOverHTTP($mode) {
return $this->setDetail('serve-over-http', $mode);
}
public function getServeOverSSH() {
$serve = $this->getDetail('serve-over-ssh', self::SERVE_OFF);
return $this->normalizeServeConfigSetting($serve);
}
public function setServeOverSSH($mode) {
return $this->setDetail('serve-over-ssh', $mode);
}
public static function getProtocolAvailabilityName($constant) {
switch ($constant) {
case self::SERVE_OFF:
return pht('Off');
case self::SERVE_READONLY:
return pht('Read Only');
case self::SERVE_READWRITE:
return pht('Read/Write');
default:
return pht('Unknown');
}
}
private function normalizeServeConfigSetting($value) {
switch ($value) {
case self::SERVE_OFF:
case self::SERVE_READONLY:
return $value;
case self::SERVE_READWRITE:
if ($this->isHosted()) {
return self::SERVE_READWRITE;
} else {
return self::SERVE_READONLY;
}
default:
return self::SERVE_OFF;
}
}
/**
* Raise more useful errors when there are basic filesystem problems.
*/
private function assertLocalExists() {
if (!$this->usesLocalWorkingCopy()) {
return;
}
$local = $this->getLocalPath();
Filesystem::assertExists($local);
Filesystem::assertIsDirectory($local);
Filesystem::assertReadable($local);
}
/**
* Determine if the working copy is bare or not. In Git, this corresponds
* to `--bare`. In Mercurial, `--noupdate`.
*/
public function isWorkingCopyBare() {
switch ($this->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
return false;
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$local = $this->getLocalPath();
if (Filesystem::pathExists($local.'/.git')) {
return false;
} else {
return true;
}
}
}
public function usesLocalWorkingCopy() {
switch ($this->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
return $this->isHosted();
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
return true;
}
}
public function getHookDirectories() {
$directories = array();
if (!$this->isHosted()) {
return $directories;
}
$root = $this->getLocalPath();
switch ($this->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
if ($this->isWorkingCopyBare()) {
$directories[] = $root.'/hooks/pre-receive-phabricator.d/';
} else {
$directories[] = $root.'/.git/hooks/pre-receive-phabricator.d/';
}
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$directories[] = $root.'/hooks/pre-commit-phabricator.d/';
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
// NOTE: We don't support custom Mercurial hooks for now because they're
// messy and we can't easily just drop a `hooks.d/` directory next to
// the hooks.
break;
}
return $directories;
}
public function canDestroyWorkingCopy() {
if ($this->isHosted()) {
// Never destroy hosted working copies.
return false;
}
$default_path = PhabricatorEnv::getEnvConfig(
'repository.default-local-path');
return Filesystem::isDescendant($this->getLocalPath(), $default_path);
}
public function canUsePathTree() {
return !$this->isSVN();
}
public function canMirror() {
if ($this->isGit() || $this->isHg()) {
return true;
}
return false;
}
public function canAllowDangerousChanges() {
if (!$this->isHosted()) {
return false;
}
if ($this->isGit() || $this->isHg()) {
return true;
}
return false;
}
public function shouldAllowDangerousChanges() {
return (bool)$this->getDetail('allow-dangerous-changes');
}
public function writeStatusMessage(
$status_type,
$status_code,
array $parameters = array()) {
$table = new PhabricatorRepositoryStatusMessage();
$conn_w = $table->establishConnection('w');
$table_name = $table->getTableName();
if ($status_code === null) {
queryfx(
$conn_w,
'DELETE FROM %T WHERE repositoryID = %d AND statusType = %s',
$table_name,
$this->getID(),
$status_type);
} else {
queryfx(
$conn_w,
'INSERT INTO %T
(repositoryID, statusType, statusCode, parameters, epoch)
VALUES (%d, %s, %s, %s, %d)
ON DUPLICATE KEY UPDATE
statusCode = VALUES(statusCode),
parameters = VALUES(parameters),
epoch = VALUES(epoch)',
$table_name,
$this->getID(),
$status_type,
$status_code,
json_encode($parameters),
time());
}
return $this;
}
public static function getRemoteURIProtocol($raw_uri) {
$uri = new PhutilURI($raw_uri);
if ($uri->getProtocol()) {
return strtolower($uri->getProtocol());
}
$git_uri = new PhutilGitURI($raw_uri);
if (strlen($git_uri->getDomain()) && strlen($git_uri->getPath())) {
return 'ssh';
}
return null;
}
public static function assertValidRemoteURI($uri) {
if (trim($uri) != $uri) {
throw new Exception(
pht(
'The remote URI has leading or trailing whitespace.'));
}
$protocol = self::getRemoteURIProtocol($uri);
// Catch confusion between Git/SCP-style URIs and normal URIs. See T3619
// for discussion. This is usually a user adding "ssh://" to an implicit
// SSH Git URI.
if ($protocol == 'ssh') {
if (preg_match('(^[^:@]+://[^/:]+:[^\d])', $uri)) {
throw new Exception(
pht(
"The remote URI is not formatted correctly. Remote URIs ".
"with an explicit protocol should be in the form ".
"'proto://domain/path', not 'proto://domain:/path'. ".
"The ':/path' syntax is only valid in SCP-style URIs."));
}
}
switch ($protocol) {
case 'ssh':
case 'http':
case 'https':
case 'git':
case 'svn':
case 'svn+ssh':
break;
default:
// NOTE: We're explicitly rejecting 'file://' because it can be
// used to clone from the working copy of another repository on disk
// that you don't normally have permission to access.
throw new Exception(
pht(
"The URI protocol is unrecognized. It should begin ".
"'ssh://', 'http://', 'https://', 'git://', 'svn://', ".
"'svn+ssh://', or be in the form 'git@domain.com:path'."));
}
return true;
}
/**
* Load the pull frequency for this repository, based on the time since the
* last activity.
*
* We pull rarely used repositories less frequently. This finds the most
* recent commit which is older than the current time (which prevents us from
* spinning on repositories with a silly commit post-dated to some time in
* 2037). We adjust the pull frequency based on when the most recent commit
* occurred.
*
* @param int The minimum update interval to use, in seconds.
* @return int Repository update interval, in seconds.
*/
public function loadUpdateInterval($minimum = 15) {
// If a repository is still importing, always pull it as frequently as
// possible. This prevents us from hanging for a long time at 99.9% when
// importing an inactive repository.
if ($this->isImporting()) {
return $minimum;
}
$window_start = (PhabricatorTime::getNow() + $minimum);
$table = id(new PhabricatorRepositoryCommit());
$last_commit = queryfx_one(
$table->establishConnection('r'),
'SELECT epoch FROM %T
WHERE repositoryID = %d AND epoch <= %d
ORDER BY epoch DESC LIMIT 1',
$table->getTableName(),
$this->getID(),
$window_start);
if ($last_commit) {
$time_since_commit = ($window_start - $last_commit['epoch']);
$last_few_days = phutil_units('3 days in seconds');
if ($time_since_commit <= $last_few_days) {
// For repositories with activity in the recent past, we wait one
// extra second for every 10 minutes since the last commit. This
// shorter backoff is intended to handle weekends and other short
// breaks from development.
$smart_wait = ($time_since_commit / 600);
} else {
// For repositories without recent activity, we wait one extra second
// for every 4 minutes since the last commit. This longer backoff
// handles rarely used repositories, up to the maximum.
$smart_wait = ($time_since_commit / 240);
}
// We'll never wait more than 6 hours to pull a repository.
$longest_wait = phutil_units('6 hours in seconds');
$smart_wait = min($smart_wait, $longest_wait);
$smart_wait = max($minimum, $smart_wait);
} else {
$smart_wait = $minimum;
}
return $smart_wait;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
DiffusionPushCapability::CAPABILITY,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->getViewPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
return $this->getEditPolicy();
case DiffusionPushCapability::CAPABILITY:
return $this->getPushPolicy();
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $user) {
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
/* -( PhabricatorMarkupInterface )----------------------------------------- */
public function getMarkupFieldKey($field) {
$hash = PhabricatorHash::digestForIndex($this->getMarkupText($field));
return "repo:{$hash}";
}
public function newMarkupEngine($field) {
return PhabricatorMarkupEngine::newMarkupEngine(array());
}
public function getMarkupText($field) {
return $this->getDetail('description');
}
public function didMarkupText(
$field,
$output,
PhutilMarkupEngine $engine) {
require_celerity_resource('phabricator-remarkup-css');
return phutil_tag(
'div',
array(
'class' => 'phabricator-remarkup',
),
$output);
}
public function shouldUseMarkupCache($field) {
return true;
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$this->openTransaction();
$this->delete();
$this->saveTransaction();
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jan 19, 14:30 (3 w, 2 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1125513
Default Alt Text
(72 KB)

Event Timeline