Page MenuHomestyx hydra

No OneTemporary

diff --git a/resources/sql/autopatches/20160503.repo.01.lpath.sql b/resources/sql/autopatches/20160503.repo.01.lpath.sql
new file mode 100644
index 0000000000..437dfb1317
--- /dev/null
+++ b/resources/sql/autopatches/20160503.repo.01.lpath.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_repository.repository
+ ADD localPath VARCHAR(128) COLLATE {$COLLATE_TEXT};
diff --git a/resources/sql/autopatches/20160503.repo.02.lpathkey.sql b/resources/sql/autopatches/20160503.repo.02.lpathkey.sql
new file mode 100644
index 0000000000..b630d87c26
--- /dev/null
+++ b/resources/sql/autopatches/20160503.repo.02.lpathkey.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_repository.repository
+ ADD UNIQUE KEY `key_local` (localPath);
diff --git a/resources/sql/autopatches/20160503.repo.03.lpathmigrate.php b/resources/sql/autopatches/20160503.repo.03.lpathmigrate.php
new file mode 100644
index 0000000000..d810b074c9
--- /dev/null
+++ b/resources/sql/autopatches/20160503.repo.03.lpathmigrate.php
@@ -0,0 +1,57 @@
+<?php
+
+$table = new PhabricatorRepository();
+$conn_w = $table->establishConnection('w');
+
+$default_path = PhabricatorEnv::getEnvConfig('repository.default-local-path');
+$default_path = rtrim($default_path, '/');
+
+foreach (new LiskMigrationIterator($table) as $repository) {
+ $local_path = $repository->getLocalPath();
+ if (strlen($local_path)) {
+ // Repository already has a modern, unique local path.
+ continue;
+ }
+
+ $local_path = $repository->getDetail('local-path');
+ if (!strlen($local_path)) {
+ // Repository does not have a local path using the older format.
+ continue;
+ }
+
+ $random = Filesystem::readRandomCharacters(8);
+
+ // Try the configured path first, then a default path, then a path with some
+ // random noise.
+ $paths = array(
+ $local_path,
+ $default_path.'/'.$repository->getID().'/',
+ $default_path.'/'.$repository->getID().'-'.$random.'/',
+ );
+
+ foreach ($paths as $path) {
+ // Set, then get the path to normalize it.
+ $repository->setLocalPath($path);
+ $path = $repository->getLocalPath();
+
+ try {
+ queryfx(
+ $conn_w,
+ 'UPDATE %T SET localPath = %s WHERE id = %d',
+ $table->getTableName(),
+ $path,
+ $repository->getID());
+
+ echo tsprintf(
+ "%s\n",
+ pht(
+ 'Assigned repository "%s" to local path "%s".',
+ $repository->getDisplayName(),
+ $path));
+
+ break;
+ } catch (AphrontDuplicateKeyQueryException $ex) {
+ // Ignore, try the next one.
+ }
+ }
+}
diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php
index d8a4cf40ba..08fc9778bc 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php
@@ -1,1338 +1,1336 @@
<?php
final class DiffusionRepositoryEditMainController
extends DiffusionRepositoryEditController {
public function handleRequest(AphrontRequest $request) {
$response = $this->loadDiffusionContextForEdit();
if ($response) {
return $response;
}
$viewer = $this->getViewer();
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
$is_svn = false;
$is_git = false;
$is_hg = false;
switch ($repository->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$is_git = true;
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$is_svn = true;
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$is_hg = true;
break;
}
$has_branches = ($is_git || $is_hg);
$has_local = $repository->usesLocalWorkingCopy();
$supports_staging = $repository->supportsStaging();
$supports_automation = $repository->supportsAutomation();
$crumbs = $this->buildApplicationCrumbs($is_main = true);
$title = pht('Edit %s', $repository->getName());
$header = id(new PHUIHeaderView())
->setHeader($title)
->setHeaderIcon('fa-pencil');
if ($repository->isTracked()) {
$header->setStatus('fa-check', 'bluegrey', pht('Active'));
} else {
$header->setStatus('fa-ban', 'dark', pht('Inactive'));
}
$curtain = $this->buildCurtain($repository);
$basic_properties = $this->buildBasicProperties($repository);
$policy_actions = $this->buildPolicyActions($repository);
$policy_properties =
$this->buildPolicyProperties($repository, $policy_actions);
$remote_properties = null;
if (!$repository->isHosted()) {
$remote_properties = $this->buildRemoteProperties(
$repository,
$this->buildRemoteActions($repository));
}
$encoding_actions = $this->buildEncodingActions($repository);
$encoding_properties =
$this->buildEncodingProperties($repository, $encoding_actions);
$symbols_actions = $this->buildSymbolsActions($repository);
$symbols_properties =
$this->buildSymbolsProperties($repository, $symbols_actions);
$hosting_properties = $this->buildHostingProperties(
$repository,
$this->buildHostingActions($repository));
$branches_properties = null;
if ($has_branches) {
$branches_properties = $this->buildBranchesProperties(
$repository,
$this->buildBranchesActions($repository));
}
$subversion_properties = null;
if ($is_svn) {
$subversion_properties = $this->buildSubversionProperties(
$repository,
$this->buildSubversionActions($repository));
}
$storage_properties = null;
if ($has_local) {
$storage_properties = $this->buildStorageProperties(
$repository,
$this->buildStorageActions($repository));
}
$staging_properties = null;
if ($supports_staging) {
$staging_properties = $this->buildStagingProperties(
$repository,
$this->buildStagingActions($repository));
}
$automation_properties = null;
if ($supports_automation) {
$automation_properties = $this->buildAutomationProperties(
$repository,
$this->buildAutomationActions($repository));
}
$actions_properties = $this->buildActionsProperties(
$repository,
$this->buildActionsActions($repository));
$timeline = $this->buildTransactionTimeline(
$repository,
new PhabricatorRepositoryTransactionQuery());
$timeline->setShouldTerminate(true);
$boxes = array();
$boxes[] = id(new PHUIObjectBoxView())
->setHeaderText(pht('Policies'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->addPropertyList($policy_properties);
$boxes[] = id(new PHUIObjectBoxView())
->setHeaderText(pht('Hosting'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->addPropertyList($hosting_properties);
if ($repository->canMirror()) {
$mirror_actions = $this->buildMirrorActions($repository);
$mirror_properties = $this->buildMirrorProperties(
$repository,
$mirror_actions);
$mirrors = id(new PhabricatorRepositoryMirrorQuery())
->setViewer($viewer)
->withRepositoryPHIDs(array($repository->getPHID()))
->execute();
$mirror_list = $this->buildMirrorList($repository, $mirrors);
$boxes[] = id(new PhabricatorAnchorView())->setAnchorName('mirrors');
$mirror_info = array();
if (PhabricatorEnv::getEnvConfig('phabricator.silent')) {
$mirror_info[] = pht(
'Phabricator is running in silent mode, so changes will not '.
'be pushed to mirrors.');
}
$boxes[] = id(new PHUIObjectBoxView())
->setFormErrors($mirror_info)
->setHeaderText(pht('Mirrors'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->addPropertyList($mirror_properties);
$boxes[] = $mirror_list;
}
if ($remote_properties) {
$boxes[] = id(new PHUIObjectBoxView())
->setHeaderText(pht('Remote'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->addPropertyList($remote_properties);
}
if ($storage_properties) {
$boxes[] = id(new PHUIObjectBoxView())
->setHeaderText(pht('Storage'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->addPropertyList($storage_properties);
}
if ($staging_properties) {
$boxes[] = id(new PHUIObjectBoxView())
->setHeaderText(pht('Staging'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->addPropertyList($staging_properties);
}
if ($automation_properties) {
$boxes[] = id(new PHUIObjectBoxView())
->setHeaderText(pht('Automation'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->addPropertyList($automation_properties);
}
$boxes[] = id(new PHUIObjectBoxView())
->setHeaderText(pht('Text Encoding'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->addPropertyList($encoding_properties);
$boxes[] = id(new PHUIObjectBoxView())
->setHeaderText(pht('Symbols'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->addPropertyList($symbols_properties);
if ($branches_properties) {
$boxes[] = id(new PHUIObjectBoxView())
->setHeaderText(pht('Branches'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->addPropertyList($branches_properties);
}
if ($subversion_properties) {
$boxes[] = id(new PHUIObjectBoxView())
->setHeaderText(pht('Subversion'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->addPropertyList($subversion_properties);
}
$boxes[] = id(new PHUIObjectBoxView())
->setHeaderText(pht('Actions'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->addPropertyList($actions_properties);
$crumbs->setBorder(true);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setCurtain($curtain)
->addPropertySection(pht('Properties'), $basic_properties)
->setMainColumn(array(
$boxes,
$timeline,
));
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($view);
}
private function buildCurtain(PhabricatorRepository $repository) {
$viewer = $this->getViewer();
$curtain = $this->newCurtainView($repository);
$edit = id(new PhabricatorActionView())
->setIcon('fa-pencil')
->setName(pht('Edit Basic Information'))
->setHref($this->getRepositoryControllerURI($repository, 'edit/basic/'));
$curtain->addAction($edit);
$edit = id(new PhabricatorActionView())
->setIcon('fa-refresh')
->setName(pht('Update Now'))
->setWorkflow(true)
->setHref(
$this->getRepositoryControllerURI($repository, 'edit/update/'));
$curtain->addAction($edit);
$activate = id(new PhabricatorActionView())
->setHref(
$this->getRepositoryControllerURI($repository, 'edit/activate/'))
->setWorkflow(true);
if ($repository->isTracked()) {
$activate
->setIcon('fa-pause')
->setName(pht('Deactivate Repository'));
} else {
$activate
->setIcon('fa-play')
->setName(pht('Activate Repository'));
}
$curtain->addAction($activate);
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Delete Repository'))
->setIcon('fa-times')
->setHref(
$this->getRepositoryControllerURI($repository, 'edit/delete/'))
->setDisabled(true)
->setWorkflow(true));
return $curtain;
}
private function buildBasicProperties(
PhabricatorRepository $repository) {
$viewer = $this->getViewer();
$view = id(new PHUIPropertyListView())
->setUser($viewer);
$type = PhabricatorRepositoryType::getNameForRepositoryType(
$repository->getVersionControlSystem());
$view->addProperty(pht('Type'), $type);
$callsign = $repository->getCallsign();
if (!strlen($callsign)) {
$callsign = phutil_tag('em', array(), pht('No Callsign'));
}
$view->addProperty(pht('Callsign'), $callsign);
$short_name = $repository->getRepositorySlug();
if ($short_name === null) {
$short_name = $repository->getCloneName();
$short_name = phutil_tag('em', array(), $short_name);
}
$view->addProperty(pht('Short Name'), $short_name);
$view->invokeWillRenderEvent();
$view->addProperty(
pht('Status'),
$this->buildRepositoryStatus($repository));
$view->addProperty(
pht('Update Frequency'),
$this->buildRepositoryUpdateInterval($repository));
$description = $repository->getDetail('description');
$view->addSectionHeader(
pht('Description'), PHUIPropertyListView::ICON_SUMMARY);
if (!strlen($description)) {
$description = phutil_tag('em', array(), pht('No description provided.'));
} else {
$description = new PHUIRemarkupView($viewer, $description);
}
$view->addTextContent($description);
return $view;
}
private function buildEncodingActions(PhabricatorRepository $repository) {
$viewer = $this->getViewer();
$view = id(new PhabricatorActionListView())
->setUser($viewer);
$edit = id(new PhabricatorActionView())
->setIcon('fa-pencil')
->setName(pht('Edit Text Encoding'))
->setHref(
$this->getRepositoryControllerURI($repository, 'edit/encoding/'));
$view->addAction($edit);
return $view;
}
private function buildEncodingProperties(
PhabricatorRepository $repository,
PhabricatorActionListView $actions) {
$viewer = $this->getViewer();
$view = id(new PHUIPropertyListView())
->setUser($viewer)
->setActionList($actions);
$encoding = $repository->getDetail('encoding');
if (!$encoding) {
$encoding = phutil_tag('em', array(), pht('Use Default (UTF-8)'));
}
$view->addProperty(pht('Encoding'), $encoding);
return $view;
}
private function buildPolicyActions(PhabricatorRepository $repository) {
$viewer = $this->getViewer();
$view = id(new PhabricatorActionListView())
->setUser($viewer);
$edit = id(new PhabricatorActionView())
->setIcon('fa-pencil')
->setName(pht('Edit Policies'))
->setHref(
$this->getRepositoryControllerURI($repository, 'edit/policy/'));
$view->addAction($edit);
return $view;
}
private function buildPolicyProperties(
PhabricatorRepository $repository,
PhabricatorActionListView $actions) {
$viewer = $this->getViewer();
$view = id(new PHUIPropertyListView())
->setUser($viewer)
->setActionList($actions);
$descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
$viewer,
$repository);
$view_parts = array();
if (PhabricatorSpacesNamespaceQuery::getViewerSpacesExist($viewer)) {
$space_phid = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID(
$repository);
$view_parts[] = $viewer->renderHandle($space_phid);
}
$view_parts[] = $descriptions[PhabricatorPolicyCapability::CAN_VIEW];
$view->addProperty(
pht('Visible To'),
phutil_implode_html(" \xC2\xB7 ", $view_parts));
$view->addProperty(
pht('Editable By'),
$descriptions[PhabricatorPolicyCapability::CAN_EDIT]);
$pushable = $repository->isHosted()
? $descriptions[DiffusionPushCapability::CAPABILITY]
: phutil_tag('em', array(), pht('Not a Hosted Repository'));
$view->addProperty(pht('Pushable By'), $pushable);
return $view;
}
private function buildBranchesActions(PhabricatorRepository $repository) {
$viewer = $this->getViewer();
$view = id(new PhabricatorActionListView())
->setUser($viewer);
$edit = id(new PhabricatorActionView())
->setIcon('fa-pencil')
->setName(pht('Edit Branches'))
->setHref(
$this->getRepositoryControllerURI($repository, 'edit/branches/'));
$view->addAction($edit);
return $view;
}
private function buildBranchesProperties(
PhabricatorRepository $repository,
PhabricatorActionListView $actions) {
$viewer = $this->getViewer();
$view = id(new PHUIPropertyListView())
->setUser($viewer)
->setActionList($actions);
$default_branch = nonempty(
$repository->getHumanReadableDetail('default-branch'),
phutil_tag('em', array(), $repository->getDefaultBranch()));
$view->addProperty(pht('Default Branch'), $default_branch);
$track_only = nonempty(
$repository->getHumanReadableDetail('branch-filter', array()),
phutil_tag('em', array(), pht('Track All Branches')));
$view->addProperty(pht('Track Only'), $track_only);
$autoclose_only = nonempty(
$repository->getHumanReadableDetail('close-commits-filter', array()),
phutil_tag('em', array(), pht('Autoclose On All Branches')));
if ($repository->getDetail('disable-autoclose')) {
$autoclose_only = phutil_tag('em', array(), pht('Disabled'));
}
$view->addProperty(pht('Autoclose Only'), $autoclose_only);
return $view;
}
private function buildSubversionActions(PhabricatorRepository $repository) {
$viewer = $this->getViewer();
$view = id(new PhabricatorActionListView())
->setUser($viewer);
$edit = id(new PhabricatorActionView())
->setIcon('fa-pencil')
->setName(pht('Edit Subversion Info'))
->setHref(
$this->getRepositoryControllerURI($repository, 'edit/subversion/'));
$view->addAction($edit);
return $view;
}
private function buildSubversionProperties(
PhabricatorRepository $repository,
PhabricatorActionListView $actions) {
$viewer = $this->getViewer();
$view = id(new PHUIPropertyListView())
->setUser($viewer)
->setActionList($actions);
$svn_uuid = nonempty(
$repository->getUUID(),
phutil_tag('em', array(), pht('Not Configured')));
$view->addProperty(pht('Subversion UUID'), $svn_uuid);
$svn_subpath = nonempty(
$repository->getHumanReadableDetail('svn-subpath'),
phutil_tag('em', array(), pht('Import Entire Repository')));
$view->addProperty(pht('Import Only'), $svn_subpath);
return $view;
}
private function buildActionsActions(PhabricatorRepository $repository) {
$viewer = $this->getViewer();
$view = id(new PhabricatorActionListView())
->setUser($viewer);
$edit = id(new PhabricatorActionView())
->setIcon('fa-pencil')
->setName(pht('Edit Actions'))
->setHref(
$this->getRepositoryControllerURI($repository, 'edit/actions/'));
$view->addAction($edit);
return $view;
}
private function buildActionsProperties(
PhabricatorRepository $repository,
PhabricatorActionListView $actions) {
$viewer = $this->getViewer();
$view = id(new PHUIPropertyListView())
->setUser($viewer)
->setActionList($actions);
$notify = $repository->getDetail('herald-disabled')
? pht('Off')
: pht('On');
$notify = phutil_tag('em', array(), $notify);
$view->addProperty(pht('Publish/Notify'), $notify);
$autoclose = $repository->getDetail('disable-autoclose')
? pht('Off')
: pht('On');
$autoclose = phutil_tag('em', array(), $autoclose);
$view->addProperty(pht('Autoclose'), $autoclose);
return $view;
}
private function buildRemoteActions(PhabricatorRepository $repository) {
$viewer = $this->getViewer();
$view = id(new PhabricatorActionListView())
->setUser($viewer);
$edit = id(new PhabricatorActionView())
->setIcon('fa-pencil')
->setName(pht('Edit Remote'))
->setHref(
$this->getRepositoryControllerURI($repository, 'edit/remote/'));
$view->addAction($edit);
return $view;
}
private function buildRemoteProperties(
PhabricatorRepository $repository,
PhabricatorActionListView $actions) {
$viewer = $this->getViewer();
$view = id(new PHUIPropertyListView())
->setUser($viewer)
->setActionList($actions);
$view->addProperty(
pht('Remote URI'),
$repository->getHumanReadableDetail('remote-uri'));
$credential_phid = $repository->getCredentialPHID();
if ($credential_phid) {
$view->addProperty(
pht('Credential'),
$viewer->renderHandle($credential_phid));
}
return $view;
}
private function buildStorageActions(PhabricatorRepository $repository) {
$viewer = $this->getViewer();
$view = id(new PhabricatorActionListView())
->setUser($viewer);
$edit = id(new PhabricatorActionView())
->setIcon('fa-pencil')
->setName(pht('Edit Storage'))
->setHref(
$this->getRepositoryControllerURI($repository, 'edit/storage/'));
$view->addAction($edit);
return $view;
}
private function buildStorageProperties(
PhabricatorRepository $repository,
PhabricatorActionListView $actions) {
$viewer = $this->getViewer();
$view = id(new PHUIPropertyListView())
->setUser($viewer)
->setActionList($actions);
$service_phid = $repository->getAlmanacServicePHID();
if ($service_phid) {
$v_service = $viewer->renderHandle($service_phid);
} else {
$v_service = phutil_tag(
'em',
array(),
pht('Local'));
}
$view->addProperty(
pht('Storage Service'),
$v_service);
- $view->addProperty(
- pht('Storage Path'),
- $repository->getHumanReadableDetail('local-path'));
+ $view->addProperty(pht('Storage Path'), $repository->getLocalPath());
return $view;
}
private function buildStagingActions(PhabricatorRepository $repository) {
$viewer = $this->getViewer();
$view = id(new PhabricatorActionListView())
->setUser($viewer);
$edit = id(new PhabricatorActionView())
->setIcon('fa-pencil')
->setName(pht('Edit Staging'))
->setHref(
$this->getRepositoryControllerURI($repository, 'edit/staging/'));
$view->addAction($edit);
return $view;
}
private function buildStagingProperties(
PhabricatorRepository $repository,
PhabricatorActionListView $actions) {
$viewer = $this->getViewer();
$view = id(new PHUIPropertyListView())
->setUser($viewer)
->setActionList($actions);
$staging_uri = $repository->getStagingURI();
if (!$staging_uri) {
$staging_uri = phutil_tag('em', array(), pht('No Staging Area'));
}
$view->addProperty(
pht('Staging Area'),
$staging_uri);
return $view;
}
private function buildAutomationActions(PhabricatorRepository $repository) {
$viewer = $this->getViewer();
$view = id(new PhabricatorActionListView())
->setUser($viewer);
$edit = id(new PhabricatorActionView())
->setIcon('fa-pencil')
->setName(pht('Edit Automation'))
->setHref(
$this->getRepositoryControllerURI($repository, 'edit/automation/'));
$view->addAction($edit);
$can_test = $repository->canPerformAutomation();
$test = id(new PhabricatorActionView())
->setIcon('fa-gamepad')
->setName(pht('Test Configuration'))
->setWorkflow(true)
->setDisabled(!$can_test)
->setHref(
$this->getRepositoryControllerURI(
$repository,
'edit/testautomation/'));
$view->addAction($test);
return $view;
}
private function buildAutomationProperties(
PhabricatorRepository $repository,
PhabricatorActionListView $actions) {
$viewer = $this->getViewer();
$view = id(new PHUIPropertyListView())
->setUser($viewer)
->setActionList($actions);
$blueprint_phids = $repository->getAutomationBlueprintPHIDs();
if (!$blueprint_phids) {
$blueprint_view = phutil_tag('em', array(), pht('Not Configured'));
} else {
$blueprint_view = id(new DrydockObjectAuthorizationView())
->setUser($viewer)
->setObjectPHID($repository->getPHID())
->setBlueprintPHIDs($blueprint_phids);
}
$view->addProperty(pht('Automation'), $blueprint_view);
return $view;
}
private function buildHostingActions(PhabricatorRepository $repository) {
$user = $this->getRequest()->getUser();
$view = id(new PhabricatorActionListView())
->setUser($user);
$edit = id(new PhabricatorActionView())
->setIcon('fa-pencil')
->setName(pht('Edit Hosting'))
->setHref(
$this->getRepositoryControllerURI($repository, 'edit/hosting/'));
$view->addAction($edit);
if ($repository->canAllowDangerousChanges()) {
if ($repository->shouldAllowDangerousChanges()) {
$changes = id(new PhabricatorActionView())
->setIcon('fa-shield')
->setName(pht('Prevent Dangerous Changes'))
->setHref(
$this->getRepositoryControllerURI($repository, 'edit/dangerous/'))
->setWorkflow(true);
} else {
$changes = id(new PhabricatorActionView())
->setIcon('fa-bullseye')
->setName(pht('Allow Dangerous Changes'))
->setHref(
$this->getRepositoryControllerURI($repository, 'edit/dangerous/'))
->setWorkflow(true);
}
$view->addAction($changes);
}
return $view;
}
private function buildHostingProperties(
PhabricatorRepository $repository,
PhabricatorActionListView $actions) {
$user = $this->getRequest()->getUser();
$view = id(new PHUIPropertyListView())
->setUser($user)
->setActionList($actions);
$hosting = $repository->isHosted()
? pht('Hosted on Phabricator')
: pht('Hosted Elsewhere');
$view->addProperty(pht('Hosting'), phutil_tag('em', array(), $hosting));
$view->addProperty(
pht('Serve over HTTP'),
phutil_tag(
'em',
array(),
PhabricatorRepository::getProtocolAvailabilityName(
$repository->getServeOverHTTP())));
$view->addProperty(
pht('Serve over SSH'),
phutil_tag(
'em',
array(),
PhabricatorRepository::getProtocolAvailabilityName(
$repository->getServeOverSSH())));
if ($repository->canAllowDangerousChanges()) {
if ($repository->shouldAllowDangerousChanges()) {
$description = pht('Allowed');
} else {
$description = pht('Not Allowed');
}
$view->addProperty(
pht('Dangerous Changes'),
$description);
}
return $view;
}
private function buildRepositoryStatus(
PhabricatorRepository $repository) {
$viewer = $this->getViewer();
$is_cluster = $repository->getAlmanacServicePHID();
$view = new PHUIStatusListView();
$messages = id(new PhabricatorRepositoryStatusMessage())
->loadAllWhere('repositoryID = %d', $repository->getID());
$messages = mpull($messages, null, 'getStatusType');
if ($repository->isTracked()) {
$view->addItem(
id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')
->setTarget(pht('Repository Active')));
} else {
$view->addItem(
id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_WARNING, 'bluegrey')
->setTarget(pht('Repository Inactive'))
->setNote(
pht('Activate this repository to begin or resume import.')));
return $view;
}
$binaries = array();
$svnlook_check = false;
switch ($repository->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$binaries[] = 'git';
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$binaries[] = 'svn';
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$binaries[] = 'hg';
break;
}
if ($repository->isHosted()) {
if ($repository->getServeOverHTTP() != PhabricatorRepository::SERVE_OFF) {
switch ($repository->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$binaries[] = 'git-http-backend';
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$binaries[] = 'svnserve';
$binaries[] = 'svnadmin';
$binaries[] = 'svnlook';
$svnlook_check = true;
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$binaries[] = 'hg';
break;
}
}
if ($repository->getServeOverSSH() != PhabricatorRepository::SERVE_OFF) {
switch ($repository->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$binaries[] = 'git-receive-pack';
$binaries[] = 'git-upload-pack';
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$binaries[] = 'svnserve';
$binaries[] = 'svnadmin';
$binaries[] = 'svnlook';
$svnlook_check = true;
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$binaries[] = 'hg';
break;
}
}
}
$binaries = array_unique($binaries);
if (!$is_cluster) {
// We're only checking for binaries if we aren't running with a cluster
// configuration. In theory, we could check for binaries on the
// repository host machine, but we'd need to make this more complicated
// to do that.
foreach ($binaries as $binary) {
$where = Filesystem::resolveBinary($binary);
if (!$where) {
$view->addItem(
id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')
->setTarget(
pht('Missing Binary %s', phutil_tag('tt', array(), $binary)))
->setNote(pht(
"Unable to find this binary in the webserver's PATH. You may ".
"need to configure %s.",
$this->getEnvConfigLink())));
} else {
$view->addItem(
id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')
->setTarget(
pht('Found Binary %s', phutil_tag('tt', array(), $binary)))
->setNote(phutil_tag('tt', array(), $where)));
}
}
// This gets checked generically above. However, for svn commit hooks, we
// need this to be in environment.append-paths because subversion strips
// PATH.
if ($svnlook_check) {
$where = Filesystem::resolveBinary('svnlook');
if ($where) {
$path = substr($where, 0, strlen($where) - strlen('svnlook'));
$dirs = PhabricatorEnv::getEnvConfig('environment.append-paths');
$in_path = false;
foreach ($dirs as $dir) {
if (Filesystem::isDescendant($path, $dir)) {
$in_path = true;
break;
}
}
if (!$in_path) {
$view->addItem(
id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')
->setTarget(
pht('Missing Binary %s', phutil_tag('tt', array(), $binary)))
->setNote(pht(
'Unable to find this binary in `%s`. '.
'You need to configure %s and include %s.',
'environment.append-paths',
$this->getEnvConfigLink(),
$path)));
}
}
}
}
$doc_href = PhabricatorEnv::getDocLink('Managing Daemons with phd');
$daemon_instructions = pht(
'Use %s to start daemons. See %s.',
phutil_tag('tt', array(), 'bin/phd start'),
phutil_tag(
'a',
array(
'href' => $doc_href,
),
pht('Managing Daemons with phd')));
$pull_daemon = id(new PhabricatorDaemonLogQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE)
->withDaemonClasses(array('PhabricatorRepositoryPullLocalDaemon'))
->setLimit(1)
->execute();
if ($pull_daemon) {
// TODO: In a cluster environment, we need a daemon on this repository's
// host, specifically, and we aren't checking for that right now. This
// is a reasonable proxy for things being more-or-less correctly set up,
// though.
$view->addItem(
id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')
->setTarget(pht('Pull Daemon Running')));
} else {
$view->addItem(
id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')
->setTarget(pht('Pull Daemon Not Running'))
->setNote($daemon_instructions));
}
$task_daemon = id(new PhabricatorDaemonLogQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE)
->withDaemonClasses(array('PhabricatorTaskmasterDaemon'))
->setLimit(1)
->execute();
if ($task_daemon) {
$view->addItem(
id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')
->setTarget(pht('Task Daemon Running')));
} else {
$view->addItem(
id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')
->setTarget(pht('Task Daemon Not Running'))
->setNote($daemon_instructions));
}
if ($is_cluster) {
// Just omit this status check for now in cluster environments. We
// could make a service call and pull it from the repository host
// eventually.
} else if ($repository->usesLocalWorkingCopy()) {
$local_parent = dirname($repository->getLocalPath());
if (Filesystem::pathExists($local_parent)) {
$view->addItem(
id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')
->setTarget(pht('Storage Directory OK'))
->setNote(phutil_tag('tt', array(), $local_parent)));
} else {
$view->addItem(
id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')
->setTarget(pht('No Storage Directory'))
->setNote(
pht(
'Storage directory %s does not exist, or is not readable by '.
'the webserver. Create this directory or make it readable.',
phutil_tag('tt', array(), $local_parent))));
return $view;
}
$local_path = $repository->getLocalPath();
$message = idx($messages, PhabricatorRepositoryStatusMessage::TYPE_INIT);
if ($message) {
switch ($message->getStatusCode()) {
case PhabricatorRepositoryStatusMessage::CODE_ERROR:
$view->addItem(
id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')
->setTarget(pht('Initialization Error'))
->setNote($message->getParameter('message')));
return $view;
case PhabricatorRepositoryStatusMessage::CODE_OKAY:
if (Filesystem::pathExists($local_path)) {
$view->addItem(
id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')
->setTarget(pht('Working Copy OK'))
->setNote(phutil_tag('tt', array(), $local_path)));
} else {
$view->addItem(
id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')
->setTarget(pht('Working Copy Error'))
->setNote(
pht(
'Working copy %s has been deleted, or is not '.
'readable by the webserver. Make this directory '.
'readable. If it has been deleted, the daemons should '.
'restore it automatically.',
phutil_tag('tt', array(), $local_path))));
return $view;
}
break;
case PhabricatorRepositoryStatusMessage::CODE_WORKING:
$view->addItem(
id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_CLOCK, 'green')
->setTarget(pht('Initializing Working Copy'))
->setNote(pht('Daemons are initializing the working copy.')));
return $view;
default:
$view->addItem(
id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')
->setTarget(pht('Unknown Init Status'))
->setNote($message->getStatusCode()));
return $view;
}
} else {
$view->addItem(
id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_CLOCK, 'orange')
->setTarget(pht('No Working Copy Yet'))
->setNote(
pht('Waiting for daemons to build a working copy.')));
return $view;
}
}
$message = idx($messages, PhabricatorRepositoryStatusMessage::TYPE_FETCH);
if ($message) {
switch ($message->getStatusCode()) {
case PhabricatorRepositoryStatusMessage::CODE_ERROR:
$message = $message->getParameter('message');
$suggestion = null;
if (preg_match('/Permission denied \(publickey\)./', $message)) {
$suggestion = pht(
'Public Key Error: This error usually indicates that the '.
'keypair you have configured does not have permission to '.
'access the repository.');
}
$message = phutil_escape_html_newlines($message);
if ($suggestion !== null) {
$message = array(
phutil_tag('strong', array(), $suggestion),
phutil_tag('br'),
phutil_tag('br'),
phutil_tag('em', array(), pht('Raw Error')),
phutil_tag('br'),
$message,
);
}
$view->addItem(
id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')
->setTarget(pht('Update Error'))
->setNote($message));
return $view;
case PhabricatorRepositoryStatusMessage::CODE_OKAY:
$ago = (PhabricatorTime::getNow() - $message->getEpoch());
$view->addItem(
id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')
->setTarget(pht('Updates OK'))
->setNote(
pht(
'Last updated %s (%s ago).',
phabricator_datetime($message->getEpoch(), $viewer),
phutil_format_relative_time_detailed($ago))));
break;
}
} else {
$view->addItem(
id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_CLOCK, 'orange')
->setTarget(pht('Waiting For Update'))
->setNote(
pht('Waiting for daemons to read updates.')));
}
if ($repository->isImporting()) {
$ratio = $repository->loadImportProgress();
$percentage = sprintf('%.2f%%', 100 * $ratio);
$view->addItem(
id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_CLOCK, 'green')
->setTarget(pht('Importing'))
->setNote(
pht('%s Complete', $percentage)));
} else {
$view->addItem(
id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')
->setTarget(pht('Fully Imported')));
}
if (idx($messages, PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE)) {
$view->addItem(
id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_UP, 'indigo')
->setTarget(pht('Prioritized'))
->setNote(pht('This repository will be updated soon!')));
}
return $view;
}
private function buildRepositoryUpdateInterval(
PhabricatorRepository $repository) {
$smart_wait = $repository->loadUpdateInterval();
$doc_href = PhabricatorEnv::getDoclink(
'Diffusion User Guide: Repository Updates');
return array(
phutil_format_relative_time_detailed($smart_wait),
" \xC2\xB7 ",
phutil_tag(
'a',
array(
'href' => $doc_href,
'target' => '_blank',
),
pht('Learn More')),
);
}
private function buildMirrorActions(
PhabricatorRepository $repository) {
$viewer = $this->getViewer();
$mirror_actions = id(new PhabricatorActionListView())
->setUser($viewer);
$new_mirror_uri = $this->getRepositoryControllerURI(
$repository,
'mirror/edit/');
$mirror_actions->addAction(
id(new PhabricatorActionView())
->setName(pht('Add Mirror'))
->setIcon('fa-plus')
->setHref($new_mirror_uri)
->setWorkflow(true));
return $mirror_actions;
}
private function buildMirrorProperties(
PhabricatorRepository $repository,
PhabricatorActionListView $actions) {
$viewer = $this->getViewer();
$mirror_properties = id(new PHUIPropertyListView())
->setUser($viewer)
->setActionList($actions);
$mirror_properties->addProperty(
'',
phutil_tag(
'em',
array(),
pht('Automatically push changes into other remotes.')));
return $mirror_properties;
}
private function buildMirrorList(
PhabricatorRepository $repository,
array $mirrors) {
assert_instances_of($mirrors, 'PhabricatorRepositoryMirror');
$mirror_list = id(new PHUIObjectItemListView())
->setNoDataString(pht('This repository has no configured mirrors.'));
foreach ($mirrors as $mirror) {
$item = id(new PHUIObjectItemView())
->setHeader($mirror->getRemoteURI());
$edit_uri = $this->getRepositoryControllerURI(
$repository,
'mirror/edit/'.$mirror->getID().'/');
$delete_uri = $this->getRepositoryControllerURI(
$repository,
'mirror/delete/'.$mirror->getID().'/');
$item->addAction(
id(new PHUIListItemView())
->setIcon('fa-pencil')
->setHref($edit_uri)
->setWorkflow(true));
$item->addAction(
id(new PHUIListItemView())
->setIcon('fa-times')
->setHref($delete_uri)
->setWorkflow(true));
$mirror_list->addItem($item);
}
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Configured Mirrors'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setObjectList($mirror_list);
}
private function buildSymbolsActions(PhabricatorRepository $repository) {
$viewer = $this->getViewer();
$view = id(new PhabricatorActionListView())
->setUser($viewer);
$edit = id(new PhabricatorActionView())
->setIcon('fa-pencil')
->setName(pht('Edit Symbols'))
->setHref(
$this->getRepositoryControllerURI($repository, 'edit/symbol/'));
$view->addAction($edit);
return $view;
}
private function buildSymbolsProperties(
PhabricatorRepository $repository,
PhabricatorActionListView $actions) {
$viewer = $this->getViewer();
$view = id(new PHUIPropertyListView())
->setUser($viewer)
->setActionList($actions);
$languages = $repository->getSymbolLanguages();
if ($languages) {
$languages = implode(', ', $languages);
} else {
$languages = phutil_tag('em', array(), pht('Any'));
}
$view->addProperty(pht('Languages'), $languages);
$sources = $repository->getSymbolSources();
if ($sources) {
$handles = $viewer->loadHandles($sources);
$sources = $handles->renderList();
} else {
$sources = phutil_tag('em', array(), pht('This Repository Only'));
}
$view->addProperty(pht('Use Symbols From'), $sources);
return $view;
}
private function getEnvConfigLink() {
$config_href = '/config/edit/environment.append-paths/';
return phutil_tag(
'a',
array(
'href' => $config_href,
),
'environment.append-paths');
}
}
diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php
index f3a492e3b3..6aebd13500 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php
@@ -1,80 +1,76 @@
<?php
final class DiffusionRepositoryEditStorageController
extends DiffusionRepositoryEditController {
public function handleRequest(AphrontRequest $request) {
$response = $this->loadDiffusionContextForEdit();
if ($response) {
return $response;
}
$viewer = $this->getViewer();
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
$edit_uri = $this->getRepositoryControllerURI($repository, 'edit/');
- $v_local = $repository->getHumanReadableDetail('local-path');
+ $v_local = $repository->getLocalPath();
$errors = array();
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Edit Storage'));
$title = pht('Edit %s', $repository->getName());
$header = id(new PHUIHeaderView())
->setHeader($title)
->setHeaderIcon('fa-pencil');
$service_phid = $repository->getAlmanacServicePHID();
if ($service_phid) {
$handles = $this->loadViewerHandles(array($service_phid));
$v_service = $handles[$service_phid]->renderLink();
} else {
$v_service = phutil_tag(
'em',
array(),
pht('Local'));
}
$form = id(new AphrontFormView())
->setUser($viewer)
->appendChild(
id(new AphrontFormMarkupControl())
->setLabel(pht('Storage Service'))
->setValue($v_service))
->appendChild(
id(new AphrontFormMarkupControl())
->setName('local')
->setLabel(pht('Storage Path'))
->setValue($v_local))
->appendRemarkupInstructions(
pht(
"You can not adjust the local path for this repository from the ".
- "web interface. To edit it, run this command:\n\n %s",
- sprintf(
- 'phabricator/ $ ./bin/repository edit %s --as %s --local-path ...',
- $repository->getMonogram(),
- $viewer->getUsername())))
+ 'web interface.'))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($edit_uri, pht('Done')));
$form_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Storage'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setForm($form);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter(array(
$form_box,
));
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($view);
}
}
diff --git a/src/applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php
index 734b4c6f8e..0c723bd9e6 100644
--- a/src/applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php
+++ b/src/applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php
@@ -1,235 +1,235 @@
<?php
final class DiffusionRepositoryStorageManagementPanel
extends DiffusionRepositoryManagementPanel {
const PANELKEY = 'storage';
public function getManagementPanelLabel() {
return pht('Storage');
}
public function getManagementPanelOrder() {
return 600;
}
public function getManagementPanelIcon() {
$repository = $this->getRepository();
if ($repository->getAlmanacServicePHID()) {
return 'fa-sitemap';
} else if ($repository->isHosted()) {
return 'fa-folder';
} else {
return 'fa-download';
}
}
public function buildManagementPanelContent() {
return array(
$this->buildStorageStatusPanel(),
$this->buildClusterStatusPanel(),
);
}
private function buildStorageStatusPanel() {
$repository = $this->getRepository();
$viewer = $this->getViewer();
$view = id(new PHUIPropertyListView())
->setViewer($viewer);
if ($repository->usesLocalWorkingCopy()) {
- $storage_path = $repository->getHumanReadableDetail('local-path');
+ $storage_path = $repository->getLocalPath();
} else {
$storage_path = phutil_tag('em', array(), pht('No Local Working Copy'));
}
$service_phid = $repository->getAlmanacServicePHID();
if ($service_phid) {
$storage_service = $viewer->renderHandle($service_phid);
} else {
$storage_service = phutil_tag('em', array(), pht('Local'));
}
$view->addProperty(pht('Storage Path'), $storage_path);
$view->addProperty(pht('Storage Cluster'), $storage_service);
$header = id(new PHUIHeaderView())
->setHeader(pht('Storage'));
return id(new PHUIObjectBoxView())
->setHeader($header)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->addPropertyList($view);
}
private function buildClusterStatusPanel() {
$repository = $this->getRepository();
$viewer = $this->getViewer();
$service_phid = $repository->getAlmanacServicePHID();
if ($service_phid) {
$service = id(new AlmanacServiceQuery())
->setViewer($viewer)
->withServiceTypes(
array(
AlmanacClusterRepositoryServiceType::SERVICETYPE,
))
->withPHIDs(array($service_phid))
->needBindings(true)
->executeOne();
if (!$service) {
// TODO: Viewer may not have permission to see the service, or it may
// be invalid? Raise some more useful error here?
throw new Exception(pht('Unable to load cluster service.'));
}
} else {
$service = null;
}
Javelin::initBehavior('phabricator-tooltips');
$rows = array();
if ($service) {
$bindings = $service->getBindings();
$bindings = mgroup($bindings, 'getDevicePHID');
// This is an unusual read which always comes from the master.
if (PhabricatorEnv::isReadOnly()) {
$versions = array();
} else {
$versions = PhabricatorRepositoryWorkingCopyVersion::loadVersions(
$repository->getPHID());
}
$versions = mpull($versions, null, 'getDevicePHID');
foreach ($bindings as $binding_group) {
$all_disabled = true;
foreach ($binding_group as $binding) {
if (!$binding->getIsDisabled()) {
$all_disabled = false;
break;
}
}
$any_binding = head($binding_group);
if ($all_disabled) {
$binding_icon = 'fa-times grey';
$binding_tip = pht('Disabled');
} else {
$binding_icon = 'fa-folder-open green';
$binding_tip = pht('Active');
}
$binding_icon = id(new PHUIIconView())
->setIcon($binding_icon)
->addSigil('has-tooltip')
->setMetadata(
array(
'tip' => $binding_tip,
));
$device = $any_binding->getDevice();
$version = idx($versions, $device->getPHID());
if ($version) {
$version_number = $version->getRepositoryVersion();
$version_number = phutil_tag(
'a',
array(
'href' => "/diffusion/pushlog/view/{$version_number}/",
),
$version_number);
} else {
$version_number = '-';
}
if ($version && $version->getIsWriting()) {
$is_writing = id(new PHUIIconView())
->setIcon('fa-pencil green');
} else {
$is_writing = id(new PHUIIconView())
->setIcon('fa-pencil grey');
}
$write_properties = null;
if ($version) {
$write_properties = $version->getWriteProperties();
if ($write_properties) {
try {
$write_properties = phutil_json_decode($write_properties);
} catch (Exception $ex) {
$write_properties = null;
}
}
}
if ($write_properties) {
$writer_phid = idx($write_properties, 'userPHID');
$last_writer = $viewer->renderHandle($writer_phid);
$writer_epoch = idx($write_properties, 'epoch');
$writer_epoch = phabricator_datetime($writer_epoch, $viewer);
} else {
$last_writer = null;
$writer_epoch = null;
}
$rows[] = array(
$binding_icon,
phutil_tag(
'a',
array(
'href' => $device->getURI(),
),
$device->getName()),
$version_number,
$is_writing,
$last_writer,
$writer_epoch,
);
}
}
$table = id(new AphrontTableView($rows))
->setNoDataString(pht('This is not a cluster repository.'))
->setHeaders(
array(
null,
pht('Device'),
pht('Version'),
pht('Writing'),
pht('Last Writer'),
pht('Last Write At'),
))
->setColumnClasses(
array(
null,
null,
null,
'right wide',
null,
'date',
));
$doc_href = PhabricatorEnv::getDoclink('Cluster: Repositories');
$header = id(new PHUIHeaderView())
->setHeader(pht('Cluster Status'))
->addActionLink(
id(new PHUIButtonView())
->setIcon('fa-book')
->setHref($doc_href)
->setTag('a')
->setText(pht('Documentation')));
return id(new PHUIObjectBoxView())
->setHeader($header)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setTable($table);
}
}
diff --git a/src/applications/repository/conduit/RepositoryCreateConduitAPIMethod.php b/src/applications/repository/conduit/RepositoryCreateConduitAPIMethod.php
index ad4b62104e..bec9b3b9a3 100644
--- a/src/applications/repository/conduit/RepositoryCreateConduitAPIMethod.php
+++ b/src/applications/repository/conduit/RepositoryCreateConduitAPIMethod.php
@@ -1,140 +1,141 @@
<?php
final class RepositoryCreateConduitAPIMethod
extends RepositoryConduitAPIMethod {
public function getAPIMethodName() {
return 'repository.create';
}
public function getMethodStatus() {
return self::METHOD_STATUS_UNSTABLE;
}
public function getMethodStatusDescription() {
return pht('Repository methods are new and subject to change.');
}
public function getMethodDescription() {
return pht('Create a new repository.');
}
protected function defineParamTypes() {
$vcs_const = $this->formatStringConstants(array('git', 'hg', 'svn'));
return array(
'name' => 'required string',
'vcs' => 'required '.$vcs_const,
'callsign' => 'required string',
'description' => 'optional string',
'encoding' => 'optional string',
'tracking' => 'optional bool',
'uri' => 'required string',
'credentialPHID' => 'optional string',
'svnSubpath' => 'optional string',
'branchFilter' => 'optional list<string>',
'closeCommitsFilter' => 'optional list<string>',
'pullFrequency' => 'optional int',
'defaultBranch' => 'optional string',
'heraldEnabled' => 'optional bool, default = true',
'autocloseEnabled' => 'optional bool, default = true',
'svnUUID' => 'optional string',
);
}
protected function defineReturnType() {
return 'nonempty dict';
}
protected function defineErrorTypes() {
return array(
'ERR-DUPLICATE' => pht('Duplicate repository callsign.'),
'ERR-BAD-CALLSIGN' => pht(
'Callsign is required and must be ALL UPPERCASE LETTERS.'),
'ERR-UNKNOWN-REPOSITORY-VCS' => pht('Unknown repository VCS type.'),
);
}
protected function execute(ConduitAPIRequest $request) {
$application = id(new PhabricatorApplicationQuery())
->setViewer($request->getUser())
->withClasses(array('PhabricatorDiffusionApplication'))
->executeOne();
PhabricatorPolicyFilter::requireCapability(
$request->getUser(),
$application,
DiffusionCreateRepositoriesCapability::CAPABILITY);
// TODO: This has some duplication with (and lacks some of the validation
// of) the web workflow; refactor things so they can share more code as this
// stabilizes. Specifically, this should move to transactions since they
// work properly now.
$repository = PhabricatorRepository::initializeNewRepository(
$request->getUser());
$repository->setName($request->getValue('name'));
$callsign = $request->getValue('callsign');
if (!preg_match('/^[A-Z]+\z/', $callsign)) {
throw new ConduitException('ERR-BAD-CALLSIGN');
}
$repository->setCallsign($callsign);
$local_path = PhabricatorEnv::getEnvConfig(
'repository.default-local-path');
$local_path = rtrim($local_path, '/');
$local_path = $local_path.'/'.$callsign.'/';
$vcs = $request->getValue('vcs');
$map = array(
'git' => PhabricatorRepositoryType::REPOSITORY_TYPE_GIT,
'hg' => PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL,
'svn' => PhabricatorRepositoryType::REPOSITORY_TYPE_SVN,
);
if (empty($map[$vcs])) {
throw new ConduitException('ERR-UNKNOWN-REPOSITORY-VCS');
}
$repository->setVersionControlSystem($map[$vcs]);
$repository->setCredentialPHID($request->getValue('credentialPHID'));
$remote_uri = $request->getValue('uri');
PhabricatorRepository::assertValidRemoteURI($remote_uri);
$details = array(
'encoding' => $request->getValue('encoding'),
'description' => $request->getValue('description'),
'tracking-enabled' => (bool)$request->getValue('tracking', true),
'remote-uri' => $remote_uri,
- 'local-path' => $local_path,
'branch-filter' => array_fill_keys(
$request->getValue('branchFilter', array()),
true),
'close-commits-filter' => array_fill_keys(
$request->getValue('closeCommitsFilter', array()),
true),
'pull-frequency' => $request->getValue('pullFrequency'),
'default-branch' => $request->getValue('defaultBranch'),
'herald-disabled' => !$request->getValue('heraldEnabled', true),
'svn-subpath' => $request->getValue('svnSubpath'),
'disable-autoclose' => !$request->getValue('autocloseEnabled', true),
);
foreach ($details as $key => $value) {
$repository->setDetail($key, $value);
}
+ $repository->setLocalPath($local_path);
+
try {
$repository->save();
} catch (AphrontDuplicateKeyQueryException $ex) {
throw new ConduitException('ERR-DUPLICATE');
}
return $repository->toDictionary();
}
}
diff --git a/src/applications/repository/editor/PhabricatorRepositoryEditor.php b/src/applications/repository/editor/PhabricatorRepositoryEditor.php
index 3dc842d94f..c528c91c99 100644
--- a/src/applications/repository/editor/PhabricatorRepositoryEditor.php
+++ b/src/applications/repository/editor/PhabricatorRepositoryEditor.php
@@ -1,733 +1,733 @@
<?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_SLUG;
$types[] = PhabricatorRepositoryTransaction::TYPE_SERVICE;
$types[] = PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE;
$types[] = PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES;
$types[] = PhabricatorRepositoryTransaction::TYPE_STAGING_URI;
$types[] = PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS;
$types[] = PhabricatorRepositoryTransaction::TYPE_CALLSIGN;
$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');
+ return $object->getLocalPath();
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_SLUG:
return $object->getRepositorySlug();
case PhabricatorRepositoryTransaction::TYPE_SERVICE:
return $object->getAlmanacServicePHID();
case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE:
return $object->getSymbolLanguages();
case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES:
return $object->getSymbolSources();
case PhabricatorRepositoryTransaction::TYPE_STAGING_URI:
return $object->getDetail('staging-uri');
case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS:
return $object->getDetail('automation.blueprintPHIDs', array());
case PhabricatorRepositoryTransaction::TYPE_CALLSIGN:
return $object->getCallsign();
}
}
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_SERVICE:
case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE:
case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES:
case PhabricatorRepositoryTransaction::TYPE_STAGING_URI:
case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS:
return $xaction->getNewValue();
case PhabricatorRepositoryTransaction::TYPE_SLUG:
case PhabricatorRepositoryTransaction::TYPE_CALLSIGN:
$name = $xaction->getNewValue();
if (strlen($name)) {
return $name;
}
return null;
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());
+ $object->setLocalPath($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_SLUG:
$object->setRepositorySlug($xaction->getNewValue());
return;
case PhabricatorRepositoryTransaction::TYPE_SERVICE:
$object->setAlmanacServicePHID($xaction->getNewValue());
return;
case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE:
$object->setDetail('symbol-languages', $xaction->getNewValue());
return;
case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES:
$object->setDetail('symbol-sources', $xaction->getNewValue());
return;
case PhabricatorRepositoryTransaction::TYPE_STAGING_URI:
$object->setDetail('staging-uri', $xaction->getNewValue());
return;
case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS:
$object->setDetail(
'automation.blueprintPHIDs',
$xaction->getNewValue());
return;
case PhabricatorRepositoryTransaction::TYPE_CALLSIGN:
$object->setCallsign($xaction->getNewValue());
return;
case PhabricatorRepositoryTransaction::TYPE_ENCODING:
$object->setDetail('encoding', $xaction->getNewValue());
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 = PhabricatorObjectUsesCredentialsEdgeType::EDGECONST;
$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;
case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS:
DrydockAuthorization::applyAuthorizationChanges(
$this->getActor(),
$object->getPHID(),
$xaction->getOldValue(),
$xaction->getNewValue());
break;
}
}
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_SLUG:
case PhabricatorRepositoryTransaction::TYPE_SERVICE:
case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES:
case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE:
case PhabricatorRepositoryTransaction::TYPE_STAGING_URI:
case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS:
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_ONLY:
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;
case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS:
foreach ($xactions as $xaction) {
$old = nonempty($xaction->getOldValue(), array());
$new = nonempty($xaction->getNewValue(), array());
$add = array_diff($new, $old);
$invalid = PhabricatorObjectQuery::loadInvalidPHIDsForViewer(
$this->getActor(),
$add);
if ($invalid) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht(
'Some of the selected automation blueprints are invalid '.
'or restricted: %s.',
implode(', ', $invalid)),
$xaction);
}
}
break;
case PhabricatorRepositoryTransaction::TYPE_VCS:
$vcs_map = PhabricatorRepositoryType::getAllRepositoryTypes();
$current_vcs = $object->getVersionControlSystem();
if (!$this->getIsNewObject()) {
foreach ($xactions as $xaction) {
if ($xaction->getNewValue() == $current_vcs) {
continue;
}
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Immutable'),
pht(
'You can not change the version control system an existing '.
'repository uses. It can only be set when a repository is '.
'first created.'),
$xaction);
}
} else {
$value = $object->getVersionControlSystem();
foreach ($xactions as $xaction) {
$value = $xaction->getNewValue();
if (empty($vcs_map[$value])) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht(
'Specified version control system must be a VCS '.
'recognized by Phabricator: %s.',
implode(', ', array_keys($vcs_map))),
$xaction);
}
}
if (!strlen($value)) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Required'),
pht(
'When creating a repository, you must specify a valid '.
'underlying version control system: %s.',
implode(', ', array_keys($vcs_map))),
nonempty(last($xactions), null));
$error->setIsMissingFieldError(true);
$errors[] = $error;
}
}
break;
case PhabricatorRepositoryTransaction::TYPE_NAME:
$missing = $this->validateIsEmptyTextField(
$object->getName(),
$xactions);
if ($missing) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Required'),
pht('Repository name is required.'),
nonempty(last($xactions), null));
$error->setIsMissingFieldError(true);
$errors[] = $error;
}
break;
case PhabricatorRepositoryTransaction::TYPE_ACTIVATE:
$status_map = PhabricatorRepository::getStatusMap();
foreach ($xactions as $xaction) {
$status = $xaction->getNewValue();
if (empty($status_map[$status])) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht(
'Repository status "%s" is not valid.',
$status),
$xaction);
}
}
break;
case PhabricatorRepositoryTransaction::TYPE_ENCODING:
foreach ($xactions as $xaction) {
// 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)) {
continue;
}
try {
phutil_utf8_convert('.', $encoding, 'UTF-8');
} catch (Exception $ex) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht(
'Repository encoding "%s" is not valid: %s',
$encoding,
$ex->getMessage()),
$xaction);
}
}
break;
case PhabricatorRepositoryTransaction::TYPE_SLUG:
foreach ($xactions as $xaction) {
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
if (!strlen($new)) {
continue;
}
if ($new === $old) {
continue;
}
try {
PhabricatorRepository::assertValidRepositorySlug($new);
} catch (Exception $ex) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
$ex->getMessage(),
$xaction);
continue;
}
$other = id(new PhabricatorRepositoryQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withSlugs(array($new))
->executeOne();
if ($other && ($other->getID() !== $object->getID())) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Duplicate'),
pht(
'The selected repository short name is already in use by '.
'another repository. Choose a unique short name.'),
$xaction);
continue;
}
}
break;
case PhabricatorRepositoryTransaction::TYPE_CALLSIGN:
foreach ($xactions as $xaction) {
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
if (!strlen($new)) {
continue;
}
if ($new === $old) {
continue;
}
try {
PhabricatorRepository::assertValidCallsign($new);
} catch (Exception $ex) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
$ex->getMessage(),
$xaction);
continue;
}
$other = id(new PhabricatorRepositoryQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withCallsigns(array($new))
->executeOne();
if ($other && ($other->getID() !== $object->getID())) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Duplicate'),
pht(
'The selected callsign ("%s") is already in use by another '.
'repository. Choose a unique callsign.',
$new),
$xaction);
continue;
}
}
break;
case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES:
foreach ($xactions as $xaction) {
$old = $object->getSymbolSources();
$new = $xaction->getNewValue();
// If the viewer is adding new repositories, make sure they are
// valid and visible.
$add = array_diff($new, $old);
if (!$add) {
continue;
}
$repositories = id(new PhabricatorRepositoryQuery())
->setViewer($this->getActor())
->withPHIDs($add)
->execute();
$repositories = mpull($repositories, null, 'getPHID');
foreach ($add as $phid) {
if (isset($repositories[$phid])) {
continue;
}
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht(
'Repository ("%s") does not exist, or you do not have '.
'permission to see it.',
$phid),
$xaction);
break;
}
}
break;
}
return $errors;
}
protected function didCatchDuplicateKeyException(
PhabricatorLiskDAO $object,
array $xactions,
Exception $ex) {
$errors = array();
$errors[] = new PhabricatorApplicationTransactionValidationError(
null,
pht('Invalid'),
pht(
'The chosen callsign or repository short name is already in '.
'use by another repository.'),
null);
throw new PhabricatorApplicationTransactionValidationException($errors);
}
protected function supportsSearch() {
return true;
}
protected function applyFinalEffects(
PhabricatorLiskDAO $object,
array $xactions) {
// If the repository does not have a local path yet, assign it one based
// on its ID. We can't do this earlier because we won't have an ID yet.
- $local_path = $object->getDetail('local-path');
+ $local_path = $object->getLocalPath();
if (!strlen($local_path)) {
$local_key = 'repository.default-local-path';
$local_root = PhabricatorEnv::getEnvConfig($local_key);
$local_root = rtrim($local_root, '/');
$id = $object->getID();
$local_path = "{$local_root}/{$id}/";
- $object->setDetail('local-path', $local_path);
+ $object->setLocalPath($local_path);
$object->save();
}
if ($this->getIsNewObject()) {
id(new DiffusionRepositoryClusterEngine())
->setViewer($this->getActor())
->setRepository($object)
->synchronizeWorkingCopyAfterCreation();
}
return $xactions;
}
}
diff --git a/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyTestCase.php b/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyTestCase.php
index 5a9be99212..5b37a9e655 100644
--- a/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyTestCase.php
+++ b/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyTestCase.php
@@ -1,108 +1,108 @@
<?php
abstract class PhabricatorWorkingCopyTestCase extends PhabricatorTestCase {
private $dirs = array();
protected function getPhabricatorTestCaseConfiguration() {
return array(
self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => true,
);
}
protected function buildBareRepository($callsign) {
$existing_repository = id(new PhabricatorRepositoryQuery())
->withCallsigns(array($callsign))
->setViewer(PhabricatorUser::getOmnipotentUser())
->executeOne();
if ($existing_repository) {
$existing_repository->delete();
}
$data_dir = dirname(__FILE__).'/data/';
$types = array(
'svn' => PhabricatorRepositoryType::REPOSITORY_TYPE_SVN,
'hg' => PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL,
'git' => PhabricatorRepositoryType::REPOSITORY_TYPE_GIT,
);
$hits = array();
foreach ($types as $type => $const) {
$path = $data_dir.$callsign.'.'.$type.'.tgz';
if (Filesystem::pathExists($path)) {
$hits[$const] = $path;
}
}
if (!$hits) {
throw new Exception(
pht(
"No test data for callsign '%s'. Expected an archive ".
"like '%s' in '%s'.",
$callsign,
"{$callsign}.git.tgz",
$data_dir));
}
if (count($hits) > 1) {
throw new Exception(
pht(
"Expected exactly one archive matching callsign '%s', ".
"found too many: %s",
$callsign,
implode(', ', $hits)));
}
$path = head($hits);
$vcs_type = head_key($hits);
$dir = PhutilDirectoryFixture::newFromArchive($path);
$local = new TempFile('.ignore');
$user = $this->generateNewTestUser();
$repo = PhabricatorRepository::initializeNewRepository($user)
->setCallsign($callsign)
->setName(pht('Test Repo "%s"', $callsign))
->setVersionControlSystem($vcs_type)
- ->setDetail('local-path', dirname($local).'/'.$callsign)
+ ->setLocalPath(dirname($local).'/'.$callsign)
->setDetail('remote-uri', 'file://'.$dir->getPath().'/');
$this->didConstructRepository($repo);
$repo->save();
$repo->makeEphemeral();
// Keep the disk resources around until we exit.
$this->dirs[] = $dir;
$this->dirs[] = $local;
return $repo;
}
protected function didConstructRepository(PhabricatorRepository $repository) {
return;
}
protected function buildPulledRepository($callsign) {
$repository = $this->buildBareRepository($callsign);
id(new PhabricatorRepositoryPullEngine())
->setRepository($repository)
->pullRepository();
return $repository;
}
protected function buildDiscoveredRepository($callsign) {
$repository = $this->buildPulledRepository($callsign);
id(new PhabricatorRepositoryDiscoveryEngine())
->setRepository($repository)
->discoverCommits();
return $repository;
}
}
diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php
index cc33f1021c..8bf31c63d9 100644
--- a/src/applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php
+++ b/src/applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php
@@ -1,135 +1,124 @@
<?php
final class PhabricatorRepositoryManagementEditWorkflow
extends PhabricatorRepositoryManagementWorkflow {
protected function didConstruct() {
$this
->setName('edit')
->setExamples('**edit** --as __username__ __repository__ ...')
->setSynopsis(
pht(
'Edit __repository__ (will eventually be deprecated by Conduit).'))
->setArguments(
array(
array(
'name' => 'repos',
'wildcard' => true,
),
array(
'name' => 'as',
'param' => 'user',
'help' => pht('Edit as user.'),
),
- array(
- 'name' => 'local-path',
- 'param' => 'path',
- 'help' => pht('Edit the local path.'),
- ),
array(
'name' => 'serve-http',
'param' => 'string',
'help' => pht('Edit the http serving policy.'),
),
array(
'name' => 'serve-ssh',
'param' => 'string',
'help' => pht('Edit the ssh serving policy.'),
),
));
}
public function execute(PhutilArgumentParser $args) {
$repos = $this->loadRepositories($args, 'repos');
if (!$repos) {
throw new PhutilArgumentUsageException(
pht('Specify one or more repositories to edit.'));
}
$console = PhutilConsole::getConsole();
// TODO: It would be nice to just take this action as "Administrator" or
// similar, since that would make it easier to use this script, harder to
// impersonate users, and more clear to viewers what happened. However,
// the omnipotent user doesn't have a PHID right now, can't be loaded,
// doesn't have a handle, etc. Adding all of that is fairly involved, and
// I want to wait for stronger use cases first.
$username = $args->getArg('as');
if (!$username) {
throw new PhutilArgumentUsageException(
pht(
'Specify a user to edit as with %s.',
'--as <username>'));
}
$actor = id(new PhabricatorPeopleQuery())
->setViewer($this->getViewer())
->withUsernames(array($username))
->executeOne();
if (!$actor) {
throw new PhutilArgumentUsageException(
pht("No such user '%s'!", $username));
}
foreach ($repos as $repo) {
$console->writeOut(
"%s\n",
pht(
'Editing "%s"...',
$repo->getDisplayName()));
$xactions = array();
- $type_local_path = PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH;
$type_protocol_http =
PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP;
$type_protocol_ssh = PhabricatorRepositoryTransaction::TYPE_PROTOCOL_SSH;
$allowed_serve_modes = array(
PhabricatorRepository::SERVE_OFF,
PhabricatorRepository::SERVE_READONLY,
PhabricatorRepository::SERVE_READWRITE,
);
- if ($args->getArg('local-path')) {
- $xactions[] = id(new PhabricatorRepositoryTransaction())
- ->setTransactionType($type_local_path)
- ->setNewValue($args->getArg('local-path'));
- }
$serve_http = $args->getArg('serve-http');
if ($serve_http && in_array($serve_http, $allowed_serve_modes)) {
$xactions[] = id(new PhabricatorRepositoryTransaction())
->setTransactionType($type_protocol_http)
->setNewValue($serve_http);
}
$serve_ssh = $args->getArg('serve-ssh');
if ($serve_ssh && in_array($serve_ssh, $allowed_serve_modes)) {
$xactions[] = id(new PhabricatorRepositoryTransaction())
->setTransactionType($type_protocol_ssh)
->setNewValue($serve_ssh);
}
if (!$xactions) {
throw new PhutilArgumentUsageException(
pht('Specify one or more fields to edit!'));
}
$content_source = $this->newContentSource();
$editor = id(new PhabricatorRepositoryEditor())
->setActor($actor)
->setContentSource($content_source)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true)
->applyTransactions($repo, $xactions);
}
$console->writeOut("%s\n", pht('Done.'));
return 0;
}
}
diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementMovePathsWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementMovePathsWorkflow.php
index f0146847ae..baee437883 100644
--- a/src/applications/repository/management/PhabricatorRepositoryManagementMovePathsWorkflow.php
+++ b/src/applications/repository/management/PhabricatorRepositoryManagementMovePathsWorkflow.php
@@ -1,146 +1,144 @@
<?php
final class PhabricatorRepositoryManagementMovePathsWorkflow
extends PhabricatorRepositoryManagementWorkflow {
protected function didConstruct() {
$this
->setName('move-paths')
->setSynopsis(pht('Move repository local paths.'))
->setArguments(
array(
array(
'name' => 'from',
'param' => 'prefix',
'help' => pht('Move paths with this prefix.'),
),
array(
'name' => 'to',
'param' => 'prefix',
'help' => pht('Replace matching prefixes with this string.'),
),
));
}
public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$repos = id(new PhabricatorRepositoryQuery())
->setViewer($this->getViewer())
->execute();
if (!$repos) {
$console->writeErr("%s\n", pht('There are no repositories.'));
return 0;
}
$from = $args->getArg('from');
if (!strlen($from)) {
throw new Exception(
pht(
'You must specify a path prefix to move from with --from.'));
}
$to = $args->getArg('to');
if (!strlen($to)) {
throw new Exception(
pht(
'You must specify a path prefix to move to with --to.'));
}
$rows = array();
$any_changes = false;
foreach ($repos as $repo) {
$src = $repo->getLocalPath();
$row = array(
'repository' => $repo,
'move' => false,
'monogram' => $repo->getMonogram(),
'src' => $src,
'dst' => '',
);
if (strncmp($src, $from, strlen($from))) {
$row['action'] = pht('Ignore');
} else {
$dst = $to.substr($src, strlen($from));
$row['action'] = tsprintf('**%s**', pht('Move'));
$row['dst'] = $dst;
$row['move'] = true;
$any_changes = true;
}
$rows[] = $row;
}
$table = id(new PhutilConsoleTable())
->addColumn(
'action',
array(
'title' => pht('Action'),
))
->addColumn(
'monogram',
array(
'title' => pht('Repository'),
))
->addColumn(
'src',
array(
'title' => pht('Src'),
))
->addColumn(
'dst',
array(
'title' => pht('Dst'),
))
->setBorders(true);
foreach ($rows as $row) {
$display = array_select_keys(
$row,
array(
'action',
'monogram',
'src',
'dst',
));
$table->addRow($display);
}
$table->draw();
if (!$any_changes) {
$console->writeOut(pht('No matching repositories.')."\n");
return 0;
}
$prompt = pht('Apply these changes?');
if (!phutil_console_confirm($prompt)) {
throw new Exception(pht('Declining to apply changes.'));
}
foreach ($rows as $row) {
if (empty($row['move'])) {
continue;
}
$repo = $row['repository'];
- $details = $repo->getDetails();
- $details['local-path'] = $row['dst'];
queryfx(
$repo->establishConnection('w'),
- 'UPDATE %T SET details = %s WHERE id = %d',
+ 'UPDATE %T SET localPath = %s WHERE id = %d',
$repo->getTableName(),
- phutil_json_encode($details),
+ $row['dst'],
$repo->getID());
}
$console->writeOut(pht('Applied changes.')."\n");
return 0;
}
}
diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php
index be77852be8..9785a32611 100644
--- a/src/applications/repository/storage/PhabricatorRepository.php
+++ b/src/applications/repository/storage/PhabricatorRepository.php
@@ -1,2481 +1,2490 @@
<?php
/**
* @task uri Repository URI Management
* @task autoclose Autoclose
* @task sync Cluster Synchronization
*/
final class PhabricatorRepository extends PhabricatorRepositoryDAO
implements
PhabricatorApplicationTransactionInterface,
PhabricatorPolicyInterface,
PhabricatorFlaggableInterface,
PhabricatorMarkupInterface,
PhabricatorDestructibleInterface,
PhabricatorProjectInterface,
PhabricatorSpacesInterface,
PhabricatorConduitResultInterface {
/**
* 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 TABLE_COVERAGE = 'repository_coverage';
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';
const BECAUSE_AUTOCLOSE_FORCED = 'auto/forced';
const STATUS_ACTIVE = 'active';
const STATUS_INACTIVE = 'inactive';
protected $name;
protected $callsign;
protected $repositorySlug;
protected $uuid;
protected $viewPolicy;
protected $editPolicy;
protected $pushPolicy;
protected $versionControlSystem;
protected $details = array();
protected $credentialPHID;
protected $almanacServicePHID;
protected $spacePHID;
+ protected $localPath;
private $commitCount = self::ATTACHABLE;
private $mostRecentCommit = self::ATTACHABLE;
private $projectPHIDs = self::ATTACHABLE;
private $uris = 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);
$repository = id(new PhabricatorRepository())
->setViewPolicy($view_policy)
->setEditPolicy($edit_policy)
->setPushPolicy($push_policy)
->setSpacePHID($actor->getDefaultSpacePHID());
// Put the repository in "Importing" mode until we finish
// parsing it.
$repository->setDetail('importing', true);
return $repository;
}
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'details' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'sort255',
'callsign' => 'sort32?',
'repositorySlug' => 'sort64?',
'versionControlSystem' => 'text32',
'uuid' => 'text64?',
'pushPolicy' => 'policy',
'credentialPHID' => 'phid?',
'almanacServicePHID' => 'phid?',
+ 'localPath' => 'text128?',
),
self::CONFIG_KEY_SCHEMA => array(
'callsign' => array(
'columns' => array('callsign'),
'unique' => true,
),
'key_name' => array(
'columns' => array('name(128)'),
),
'key_vcs' => array(
'columns' => array('versionControlSystem'),
),
'key_slug' => array(
'columns' => array('repositorySlug'),
'unique' => true,
),
+ 'key_local' => array(
+ 'columns' => array('localPath'),
+ 'unique' => true,
+ ),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorRepositoryRepositoryPHIDType::TYPECONST);
}
public static function getStatusMap() {
return array(
self::STATUS_ACTIVE => array(
'name' => pht('Active'),
'isTracked' => 1,
),
self::STATUS_INACTIVE => array(
'name' => pht('Inactive'),
'isTracked' => 0,
),
);
}
public static function getStatusNameMap() {
return ipull(self::getStatusMap(), 'name');
}
public function getStatus() {
if ($this->isTracked()) {
return self::STATUS_ACTIVE;
} else {
return self::STATUS_INACTIVE;
}
}
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(),
'encoding' => $this->getDefaultTextEncoding(),
'staging' => array(
'supported' => $this->supportsStaging(),
'prefix' => 'phabricator',
'uri' => $this->getStagingURI(),
),
);
}
public function getDefaultTextEncoding() {
return $this->getDetail('encoding', 'UTF-8');
}
public function getMonogram() {
$callsign = $this->getCallsign();
if (strlen($callsign)) {
return "r{$callsign}";
}
$id = $this->getID();
return "R{$id}";
}
public function getDisplayName() {
$slug = $this->getRepositorySlug();
if (strlen($slug)) {
return $slug;
}
return $this->getMonogram();
}
public function getAllMonograms() {
$monograms = array();
$monograms[] = 'R'.$this->getID();
$callsign = $this->getCallsign();
if (strlen($callsign)) {
$monograms[] = 'r'.$callsign;
}
return $monograms;
}
+ public function setLocalPath($path) {
+ // Convert any extra slashes ("//") in the path to a single slash ("/").
+ $path = preg_replace('(//+)', '/', $path);
+
+ return parent::setLocalPath($path);
+ }
+
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(pht('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->getRepositorySlug();
// 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;
}
public static function isValidRepositorySlug($slug) {
try {
self::assertValidRepositorySlug($slug);
return true;
} catch (Exception $ex) {
return false;
}
}
public static function assertValidRepositorySlug($slug) {
if (!strlen($slug)) {
throw new Exception(
pht(
'The empty string is not a valid repository short name. '.
'Repository short names must be at least one character long.'));
}
if (strlen($slug) > 64) {
throw new Exception(
pht(
'The name "%s" is not a valid repository short name. Repository '.
'short names must not be longer than 64 characters.',
$slug));
}
if (preg_match('/[^a-zA-Z0-9._-]/', $slug)) {
throw new Exception(
pht(
'The name "%s" is not a valid repository short name. Repository '.
'short names may only contain letters, numbers, periods, hyphens '.
'and underscores.',
$slug));
}
if (!preg_match('/^[a-zA-Z0-9]/', $slug)) {
throw new Exception(
pht(
'The name "%s" is not a valid repository short name. Repository '.
'short names must begin with a letter or number.',
$slug));
}
if (!preg_match('/[a-zA-Z0-9]\z/', $slug)) {
throw new Exception(
pht(
'The name "%s" is not a valid repository short name. Repository '.
'short names must end with a letter or number.',
$slug));
}
if (preg_match('/__|--|\\.\\./', $slug)) {
throw new Exception(
pht(
'The name "%s" is not a valid repository short name. Repository '.
'short names must not contain multiple consecutive underscores, '.
'hyphens, or periods.',
$slug));
}
if (preg_match('/^[A-Z]+\z/', $slug)) {
throw new Exception(
pht(
'The name "%s" is not a valid repository short name. Repository '.
'short names may not contain only uppercase letters.',
$slug));
}
if (preg_match('/^\d+\z/', $slug)) {
throw new Exception(
pht(
'The name "%s" is not a valid repository short name. Repository '.
'short names may not contain only numbers.',
$slug));
}
}
public static function assertValidCallsign($callsign) {
if (!strlen($callsign)) {
throw new Exception(
pht(
'A repository callsign must be at least one character long.'));
}
if (strlen($callsign) > 32) {
throw new Exception(
pht(
'The callsign "%s" is not a valid repository callsign. Callsigns '.
'must be no more than 32 bytes long.',
$callsign));
}
if (!preg_match('/^[A-Z]+\z/', $callsign)) {
throw new Exception(
pht(
'The callsign "%s" is not a valid repository callsign. Callsigns '.
'may only contain UPPERCASE letters.',
$callsign));
}
}
/* -( 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) {
return $this->newRemoteCommandEngine($argv)
->newFuture();
}
private function newRemoteCommandPassthru(array $argv) {
return $this->newRemoteCommandEngine($argv)
->setPassthru(true)
->newFuture();
}
private function newRemoteCommandEngine(array $argv) {
return DiffusionCommandEngine::newCommandEngine($this)
->setArgv($argv)
->setCredentialPHID($this->getCredentialPHID())
->setProtocol($this->getRemoteProtocol());
}
/* -( 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();
$future = DiffusionCommandEngine::newCommandEngine($this)
->setArgv($argv)
->newFuture();
if ($this->usesLocalWorkingCopy()) {
$future->setCWD($this->getLocalPath());
}
return $future;
}
private function newLocalCommandPassthru(array $argv) {
$this->assertLocalExists();
$future = DiffusionCommandEngine::newCommandEngine($this)
->setArgv($argv)
->setPassthru(true)
->newFuture();
if ($this->usesLocalWorkingCopy()) {
$future->setCWD($this->getLocalPath());
}
return $future;
}
public function getURI() {
$callsign = $this->getCallsign();
if (strlen($callsign)) {
return "/diffusion/{$callsign}/";
}
$id = $this->getID();
return "/diffusion/{$id}/";
}
public function getPathURI($path) {
return $this->getURI().$path;
}
public function getCommitURI($identifier) {
$callsign = $this->getCallsign();
if (strlen($callsign)) {
return "/r{$callsign}{$identifier}";
}
$id = $this->getID();
return "/R{$id}:{$identifier}";
}
public static function parseRepositoryServicePath($request_path) {
// NOTE: In Mercurial over SSH, the path will begin without a leading "/",
// so we're matching it optionally.
$patterns = array(
'(^'.
'(?P<base>/?diffusion/(?P<identifier>[A-Z]+|[0-9]\d*))'.
'(?P<path>(?:/.*)?)'.
'\z)',
);
$identifier = null;
foreach ($patterns as $pattern) {
$matches = null;
if (!preg_match($pattern, $request_path, $matches)) {
continue;
}
$identifier = $matches['identifier'];
$base = $matches['base'];
$path = $matches['path'];
break;
}
if ($identifier === null) {
return null;
}
return array(
'identifier' => $identifier,
'base' => $base,
'path' => $path,
);
}
public function getCanonicalPath($request_path) {
$standard_pattern =
'(^'.
'(?P<prefix>/diffusion/)'.
'(?P<identifier>[^/]+)'.
'(?P<suffix>(?:/.*)?)'.
'\z)';
$matches = null;
if (preg_match($standard_pattern, $request_path, $matches)) {
$prefix = $matches['prefix'];
$callsign = $this->getCallsign();
if ($callsign) {
$identifier = $callsign;
} else {
$identifier = $this->getID();
}
$suffix = $matches['suffix'];
if (!strlen($suffix)) {
$suffix = '/';
}
return $prefix.$identifier.$suffix;
}
$commit_pattern =
'(^'.
'(?P<prefix>/)'.
'(?P<monogram>'.
'(?:'.
'r(?P<repositoryCallsign>[A-Z]+)'.
'|'.
'R(?P<repositoryID>[1-9]\d*):'.
')'.
'(?P<commit>[a-f0-9]+)'.
')'.
'\z)';
$matches = null;
if (preg_match($commit_pattern, $request_path, $matches)) {
$commit = $matches['commit'];
return $this->getCommitURI($commit);
}
return null;
}
public function generateURI(array $params) {
$req_branch = false;
$req_commit = false;
$action = idx($params, 'action');
switch ($action) {
case 'history':
case 'browse':
case 'change':
case 'lastmodified':
case 'tags':
case 'branches':
case 'lint':
case 'pathtree':
case 'refs':
break;
case 'branch':
// NOTE: This does not actually require a branch, and won't have one
// in Subversion. Possibly this should be more clear.
break;
case 'commit':
case 'rendering-ref':
$req_commit = true;
break;
default:
throw new Exception(
pht(
'Action "%s" is not a valid repository URI action.',
$action));
}
$path = idx($params, 'path');
$branch = idx($params, 'branch');
$commit = idx($params, 'commit');
$line = idx($params, 'line');
if ($req_commit && !strlen($commit)) {
throw new Exception(
pht(
'Diffusion URI action "%s" requires commit!',
$action));
}
if ($req_branch && !strlen($branch)) {
throw new Exception(
pht(
'Diffusion URI action "%s" requires branch!',
$action));
}
if ($action === 'commit') {
return $this->getCommitURI($commit);
}
$identifier = $this->getID();
$callsign = $this->getCallsign();
if ($callsign !== null) {
$identifier = $callsign;
}
if (strlen($identifier)) {
$identifier = phutil_escape_uri_path_component($identifier);
}
if (strlen($path)) {
$path = ltrim($path, '/');
$path = str_replace(array(';', '$'), array(';;', '$$'), $path);
$path = phutil_escape_uri($path);
}
if (strlen($branch)) {
$branch = phutil_escape_uri_path_component($branch);
$path = "{$branch}/{$path}";
}
if (strlen($commit)) {
$commit = str_replace('$', '$$', $commit);
$commit = ';'.phutil_escape_uri($commit);
}
if (strlen($line)) {
$line = '$'.phutil_escape_uri($line);
}
switch ($action) {
case 'change':
case 'history':
case 'browse':
case 'lastmodified':
case 'tags':
case 'branches':
case 'lint':
case 'pathtree':
case 'refs':
$uri = "/diffusion/{$identifier}/{$action}/{$path}{$commit}{$line}";
break;
case 'branch':
if (strlen($path)) {
$uri = "/diffusion/{$identifier}/repository/{$path}";
} else {
$uri = "/diffusion/{$identifier}/";
}
break;
case 'external':
$commit = ltrim($commit, ';');
$uri = "/diffusion/external/{$commit}/";
break;
case 'rendering-ref':
// This isn't a real URI per se, it's passed as a query parameter to
// the ajax changeset stuff but then we parse it back out as though
// it came from a URI.
$uri = rawurldecode("{$path}{$commit}");
break;
}
if ($action == 'rendering-ref') {
return $uri;
}
$uri = new PhutilURI($uri);
if (isset($params['lint'])) {
$params['params'] = idx($params, 'params', array()) + array(
'lint' => $params['lint'],
);
}
if (idx($params, 'params')) {
$uri->setQueryParams($params['params']);
}
return $uri;
}
public function updateURIIndex() {
$uris = array(
(string)$this->getCloneURIObject(),
);
foreach ($uris as $key => $uri) {
$uris[$key] = $this->getNormalizedURI($uri)
->getNormalizedPath();
}
PhabricatorRepositoryURIIndex::updateRepositoryURIs(
$this->getPHID(),
$uris);
return $this;
}
private function getNormalizedURI($uri) {
switch ($this->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
return new PhabricatorRepositoryURINormalizer(
PhabricatorRepositoryURINormalizer::TYPE_GIT,
$uri);
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
return new PhabricatorRepositoryURINormalizer(
PhabricatorRepositoryURINormalizer::TYPE_SVN,
$uri);
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
return new PhabricatorRepositoryURINormalizer(
PhabricatorRepositoryURINormalizer::TYPE_MERCURIAL,
$uri);
default:
throw new Exception(pht('Unrecognized version control system.'));
}
}
public function isTracked() {
$status = $this->getDetail('tracking-enabled');
$map = self::getStatusMap();
$spec = idx($map, $status);
if (!$spec) {
if ($status) {
$status = self::STATUS_ACTIVE;
} else {
$status = self::STATUS_INACTIVE;
}
$spec = idx($map, $status);
}
return (bool)idx($spec, 'isTracked', 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;
}
$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;
}
}
// 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, $local = false) {
$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) {
$name = substr($commit_identifier, 0, 12);
$need_scope = false;
} else {
$name = $commit_identifier;
$need_scope = true;
}
if (!$local) {
$need_scope = true;
}
if ($need_scope) {
$callsign = $this->getCallsign();
if ($callsign) {
$scope = "r{$callsign}";
} else {
$id = $this->getID();
$scope = "R{$id}:";
}
$name = $scope.$name;
}
return $name;
}
public function isImporting() {
return (bool)$this->getDetail('importing', false);
}
public function loadImportProgress() {
$progress = queryfx_all(
$this->establishConnection('r'),
'SELECT importStatus, count(*) N FROM %T WHERE repositoryID = %d
GROUP BY importStatus',
id(new PhabricatorRepositoryCommit())->getTableName(),
$this->getID());
$done = 0;
$total = 0;
foreach ($progress as $row) {
$total += $row['N'] * 4;
$status = $row['importStatus'];
if ($status & PhabricatorRepositoryCommit::IMPORTED_MESSAGE) {
$done += $row['N'];
}
if ($status & PhabricatorRepositoryCommit::IMPORTED_CHANGE) {
$done += $row['N'];
}
if ($status & PhabricatorRepositoryCommit::IMPORTED_OWNERS) {
$done += $row['N'];
}
if ($status & PhabricatorRepositoryCommit::IMPORTED_HERALD) {
$done += $row['N'];
}
}
if ($total) {
$ratio = ($done / $total);
} else {
$ratio = 0;
}
// Cap this at "99.99%", because it's confusing to users when the actual
// fraction is "99.996%" and it rounds up to "100.00%".
if ($ratio > 0.9999) {
$ratio = 0.9999;
}
return $ratio;
}
/**
* Should this repository publish feed, notifications, audits, and email?
*
* We do not publish information about repositories during initial import,
* or if the repository has been set not to publish.
*/
public function shouldPublish() {
if ($this->isImporting()) {
return false;
}
if ($this->getDetail('herald-disabled')) {
return false;
}
return true;
}
/* -( 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(pht('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(pht("Remote URI '%s' could not be parsed!", $raw_uri));
}
/**
* 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 = AlmanacKeys::getClusterSSHUser();
if (strlen($ssh_user)) {
$uri->setUser($ssh_user);
}
$ssh_host = PhabricatorEnv::getEnvConfig('diffusion.ssh-host');
if (strlen($ssh_host)) {
$uri->setDomain($ssh_host);
}
$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;
}
return $this->getRawHTTPCloneURIObject();
}
private function getRawHTTPCloneURIObject() {
$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();
}
queryfx(
$this->establishConnection('w'),
'DELETE FROM %T WHERE repositoryPHID = %s',
id(new PhabricatorRepositorySymbol())->getTableName(),
$this->getPHID());
$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 canUseGitLFS() {
if (!$this->isGit()) {
return false;
}
if (!$this->isHosted()) {
return false;
}
// TODO: Unprototype this feature.
if (!PhabricatorEnv::getEnvConfig('phabricator.show-prototypes')) {
return false;
}
return true;
}
public function getGitLFSURI($path = null) {
if (!$this->canUseGitLFS()) {
throw new Exception(
pht(
'This repository does not support Git LFS, so Git LFS URIs can '.
'not be generated for it.'));
}
$uri = $this->getRawHTTPCloneURIObject();
$uri = (string)$uri;
$uri = $uri.'/'.$path;
return $uri;
}
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 ".
"'%s', not '%s'. The '%s' syntax is only valid in SCP-style URIs.",
'proto://domain/path',
'proto://domain:/path',
':/path'));
}
}
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 with '.
'"%s", "%s", "%s", "%s", "%s", "%s", or be in the form "%s".',
'ssh://',
'http://',
'https://',
'git://',
'svn://',
'svn+ssh://',
'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;
}
/**
* Retrieve the sevice URI for the device hosting this repository.
*
* See @{method:newConduitClient} for a general discussion of interacting
* with repository services. This method provides lower-level resolution of
* services, returning raw URIs.
*
* @param PhabricatorUser Viewing user.
* @param bool `true` to throw if a remote URI would be returned.
* @param list<string> List of allowable protocols.
* @return string|null URI, or `null` for local repositories.
*/
public function getAlmanacServiceURI(
PhabricatorUser $viewer,
$never_proxy,
array $protocols) {
$service = $this->loadAlmanacService();
if (!$service) {
return null;
}
$bindings = $service->getActiveBindings();
if (!$bindings) {
throw new Exception(
pht(
'The Almanac service for this repository is not bound to any '.
'interfaces.'));
}
$local_device = AlmanacKeys::getDeviceID();
if ($never_proxy && !$local_device) {
throw new Exception(
pht(
'Unable to handle proxied service request. This device is not '.
'registered, so it can not identify local services. Register '.
'this device before sending requests here.'));
}
$protocol_map = array_fuse($protocols);
$uris = array();
foreach ($bindings as $binding) {
$iface = $binding->getInterface();
// If we're never proxying this and it's locally satisfiable, return
// `null` to tell the caller to handle it locally. If we're allowed to
// proxy, we skip this check and may proxy the request to ourselves.
// (That proxied request will end up here with proxying forbidden,
// return `null`, and then the request will actually run.)
if ($local_device && $never_proxy) {
if ($iface->getDevice()->getName() == $local_device) {
return null;
}
}
$uri = $this->getClusterRepositoryURIFromBinding($binding);
$protocol = $uri->getProtocol();
if (empty($protocol_map[$protocol])) {
continue;
}
$uris[] = $uri;
}
if (!$uris) {
throw new Exception(
pht(
'The Almanac service for this repository is not bound to any '.
'interfaces which support the required protocols (%s).',
implode(', ', $protocols)));
}
if ($never_proxy) {
throw new Exception(
pht(
'Refusing to proxy a repository request from a cluster host. '.
'Cluster hosts must correctly route their intracluster requests.'));
}
shuffle($uris);
return head($uris);
}
/**
* Build a new Conduit client in order to make a service call to this
* repository.
*
* If the repository is hosted locally, this method may return `null`. The
* caller should use `ConduitCall` or other local logic to complete the
* request.
*
* By default, we will return a @{class:ConduitClient} for any repository with
* a service, even if that service is on the current device.
*
* We do this because this configuration does not make very much sense in a
* production context, but is very common in a test/development context
* (where the developer's machine is both the web host and the repository
* service). By proxying in development, we get more consistent behavior
* between development and production, and don't have a major untested
* codepath.
*
* The `$never_proxy` parameter can be used to prevent this local proxying.
* If the flag is passed:
*
* - The method will return `null` (implying a local service call)
* if the repository service is hosted on the current device.
* - The method will throw if it would need to return a client.
*
* This is used to prevent loops in Conduit: the first request will proxy,
* even in development, but the second request will be identified as a
* cluster request and forced not to proxy.
*
* For lower-level service resolution, see @{method:getAlmanacServiceURI}.
*
* @param PhabricatorUser Viewing user.
* @param bool `true` to throw if a client would be returned.
* @return ConduitClient|null Client, or `null` for local repositories.
*/
public function newConduitClient(
PhabricatorUser $viewer,
$never_proxy = false) {
$uri = $this->getAlmanacServiceURI(
$viewer,
$never_proxy,
array(
'http',
'https',
));
if ($uri === null) {
return null;
}
$domain = id(new PhutilURI(PhabricatorEnv::getURI('/')))->getDomain();
$client = id(new ConduitClient($uri))
->setHost($domain);
if ($viewer->isOmnipotent()) {
// If the caller is the omnipotent user (normally, a daemon), we will
// sign the request with this host's asymmetric keypair.
$public_path = AlmanacKeys::getKeyPath('device.pub');
try {
$public_key = Filesystem::readFile($public_path);
} catch (Exception $ex) {
throw new PhutilAggregateException(
pht(
'Unable to read device public key while attempting to make '.
'authenticated method call within the Phabricator cluster. '.
'Use `%s` to register keys for this device. Exception: %s',
'bin/almanac register',
$ex->getMessage()),
array($ex));
}
$private_path = AlmanacKeys::getKeyPath('device.key');
try {
$private_key = Filesystem::readFile($private_path);
$private_key = new PhutilOpaqueEnvelope($private_key);
} catch (Exception $ex) {
throw new PhutilAggregateException(
pht(
'Unable to read device private key while attempting to make '.
'authenticated method call within the Phabricator cluster. '.
'Use `%s` to register keys for this device. Exception: %s',
'bin/almanac register',
$ex->getMessage()),
array($ex));
}
$client->setSigningKeys($public_key, $private_key);
} else {
// If the caller is a normal user, we generate or retrieve a cluster
// API token.
$token = PhabricatorConduitToken::loadClusterTokenForUser($viewer);
if ($token) {
$client->setConduitToken($token->getToken());
}
}
return $client;
}
/* -( Repository URIs )---------------------------------------------------- */
public function attachURIs(array $uris) {
$custom_map = array();
foreach ($uris as $key => $uri) {
$builtin_key = $uri->getRepositoryURIBuiltinKey();
if ($builtin_key !== null) {
$custom_map[$builtin_key] = $key;
}
}
$builtin_uris = $this->newBuiltinURIs();
$seen_builtins = array();
foreach ($builtin_uris as $builtin_uri) {
$builtin_key = $builtin_uri->getRepositoryURIBuiltinKey();
$seen_builtins[$builtin_key] = true;
// If this builtin URI is disabled, don't attach it and remove the
// persisted version if it exists.
if ($builtin_uri->getIsDisabled()) {
if (isset($custom_map[$builtin_key])) {
unset($uris[$custom_map[$builtin_key]]);
}
continue;
}
// If we don't have a persisted version of the URI, add the builtin
// version.
if (empty($custom_map[$builtin_key])) {
$uris[] = $builtin_uri;
}
}
// Remove any builtins which no longer exist.
foreach ($custom_map as $builtin_key => $key) {
if (empty($seen_builtins[$builtin_key])) {
unset($uris[$key]);
}
}
$this->uris = $uris;
return $this;
}
public function getURIs() {
return $this->assertAttached($this->uris);
}
protected function newBuiltinURIs() {
$has_callsign = ($this->getCallsign() !== null);
$has_shortname = ($this->getRepositorySlug() !== null);
$identifier_map = array(
PhabricatorRepositoryURI::BUILTIN_IDENTIFIER_CALLSIGN => $has_callsign,
PhabricatorRepositoryURI::BUILTIN_IDENTIFIER_SHORTNAME => $has_shortname,
PhabricatorRepositoryURI::BUILTIN_IDENTIFIER_ID => true,
);
$allow_http = PhabricatorEnv::getEnvConfig('diffusion.allow-http-auth');
$base_uri = PhabricatorEnv::getURI('/');
$base_uri = new PhutilURI($base_uri);
$has_https = ($base_uri->getProtocol() == 'https');
$has_https = ($has_https && $allow_http);
$has_http = !PhabricatorEnv::getEnvConfig('security.require-https');
$has_http = ($has_http && $allow_http);
// TODO: Maybe allow users to disable this by default somehow?
$has_ssh = true;
$protocol_map = array(
PhabricatorRepositoryURI::BUILTIN_PROTOCOL_SSH => $has_ssh,
PhabricatorRepositoryURI::BUILTIN_PROTOCOL_HTTPS => $has_https,
PhabricatorRepositoryURI::BUILTIN_PROTOCOL_HTTP => $has_http,
);
$uris = array();
foreach ($protocol_map as $protocol => $proto_supported) {
foreach ($identifier_map as $identifier => $id_supported) {
$uris[] = PhabricatorRepositoryURI::initializeNewURI()
->setRepositoryPHID($this->getPHID())
->attachRepository($this)
->setBuiltinProtocol($protocol)
->setBuiltinIdentifier($identifier)
->setIsDisabled(!$proto_supported || !$id_supported);
}
}
return $uris;
}
public function getClusterRepositoryURIFromBinding(
AlmanacBinding $binding) {
$protocol = $binding->getAlmanacPropertyValue('protocol');
if ($protocol === null) {
$protocol = 'https';
}
$iface = $binding->getInterface();
$address = $iface->renderDisplayAddress();
$path = $this->getURI();
return id(new PhutilURI("{$protocol}://{$address}"))
->setPath($path);
}
public function loadAlmanacService() {
$service_phid = $this->getAlmanacServicePHID();
if (!$service_phid) {
// No service, so this is a local repository.
return null;
}
$service = id(new AlmanacServiceQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withPHIDs(array($service_phid))
->needBindings(true)
->needProperties(true)
->executeOne();
if (!$service) {
throw new Exception(
pht(
'The Almanac service for this repository is invalid or could not '.
'be loaded.'));
}
$service_type = $service->getServiceImplementation();
if (!($service_type instanceof AlmanacClusterRepositoryServiceType)) {
throw new Exception(
pht(
'The Almanac service for this repository does not have the correct '.
'service type.'));
}
return $service;
}
/* -( Symbols )-------------------------------------------------------------*/
public function getSymbolSources() {
return $this->getDetail('symbol-sources', array());
}
public function getSymbolLanguages() {
return $this->getDetail('symbol-languages', array());
}
/* -( Staging )------------------------------------------------------------ */
public function supportsStaging() {
return $this->isGit();
}
public function getStagingURI() {
if (!$this->supportsStaging()) {
return null;
}
return $this->getDetail('staging-uri', null);
}
/* -( Automation )--------------------------------------------------------- */
public function supportsAutomation() {
return $this->isGit();
}
public function canPerformAutomation() {
if (!$this->supportsAutomation()) {
return false;
}
if (!$this->getAutomationBlueprintPHIDs()) {
return false;
}
return true;
}
public function getAutomationBlueprintPHIDs() {
if (!$this->supportsAutomation()) {
return array();
}
return $this->getDetail('automation.blueprintPHIDs', array());
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new PhabricatorRepositoryEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new PhabricatorRepositoryTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( 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) {
$phid = $this->getPHID();
$this->openTransaction();
$this->delete();
PhabricatorRepositoryURIIndex::updateRepositoryURIs($phid, array());
$books = id(new DivinerBookQuery())
->setViewer($engine->getViewer())
->withRepositoryPHIDs(array($phid))
->execute();
foreach ($books as $book) {
$engine->destroyObject($book);
}
$atoms = id(new DivinerAtomQuery())
->setViewer($engine->getViewer())
->withRepositoryPHIDs(array($phid))
->execute();
foreach ($atoms as $atom) {
$engine->destroyObject($atom);
}
$lfs_refs = id(new PhabricatorRepositoryGitLFSRefQuery())
->setViewer($engine->getViewer())
->withRepositoryPHIDs(array($phid))
->execute();
foreach ($lfs_refs as $ref) {
$engine->destroyObject($ref);
}
$this->saveTransaction();
}
/* -( PhabricatorSpacesInterface )----------------------------------------- */
public function getSpacePHID() {
return $this->spacePHID;
}
/* -( PhabricatorConduitResultInterface )---------------------------------- */
public function getFieldSpecificationsForConduit() {
return array(
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('name')
->setType('string')
->setDescription(pht('The repository name.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('vcs')
->setType('string')
->setDescription(
pht('The VCS this repository uses ("git", "hg" or "svn").')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('callsign')
->setType('string')
->setDescription(pht('The repository callsign, if it has one.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('shortName')
->setType('string')
->setDescription(pht('Unique short name, if the repository has one.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('status')
->setType('string')
->setDescription(pht('Active or inactive status.')),
);
}
public function getFieldValuesForConduit() {
return array(
'name' => $this->getName(),
'vcs' => $this->getVersionControlSystem(),
'callsign' => $this->getCallsign(),
'shortName' => $this->getRepositorySlug(),
'status' => $this->getStatus(),
);
}
public function getConduitSearchAttachments() {
return array(
id(new DiffusionRepositoryURIsSearchEngineAttachment())
->setAttachmentKey('uris'),
);
}
}
diff --git a/src/applications/repository/storage/__tests__/PhabricatorRepositoryTestCase.php b/src/applications/repository/storage/__tests__/PhabricatorRepositoryTestCase.php
index c6d2278262..63bae03d80 100644
--- a/src/applications/repository/storage/__tests__/PhabricatorRepositoryTestCase.php
+++ b/src/applications/repository/storage/__tests__/PhabricatorRepositoryTestCase.php
@@ -1,220 +1,220 @@
<?php
final class PhabricatorRepositoryTestCase
extends PhabricatorTestCase {
public function testRepositoryURIProtocols() {
$tests = array(
'/path/to/repo' => 'file',
'file:///path/to/repo' => 'file',
'ssh://user@domain.com/path' => 'ssh',
'git@example.com:path' => 'ssh',
'git://git@example.com/path' => 'git',
'svn+ssh://example.com/path' => 'svn+ssh',
'https://example.com/repo/' => 'https',
'http://example.com/' => 'http',
'https://user@example.com/' => 'https',
);
foreach ($tests as $uri => $expect) {
$repository = new PhabricatorRepository();
$repository->setDetail('remote-uri', $uri);
$this->assertEqual(
$expect,
$repository->getRemoteProtocol(),
pht("Protocol for '%s'.", $uri));
}
}
public function testBranchFilter() {
$git = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
$repo = new PhabricatorRepository();
$repo->setVersionControlSystem($git);
$this->assertTrue(
$repo->shouldTrackBranch('imaginary'),
pht('Track all branches by default.'));
$repo->setDetail(
'branch-filter',
array(
'master' => true,
));
$this->assertTrue(
$repo->shouldTrackBranch('master'),
pht('Track listed branches.'));
$this->assertFalse(
$repo->shouldTrackBranch('imaginary'),
pht('Do not track unlisted branches.'));
}
public function testSubversionPathInfo() {
$svn = PhabricatorRepositoryType::REPOSITORY_TYPE_SVN;
$repo = new PhabricatorRepository();
$repo->setVersionControlSystem($svn);
$repo->setDetail('remote-uri', 'http://svn.example.com/repo');
$this->assertEqual(
'http://svn.example.com/repo',
$repo->getSubversionPathURI());
$repo->setDetail('remote-uri', 'http://svn.example.com/repo/');
$this->assertEqual(
'http://svn.example.com/repo',
$repo->getSubversionPathURI());
$repo->setDetail('hosting-enabled', true);
- $repo->setDetail('local-path', '/var/repo/SVN');
+ $repo->setLocalPath('/var/repo/SVN');
$this->assertEqual(
'file:///var/repo/SVN',
$repo->getSubversionPathURI());
- $repo->setDetail('local-path', '/var/repo/SVN/');
+ $repo->setLocalPath('/var/repo/SVN/');
$this->assertEqual(
'file:///var/repo/SVN',
$repo->getSubversionPathURI());
$this->assertEqual(
'file:///var/repo/SVN/a@',
$repo->getSubversionPathURI('a'));
$this->assertEqual(
'file:///var/repo/SVN/a@1',
$repo->getSubversionPathURI('a', 1));
$this->assertEqual(
'file:///var/repo/SVN/%3F@22',
$repo->getSubversionPathURI('?', 22));
$repo->setDetail('svn-subpath', 'quack/trunk/');
$this->assertEqual(
'file:///var/repo/SVN/quack/trunk/@',
$repo->getSubversionBaseURI());
$this->assertEqual(
'file:///var/repo/SVN/quack/trunk/@HEAD',
$repo->getSubversionBaseURI('HEAD'));
}
public function testFilterMercurialDebugOutput() {
$map = array(
'' => '',
"quack\n" => "quack\n",
"ignoring untrusted configuration option x.y = z\nquack\n" =>
"quack\n",
"ignoring untrusted configuration option x.y = z\n".
"ignoring untrusted configuration option x.y = z\n".
"quack\n" =>
"quack\n",
"ignoring untrusted configuration option x.y = z\n".
"ignoring untrusted configuration option x.y = z\n".
"ignoring untrusted configuration option x.y = z\n".
"quack\n" =>
"quack\n",
"quack\n".
"ignoring untrusted configuration option x.y = z\n".
"ignoring untrusted configuration option x.y = z\n".
"ignoring untrusted configuration option x.y = z\n" =>
"quack\n",
"ignoring untrusted configuration option x.y = z\n".
"ignoring untrusted configuration option x.y = z\n".
"duck\n".
"ignoring untrusted configuration option x.y = z\n".
"ignoring untrusted configuration option x.y = z\n".
"bread\n".
"ignoring untrusted configuration option x.y = z\n".
"quack\n" =>
"duck\nbread\nquack\n",
"ignoring untrusted configuration option x.y = z\n".
"duckignoring untrusted configuration option x.y = z\n".
"quack" =>
'duckquack',
);
foreach ($map as $input => $expect) {
$actual = DiffusionMercurialCommandEngine::filterMercurialDebugOutput(
$input);
$this->assertEqual($expect, $actual, $input);
}
}
public function testRepositoryShortNameValidation() {
$good = array(
'sensible-repository',
'AReasonableName',
'ACRONYM-project',
'sol-123',
'46-helixes',
'node.io',
'internet.com',
'www.internet-site.com.repository',
'with_under-scores',
// Can't win them all.
'A-_._-_._-_._-_._-_._-_._-1',
// 64-character names are fine.
str_repeat('a', 64),
);
$poor = array(
'',
'1',
'.',
'-_-',
'AAAA',
'..',
'a/b',
'../../etc/passwd',
'/',
'!',
'@',
'ca$hmoney',
'repo with spaces',
'hyphen-',
'-ated',
'_underscores_',
'yes!',
// 65-character names are no good.
str_repeat('a', 65),
);
foreach ($good as $nice_name) {
$actual = PhabricatorRepository::isValidRepositorySlug($nice_name);
$this->assertEqual(
true,
$actual,
pht(
'Expected "%s" to be a valid repository short name.',
$nice_name));
}
foreach ($poor as $poor_name) {
$actual = PhabricatorRepository::isValidRepositorySlug($poor_name);
$this->assertEqual(
false,
$actual,
pht(
'Expected "%s" to be rejected as an invalid repository '.
'short name.',
$poor_name));
}
}
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Jul 3, 3:27 PM (10 h, 4 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
165956
Default Alt Text
(177 KB)

Event Timeline