@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'master' of github.com:facebook/phabricator

+1332 -184
+11
resources/sql/patches/20131105.buildstep.sql
··· 1 + CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_buildstep ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + phid VARCHAR(64) NOT NULL COLLATE utf8_bin, 4 + buildPlanPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, 5 + className VARCHAR(255) NOT NULL COLLATE utf8_bin, 6 + details LONGTEXT CHARACTER SET utf8 COLLATE utf8_bin NOT NULL, 7 + dateCreated INT UNSIGNED NOT NULL, 8 + dateModified INT UNSIGNED NOT NULL, 9 + KEY `key_plan` (buildPlanPHID), 10 + UNIQUE KEY `key_phid` (phid) 11 + ) ENGINE=InnoDB, COLLATE utf8_general_ci;
+26 -5
src/__phutil_library_map__.php
··· 91 91 'AphrontView' => 'view/AphrontView.php', 92 92 'AphrontWebpageResponse' => 'aphront/response/AphrontWebpageResponse.php', 93 93 'AuditActionMenuEventListener' => 'applications/audit/events/AuditActionMenuEventListener.php', 94 + 'BuildStepImplementation' => 'applications/harbormaster/step/BuildStepImplementation.php', 94 95 'CelerityAPI' => 'infrastructure/celerity/CelerityAPI.php', 95 96 'CelerityPhabricatorResourceController' => 'infrastructure/celerity/CelerityPhabricatorResourceController.php', 96 97 'CelerityResourceController' => 'infrastructure/celerity/CelerityResourceController.php', ··· 369 370 'DifferentialFieldValidationException' => 'applications/differential/field/exception/DifferentialFieldValidationException.php', 370 371 'DifferentialFreeformFieldSpecification' => 'applications/differential/field/specification/DifferentialFreeformFieldSpecification.php', 371 372 'DifferentialFreeformFieldTestCase' => 'applications/differential/field/specification/__tests__/DifferentialFreeformFieldTestCase.php', 373 + 'DifferentialGetWorkingCopy' => 'applications/differential/DifferentialGetWorkingCopy.php', 372 374 'DifferentialGitSVNIDFieldSpecification' => 'applications/differential/field/specification/DifferentialGitSVNIDFieldSpecification.php', 373 375 'DifferentialHostFieldSpecification' => 'applications/differential/field/specification/DifferentialHostFieldSpecification.php', 374 376 'DifferentialHovercardEventListener' => 'applications/differential/event/DifferentialHovercardEventListener.php', ··· 383 385 'DifferentialInlineCommentQuery' => 'applications/differential/query/DifferentialInlineCommentQuery.php', 384 386 'DifferentialInlineCommentView' => 'applications/differential/view/DifferentialInlineCommentView.php', 385 387 'DifferentialJIRAIssuesFieldSpecification' => 'applications/differential/field/specification/DifferentialJIRAIssuesFieldSpecification.php', 388 + 'DifferentialLandingActionMenuEventListener' => 'applications/differential/landing/DifferentialLandingActionMenuEventListener.php', 389 + 'DifferentialLandingStrategy' => 'applications/differential/landing/DifferentialLandingStrategy.php', 390 + 'DifferentialLandingToHostedGit' => 'applications/differential/landing/DifferentialLandingToHostedGit.php', 386 391 'DifferentialLinesFieldSpecification' => 'applications/differential/field/specification/DifferentialLinesFieldSpecification.php', 387 392 'DifferentialLintFieldSpecification' => 'applications/differential/field/specification/DifferentialLintFieldSpecification.php', 388 393 'DifferentialLintStatus' => 'applications/differential/constants/DifferentialLintStatus.php', ··· 420 425 'DifferentialRevisionEditor' => 'applications/differential/editor/DifferentialRevisionEditor.php', 421 426 'DifferentialRevisionIDFieldParserTestCase' => 'applications/differential/field/specification/__tests__/DifferentialRevisionIDFieldParserTestCase.php', 422 427 'DifferentialRevisionIDFieldSpecification' => 'applications/differential/field/specification/DifferentialRevisionIDFieldSpecification.php', 428 + 'DifferentialRevisionLandController' => 'applications/differential/controller/DifferentialRevisionLandController.php', 423 429 'DifferentialRevisionListController' => 'applications/differential/controller/DifferentialRevisionListController.php', 424 430 'DifferentialRevisionListView' => 'applications/differential/view/DifferentialRevisionListView.php', 425 431 'DifferentialRevisionMailReceiver' => 'applications/differential/mail/DifferentialRevisionMailReceiver.php', ··· 656 662 'HarbormasterBuildStepQuery' => 'applications/harbormaster/query/HarbormasterBuildStepQuery.php', 657 663 'HarbormasterBuildTarget' => 'applications/harbormaster/storage/build/HarbormasterBuildTarget.php', 658 664 'HarbormasterBuildTargetQuery' => 'applications/harbormaster/query/HarbormasterBuildTargetQuery.php', 665 + 'HarbormasterBuildWorker' => 'applications/harbormaster/worker/HarbormasterBuildWorker.php', 659 666 'HarbormasterBuildable' => 'applications/harbormaster/storage/HarbormasterBuildable.php', 667 + 'HarbormasterBuildableApplyController' => 'applications/harbormaster/controller/HarbormasterBuildableApplyController.php', 660 668 'HarbormasterBuildableArtifactQuery' => 'applications/harbormaster/query/HarbormasterBuildableArtifactQuery.php', 661 669 'HarbormasterBuildableEditController' => 'applications/harbormaster/controller/HarbormasterBuildableEditController.php', 662 670 'HarbormasterBuildableListController' => 'applications/harbormaster/controller/HarbormasterBuildableListController.php', ··· 675 683 'HarbormasterPHIDTypeBuildable' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildable.php', 676 684 'HarbormasterPlanController' => 'applications/harbormaster/controller/HarbormasterPlanController.php', 677 685 'HarbormasterPlanEditController' => 'applications/harbormaster/controller/HarbormasterPlanEditController.php', 678 - 'HarbormasterPlanExecuteController' => 'applications/harbormaster/controller/HarbormasterPlanExecuteController.php', 679 686 'HarbormasterPlanListController' => 'applications/harbormaster/controller/HarbormasterPlanListController.php', 680 687 'HarbormasterPlanViewController' => 'applications/harbormaster/controller/HarbormasterPlanViewController.php', 681 688 'HarbormasterRemarkupRule' => 'applications/harbormaster/remarkup/HarbormasterRemarkupRule.php', 682 - 'HarbormasterRunnerWorker' => 'applications/harbormaster/worker/HarbormasterRunnerWorker.php', 683 689 'HarbormasterScratchTable' => 'applications/harbormaster/storage/HarbormasterScratchTable.php', 690 + 'HarbormasterStepAddController' => 'applications/harbormaster/controller/HarbormasterStepAddController.php', 691 + 'HarbormasterStepDeleteController' => 'applications/harbormaster/controller/HarbormasterStepDeleteController.php', 692 + 'HarbormasterStepEditController' => 'applications/harbormaster/controller/HarbormasterStepEditController.php', 684 693 'HeraldAction' => 'applications/herald/storage/HeraldAction.php', 685 694 'HeraldAdapter' => 'applications/herald/adapter/HeraldAdapter.php', 686 695 'HeraldApplyTranscript' => 'applications/herald/storage/transcript/HeraldApplyTranscript.php', ··· 2190 2199 'ReleephStatusFieldSpecification' => 'applications/releeph/field/specification/ReleephStatusFieldSpecification.php', 2191 2200 'ReleephSummaryFieldSpecification' => 'applications/releeph/field/specification/ReleephSummaryFieldSpecification.php', 2192 2201 'ReleephUserView' => 'applications/releeph/view/user/ReleephUserView.php', 2202 + 'SleepBuildStepImplementation' => 'applications/harbormaster/step/SleepBuildStepImplementation.php', 2193 2203 'SlowvoteEmbedView' => 'applications/slowvote/view/SlowvoteEmbedView.php', 2194 2204 'SlowvoteRemarkupRule' => 'applications/slowvote/remarkup/SlowvoteRemarkupRule.php', 2195 2205 ), ··· 2586 2596 'DifferentialInlineCommentQuery' => 'PhabricatorOffsetPagedQuery', 2587 2597 'DifferentialInlineCommentView' => 'AphrontView', 2588 2598 'DifferentialJIRAIssuesFieldSpecification' => 'DifferentialFieldSpecification', 2599 + 'DifferentialLandingActionMenuEventListener' => 'PhabricatorEventListener', 2600 + 'DifferentialLandingToHostedGit' => 'DifferentialLandingStrategy', 2589 2601 'DifferentialLinesFieldSpecification' => 'DifferentialFieldSpecification', 2590 2602 'DifferentialLintFieldSpecification' => 'DifferentialFieldSpecification', 2591 2603 'DifferentialLocalCommitsView' => 'AphrontView', ··· 2623 2635 'DifferentialRevisionEditor' => 'PhabricatorEditor', 2624 2636 'DifferentialRevisionIDFieldParserTestCase' => 'PhabricatorTestCase', 2625 2637 'DifferentialRevisionIDFieldSpecification' => 'DifferentialFieldSpecification', 2638 + 'DifferentialRevisionLandController' => 'DifferentialController', 2626 2639 'DifferentialRevisionListController' => 2627 2640 array( 2628 2641 0 => 'DifferentialController', ··· 2864 2877 'HarbormasterBuildPlanTransactionComment' => 'PhabricatorApplicationTransactionComment', 2865 2878 'HarbormasterBuildPlanTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 2866 2879 'HarbormasterBuildQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 2867 - 'HarbormasterBuildStep' => 'HarbormasterDAO', 2880 + 'HarbormasterBuildStep' => 2881 + array( 2882 + 0 => 'HarbormasterDAO', 2883 + 1 => 'PhabricatorPolicyInterface', 2884 + ), 2868 2885 'HarbormasterBuildStepQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 2869 2886 'HarbormasterBuildTarget' => 'HarbormasterDAO', 2870 2887 'HarbormasterBuildTargetQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 2888 + 'HarbormasterBuildWorker' => 'PhabricatorWorker', 2871 2889 'HarbormasterBuildable' => 2872 2890 array( 2873 2891 0 => 'HarbormasterDAO', 2874 2892 1 => 'PhabricatorPolicyInterface', 2875 2893 ), 2894 + 'HarbormasterBuildableApplyController' => 'HarbormasterController', 2876 2895 'HarbormasterBuildableArtifactQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 2877 2896 'HarbormasterBuildableEditController' => 'HarbormasterController', 2878 2897 'HarbormasterBuildableListController' => ··· 2895 2914 'HarbormasterPHIDTypeBuildable' => 'PhabricatorPHIDType', 2896 2915 'HarbormasterPlanController' => 'PhabricatorController', 2897 2916 'HarbormasterPlanEditController' => 'HarbormasterPlanController', 2898 - 'HarbormasterPlanExecuteController' => 'HarbormasterPlanController', 2899 2917 'HarbormasterPlanListController' => 2900 2918 array( 2901 2919 0 => 'HarbormasterPlanController', ··· 2903 2921 ), 2904 2922 'HarbormasterPlanViewController' => 'HarbormasterPlanController', 2905 2923 'HarbormasterRemarkupRule' => 'PhabricatorRemarkupRuleObject', 2906 - 'HarbormasterRunnerWorker' => 'PhabricatorWorker', 2907 2924 'HarbormasterScratchTable' => 'HarbormasterDAO', 2925 + 'HarbormasterStepAddController' => 'HarbormasterController', 2926 + 'HarbormasterStepDeleteController' => 'HarbormasterController', 2927 + 'HarbormasterStepEditController' => 'HarbormasterController', 2908 2928 'HeraldAction' => 'HeraldDAO', 2909 2929 'HeraldApplyTranscript' => 'HeraldDAO', 2910 2930 'HeraldCapabilityManageGlobalRules' => 'PhabricatorPolicyCapability', ··· 4643 4663 'ReleephStatusFieldSpecification' => 'ReleephFieldSpecification', 4644 4664 'ReleephSummaryFieldSpecification' => 'ReleephFieldSpecification', 4645 4665 'ReleephUserView' => 'AphrontView', 4666 + 'SleepBuildStepImplementation' => 'BuildStepImplementation', 4646 4667 'SlowvoteEmbedView' => 'AphrontView', 4647 4668 'SlowvoteRemarkupRule' => 'PhabricatorRemarkupRuleObject', 4648 4669 ),
+2
src/aphront/configuration/AphrontDefaultApplicationConfiguration.php
··· 222 222 223 223 $response = new AphrontWebpageResponse(); 224 224 $response->setContent($view->render()); 225 + $response->setHTTPResponseCode(500); 225 226 226 227 return $response; 227 228 } ··· 268 269 269 270 $response = new AphrontDialogResponse(); 270 271 $response->setDialog($dialog); 272 + $response->setHTTPResponseCode(500); 271 273 272 274 return $response; 273 275 }
+4 -3
src/applications/base/controller/PhabricatorController.php
··· 249 249 $view->appendChild(hsprintf( 250 250 '<div style="padding: 2em 0;">%s</div>', 251 251 $response->buildResponseString())); 252 - $response = new AphrontWebpageResponse(); 253 - $response->setContent($view->render()); 254 - return $response; 252 + $page_response = new AphrontWebpageResponse(); 253 + $page_response->setContent($view->render()); 254 + $page_response->setHTTPResponseCode($response->getHTTPResponseCode()); 255 + return $page_response; 255 256 } else { 256 257 $response->getDialog()->setIsStandalone(true); 257 258
+39
src/applications/differential/DifferentialGetWorkingCopy.php
··· 1 + <?php 2 + 3 + /** 4 + * Can't find a good place for this, so I'm putting it in the most notably 5 + * wrong place. 6 + */ 7 + final class DifferentialGetWorkingCopy { 8 + 9 + /** 10 + * Creates and/or cleans a workspace for the requested repo. 11 + * 12 + * return ArcanistGitAPI 13 + */ 14 + public static function getCleanGitWorkspace( 15 + PhabricatorRepository $repo) { 16 + 17 + $origin_path = $repo->getLocalPath(); 18 + 19 + $path = rtrim($origin_path, '/'); 20 + $path = $path . '__workspace'; 21 + 22 + if (!Filesystem::pathExists($path)) { 23 + $repo->execxLocalCommand( 24 + 'clone -- file://%s %s', 25 + $origin_path, 26 + $path); 27 + } 28 + 29 + $workspace = new ArcanistGitAPI($path); 30 + $workspace->execxLocal('clean -f -d'); 31 + $workspace->execxLocal('checkout master'); 32 + $workspace->execxLocal('fetch'); 33 + $workspace->execxLocal('reset --hard origin/master'); 34 + $workspace->reloadWorkingCopy(); 35 + 36 + return $workspace; 37 + } 38 + 39 + }
+3
src/applications/differential/application/PhabricatorApplicationDifferential.php
··· 32 32 return array( 33 33 new DifferentialActionMenuEventListener(), 34 34 new DifferentialHovercardEventListener(), 35 + new DifferentialLandingActionMenuEventListener(), 35 36 ); 36 37 } 37 38 ··· 48 49 'changeset/' => 'DifferentialChangesetViewController', 49 50 'revision/edit/(?:(?P<id>[1-9]\d*)/)?' 50 51 => 'DifferentialRevisionEditController', 52 + 'revision/land/(?:(?P<id>[1-9]\d*))/(?P<strategy>[^/]+)/' 53 + => 'DifferentialRevisionLandController', 51 54 'comment/' => array( 52 55 'preview/(?P<id>[1-9]\d*)/' => 'DifferentialCommentPreviewController', 53 56 'save/' => 'DifferentialCommentSaveController',
+130
src/applications/differential/controller/DifferentialRevisionLandController.php
··· 1 + <?php 2 + 3 + final class DifferentialRevisionLandController extends DifferentialController { 4 + 5 + private $revisionID; 6 + private $strategyClass; 7 + private $pushStrategy; 8 + 9 + public function willProcessRequest(array $data) { 10 + $this->revisionID = $data['id']; 11 + $this->strategyClass = $data['strategy']; 12 + } 13 + 14 + public function processRequest() { 15 + $request = $this->getRequest(); 16 + $viewer = $request->getUser(); 17 + 18 + $revision_id = $this->revisionID; 19 + 20 + $revision = id(new DifferentialRevisionQuery()) 21 + ->withIDs(array($revision_id)) 22 + ->setViewer($viewer) 23 + ->executeOne(); 24 + if (!$revision) { 25 + return new Aphront404Response(); 26 + } 27 + 28 + if (is_subclass_of($this->strategyClass, 'DifferentialLandingStrategy')) { 29 + $this->pushStrategy = newv($this->strategyClass, array()); 30 + } else { 31 + throw new Exception( 32 + "Strategy type must be a valid class name and must subclass ". 33 + "DifferentialLandingStrategy. ". 34 + "'{$this->strategyClass}' is not a subclass of ". 35 + "DifferentialLandingStrategy."); 36 + } 37 + 38 + if ($request->isDialogFormPost()) { 39 + try { 40 + $this->attemptLand($revision, $request); 41 + $title = pht("Success!"); 42 + $text = pht("Revision was successfully landed."); 43 + } catch (Exception $ex) { 44 + $title = pht("Failed to land revision"); 45 + $text = 'moo'; 46 + if ($ex instanceof PhutilProxyException) { 47 + $text = hsprintf( 48 + '%s:<br><pre>%s</pre>', 49 + $ex->getMessage(), 50 + $ex->getPreviousException()->getMessage()); 51 + } else { 52 + $text = hsprintf('<pre>%s</pre>', $ex->getMessage()); 53 + } 54 + $text = id(new AphrontErrorView()) 55 + ->appendChild($text); 56 + } 57 + 58 + $dialog = id(new AphrontDialogView()) 59 + ->setUser($viewer) 60 + ->setTitle($title) 61 + ->appendChild(phutil_tag('p', array(), $text)) 62 + ->setSubmitURI('/D'.$revision_id) 63 + ->addSubmitButton(pht('Done')); 64 + 65 + return id(new AphrontDialogResponse())->setDialog($dialog); 66 + } 67 + 68 + $prompt = hsprintf('%s<br><br>%s', 69 + pht( 70 + 'This will squash and rebase revision %s, and push it to '. 71 + 'origin/master.', 72 + $revision_id), 73 + pht('It is an experimental feature and may not work.')); 74 + 75 + $dialog = id(new AphrontDialogView()) 76 + ->setUser($viewer) 77 + ->setTitle(pht("Land Revision %s?", $revision_id)) 78 + ->appendChild($prompt) 79 + ->setSubmitURI($request->getRequestURI()) 80 + ->addSubmitButton(pht('Land it!')) 81 + ->addCancelButton('/D'.$revision_id); 82 + 83 + return id(new AphrontDialogResponse())->setDialog($dialog); 84 + } 85 + 86 + private function attemptLand($revision, $request) { 87 + $status = $revision->getStatus(); 88 + if ($status != ArcanistDifferentialRevisionStatus::ACCEPTED) { 89 + throw new Exception("Only Accepted revisions can be landed."); 90 + } 91 + 92 + $repository = $revision->getRepository(); 93 + 94 + if ($repository === null) { 95 + throw new Exception("revision is not attached to a repository."); 96 + } 97 + 98 + $can_push = PhabricatorPolicyFilter::hasCapability( 99 + $request->getUser(), 100 + $repository, 101 + DiffusionCapabilityPush::CAPABILITY); 102 + 103 + if (!$can_push) { 104 + throw new Exception( 105 + pht('You do not have permission to push to this repository.')); 106 + } 107 + 108 + $lock = $this->lockRepository($repository); 109 + 110 + try { 111 + $this->pushStrategy->processLandRequest( 112 + $request, 113 + $revision, 114 + $repository); 115 + } catch (Exception $e) { 116 + $lock->unlock(); 117 + throw $e; 118 + } 119 + 120 + $lock->unlock(); 121 + } 122 + 123 + private function lockRepository($repository) { 124 + $lock_name = __CLASS__.':'.($repository->getCallsign()); 125 + $lock = PhabricatorGlobalLock::newLock($lock_name); 126 + $lock->lock(); 127 + return $lock; 128 + } 129 + } 130 +
+54
src/applications/differential/landing/DifferentialLandingActionMenuEventListener.php
··· 1 + <?php 2 + 3 + final class DifferentialLandingActionMenuEventListener 4 + extends PhabricatorEventListener { 5 + 6 + public function register() { 7 + $this->listen(PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS); 8 + } 9 + 10 + public function handleEvent(PhutilEvent $event) { 11 + switch ($event->getType()) { 12 + case PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS: 13 + $this->handleActionsEvent($event); 14 + break; 15 + } 16 + } 17 + 18 + private function handleActionsEvent(PhutilEvent $event) { 19 + $object = $event->getValue('object'); 20 + 21 + $actions = null; 22 + if ($object instanceof DifferentialRevision) { 23 + $actions = $this->renderRevisionAction($event); 24 + } 25 + 26 + $this->addActionMenuItems($event, $actions); 27 + } 28 + 29 + private function renderRevisionAction(PhutilEvent $event) { 30 + if (!$this->canUseApplication($event->getUser())) { 31 + return null; 32 + } 33 + 34 + $revision = $event->getValue('object'); 35 + 36 + $repository = $revision->getRepository(); 37 + if ($repository === null) { 38 + return null; 39 + } 40 + 41 + $strategies = id(new PhutilSymbolLoader()) 42 + ->setAncestorClass('DifferentialLandingStrategy') 43 + ->loadObjects(); 44 + foreach ($strategies as $strategy) { 45 + $actions = $strategy->createMenuItems( 46 + $event->getUser(), 47 + $revision, 48 + $repository); 49 + $this->addActionMenuItems($event, $actions); 50 + } 51 + } 52 + 53 + } 54 +
+43
src/applications/differential/landing/DifferentialLandingStrategy.php
··· 1 + <?php 2 + 3 + abstract class DifferentialLandingStrategy { 4 + 5 + public abstract function processLandRequest( 6 + AphrontRequest $request, 7 + DifferentialRevision $revision, 8 + PhabricatorRepository $repository); 9 + 10 + /** 11 + * returns PhabricatorActionView or an array of PhabricatorActionView or null. 12 + */ 13 + abstract function createMenuItems( 14 + PhabricatorUser $viewer, 15 + DifferentialRevision $revision, 16 + PhabricatorRepository $repository); 17 + 18 + /** 19 + * returns PhabricatorActionView which can be attached to the revision view. 20 + */ 21 + protected function createActionView($revision, $name, $disabled = false) { 22 + $strategy = get_class($this); 23 + $revision_id = $revision->getId(); 24 + return id(new PhabricatorActionView()) 25 + ->setRenderAsForm(true) 26 + ->setName($name) 27 + ->setHref("/differential/revision/land/{$revision_id}/{$strategy}/") 28 + ->setDisabled($disabled); 29 + } 30 + 31 + /** 32 + * might break if repository is not Git. 33 + */ 34 + protected function getGitWorkspace(PhabricatorRepository $repository) { 35 + try { 36 + return DifferentialGetWorkingCopy::getCleanGitWorkspace($repository); 37 + } catch (Exception $e) { 38 + throw new PhutilProxyException ( 39 + 'Failed to allocate a workspace', 40 + $e); 41 + } 42 + } 43 + }
+136
src/applications/differential/landing/DifferentialLandingToHostedGit.php
··· 1 + <?php 2 + 3 + final class DifferentialLandingToHostedGit 4 + extends DifferentialLandingStrategy { 5 + 6 + public function processLandRequest( 7 + AphrontRequest $request, 8 + DifferentialRevision $revision, 9 + PhabricatorRepository $repository) { 10 + 11 + $viewer = $request->getUser(); 12 + 13 + $workspace = $this->getGitWorkspace($repository); 14 + 15 + try { 16 + $this->commitRevisionToWorkspace( 17 + $revision, 18 + $workspace, 19 + $viewer); 20 + } catch (Exception $e) { 21 + throw new PhutilProxyException( 22 + 'Failed to commit patch', 23 + $e); 24 + } 25 + 26 + try { 27 + $this->pushWorkspaceRepository( 28 + $repository, 29 + $workspace, 30 + $viewer); 31 + } catch (Exception $e) { 32 + throw new PhutilProxyException( 33 + 'Failed to push changes upstream', 34 + $e); 35 + } 36 + } 37 + 38 + public function commitRevisionToWorkspace( 39 + DifferentialRevision $revision, 40 + ArcanistRepositoryAPI $workspace, 41 + PhabricatorUser $user) { 42 + 43 + $diff_id = $revision->loadActiveDiff()->getID(); 44 + 45 + $call = new ConduitCall( 46 + 'differential.getrawdiff', 47 + array( 48 + 'diffID' => $diff_id, 49 + )); 50 + 51 + $call->setUser($user); 52 + $raw_diff = $call->execute(); 53 + 54 + $missing_binary = 55 + "\nindex " 56 + . "0000000000000000000000000000000000000000.." 57 + . "0000000000000000000000000000000000000000\n"; 58 + if (strpos($raw_diff, $missing_binary) !== false) { 59 + throw new Exception("Patch is missing content for a binary file"); 60 + } 61 + 62 + $future = $workspace->execFutureLocal('apply --index -'); 63 + $future->write($raw_diff); 64 + $future->resolvex(); 65 + 66 + $workspace->reloadWorkingCopy(); 67 + 68 + $call = new ConduitCall( 69 + 'differential.getcommitmessage', 70 + array( 71 + 'revision_id' => $revision->getID(), 72 + )); 73 + 74 + $call->setUser($user); 75 + $message = $call->execute(); 76 + 77 + $author = id(new PhabricatorUser())->loadOneWhere( 78 + 'phid = %s', 79 + $revision->getAuthorPHID()); 80 + 81 + $author_string = sprintf( 82 + '%s <%s>', 83 + $author->getRealName(), 84 + $author->loadPrimaryEmailAddress()); 85 + $author_date = $revision->getDateCreated(); 86 + 87 + $workspace->execxLocal( 88 + '-c user.name=%s -c user.email=%s ' . 89 + 'commit --date=%s --author=%s '. 90 + '--message=%s', 91 + // -c will set the 'committer' 92 + $user->getRealName(), 93 + $user->loadPrimaryEmailAddress(), 94 + $author_date, 95 + $author_string, 96 + $message); 97 + } 98 + 99 + 100 + public function pushWorkspaceRepository( 101 + PhabricatorRepository $repository, 102 + ArcanistRepositoryAPI $workspace, 103 + PhabricatorUser $user) { 104 + 105 + $workspace->execxLocal("push origin HEAD:master"); 106 + } 107 + 108 + public function createMenuItems( 109 + PhabricatorUser $viewer, 110 + DifferentialRevision $revision, 111 + PhabricatorRepository $repository) { 112 + 113 + $vcs = $repository->getVersionControlSystem(); 114 + if ($vcs !== PhabricatorRepositoryType::REPOSITORY_TYPE_GIT) { 115 + return; 116 + } 117 + 118 + if (!$repository->isHosted()) { 119 + return; 120 + } 121 + 122 + if (!$repository->isWorkingCopyBare()) { 123 + return; 124 + } 125 + 126 + $can_push = PhabricatorPolicyFilter::hasCapability( 127 + $viewer, 128 + $repository, 129 + DiffusionCapabilityPush::CAPABILITY); 130 + 131 + return $this->createActionView( 132 + $revision, 133 + pht('Land to Hosted Repository'), 134 + !$can_push); 135 + } 136 + }
+14
src/applications/diffusion/controller/DiffusionController.php
··· 8 8 $request = $this->getRequest(); 9 9 $uri = $request->getRequestURI(); 10 10 11 + $user_agent = idx($_SERVER, 'HTTP_USER_AGENT'); 12 + 11 13 // Check if this is a VCS request, e.g. from "git clone", "hg clone", or 12 14 // "svn checkout". If it is, we jump off into repository serving code to 13 15 // process the request. ··· 27 29 // 28 30 // ...to get a human-readable error. 29 31 $vcs = $request->getExists('__vcs__'); 32 + } else if (strncmp($user_agent, "git/", 4) === 0) { 33 + $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; 30 34 } else if ($request->getExists('service')) { 31 35 $service = $request->getStr('service'); 32 36 // We get this initially for `info/refs`. ··· 538 542 539 543 if (!PhabricatorEnv::getEnvConfig('diffusion.allow-http-auth')) { 540 544 // No HTTP auth permitted. 545 + return null; 546 + } 547 + 548 + if (!strlen($username)) { 549 + // No username. 550 + return null; 551 + } 552 + 553 + if (!strlen($password->openEnvelope())) { 554 + // No password. 541 555 return null; 542 556 } 543 557
+20 -20
src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php
··· 726 726 ->setNote($daemon_instructions)); 727 727 } 728 728 729 - $local_parent = dirname($repository->getLocalPath()); 730 - if (Filesystem::pathExists($local_parent)) { 731 - $view->addItem( 732 - id(new PHUIStatusItemView()) 733 - ->setIcon('accept-green') 734 - ->setTarget(pht('Storage Directory OK')) 735 - ->setNote(phutil_tag('tt', array(), $local_parent))); 736 - } else { 737 - $view->addItem( 738 - id(new PHUIStatusItemView()) 739 - ->setIcon('warning-red') 740 - ->setTarget(pht('No Storage Directory')) 741 - ->setNote( 742 - pht( 743 - 'Storage directory %s does not exist, or is not readable by '. 744 - 'the webserver. Create this directory or make it readable.', 745 - phutil_tag('tt', array(), $local_parent)))); 746 - return $view; 747 - } 748 - 749 729 if ($repository->usesLocalWorkingCopy()) { 730 + $local_parent = dirname($repository->getLocalPath()); 731 + if (Filesystem::pathExists($local_parent)) { 732 + $view->addItem( 733 + id(new PHUIStatusItemView()) 734 + ->setIcon('accept-green') 735 + ->setTarget(pht('Storage Directory OK')) 736 + ->setNote(phutil_tag('tt', array(), $local_parent))); 737 + } else { 738 + $view->addItem( 739 + id(new PHUIStatusItemView()) 740 + ->setIcon('warning-red') 741 + ->setTarget(pht('No Storage Directory')) 742 + ->setNote( 743 + pht( 744 + 'Storage directory %s does not exist, or is not readable by '. 745 + 'the webserver. Create this directory or make it readable.', 746 + phutil_tag('tt', array(), $local_parent)))); 747 + return $view; 748 + } 749 + 750 750 $local_path = $repository->getLocalPath(); 751 751 $message = idx($messages, PhabricatorRepositoryStatusMessage::TYPE_INIT); 752 752 if ($message) {
+6 -2
src/applications/diffusion/request/DiffusionGitRequest.php
··· 32 32 return $this->commit; 33 33 } 34 34 35 + return $this->getResolvableBranchName($this->getBranch()); 36 + } 37 + 38 + protected function getResolvableBranchName($branch) { 35 39 if ($this->repository->isWorkingCopyBare()) { 36 - return $this->getBranch(); 40 + return $branch; 37 41 } else { 38 42 $remote = DiffusionBranchInformation::DEFAULT_GIT_REMOTE; 39 - return $remote.'/'.$this->getBranch(); 43 + return $remote.'/'.$branch; 40 44 } 41 45 } 42 46
+5 -1
src/applications/diffusion/request/DiffusionRequest.php
··· 643 643 } 644 644 645 645 if ($this->getSupportsBranches()) { 646 - $branch = $this->getBranch(); 646 + $branch = $this->getResolvableBranchName($this->getBranch()); 647 647 } else { 648 648 $branch = 'HEAD'; 649 649 } ··· 658 658 659 659 $this->stableCommitName = idx(head($matches), 'identifier'); 660 660 return $this->stableCommitName; 661 + } 662 + 663 + protected function getResolvableBranchName($branch) { 664 + return $branch; 661 665 } 662 666 663 667 private function resolveRefs(array $refs) {
+6 -1
src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php
··· 44 44 => 'HarbormasterBuildableListController', 45 45 'buildable/' => array( 46 46 'edit/(?:(?P<id>\d+)/)?' => 'HarbormasterBuildableEditController', 47 + 'apply/(?:(?P<id>\d+)/)?' => 'HarbormasterBuildableApplyController', 48 + ), 49 + 'step/' => array( 50 + 'add/(?:(?P<id>\d+)/)?' => 'HarbormasterStepAddController', 51 + 'edit/(?:(?P<id>\d+)/)?' => 'HarbormasterStepEditController', 52 + 'delete/(?:(?P<id>\d+)/)?' => 'HarbormasterStepDeleteController', 47 53 ), 48 54 'plan/' => array( 49 55 '(?:query/(?P<queryKey>[^/]+)/)?' 50 56 => 'HarbormasterPlanListController', 51 57 'edit/(?:(?P<id>\d+)/)?' => 'HarbormasterPlanEditController', 52 58 '(?P<id>\d+)/' => 'HarbormasterPlanViewController', 53 - 'execute/(?P<id>\d+)/' => 'HarbormasterPlanExecuteController', 54 59 ), 55 60 ), 56 61 );
+81
src/applications/harbormaster/controller/HarbormasterBuildableApplyController.php
··· 1 + <?php 2 + 3 + final class HarbormasterBuildableApplyController 4 + extends HarbormasterController { 5 + 6 + private $id; 7 + 8 + public function willProcessRequest(array $data) { 9 + $this->id = $data['id']; 10 + } 11 + 12 + public function processRequest() { 13 + $request = $this->getRequest(); 14 + $viewer = $request->getUser(); 15 + 16 + $id = $this->id; 17 + 18 + $buildable = id(new HarbormasterBuildableQuery()) 19 + ->setViewer($viewer) 20 + ->withIDs(array($id)) 21 + ->executeOne(); 22 + if ($buildable === null) { 23 + throw new Exception("Buildable not found!"); 24 + } 25 + 26 + $buildable_uri = '/B'.$buildable->getID(); 27 + 28 + if ($request->isDialogFormPost()) { 29 + $plan = id(new HarbormasterBuildPlanQuery()) 30 + ->setViewer($viewer) 31 + ->withIDs(array($request->getInt('build-plan'))) 32 + ->executeOne(); 33 + 34 + $build = HarbormasterBuild::initializeNewBuild($viewer); 35 + $build->setBuildablePHID($buildable->getPHID()); 36 + $build->setBuildPlanPHID($plan->getPHID()); 37 + $build->setBuildStatus(HarbormasterBuild::STATUS_PENDING); 38 + $build->save(); 39 + 40 + PhabricatorWorker::scheduleTask( 41 + 'HarbormasterBuildWorker', 42 + array( 43 + 'buildID' => $build->getID() 44 + )); 45 + 46 + return id(new AphrontRedirectResponse())->setURI($buildable_uri); 47 + } 48 + 49 + $plans = id(new HarbormasterBuildPlanQuery()) 50 + ->setViewer($viewer) 51 + ->execute(); 52 + 53 + $options = array(); 54 + foreach ($plans as $plan) { 55 + $options[$plan->getID()] = $plan->getName(); 56 + } 57 + 58 + // FIXME: I'd really like to use the dialog that "Edit Differential 59 + // Revisions" uses, but that code is quite hard-coded for the particular 60 + // uses, so for now we just give a single dropdown. 61 + 62 + $dialog = new AphrontDialogView(); 63 + $dialog->setTitle(pht('Apply which plan?')) 64 + ->setUser($viewer) 65 + ->addSubmitButton(pht('Apply')) 66 + ->addCancelButton($buildable_uri); 67 + $dialog->appendChild( 68 + phutil_tag( 69 + 'p', 70 + array(), 71 + pht( 72 + 'Select what build plan you want to apply to this buildable:'))); 73 + $dialog->appendChild( 74 + id(new AphrontFormSelectControl()) 75 + ->setUser($viewer) 76 + ->setName('build-plan') 77 + ->setOptions($options)); 78 + return id(new AphrontDialogResponse())->setDialog($dialog); 79 + } 80 + 81 + }
+1 -1
src/applications/harbormaster/controller/HarbormasterBuildableListController.php
··· 36 36 $id = $buildable->getID(); 37 37 38 38 $item = id(new PHUIObjectItemView()) 39 - ->setHeader(pht('Build %d', $buildable->getID())); 39 + ->setHeader(pht('Buildable %d', $buildable->getID())); 40 40 41 41 if ($id) { 42 42 $item->setHref("/B{$id}");
+39
src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
··· 37 37 $item = id(new PHUIObjectItemView()) 38 38 ->setObjectName(pht('Build %d', $build->getID())) 39 39 ->setHeader($build->getName()); 40 + switch ($build->getBuildStatus()) { 41 + case HarbormasterBuild::STATUS_INACTIVE: 42 + $item->setBarColor('grey'); 43 + $item->addAttribute(pht('Inactive')); 44 + break; 45 + case HarbormasterBuild::STATUS_PENDING: 46 + $item->setBarColor('blue'); 47 + $item->addAttribute(pht('Pending')); 48 + break; 49 + case HarbormasterBuild::STATUS_WAITING: 50 + $item->setBarColor('blue'); 51 + $item->addAttribute(pht('Waiting on Resource')); 52 + break; 53 + case HarbormasterBuild::STATUS_BUILDING: 54 + $item->setBarColor('yellow'); 55 + $item->addAttribute(pht('Building')); 56 + break; 57 + case HarbormasterBuild::STATUS_PASSED: 58 + $item->setBarColor('green'); 59 + $item->addAttribute(pht('Passed')); 60 + break; 61 + case HarbormasterBuild::STATUS_FAILED: 62 + $item->setBarColor('red'); 63 + $item->addAttribute(pht('Failed')); 64 + break; 65 + case HarbormasterBuild::STATUS_ERROR: 66 + $item->setBarColor('red'); 67 + $item->addAttribute(pht('Unexpected Error')); 68 + break; 69 + } 40 70 $build_list->addItem($item); 41 71 } 42 72 ··· 79 109 ->setUser($viewer) 80 110 ->setObject($buildable) 81 111 ->setObjectURI("/B{$id}"); 112 + 113 + $apply_uri = $this->getApplicationURI('/buildable/apply/'.$id.'/'); 114 + 115 + $list->addAction( 116 + id(new PhabricatorActionView()) 117 + ->setName(pht('Apply Build Plan')) 118 + ->setIcon('edit') 119 + ->setHref($apply_uri) 120 + ->setWorkflow(true)); 82 121 83 122 return $list; 84 123 }
-88
src/applications/harbormaster/controller/HarbormasterPlanExecuteController.php
··· 1 - <?php 2 - 3 - final class HarbormasterPlanExecuteController 4 - extends HarbormasterPlanController { 5 - 6 - private $id; 7 - 8 - public function willProcessRequest(array $data) { 9 - $this->id = $data['id']; 10 - } 11 - 12 - public function processRequest() { 13 - $request = $this->getRequest(); 14 - $viewer = $request->getUser(); 15 - 16 - $this->requireApplicationCapability( 17 - HarbormasterCapabilityManagePlans::CAPABILITY); 18 - 19 - $id = $this->id; 20 - 21 - $plan = id(new HarbormasterBuildPlanQuery()) 22 - ->setViewer($viewer) 23 - ->withIDs(array($id)) 24 - ->executeOne(); 25 - if (!$plan) { 26 - return new Aphront404Response(); 27 - } 28 - 29 - $cancel_uri = $this->getApplicationURI("plan/{$id}/"); 30 - 31 - $v_buildable = null; 32 - $e_buildable = null; 33 - 34 - $errors = array(); 35 - if ($request->isFormPost()) { 36 - $v_buildable = $request->getStr('buildable'); 37 - 38 - if ($v_buildable) { 39 - $buildable = id(new HarbormasterBuildableQuery()) 40 - ->setViewer($viewer) 41 - ->withIDs(array(trim($v_buildable, 'B'))) 42 - ->executeOne(); 43 - if (!$buildable) { 44 - $e_buildable = pht('Invalid'); 45 - } 46 - } else { 47 - $e_buildable = pht('Required'); 48 - $errors[] = pht('You must provide a buildable.'); 49 - } 50 - 51 - if (!$errors) { 52 - $build_plan = HarbormasterBuild::initializeNewBuild($viewer) 53 - ->setBuildablePHID($buildable->getPHID()) 54 - ->setBuildPlanPHID($plan->getPHID()) 55 - ->save(); 56 - 57 - $buildable_id = $buildable->getID(); 58 - 59 - return id(new AphrontRedirectResponse()) 60 - ->setURI("/B{$buildable_id}"); 61 - } 62 - } 63 - 64 - if ($errors) { 65 - $errors = id(new AphrontErrorView())->setErrors($errors); 66 - } 67 - 68 - $form = id(new PHUIFormLayoutView()) 69 - ->appendChild( 70 - id(new AphrontFormTextControl()) 71 - ->setLabel(pht('Buildable')) 72 - ->setName('buildable') 73 - ->setValue($v_buildable) 74 - ->setError($e_buildable)); 75 - 76 - $dialog = id(new AphrontDialogView()) 77 - ->setUser($viewer) 78 - ->setTitle(pht('Execute Build Plan')) 79 - ->setWidth(AphrontDialogView::WIDTH_FORM) 80 - ->appendChild($errors) 81 - ->appendChild($form) 82 - ->addSubmitButton(pht('Execute Build Plan')) 83 - ->addCancelButton($cancel_uri); 84 - 85 - return id(new AphrontDialogResponse())->setDialog($dialog); 86 - } 87 - 88 - }
+57 -4
src/applications/harbormaster/controller/HarbormasterPlanViewController.php
··· 55 55 id(new PhabricatorCrumbView()) 56 56 ->setName(pht("Plan %d", $id))); 57 57 58 + $step_list = $this->buildStepList($plan); 59 + 58 60 return $this->buildApplicationPage( 59 61 array( 60 62 $crumbs, 61 63 $box, 64 + $step_list, 62 65 $xaction_view, 63 66 ), 64 67 array( ··· 67 70 )); 68 71 } 69 72 73 + private function buildStepList(HarbormasterBuildPlan $plan) { 74 + $request = $this->getRequest(); 75 + $viewer = $request->getUser(); 76 + 77 + $steps = id(new HarbormasterBuildStepQuery()) 78 + ->setViewer($viewer) 79 + ->withBuildPlanPHIDs(array($plan->getPHID())) 80 + ->execute(); 81 + 82 + $can_edit = $this->hasApplicationCapability( 83 + HarbormasterCapabilityManagePlans::CAPABILITY); 84 + 85 + $i = 1; 86 + $step_list = id(new PHUIObjectItemListView()) 87 + ->setUser($viewer); 88 + foreach ($steps as $step) { 89 + $implementation = $step->getStepImplementation(); 90 + $item = id(new PHUIObjectItemView()) 91 + ->setObjectName("Step ".$i++) 92 + ->setHeader($implementation->getName()); 93 + 94 + if (!$implementation->validateSettings()) { 95 + $item 96 + ->setBarColor('red') 97 + ->addAttribute(pht('This step is not configured correctly.')); 98 + } else { 99 + $item->addAttribute($implementation->getDescription()); 100 + } 101 + 102 + if ($can_edit) { 103 + $edit_uri = $this->getApplicationURI("step/edit/".$step->getID()."/"); 104 + $item 105 + ->setHref($edit_uri) 106 + ->addAction( 107 + id(new PHUIListItemView()) 108 + ->setIcon('delete') 109 + ->addSigil('harbormaster-build-step-delete') 110 + ->setWorkflow(true) 111 + ->setRenderNameAsTooltip(true) 112 + ->setName(pht("Delete")) 113 + ->setHref( 114 + $this->getApplicationURI("step/delete/".$step->getID()."/"))); 115 + } 116 + 117 + $step_list->addItem($item); 118 + } 119 + 120 + return $step_list; 121 + } 122 + 70 123 private function buildActionList(HarbormasterBuildPlan $plan) { 71 124 $request = $this->getRequest(); 72 125 $viewer = $request->getUser(); ··· 90 143 91 144 $list->addAction( 92 145 id(new PhabricatorActionView()) 93 - ->setName(pht('Manually Execute Plan')) 94 - ->setHref($this->getApplicationURI("plan/execute/{$id}/")) 95 - ->setWorkflow(true) 146 + ->setName(pht('Add Build Step')) 147 + ->setHref($this->getApplicationURI("step/add/{$id}/")) 148 + ->setWorkflow($can_edit) 96 149 ->setDisabled(!$can_edit) 97 - ->setIcon('arrow_right')); 150 + ->setIcon('new')); 98 151 99 152 return $list; 100 153 }
+84
src/applications/harbormaster/controller/HarbormasterStepAddController.php
··· 1 + <?php 2 + 3 + final class HarbormasterStepAddController 4 + extends HarbormasterController { 5 + 6 + private $id; 7 + 8 + public function willProcessRequest(array $data) { 9 + $this->id = $data['id']; 10 + } 11 + 12 + public function processRequest() { 13 + $request = $this->getRequest(); 14 + $viewer = $request->getUser(); 15 + 16 + $this->requireApplicationCapability( 17 + HarbormasterCapabilityManagePlans::CAPABILITY); 18 + 19 + $id = $this->id; 20 + 21 + $plan = id(new HarbormasterBuildPlanQuery()) 22 + ->setViewer($viewer) 23 + ->withIDs(array($id)) 24 + ->executeOne(); 25 + if ($plan === null) { 26 + throw new Exception("Build plan not found!"); 27 + } 28 + 29 + $implementations = BuildStepImplementation::getImplementations(); 30 + 31 + $cancel_uri = $this->getApplicationURI('plan/'.$plan->getID().'/'); 32 + 33 + if ($request->isDialogFormPost()) { 34 + $class = $request->getStr('step-type'); 35 + if (!in_array($class, $implementations)) { 36 + return $this->createDialog($implementations); 37 + } 38 + 39 + $step = new HarbormasterBuildStep(); 40 + $step->setBuildPlanPHID($plan->getPHID()); 41 + $step->setClassName($class); 42 + $step->setDetails(array()); 43 + $step->save(); 44 + 45 + $edit_uri = $this->getApplicationURI("step/edit/".$step->getID()."/"); 46 + 47 + return id(new AphrontRedirectResponse())->setURI($edit_uri); 48 + } 49 + 50 + return $this->createDialog($implementations, $cancel_uri); 51 + } 52 + 53 + function createDialog(array $implementations, $cancel_uri) { 54 + $request = $this->getRequest(); 55 + $viewer = $request->getUser(); 56 + 57 + $control = id(new AphrontFormRadioButtonControl()) 58 + ->setName('step-type'); 59 + 60 + foreach ($implementations as $implementation_name) { 61 + $implementation = new $implementation_name(); 62 + $control 63 + ->addButton( 64 + $implementation_name, 65 + $implementation->getName(), 66 + $implementation->getGenericDescription()); 67 + } 68 + 69 + $dialog = new AphrontDialogView(); 70 + $dialog->setTitle(pht('Add New Step')) 71 + ->setUser($viewer) 72 + ->addSubmitButton(pht('Add Build Step')) 73 + ->addCancelButton($cancel_uri); 74 + $dialog->appendChild( 75 + phutil_tag( 76 + 'p', 77 + array(), 78 + pht( 79 + 'Select what type of build step you want to add: '))); 80 + $dialog->appendChild($control); 81 + return id(new AphrontDialogResponse())->setDialog($dialog); 82 + } 83 + 84 + }
+52
src/applications/harbormaster/controller/HarbormasterStepDeleteController.php
··· 1 + <?php 2 + 3 + final class HarbormasterStepDeleteController 4 + extends HarbormasterController { 5 + 6 + private $id; 7 + 8 + public function willProcessRequest(array $data) { 9 + $this->id = $data['id']; 10 + } 11 + 12 + public function processRequest() { 13 + $request = $this->getRequest(); 14 + $viewer = $request->getUser(); 15 + 16 + $this->requireApplicationCapability( 17 + HarbormasterCapabilityManagePlans::CAPABILITY); 18 + 19 + $id = $this->id; 20 + 21 + $step = id(new HarbormasterBuildStepQuery()) 22 + ->setViewer($viewer) 23 + ->withIDs(array($id)) 24 + ->executeOne(); 25 + if ($step === null) { 26 + throw new Exception("Build step not found!"); 27 + } 28 + 29 + $plan_id = $step->getBuildPlan()->getID(); 30 + $done_uri = $this->getApplicationURI('plan/'.$plan_id.'/'); 31 + 32 + if ($request->isDialogFormPost()) { 33 + $step->delete(); 34 + return id(new AphrontRedirectResponse())->setURI($done_uri); 35 + } 36 + 37 + $dialog = new AphrontDialogView(); 38 + $dialog->setTitle(pht('Really Delete Step?')) 39 + ->setUser($viewer) 40 + ->addSubmitButton(pht('Delete Build Step')) 41 + ->addCancelButton($done_uri); 42 + $dialog->appendChild( 43 + phutil_tag( 44 + 'p', 45 + array(), 46 + pht( 47 + 'Are you sure you want to delete this '. 48 + 'step? This can\'t be undone!'))); 49 + return id(new AphrontDialogResponse())->setDialog($dialog); 50 + } 51 + 52 + }
+156
src/applications/harbormaster/controller/HarbormasterStepEditController.php
··· 1 + <?php 2 + 3 + final class HarbormasterStepEditController 4 + extends HarbormasterController { 5 + 6 + private $id; 7 + 8 + public function willProcessRequest(array $data) { 9 + $this->id = idx($data, 'id'); 10 + } 11 + 12 + public function processRequest() { 13 + $request = $this->getRequest(); 14 + $viewer = $request->getUser(); 15 + 16 + $this->requireApplicationCapability( 17 + HarbormasterCapabilityManagePlans::CAPABILITY); 18 + 19 + $step = id(new HarbormasterBuildStepQuery()) 20 + ->setViewer($viewer) 21 + ->withIDs(array($this->id)) 22 + ->executeOne(); 23 + if (!$step) { 24 + return new Aphront404Response(); 25 + } 26 + 27 + $plan = id(new HarbormasterBuildPlanQuery()) 28 + ->setViewer($viewer) 29 + ->withPHIDs(array($step->getBuildPlanPHID())) 30 + ->executeOne(); 31 + if (!$plan) { 32 + return new Aphront404Response(); 33 + } 34 + 35 + $implementation = $step->getStepImplementation(); 36 + $implementation->validateSettingDefinitions(); 37 + $settings = $implementation->getSettings(); 38 + 39 + $errors = array(); 40 + if ($request->isFormPost()) { 41 + foreach ($implementation->getSettingDefinitions() as $name => $opt) { 42 + $readable_name = $this->getReadableName($name, $opt); 43 + $value = $this->getValueFromRequest($request, $name, $opt['type']); 44 + 45 + // TODO: This won't catch any validation issues unless the field 46 + // is missing completely. How should we check if the user is 47 + // required to enter an integer? 48 + if ($value === null) { 49 + $errors[] = $readable_name.' is not valid.'; 50 + } else { 51 + $step->setDetail($name, $value); 52 + } 53 + } 54 + 55 + if (count($errors) === 0) { 56 + $step->save(); 57 + 58 + return id(new AphrontRedirectResponse()) 59 + ->setURI($this->getApplicationURI('plan/'.$plan->getID().'/')); 60 + } 61 + } 62 + 63 + $form = id(new AphrontFormView()) 64 + ->setUser($viewer); 65 + 66 + // We need to render out all of the fields for the settings that 67 + // the implementation has. 68 + foreach ($implementation->getSettingDefinitions() as $name => $opt) { 69 + if ($request->isFormPost()) { 70 + $value = $this->getValueFromRequest($request, $name, $opt['type']); 71 + } else { 72 + $value = $settings[$name]; 73 + } 74 + 75 + switch ($opt['type']) { 76 + case BuildStepImplementation::SETTING_TYPE_STRING: 77 + case BuildStepImplementation::SETTING_TYPE_INTEGER: 78 + $control = id(new AphrontFormTextControl()) 79 + ->setLabel($this->getReadableName($name, $opt)) 80 + ->setName($name) 81 + ->setValue($value); 82 + break; 83 + case BuildStepImplementation::SETTING_TYPE_BOOLEAN: 84 + $control = id(new AphrontFormCheckboxControl()) 85 + ->setLabel($this->getReadableName($name, $opt)) 86 + ->setName($name) 87 + ->setValue($value); 88 + break; 89 + default: 90 + throw new Exception("Unable to render field with unknown type."); 91 + } 92 + 93 + if (isset($opt['description'])) { 94 + $control->setCaption($opt['description']); 95 + } 96 + 97 + $form->appendChild($control); 98 + } 99 + 100 + $form->appendChild( 101 + id(new AphrontFormSubmitControl()) 102 + ->setValue(pht('Save Step Configuration')) 103 + ->addCancelButton( 104 + $this->getApplicationURI('plan/'.$plan->getID().'/'))); 105 + 106 + $box = id(new PHUIObjectBoxView()) 107 + ->setHeaderText('Edit Step: '.$implementation->getName()) 108 + ->setValidationException(null) 109 + ->setForm($form); 110 + 111 + $crumbs = $this->buildApplicationCrumbs(); 112 + $id = $plan->getID(); 113 + $crumbs->addCrumb( 114 + id(new PhabricatorCrumbView()) 115 + ->setName(pht("Plan %d", $id)) 116 + ->setHref($this->getApplicationURI("plan/{$id}/"))); 117 + $crumbs->addCrumb( 118 + id(new PhabricatorCrumbView()) 119 + ->setName(pht('Edit Step'))); 120 + 121 + return $this->buildApplicationPage( 122 + array( 123 + $crumbs, 124 + $box, 125 + ), 126 + array( 127 + 'title' => $implementation->getName(), 128 + 'device' => true, 129 + )); 130 + } 131 + 132 + public function getReadableName($name, $opt) { 133 + $readable_name = $name; 134 + if (isset($opt['name'])) { 135 + $readable_name = $opt['name']; 136 + } 137 + return $readable_name; 138 + } 139 + 140 + public function getValueFromRequest(AphrontRequest $request, $name, $type) { 141 + switch ($type) { 142 + case BuildStepImplementation::SETTING_TYPE_STRING: 143 + return $request->getStr($name); 144 + break; 145 + case BuildStepImplementation::SETTING_TYPE_INTEGER: 146 + return $request->getInt($name); 147 + break; 148 + case BuildStepImplementation::SETTING_TYPE_BOOLEAN: 149 + return $request->getBool($name); 150 + break; 151 + default: 152 + throw new Exception("Unsupported setting type '".$type."'."); 153 + } 154 + } 155 + 156 + }
+13
src/applications/harbormaster/query/HarbormasterBuildQuery.php
··· 5 5 6 6 private $ids; 7 7 private $phids; 8 + private $buildStatuses; 8 9 private $buildablePHIDs; 9 10 private $buildPlanPHIDs; 10 11 ··· 17 18 18 19 public function withPHIDs(array $phids) { 19 20 $this->phids = $phids; 21 + return $this; 22 + } 23 + 24 + public function withBuildStatuses(array $build_statuses) { 25 + $this->buildStatuses = $build_statuses; 20 26 return $this; 21 27 } 22 28 ··· 113 119 $conn_r, 114 120 'phid in (%Ls)', 115 121 $this->phids); 122 + } 123 + 124 + if ($this->buildStatuses) { 125 + $where[] = qsprintf( 126 + $conn_r, 127 + 'buildStatus in (%Ls)', 128 + $this->buildStatuses); 116 129 } 117 130 118 131 if ($this->buildablePHIDs) {
+46
src/applications/harbormaster/query/HarbormasterBuildStepQuery.php
··· 5 5 6 6 private $ids; 7 7 private $phids; 8 + private $buildPlanPHIDs; 8 9 9 10 public function withIDs(array $ids) { 10 11 $this->ids = $ids; ··· 16 17 return $this; 17 18 } 18 19 20 + public function withBuildPlanPHIDs(array $phids) { 21 + $this->buildPlanPHIDs = $phids; 22 + return $this; 23 + } 24 + 25 + public function getPagingColumn() { 26 + return 'id'; 27 + } 28 + 29 + public function getReversePaging() { 30 + return true; 31 + } 32 + 19 33 protected function loadPage() { 20 34 $table = new HarbormasterBuildStep(); 21 35 $conn_r = $table->establishConnection('r'); ··· 48 62 $this->phids); 49 63 } 50 64 65 + if ($this->buildPlanPHIDs) { 66 + $where[] = qsprintf( 67 + $conn_r, 68 + 'buildPlanPHID in (%Ls)', 69 + $this->buildPlanPHIDs); 70 + } 71 + 51 72 $where[] = $this->buildPagingClause($conn_r); 52 73 53 74 return $this->formatWhereClause($where); 75 + } 76 + 77 + protected function willFilterPage(array $page) { 78 + $plans = array(); 79 + 80 + $buildplan_phids = array_filter(mpull($page, 'getBuildPlanPHID')); 81 + if ($buildplan_phids) { 82 + $plans = id(new PhabricatorObjectQuery()) 83 + ->setViewer($this->getViewer()) 84 + ->withPHIDs($buildplan_phids) 85 + ->setParentQuery($this) 86 + ->execute(); 87 + $plans = mpull($plans, null, 'getPHID'); 88 + } 89 + 90 + foreach ($page as $key => $build) { 91 + $buildable_phid = $build->getBuildPlanPHID(); 92 + if (empty($plans[$buildable_phid])) { 93 + unset($page[$key]); 94 + continue; 95 + } 96 + $build->attachBuildPlan($plans[$buildable_phid]); 97 + } 98 + 99 + return $page; 54 100 } 55 101 56 102 public function getQueryApplicationClass() {
+88
src/applications/harbormaster/step/BuildStepImplementation.php
··· 1 + <?php 2 + 3 + abstract class BuildStepImplementation { 4 + 5 + private $settings; 6 + 7 + const SETTING_TYPE_STRING = 'string'; 8 + const SETTING_TYPE_INTEGER = 'integer'; 9 + const SETTING_TYPE_BOOLEAN = 'boolean'; 10 + 11 + public static function getImplementations() { 12 + $symbols = id(new PhutilSymbolLoader()) 13 + ->setAncestorClass("BuildStepImplementation") 14 + ->setConcreteOnly(true) 15 + ->selectAndLoadSymbols(); 16 + return ipull($symbols, 'name'); 17 + } 18 + 19 + /** 20 + * The name of the implementation. 21 + */ 22 + abstract public function getName(); 23 + 24 + /** 25 + * The generic description of the implementation. 26 + */ 27 + public function getGenericDescription() { 28 + return ''; 29 + } 30 + 31 + /** 32 + * The description of the implementation, based on the current settings. 33 + */ 34 + public function getDescription() { 35 + return ''; 36 + } 37 + 38 + /** 39 + * Run the build step against the specified build. 40 + */ 41 + abstract public function execute(HarbormasterBuild $build); 42 + 43 + /** 44 + * Gets the settings for this build step. 45 + */ 46 + public function getSettings() { 47 + return $this->settings; 48 + } 49 + 50 + /** 51 + * Validate the current settings of this build step. 52 + */ 53 + public function validate() { 54 + return true; 55 + } 56 + 57 + /** 58 + * Loads the settings for this build step implementation from the build step. 59 + */ 60 + public final function loadSettings(HarbormasterBuildStep $build_step) { 61 + $this->settings = array(); 62 + $this->validateSettingDefinitions(); 63 + foreach ($this->getSettingDefinitions() as $name => $opt) { 64 + $this->settings[$name] = $build_step->getDetail($name); 65 + } 66 + return $this->settings; 67 + } 68 + 69 + /** 70 + * Validates that the setting definitions for this implementation are valid. 71 + */ 72 + public final function validateSettingDefinitions() { 73 + foreach ($this->getSettingDefinitions() as $name => $opt) { 74 + if (!isset($opt['type'])) { 75 + throw new Exception( 76 + 'Setting definition \''.$name. 77 + '\' is missing type requirement.'); 78 + } 79 + } 80 + } 81 + 82 + /** 83 + * Return an array of settings for this step implementation. 84 + */ 85 + public function getSettingDefinitions() { 86 + return array(); 87 + } 88 + }
+45
src/applications/harbormaster/step/SleepBuildStepImplementation.php
··· 1 + <?php 2 + 3 + final class SleepBuildStepImplementation extends BuildStepImplementation { 4 + 5 + public function getName() { 6 + return pht('Sleep'); 7 + } 8 + 9 + public function getGenericDescription() { 10 + return pht('Sleep for a specified number of seconds.'); 11 + } 12 + 13 + public function getDescription() { 14 + $settings = $this->getSettings(); 15 + 16 + return pht('Sleep for %s seconds.', $settings['seconds']); 17 + } 18 + 19 + public function execute(HarbormasterBuild $build) { 20 + $settings = $this->getSettings(); 21 + 22 + sleep($settings['seconds']); 23 + } 24 + 25 + public function validateSettings() { 26 + $settings = $this->getSettings(); 27 + 28 + if ($settings['seconds'] === null) { 29 + return false; 30 + } 31 + if (!is_int($settings['seconds'])) { 32 + return false; 33 + } 34 + return true; 35 + } 36 + 37 + public function getSettingDefinitions() { 38 + return array( 39 + 'seconds' => array( 40 + 'name' => 'Seconds', 41 + 'description' => 'The number of seconds to sleep for.', 42 + 'type' => BuildStepImplementation::SETTING_TYPE_INTEGER)); 43 + } 44 + 45 + }
+4 -2
src/applications/harbormaster/storage/HarbormasterBuildable.php
··· 12 12 private $containerObject = self::ATTACHABLE; 13 13 private $buildableHandle = self::ATTACHABLE; 14 14 15 + const STATUS_WHATEVER = 'whatever'; 16 + 15 17 public static function initializeNewBuildable(PhabricatorUser $actor) { 16 18 return id(new HarbormasterBuildable()) 17 - ->setBuildStatus('new') // TODO: Define these. 18 - ->setBuildableStatus('active'); // TODO: Define these, too. 19 + ->setBuildStatus(self::STATUS_WHATEVER) 20 + ->setBuildableStatus(self::STATUS_WHATEVER); 19 21 } 20 22 21 23 public function getConfiguration() {
+36 -1
src/applications/harbormaster/storage/build/HarbormasterBuild.php
··· 10 10 private $buildable = self::ATTACHABLE; 11 11 private $buildPlan = self::ATTACHABLE; 12 12 13 + /** 14 + * Not currently being built. 15 + */ 16 + const STATUS_INACTIVE = 'inactive'; 17 + 18 + /** 19 + * Pending pick up by the Harbormaster daemon. 20 + */ 21 + const STATUS_PENDING = 'pending'; 22 + 23 + /** 24 + * Waiting for a resource to be allocated (not yet relevant). 25 + */ 26 + const STATUS_WAITING = 'waiting'; 27 + 28 + /** 29 + * Current building the buildable. 30 + */ 31 + const STATUS_BUILDING = 'building'; 32 + 33 + /** 34 + * The build has passed. 35 + */ 36 + const STATUS_PASSED = 'passed'; 37 + 38 + /** 39 + * The build has failed. 40 + */ 41 + const STATUS_FAILED = 'failed'; 42 + 43 + /** 44 + * The build encountered an unexpected error. 45 + */ 46 + const STATUS_ERROR = 'error'; 47 + 13 48 public static function initializeNewBuild(PhabricatorUser $actor) { 14 49 return id(new HarbormasterBuild()) 15 - ->setBuildStatus('building'); // TODO: Sort this. 50 + ->setBuildStatus(self::STATUS_INACTIVE); 16 51 } 17 52 18 53 public function getConfiguration() {
+57 -1
src/applications/harbormaster/storage/configuration/HarbormasterBuildStep.php
··· 1 1 <?php 2 2 3 - final class HarbormasterBuildStep extends HarbormasterDAO { 3 + final class HarbormasterBuildStep extends HarbormasterDAO 4 + implements PhabricatorPolicyInterface { 4 5 5 6 protected $buildPlanPHID; 7 + protected $className; 8 + protected $details = array(); 6 9 7 10 private $buildPlan = self::ATTACHABLE; 8 11 9 12 public function getConfiguration() { 10 13 return array( 11 14 self::CONFIG_AUX_PHID => true, 15 + self::CONFIG_SERIALIZATION => array( 16 + 'details' => self::SERIALIZATION_JSON, 17 + ) 12 18 ) + parent::getConfiguration(); 13 19 } 14 20 ··· 26 32 return $this->assertAttached($this->buildPlan); 27 33 } 28 34 35 + public function getDetail($key, $default = null) { 36 + return idx($this->details, $key, $default); 37 + } 38 + 39 + public function setDetail($key, $value) { 40 + $this->details[$key] = $value; 41 + return $this; 42 + } 43 + 44 + public function getStepImplementation() { 45 + if ($this->className === null) { 46 + throw new Exception("No implementation set for the given step."); 47 + } 48 + 49 + static $implementations = null; 50 + if ($implementations === null) { 51 + $implementations = BuildStepImplementation::getImplementations(); 52 + } 53 + 54 + $class = $this->className; 55 + if (!in_array($class, $implementations)) { 56 + throw new Exception( 57 + "Class name '".$class."' does not extend BuildStepImplementation."); 58 + } 59 + $implementation = newv($class, array()); 60 + $implementation->loadSettings($this); 61 + return $implementation; 62 + } 63 + 64 + 65 + /* -( PhabricatorPolicyInterface )----------------------------------------- */ 66 + 67 + 68 + public function getCapabilities() { 69 + return array( 70 + PhabricatorPolicyCapability::CAN_VIEW, 71 + ); 72 + } 73 + 74 + public function getPolicy($capability) { 75 + return $this->getBuildPlan()->getPolicy($capability); 76 + } 77 + 78 + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 79 + return $this->getBuildPlan()->hasAutomaticCapability($capability, $viewer); 80 + } 81 + 82 + public function describeAutomaticCapability($capability) { 83 + return pht('A build step has the same policies as its build plan.'); 84 + } 29 85 }
+69
src/applications/harbormaster/worker/HarbormasterBuildWorker.php
··· 1 + <?php 2 + 3 + /** 4 + * Run builds 5 + */ 6 + final class HarbormasterBuildWorker extends PhabricatorWorker { 7 + 8 + public function getRequiredLeaseTime() { 9 + return 60 * 60 * 24; 10 + } 11 + 12 + public function doWork() { 13 + $data = $this->getTaskData(); 14 + $id = idx($data, 'buildID'); 15 + 16 + // Get a reference to the build. 17 + $build = id(new HarbormasterBuildQuery()) 18 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 19 + ->withBuildStatuses(array(HarbormasterBuild::STATUS_PENDING)) 20 + ->withIDs(array($id)) 21 + ->needBuildPlans(true) 22 + ->executeOne(); 23 + if (!$build) { 24 + throw new PhabricatorWorkerPermanentFailureException( 25 + pht('Invalid build ID "%s".', $id)); 26 + } 27 + 28 + try { 29 + $build->setBuildStatus(HarbormasterBuild::STATUS_BUILDING); 30 + $build->save(); 31 + 32 + $buildable = $build->getBuildable(); 33 + $plan = $build->getBuildPlan(); 34 + 35 + $steps = id(new HarbormasterBuildStepQuery()) 36 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 37 + ->withBuildPlanPHIDs(array($plan->getPHID())) 38 + ->execute(); 39 + 40 + // Perform the build. 41 + foreach ($steps as $step) { 42 + $implementation = $step->getStepImplementation(); 43 + if (!$implementation->validateSettings()) { 44 + $build->setBuildStatus(HarbormasterBuild::STATUS_ERROR); 45 + break; 46 + } 47 + $implementation->execute($build); 48 + if ($build->getBuildStatus() !== HarbormasterBuild::STATUS_BUILDING) { 49 + break; 50 + } 51 + } 52 + 53 + // If we get to here, then the build has finished. Set it to passed 54 + // if no build step explicitly set the status. 55 + if ($build->getBuildStatus() === HarbormasterBuild::STATUS_BUILDING) { 56 + $build->setBuildStatus(HarbormasterBuild::STATUS_PASSED); 57 + } 58 + $build->save(); 59 + } catch (Exception $e) { 60 + // If any exception is raised, the build is marked as a failure and 61 + // the exception is re-thrown (this ensures we don't leave builds 62 + // in an inconsistent state). 63 + $build->setBuildStatus(HarbormasterBuild::STATUS_ERROR); 64 + $build->save(); 65 + throw $e; 66 + } 67 + } 68 + 69 + }
-53
src/applications/harbormaster/worker/HarbormasterRunnerWorker.php
··· 1 - <?php 2 - 3 - final class HarbormasterRunnerWorker extends PhabricatorWorker { 4 - 5 - public function getRequiredLeaseTime() { 6 - return 60 * 60 * 24; 7 - } 8 - 9 - protected function doWork() { 10 - $data = $this->getTaskData(); 11 - $id = idx($data, 'commitID'); 12 - 13 - $commit = id(new PhabricatorRepositoryCommit())->loadOneWhere( 14 - 'id = %d', 15 - $id); 16 - 17 - if (!$commit) { 18 - throw new PhabricatorWorkerPermanentFailureException( 19 - "Commit '{$id}' does not exist!"); 20 - } 21 - 22 - // TODO: (T603) Policy interaction? 23 - $repository = id(new PhabricatorRepository())->loadOneWhere( 24 - 'id = %d', 25 - $commit->getRepositoryID()); 26 - 27 - if (!$repository) { 28 - throw new PhabricatorWorkerPermanentFailureException( 29 - "Unable to load repository for commit '{$id}'!"); 30 - } 31 - 32 - $lease = id(new DrydockLease()) 33 - ->setResourceType('working-copy') 34 - ->setAttributes( 35 - array( 36 - 'repositoryID' => $repository->getID(), 37 - 'commit' => $commit->getCommitIdentifier(), 38 - )) 39 - ->releaseOnDestruction() 40 - ->waitUntilActive(); 41 - 42 - $cmd = $lease->getInterface('command'); 43 - list($json) = $cmd 44 - ->setWorkingDirectory($lease->getResource()->getAttribute('path')) 45 - ->execx('arc unit --everything --json'); 46 - $lease->release(); 47 - 48 - // TODO: Do something actually useful with this. Requires Harbormaster 49 - // buildout. 50 - echo $json; 51 - } 52 - 53 - }
+1 -2
src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php
··· 750 750 $branches = mpull($branches, 'getHeadCommitIdentifier', 'getName'); 751 751 752 752 $got_something = false; 753 - foreach ($branches as $name => $branch) { 754 - $commit = $branch['rev']; 753 + foreach ($branches as $name => $commit) { 755 754 if ($this->isKnownCommit($repository, $commit)) { 756 755 continue; 757 756 } else {
+4
src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
··· 1720 1720 'type' => 'sql', 1721 1721 'name' => $this->getPatchPath('20131031.vcspassword.sql'), 1722 1722 ), 1723 + '20131105.buildstep.sql' => array( 1724 + 'type' => 'sql', 1725 + 'name' => $this->getPatchPath('20131105.buildstep.sql'), 1726 + ), 1723 1727 ); 1724 1728 } 1725 1729 }