diff --git a/resources/sql/patches/072.blamerevert.sql b/resources/sql/patches/072.blamerevert.sql new file mode 100644 index 0000000000..76b4c087e6 --- /dev/null +++ b/resources/sql/patches/072.blamerevert.sql @@ -0,0 +1,20 @@ +INSERT INTO phabricator_differential.differential_auxiliaryfield + (revisionPHID, name, value, dateCreated, dateModified) +SELECT phid, 'phabricator:blame-revision', blameRevision, + dateCreated, dateModified + FROM phabricator_differential.differential_revision + WHERE blameRevision != ''; + +ALTER TABLE phabricator_differential.differential_revision + DROP blameRevision; + + +INSERT INTO phabricator_differential.differential_auxiliaryfield + (revisionPHID, name, value, dateCreated, dateModified) +SELECT phid, 'phabricator:revert-plan', revertPlan, + dateCreated, dateModified + FROM phabricator_differential.differential_revision + WHERE revertPlan != ''; + +ALTER TABLE phabricator_differential.differential_revision + DROP revertPlan; diff --git a/scripts/repository/test_connection.php b/scripts/repository/test_connection.php new file mode 100755 index 0000000000..3f511d40c5 --- /dev/null +++ b/scripts/repository/test_connection.php @@ -0,0 +1,90 @@ +#!/usr/bin/env php +\n"; + exit(1); +} + +echo phutil_console_wrap( + phutil_console_format( + 'This script will test that you have configured valid credentials for '. + 'access to a repository, so the Phabricator daemons can pull from it. '. + 'You should run this as the **same user you will run the daemons as**, '. + 'from the **same machine they will run from**. Doing this will help '. + 'detect various problems with your configuration, such as SSH issues.')); + +list($whoami) = execx('whoami'); +$whoami = trim($whoami); + +$ok = phutil_console_confirm("Do you want to continue as '{$whoami}'?"); +if (!$ok) { + die(1); +} + +$callsign = $argv[1]; +echo "Loading '{$callsign}' repository...\n"; +$repository = id(new PhabricatorRepository())->loadOneWhere( + 'callsign = %s', + $argv[1]); +if (!$repository) { + throw new Exception("No such repository exists!"); +} + +$vcs = $repository->getVersionControlSystem(); + +PhutilServiceProfiler::installEchoListener(); + +echo "Trying to connect to the remote...\n"; +switch ($vcs) { + case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: + $err = $repository->passthruRemoteCommand( + '--limit 1 log %s', + $repository->getRemoteURI()); + break; + case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: + // Do an ls-remote on a nonexistent ref, which we expect to just return + // nothing. + $err = $repository->passthruRemoteCommand( + 'ls-remote %s %s', + $repository->getRemoteURI(), + 'just-testing'); + break; + default: + throw new Exception("Unsupported repository type."); +} + +if ($err) { + echo phutil_console_format( + "** FAIL ** Connection failed. The credentials for this ". + "repository appear to be incorrectly configured.\n"); + exit(1); +} else { + echo phutil_console_format( + "** OKAY ** Connection successful. The credentials for ". + "this repository appear to be correctly configured.\n"); +} + diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php index 7ecff1e3cd..83d7e1f9cc 100644 --- a/src/__celerity_resource_map__.php +++ b/src/__celerity_resource_map__.php @@ -172,7 +172,7 @@ celerity_register_resource_map(array( ), 'differential-core-view-css' => array( - 'uri' => '/res/438fe316/rsrc/css/application/differential/core.css', + 'uri' => '/res/584d40e8/rsrc/css/application/differential/core.css', 'type' => 'css', 'requires' => array( @@ -1180,7 +1180,7 @@ celerity_register_resource_map(array( ), 'phabricator-content-source-view-css' => array( - 'uri' => '/res/7147f14c/rsrc/css/application/contentsource/content-source-view.css', + 'uri' => '/res/8c738a93/rsrc/css/application/contentsource/content-source-view.css', 'type' => 'css', 'requires' => array( @@ -1419,7 +1419,7 @@ celerity_register_resource_map(array( 'uri' => '/res/pkg/3dbf4083/javelin.pkg.js', 'type' => 'js', ), - '3f2092d7' => + '7bf96a66' => array( 'name' => 'differential.pkg.css', 'symbols' => @@ -1433,7 +1433,7 @@ celerity_register_resource_map(array( 6 => 'differential-revision-add-comment-css', 7 => 'differential-revision-comment-list-css', ), - 'uri' => '/res/pkg/3f2092d7/differential.pkg.css', + 'uri' => '/res/pkg/7bf96a66/differential.pkg.css', 'type' => 'css', ), '95c67dcd' => @@ -1518,14 +1518,14 @@ celerity_register_resource_map(array( 'aphront-table-view-css' => '70966590', 'aphront-tokenizer-control-css' => '70966590', 'aphront-typeahead-control-css' => '70966590', - 'differential-changeset-view-css' => '3f2092d7', - 'differential-core-view-css' => '3f2092d7', - 'differential-revision-add-comment-css' => '3f2092d7', - 'differential-revision-comment-css' => '3f2092d7', - 'differential-revision-comment-list-css' => '3f2092d7', - 'differential-revision-detail-css' => '3f2092d7', - 'differential-revision-history-css' => '3f2092d7', - 'differential-table-of-contents-css' => '3f2092d7', + 'differential-changeset-view-css' => '7bf96a66', + 'differential-core-view-css' => '7bf96a66', + 'differential-revision-add-comment-css' => '7bf96a66', + 'differential-revision-comment-css' => '7bf96a66', + 'differential-revision-comment-list-css' => '7bf96a66', + 'differential-revision-detail-css' => '7bf96a66', + 'differential-revision-history-css' => '7bf96a66', + 'differential-table-of-contents-css' => '7bf96a66', 'diffusion-commit-view-css' => '03ef179e', 'javelin-behavior' => '3dbf4083', 'javelin-behavior-aphront-basic-tokenizer' => 'ac869011', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index b487f2a6aa..b78249d51f 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -642,6 +642,7 @@ phutil_register_library_map(array( 'PhabricatorUserLog' => 'applications/people/storage/log', 'PhabricatorUserOAuthInfo' => 'applications/people/storage/useroauthinfo', 'PhabricatorUserOAuthSettingsPanelController' => 'applications/people/controller/settings/panels/oauth', + 'PhabricatorUserPasswordSettingsPanelController' => 'applications/people/controller/settings/panels/password', 'PhabricatorUserPreferenceSettingsPanelController' => 'applications/people/controller/settings/panels/preferences', 'PhabricatorUserPreferences' => 'applications/people/storage/preferences', 'PhabricatorUserProfile' => 'applications/people/storage/profile', @@ -1222,6 +1223,7 @@ phutil_register_library_map(array( 'PhabricatorUserLog' => 'PhabricatorUserDAO', 'PhabricatorUserOAuthInfo' => 'PhabricatorUserDAO', 'PhabricatorUserOAuthSettingsPanelController' => 'PhabricatorUserSettingsPanelController', + 'PhabricatorUserPasswordSettingsPanelController' => 'PhabricatorUserSettingsPanelController', 'PhabricatorUserPreferenceSettingsPanelController' => 'PhabricatorUserSettingsPanelController', 'PhabricatorUserPreferences' => 'PhabricatorUserDAO', 'PhabricatorUserProfile' => 'PhabricatorUserDAO', diff --git a/src/aphront/request/AphrontRequest.php b/src/aphront/request/AphrontRequest.php index d00efc6b60..e2a6313839 100644 --- a/src/aphront/request/AphrontRequest.php +++ b/src/aphront/request/AphrontRequest.php @@ -145,6 +145,22 @@ class AphrontRequest { $valid = $this->getUser()->validateCSRFToken($token); if (!$valid) { + + // Add some diagnostic details so we can figure out if some CSRF issues + // are JS problems or people accessing Ajax URIs directly with their + // browsers. + if ($token) { + $token_info = "with an invalid CSRF token"; + } else { + $token_info = "without a CSRF token"; + } + + if ($this->isAjax()) { + $more_info = "(This was an Ajax request, {$token_info}.)"; + } else { + $more_info = "(This was a web request, {$token_info}.)"; + } + // This should only be able to happen if you load a form, pull your // internet for 6 hours, and then reconnect and immediately submit, // but give the user some indication of what happened since the workflow @@ -155,7 +171,8 @@ class AphrontRequest { "certain type of login hijacking attack. However, the token can ". "become invalid if you leave a page open for more than six hours ". "without a connection to the internet. To fix this problem: reload ". - "the page, and then resubmit it."); + "the page, and then resubmit it.\n\n". + $more_info); } return true; diff --git a/src/applications/auth/controller/logout/PhabricatorLogoutController.php b/src/applications/auth/controller/logout/PhabricatorLogoutController.php index ddf31ad76b..3db12be594 100644 --- a/src/applications/auth/controller/logout/PhabricatorLogoutController.php +++ b/src/applications/auth/controller/logout/PhabricatorLogoutController.php @@ -39,7 +39,15 @@ class PhabricatorLogoutController extends PhabricatorAuthController { PhabricatorUserLog::ACTION_LOGOUT); $log->save(); + // Destroy the user's session in the database so logout works even if + // their cookies have some issues. We'll detect cookie issues when they + // try to login again and tell them to clear any junk. + $phsid = $request->getCookie('phsid'); + if ($phsid) { + $user->destroySession($phsid); + } $request->clearCookie('phsid'); + return id(new AphrontRedirectResponse()) ->setURI('/login/'); } diff --git a/src/applications/conduit/method/differential/getrevision/ConduitAPI_differential_getrevision_Method.php b/src/applications/conduit/method/differential/getrevision/ConduitAPI_differential_getrevision_Method.php index de5b3bef06..30ccd480ae 100644 --- a/src/applications/conduit/method/differential/getrevision/ConduitAPI_differential_getrevision_Method.php +++ b/src/applications/conduit/method/differential/getrevision/ConduitAPI_differential_getrevision_Method.php @@ -91,8 +91,6 @@ class ConduitAPI_differential_getrevision_Method extends ConduitAPIMethod { $revision->getStatus()), 'summary' => $revision->getSummary(), 'testPlan' => $revision->getTestPlan(), - 'revertPlan' => $revision->getRevertPlan(), - 'blameRevision' => $revision->getBlameRevision(), 'lineCount' => $revision->getLineCount(), 'reviewerPHIDs' => $reviewer_phids, 'diffs' => $diff_dicts, diff --git a/src/applications/daemon/controller/workertaskdetail/PhabricatorWorkerTaskDetailController.php b/src/applications/daemon/controller/workertaskdetail/PhabricatorWorkerTaskDetailController.php index 03cbfdcadc..d4acf74840 100644 --- a/src/applications/daemon/controller/workertaskdetail/PhabricatorWorkerTaskDetailController.php +++ b/src/applications/daemon/controller/workertaskdetail/PhabricatorWorkerTaskDetailController.php @@ -46,6 +46,35 @@ class PhabricatorWorkerTaskDetailController $data = id(new PhabricatorWorkerTaskData())->loadOneWhere( 'id = %d', $task->getDataID()); + + $extra = null; + switch ($task->getTaskClass()) { + case 'PhabricatorRepositorySvnCommitChangeParserWorker': + case 'PhabricatorRepositoryGitCommitChangeParserWorker': + $commit_id = idx($data->getData(), 'commitID'); + if ($commit_id) { + $commit = id(new PhabricatorRepositoryCommit())->load($commit_id); + if ($commit) { + $repository = id(new PhabricatorRepository())->load( + $commit->getRepositoryID()); + if ($repository) { + $extra = + "NOTE: ". + "You can manually retry this task by running this script:". + "
".
+                  "phabricator/\$ ./scripts/repository/parse_one_commit.php ".
+                  "r".
+                  phutil_escape_html($repository->getCallsign()).
+                  phutil_escape_html($commit->getCommitIdentifier()).
+                "
"; + } + } + } + break; + default: + break; + } + if ($data) { $data = json_encode($data->getData()); } @@ -75,14 +104,23 @@ class PhabricatorWorkerTaskDetailController ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Data') - ->setValue($data)) + ->setValue($data)); + + if ($extra) { + $form->appendChild( + id(new AphrontFormMarkupControl()) + ->setLabel('More') + ->setValue($extra)); + } + + $form ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton('/daemon/')); $panel = new AphrontPanelView(); $panel->setHeader('Task Detail'); - $panel->setWidth(AphrontPanelView::WIDTH_FORM); + $panel->setWidth(AphrontPanelView::WIDTH_WIDE); $panel->appendChild($form); return $this->buildStandardPageResponse( diff --git a/src/applications/daemon/controller/workertaskdetail/__init__.php b/src/applications/daemon/controller/workertaskdetail/__init__.php index 13e9017739..66839c4cf7 100644 --- a/src/applications/daemon/controller/workertaskdetail/__init__.php +++ b/src/applications/daemon/controller/workertaskdetail/__init__.php @@ -7,15 +7,19 @@ phutil_require_module('phabricator', 'applications/daemon/controller/base'); +phutil_require_module('phabricator', 'applications/repository/storage/commit'); +phutil_require_module('phabricator', 'applications/repository/storage/repository'); phutil_require_module('phabricator', 'infrastructure/daemon/workers/storage/task'); phutil_require_module('phabricator', 'infrastructure/daemon/workers/storage/taskdata'); phutil_require_module('phabricator', 'view/form/base'); +phutil_require_module('phabricator', 'view/form/control/markup'); phutil_require_module('phabricator', 'view/form/control/static'); phutil_require_module('phabricator', 'view/form/control/submit'); phutil_require_module('phabricator', 'view/form/control/textarea'); phutil_require_module('phabricator', 'view/form/error'); phutil_require_module('phabricator', 'view/layout/panel'); +phutil_require_module('phutil', 'markup'); phutil_require_module('phutil', 'utils'); diff --git a/src/applications/differential/editor/revision/DifferentialRevisionEditor.php b/src/applications/differential/editor/revision/DifferentialRevisionEditor.php index 20a3fab1f9..ee7cd58eff 100644 --- a/src/applications/differential/editor/revision/DifferentialRevisionEditor.php +++ b/src/applications/differential/editor/revision/DifferentialRevisionEditor.php @@ -175,12 +175,6 @@ class DifferentialRevisionEditor { if ($revision->getAuthorPHID() === null) { $revision->setAuthorPHID($this->getActorPHID()); } - if ($revision->getRevertPlan() === null) { - $revision->setRevertPlan(''); - } - if ($revision->getBlameRevision() === null) { - $revision->setBlameRevision(''); - } if ($revision->getSummary() === null) { $revision->setSummary(''); } diff --git a/src/applications/differential/parser/changeset/DifferentialChangesetParser.php b/src/applications/differential/parser/changeset/DifferentialChangesetParser.php index fe0878b8c9..b8ace11c4d 100644 --- a/src/applications/differential/parser/changeset/DifferentialChangesetParser.php +++ b/src/applications/differential/parser/changeset/DifferentialChangesetParser.php @@ -899,22 +899,66 @@ class DifferentialChangesetParser { } } + $this->comments = msort($this->comments, 'getID'); + $old_comments = array(); + $new_comments = array(); + foreach ($this->comments as $comment) { + if ($this->isCommentOnRightSideWhenDisplayed($comment)) { + $new_comments[] = $comment; + } else { + $old_comments[] = $comment; + } + } + + $html_old = array(); + $html_new = array(); + foreach ($old_comments as $comment) { + $xhp = $this->renderInlineComment($comment); + $html_old[] = + ''. + $xhp. + ''; + } + foreach ($new_comments as $comment) { + $xhp = $this->renderInlineComment($comment); + $html_new[] = + ''. + $xhp. + ''; + } + + $changset_id = $this->changeset->getID(); + if (!$old) { + $th_old = ''; + } + else { + $th_old = '1'; + } + if (!$cur) { + $th_new = ''; + } + else { + $th_new = '1'; + } + $output = $this->renderChangesetTable( $this->changeset, ''. - ''. + $th_old. ''. '
'. $old. '
'. ''. - ''. + $th_new. ''. '
'. $cur. '
'. ''. - ''); + ''. + implode('', $html_old). + implode('', $html_new)); return $output; case DifferentialChangeType::FILE_DIRECTORY: diff --git a/src/applications/differential/storage/revision/DifferentialRevision.php b/src/applications/differential/storage/revision/DifferentialRevision.php index fd432fce1f..2e055ef361 100644 --- a/src/applications/differential/storage/revision/DifferentialRevision.php +++ b/src/applications/differential/storage/revision/DifferentialRevision.php @@ -23,8 +23,6 @@ class DifferentialRevision extends DifferentialDAO { protected $summary; protected $testPlan; - protected $revertPlan; - protected $blameRevision; protected $phid; protected $authorPHID; diff --git a/src/applications/diffusion/controller/browse/DiffusionBrowseController.php b/src/applications/diffusion/controller/browse/DiffusionBrowseController.php index f69d18da9e..f67b9cfaf2 100644 --- a/src/applications/diffusion/controller/browse/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/browse/DiffusionBrowseController.php @@ -75,6 +75,16 @@ class DiffusionBrowseController extends DiffusionController { $controller->setDiffusionRequest($drequest); return $this->delegateToController($controller); break; + case DiffusionBrowseQuery::REASON_IS_UNTRACKED_PARENT: + $subdir = $drequest->getRepository()->getDetail('svn-subpath'); + $title = 'Directory Not Tracked'; + $body = + "This repository is configured to track only one subdirectory ". + "of the entire repository ('".phutil_escape_html($subdir)."'), ". + "but you aren't looking at something in that subdirectory, so no ". + "information is available."; + $severity = AphrontErrorView::SEVERITY_WARNING; + break; default: throw new Exception("Unknown failure reason!"); } diff --git a/src/applications/diffusion/controller/browse/__init__.php b/src/applications/diffusion/controller/browse/__init__.php index 20a7e93855..2c07414577 100644 --- a/src/applications/diffusion/controller/browse/__init__.php +++ b/src/applications/diffusion/controller/browse/__init__.php @@ -14,6 +14,7 @@ phutil_require_module('phabricator', 'applications/phid/handle/data'); phutil_require_module('phabricator', 'view/form/error'); phutil_require_module('phabricator', 'view/layout/panel'); +phutil_require_module('phutil', 'markup'); phutil_require_module('phutil', 'utils'); diff --git a/src/applications/diffusion/controller/commit/DiffusionCommitController.php b/src/applications/diffusion/controller/commit/DiffusionCommitController.php index 4b7e80178c..1d8566656d 100644 --- a/src/applications/diffusion/controller/commit/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/commit/DiffusionCommitController.php @@ -44,31 +44,47 @@ class DiffusionCommitController extends DiffusionController { $commit_data = $drequest->loadCommitData(); - $engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine(); + $is_foreign = $commit_data->getCommitDetail('foreign-svn-stub'); + if ($is_foreign) { + $subpath = $commit_data->getCommitDetail('svn-subpath'); - require_celerity_resource('diffusion-commit-view-css'); - require_celerity_resource('phabricator-remarkup-css'); + $error_panel = new AphrontErrorView(); + $error_panel->setWidth(AphrontErrorView::WIDTH_WIDE); + $error_panel->setTitle('Commit Not Tracked'); + $error_panel->setSeverity(AphrontErrorView::SEVERITY_WARNING); + $error_panel->appendChild( + "This Diffusion repository is configured to track only one ". + "subdirectory of the entire Subversion repository, and this commit ". + "didn't affect the tracked subdirectory ('". + phutil_escape_html($subpath)."'), so no information is available."); + $content[] = $error_panel; + } else { + $engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine(); - $property_table = $this->renderPropertyTable($commit, $commit_data); + require_celerity_resource('diffusion-commit-view-css'); + require_celerity_resource('phabricator-remarkup-css'); - $detail_panel->appendChild( - '
'. - ''. - '

Revision Detail

'. - '
'. - $property_table. - '
'. - '
'. - $engine->markupText($commit_data->getCommitMessage()). + $property_table = $this->renderPropertyTable($commit, $commit_data); + + $detail_panel->appendChild( + '
'. + ''. - '
'. - '
'); + '

Revision Detail

'. + '
'. + $property_table. + '
'. + '
'. + $engine->markupText($commit_data->getCommitMessage()). + '
'. + '
'. + '
'); - $content[] = $detail_panel; + $content[] = $detail_panel; + } $change_query = DiffusionPathChangeQuery::newFromDiffusionRequest( $drequest); @@ -103,6 +119,23 @@ class DiffusionCommitController extends DiffusionController { phutil_escape_html($bad_commit['description'])); $content[] = $error_panel; + } else if ($is_foreign) { + // Don't render anything else. + } else if (!count($changes)) { + $no_changes = new AphrontErrorView(); + $no_changes->setWidth(AphrontErrorView::WIDTH_WIDE); + $no_changes->setSeverity(AphrontErrorView::SEVERITY_WARNING); + $no_changes->setTitle('Not Yet Parsed'); + // TODO: This can also happen with weird SVN changes that don't do + // anything (or only alter properties?), although the real no-changes case + // is extremely rare and might be impossible to produce organically. We + // should probably write some kind of "Nothing Happened!" change into the + // DB once we parse these changes so we can distinguish between + // "not parsed yet" and "no changes". + $no_changes->appendChild( + "This commit hasn't been fully parsed yet (or doesn't affect any ". + "paths)."); + $content[] = $no_changes; } else { $change_panel = new AphrontPanelView(); $change_panel->setHeader("Changes (".number_format($count).")"); @@ -130,60 +163,52 @@ class DiffusionCommitController extends DiffusionController { $content[] = $change_panel; - if ($changes) { - $changesets = DiffusionPathChange::convertToDifferentialChangesets( - $changes); + $changesets = DiffusionPathChange::convertToDifferentialChangesets( + $changes); - $vcs = $repository->getVersionControlSystem(); - switch ($vcs) { - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: - $vcs_supports_directory_changes = true; - break; - case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: - $vcs_supports_directory_changes = false; - break; - default: - throw new Exception("Unknown VCS."); - } - - $references = array(); - foreach ($changesets as $key => $changeset) { - $file_type = $changeset->getFileType(); - if ($file_type == DifferentialChangeType::FILE_DIRECTORY) { - if (!$vcs_supports_directory_changes) { - unset($changesets[$key]); - continue; - } - } - - $branch = $drequest->getBranchURIComponent( - $drequest->getBranch()); - $filename = $changeset->getFilename(); - $commit = $drequest->getCommit(); - $reference = "{$branch}{$filename};{$commit}"; - $references[$key] = $reference; - } - - $change_list = new DifferentialChangesetListView(); - $change_list->setChangesets($changesets); - $change_list->setRenderingReferences($references); - $change_list->setRenderURI('/diffusion/'.$callsign.'/diff/'); - - // TODO: This is pretty awkward, unify the CSS between Diffusion and - // Differential better. - require_celerity_resource('differential-core-view-css'); - $change_list = - '
'. - $change_list->render(). - '
'; - } else { - $change_list = - '
'. - '(no changes blah blah)'. - '
'; + $vcs = $repository->getVersionControlSystem(); + switch ($vcs) { + case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: + $vcs_supports_directory_changes = true; + break; + case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: + $vcs_supports_directory_changes = false; + break; + default: + throw new Exception("Unknown VCS."); } + $references = array(); + foreach ($changesets as $key => $changeset) { + $file_type = $changeset->getFileType(); + if ($file_type == DifferentialChangeType::FILE_DIRECTORY) { + if (!$vcs_supports_directory_changes) { + unset($changesets[$key]); + continue; + } + } + + $branch = $drequest->getBranchURIComponent( + $drequest->getBranch()); + $filename = $changeset->getFilename(); + $commit = $drequest->getCommit(); + $reference = "{$branch}{$filename};{$commit}"; + $references[$key] = $reference; + } + + $change_list = new DifferentialChangesetListView(); + $change_list->setChangesets($changesets); + $change_list->setRenderingReferences($references); + $change_list->setRenderURI('/diffusion/'.$callsign.'/diff/'); + + // TODO: This is pretty awkward, unify the CSS between Diffusion and + // Differential better. + require_celerity_resource('differential-core-view-css'); + $change_list = + '
'. + $change_list->render(). + '
'; + $content[] = $change_list; } diff --git a/src/applications/diffusion/query/browse/base/DiffusionBrowseQuery.php b/src/applications/diffusion/query/browse/base/DiffusionBrowseQuery.php index 02c3283a87..cba77ec2b5 100644 --- a/src/applications/diffusion/query/browse/base/DiffusionBrowseQuery.php +++ b/src/applications/diffusion/query/browse/base/DiffusionBrowseQuery.php @@ -25,11 +25,12 @@ abstract class DiffusionBrowseQuery { protected $deletedAtCommit; protected $validityOnly; - const REASON_IS_FILE = 'is-file'; - const REASON_IS_DELETED = 'is-deleted'; - const REASON_IS_NONEXISTENT = 'nonexistent'; - const REASON_BAD_COMMIT = 'bad-commit'; - const REASON_IS_EMPTY = 'empty'; + const REASON_IS_FILE = 'is-file'; + const REASON_IS_DELETED = 'is-deleted'; + const REASON_IS_NONEXISTENT = 'nonexistent'; + const REASON_BAD_COMMIT = 'bad-commit'; + const REASON_IS_EMPTY = 'empty'; + const REASON_IS_UNTRACKED_PARENT = 'untracked-parent'; final private function __construct() { // diff --git a/src/applications/diffusion/query/browse/svn/DiffusionSvnBrowseQuery.php b/src/applications/diffusion/query/browse/svn/DiffusionSvnBrowseQuery.php index c47952118a..b513b50763 100644 --- a/src/applications/diffusion/query/browse/svn/DiffusionSvnBrowseQuery.php +++ b/src/applications/diffusion/query/browse/svn/DiffusionSvnBrowseQuery.php @@ -25,6 +25,15 @@ final class DiffusionSvnBrowseQuery extends DiffusionBrowseQuery { $path = $drequest->getPath(); $commit = $drequest->getCommit(); + $subpath = $repository->getDetail('svn-subpath'); + if ($subpath && strncmp($subpath, $path, strlen($subpath))) { + // If we have a subpath and the path isn't a child of it, it (almost + // certainly) won't exist since we don't track commits which affect + // it. (Even if it exists, return a consistent result.) + $this->reason = self::REASON_IS_UNTRACKED_PARENT; + return array(); + } + $conn_r = $repository->establishConnection('r'); $parent_path = dirname($path); diff --git a/src/applications/diffusion/query/diff/svn/DiffusionSvnDiffQuery.php b/src/applications/diffusion/query/diff/svn/DiffusionSvnDiffQuery.php index 80f3348f76..cf8543002b 100644 --- a/src/applications/diffusion/query/diff/svn/DiffusionSvnDiffQuery.php +++ b/src/applications/diffusion/query/diff/svn/DiffusionSvnDiffQuery.php @@ -140,9 +140,9 @@ final class DiffusionSvnDiffQuery extends DiffusionDiffQuery { $repository = $drequest->getRepository(); list($ref, $rev) = $spec; - return new ExecFuture( - 'svn --non-interactive cat %s%s@%d', - $repository->getDetail('remote-uri'), + return $repository->getRemoteCommandFuture( + 'cat %s%s@%d', + $repository->getRemoteURI(), $ref, $rev); } diff --git a/src/applications/diffusion/query/diff/svn/__init__.php b/src/applications/diffusion/query/diff/svn/__init__.php index a87b1cbdfe..c7ae1d6b3a 100644 --- a/src/applications/diffusion/query/diff/svn/__init__.php +++ b/src/applications/diffusion/query/diff/svn/__init__.php @@ -16,7 +16,6 @@ phutil_require_module('phabricator', 'applications/diffusion/query/pathchange/ba phutil_require_module('phabricator', 'infrastructure/diff/engine'); phutil_require_module('phutil', 'future'); -phutil_require_module('phutil', 'future/exec'); phutil_require_module('phutil', 'utils'); diff --git a/src/applications/diffusion/query/filecontent/svn/DiffusionSvnFileContentQuery.php b/src/applications/diffusion/query/filecontent/svn/DiffusionSvnFileContentQuery.php index 3371c3027b..2104823b64 100644 --- a/src/applications/diffusion/query/filecontent/svn/DiffusionSvnFileContentQuery.php +++ b/src/applications/diffusion/query/filecontent/svn/DiffusionSvnFileContentQuery.php @@ -33,11 +33,11 @@ final class DiffusionSvnFileContentQuery extends DiffusionFileContentQuery { $path = $drequest->getPath(); $commit = $drequest->getCommit(); - $remote_uri = $repository->getDetail('remote-uri'); + $remote_uri = $repository->getRemoteURI(); try { - list($corpus) = execx( - 'svn --non-interactive %s %s%s@%s', + list($corpus) = $repository->execxRemoteCommand( + '%s %s%s@%s', $this->getNeedsBlame() ? 'blame' : 'cat', $remote_uri, $path, diff --git a/src/applications/diffusion/query/filecontent/svn/__init__.php b/src/applications/diffusion/query/filecontent/svn/__init__.php index c10e912fa8..57fe48ee4c 100644 --- a/src/applications/diffusion/query/filecontent/svn/__init__.php +++ b/src/applications/diffusion/query/filecontent/svn/__init__.php @@ -9,7 +9,6 @@ phutil_require_module('phabricator', 'applications/diffusion/data/filecontent'); phutil_require_module('phabricator', 'applications/diffusion/query/filecontent/base'); -phutil_require_module('phutil', 'future/exec'); phutil_require_module('phutil', 'utils'); diff --git a/src/applications/diffusion/request/svn/DiffusionSvnRequest.php b/src/applications/diffusion/request/svn/DiffusionSvnRequest.php index 577e36c64e..81c9c68bd1 100644 --- a/src/applications/diffusion/request/svn/DiffusionSvnRequest.php +++ b/src/applications/diffusion/request/svn/DiffusionSvnRequest.php @@ -20,6 +20,14 @@ class DiffusionSvnRequest extends DiffusionRequest { protected function initializeFromAphrontRequestDictionary(array $data) { parent::initializeFromAphrontRequestDictionary($data); + + if ($this->path === null) { + $subpath = $this->repository->getDetail('svn-subpath'); + if ($subpath) { + $this->path = $subpath; + } + } + if (!strncmp($this->path, ':', 1)) { $this->path = substr($this->path, 1); $this->path = ltrim($this->path, '/'); diff --git a/src/applications/metamta/contentsource/source/PhabricatorContentSource.php b/src/applications/metamta/contentsource/source/PhabricatorContentSource.php index e058dfdb1d..c190df5e8e 100644 --- a/src/applications/metamta/contentsource/source/PhabricatorContentSource.php +++ b/src/applications/metamta/contentsource/source/PhabricatorContentSource.php @@ -24,6 +24,7 @@ final class PhabricatorContentSource { const SOURCE_CONDUIT = 'conduit'; const SOURCE_MOBILE = 'mobile'; const SOURCE_TABLET = 'tablet'; + const SOURCE_FAX = 'fax'; private $source; private $params = array(); diff --git a/src/applications/metamta/contentsource/view/PhabricatorContentSourceView.php b/src/applications/metamta/contentsource/view/PhabricatorContentSourceView.php index 02b83c7d68..5ad1f0d297 100644 --- a/src/applications/metamta/contentsource/view/PhabricatorContentSourceView.php +++ b/src/applications/metamta/contentsource/view/PhabricatorContentSourceView.php @@ -35,13 +35,13 @@ final class PhabricatorContentSourceView extends AphrontView { public function render() { require_celerity_resource('phabricator-content-source-view-css'); - $type = null; $map = array( - PhabricatorContentSource::SOURCE_WEB => 'web', - PhabricatorContentSource::SOURCE_CONDUIT => 'conduit', - PhabricatorContentSource::SOURCE_EMAIL => 'email', - PhabricatorContentSource::SOURCE_MOBILE => 'mobile', - PhabricatorContentSource::SOURCE_TABLET => 'tablet', + PhabricatorContentSource::SOURCE_WEB => 'Web', + PhabricatorContentSource::SOURCE_CONDUIT => 'Conduit', + PhabricatorContentSource::SOURCE_EMAIL => 'Email', + PhabricatorContentSource::SOURCE_MOBILE => 'Mobile', + PhabricatorContentSource::SOURCE_TABLET => 'Tablet', + PhabricatorContentSource::SOURCE_FAX => 'Fax', ); $source = $this->contentSource->getSource(); @@ -51,14 +51,12 @@ final class PhabricatorContentSourceView extends AphrontView { return; } - $type_class = 'phabricator-content-source-'.$type; - return phutil_render_tag( 'span', array( - 'class' => "phabricator-content-source-view {$type_class}", + 'class' => "phabricator-content-source-view", ), - 'Via'); + "Via {$type}"); } } diff --git a/src/applications/people/controller/settings/PhabricatorUserSettingsController.php b/src/applications/people/controller/settings/PhabricatorUserSettingsController.php index acc107b519..120e2d3f2e 100644 --- a/src/applications/people/controller/settings/PhabricatorUserSettingsController.php +++ b/src/applications/people/controller/settings/PhabricatorUserSettingsController.php @@ -29,16 +29,20 @@ class PhabricatorUserSettingsController extends PhabricatorPeopleController { $request = $this->getRequest(); - // TODO: Implement a password panel. - $this->pages = array( 'account' => 'Account', 'profile' => 'Profile', 'email' => 'Email', + 'password' => 'Password', 'preferences' => 'Preferences', 'conduit' => 'Conduit Certificate', ); + if (!PhabricatorEnv::getEnvConfig('account.editable') || + !PhabricatorEnv::getEnvConfig('auth.password-auth-enabled')) { + unset($this->pages['password']); + } + if (PhabricatorUserSSHKeysSettingsPanelController::isEnabled()) { $this->pages['sshkeys'] = 'SSH Public Keys'; } @@ -67,6 +71,10 @@ class PhabricatorUserSettingsController extends PhabricatorPeopleController { case 'email': $delegate = new PhabricatorUserEmailSettingsPanelController($request); break; + case 'password': + $delegate = new PhabricatorUserPasswordSettingsPanelController( + $request); + break; case 'conduit': $delegate = new PhabricatorUserConduitSettingsPanelController($request); break; diff --git a/src/applications/people/controller/settings/__init__.php b/src/applications/people/controller/settings/__init__.php index ec95b76f5a..08c21ebe1e 100644 --- a/src/applications/people/controller/settings/__init__.php +++ b/src/applications/people/controller/settings/__init__.php @@ -13,9 +13,11 @@ phutil_require_module('phabricator', 'applications/people/controller/settings/pa phutil_require_module('phabricator', 'applications/people/controller/settings/panels/conduit'); phutil_require_module('phabricator', 'applications/people/controller/settings/panels/email'); phutil_require_module('phabricator', 'applications/people/controller/settings/panels/oauth'); +phutil_require_module('phabricator', 'applications/people/controller/settings/panels/password'); phutil_require_module('phabricator', 'applications/people/controller/settings/panels/preferences'); phutil_require_module('phabricator', 'applications/people/controller/settings/panels/profile'); phutil_require_module('phabricator', 'applications/people/controller/settings/panels/sshkeys'); +phutil_require_module('phabricator', 'infrastructure/env'); phutil_require_module('phabricator', 'view/layout/sidenav'); phutil_require_module('phutil', 'markup'); diff --git a/src/applications/people/controller/settings/panels/password/PhabricatorUserPasswordSettingsPanelController.php b/src/applications/people/controller/settings/panels/password/PhabricatorUserPasswordSettingsPanelController.php new file mode 100644 index 0000000000..06dae9d2ff --- /dev/null +++ b/src/applications/people/controller/settings/panels/password/PhabricatorUserPasswordSettingsPanelController.php @@ -0,0 +1,111 @@ +getRequest(); + $user = $request->getUser(); + $editable = $this->getAccountEditable(); + + // There's no sense in showing a change password panel if the user + // can't change their password + if (!$editable || + !PhabricatorEnv::getEnvConfig('auth.password-auth-enabled')) { + return new Aphront400Response(); + } + + $errors = array(); + if ($request->isFormPost()) { + if ($user->comparePassword($request->getStr('old_pw'))) { + $pass = $request->getStr('new_pw'); + $conf = $request->getStr('conf_pw'); + if ($pass === $conf) { + if (strlen($pass)) { + $user->setPassword($pass); + // This write is unguarded because the CSRF token has already + // been checked in the call to $request->isFormPost() and + // the CSRF token depends on the password hash, so when it + // is changed here the CSRF token check will fail. + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + $user->save(); + unset($unguarded); + return id(new AphrontRedirectResponse()) + ->setURI('/settings/page/password/?saved=true'); + } else { + $errors[] = 'Your new password is too short.'; + } + } else { + $errors[] = 'New password and confirmation do not match.'; + } + } else { + $errors[] = 'The old password you entered is incorrect.'; + } + } + + $notice = null; + if (!$errors) { + if ($request->getStr('saved')) { + $notice = new AphrontErrorView(); + $notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE); + $notice->setTitle('Changes Saved'); + $notice->appendChild('

Your password has been updated.

'); + } + } else { + $notice = new AphrontErrorView(); + $notice->setTitle('Error Changing Password'); + $notice->setErrors($errors); + } + + $form = new AphrontFormView(); + $form + ->setUser($user) + ->appendChild( + id(new AphrontFormPasswordControl()) + ->setLabel('Old Password') + ->setName('old_pw')); + $form + ->appendChild( + id(new AphrontFormPasswordControl()) + ->setLabel('New Password') + ->setName('new_pw')); + $form + ->appendChild( + id(new AphrontFormPasswordControl()) + ->setLabel('Confirm Password') + ->setName('conf_pw')); + $form + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue('Save')); + + $panel = new AphrontPanelView(); + $panel->setHeader('Change Password'); + $panel->setWidth(AphrontPanelView::WIDTH_FORM); + $panel->appendChild($form); + + return id(new AphrontNullView()) + ->appendChild( + array( + $notice, + $panel, + )); + } +} diff --git a/src/applications/people/controller/settings/panels/password/__init__.php b/src/applications/people/controller/settings/panels/password/__init__.php new file mode 100644 index 0000000000..7a375a6fd8 --- /dev/null +++ b/src/applications/people/controller/settings/panels/password/__init__.php @@ -0,0 +1,24 @@ +establishConnection('w'); + queryfx( + $conn_w, + 'DELETE FROM %T WHERE userPHID = %s AND sessionKey = %s', + self::SESSION_TABLE, + $this->getPHID(), + $session_key); + } + private function generateEmailToken($offset = 0) { return $this->generateToken( time() + ($offset * self::EMAIL_CYCLE_FREQUENCY), diff --git a/src/applications/repository/controller/edit/PhabricatorRepositoryEditController.php b/src/applications/repository/controller/edit/PhabricatorRepositoryEditController.php index 159cc45c0c..414f8bd94e 100644 --- a/src/applications/repository/controller/edit/PhabricatorRepositoryEditController.php +++ b/src/applications/repository/controller/edit/PhabricatorRepositoryEditController.php @@ -205,6 +205,9 @@ class PhabricatorRepositoryEditController $is_git = false; $is_svn = false; + $e_ssh_key = null; + $e_ssh_keyfile = null; + switch ($repository->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $is_git = true; @@ -239,6 +242,22 @@ class PhabricatorRepositoryEditController 'default-owners-path', '/')); + $repository->setDetail('ssh-login', $request->getStr('ssh-login')); + $repository->setDetail('ssh-key', $request->getStr('ssh-key')); + $repository->setDetail('ssh-keyfile', $request->getStr('ssh-keyfile')); + + $repository->setDetail('http-login', $request->getStr('http-login')); + $repository->setDetail('http-pass', $request->getStr('http-pass')); + + if ($repository->getDetail('ssh-key') && + $repository->getDetail('ssh-keyfile')) { + $errors[] = + "Specify only one of 'SSH Private Key' and 'SSH Private Key File', ". + "not both."; + $e_ssh_key = 'Choose Only One'; + $e_ssh_keyfile = 'Choose Only One'; + } + $repository->setDetail( 'herald-disabled', $request->getInt('herald-disabled', 0)); @@ -329,10 +348,14 @@ class PhabricatorRepositoryEditController 'Differential, Diffusion, Herald, and other services. To enable '. 'tracking for a repository, configure it here and then start (or '. 'restart) the daemons. More information is available in the '. - ''.$user_guide_link.'.

') + ''.$user_guide_link.'.

'); + + $form + ->appendChild( + '

Basics

') ->appendChild( id(new AphrontFormStaticControl()) - ->setLabel('Repository') + ->setLabel('Repository Name') ->setValue($repository->getName())) ->appendChild( id(new AphrontFormSelectControl()) @@ -345,13 +368,19 @@ class PhabricatorRepositoryEditController ->setValue( $repository->getDetail('tracking-enabled') ? 'enabled' - : 'disabled')); + : 'disabled')) + ->appendChild('
'); + + $form->appendChild( + '

Remote URI

'. + '
'); $uri_label = 'Repository URI'; if ($is_git) { $instructions = - 'NOTE: The user the tracking daemon runs as must have permission to '. - 'git clone from this URI.'; + 'Enter the URI to clone this repository from. It should look like '. + 'git@github.com:example/example.git or '. + 'ssh://user@host.com/git/example.git'; $form->appendChild( '

'.$instructions.'

'); } else if ($is_svn) { @@ -360,10 +389,7 @@ class PhabricatorRepositoryEditController 'You can figure this out by running svn info and looking at '. 'the value in the Repository Root field. It should be a URI '. 'and look like http://svn.example.org/svn/ or '. - 'svn+ssh://svn.example.com/svnroot/.'. - '

'. - 'NOTE: The user the daemons run as must be able to execute '. - 'svn log against this URI.'; + 'svn+ssh://svn.example.com/svnroot/'; $form->appendChild( '

'.$instructions.'

'); $uri_label = 'Repository Root'; @@ -374,9 +400,85 @@ class PhabricatorRepositoryEditController id(new AphrontFormTextControl()) ->setName('uri') ->setLabel($uri_label) + ->setID('remote-uri') ->setValue($repository->getDetail('remote-uri')) ->setError($e_uri)); + $form->appendChild( + '
'. + 'If you want to connect to this repository over SSH, enter the '. + 'username and private key to use. You can leave these fields blank if '. + 'the repository does not use SSH.'. + ' NOTE: This feature is not yet fully supported.'. + '
'); + + $form + ->appendChild( + id(new AphrontFormTextControl()) + ->setName('ssh-login') + ->setLabel('SSH User') + ->setValue($repository->getDetail('ssh-login'))) + ->appendChild( + id(new AphrontFormTextAreaControl()) + ->setName('ssh-key') + ->setLabel('SSH Private Key') + ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT) + ->setValue($repository->getDetail('ssh-key')) + ->setError($e_ssh_key) + ->setCaption('Specify the entire private key, or...')) + ->appendChild( + id(new AphrontFormTextControl()) + ->setName('ssh-keyfile') + ->setLabel('SSH Private Key File') + ->setValue($repository->getDetail('ssh-keyfile')) + ->setError($e_ssh_keyfile) + ->setCaption( + '...specify a path on disk where the daemon should '. + 'look for a private key.')); + + $supports_http = $is_svn; + + if ($supports_http) { + $form + ->appendChild( + '
'. + 'If you want to connect to this repository over HTTP Basic Auth, '. + 'enter the username and password to use. You can leave these '. + 'fields blank if the repository does not use HTTP Basic Auth.'. + ' NOTE: This feature is not yet fully supported.'. + '
') + ->appendChild( + id(new AphrontFormTextControl()) + ->setName('http-login') + ->setLabel('HTTP Basic Login') + ->setValue($repository->getDetail('http-login'))) + ->appendChild( + id(new AphrontFormTextControl()) + ->setName('http-pass') + ->setLabel('HTTP Basic Password') + ->setValue($repository->getDetail('http-pass'))); + } + + $form + ->appendChild( + '
'. + 'To test your authentication configuration, save this '. + 'form and then run this script:'. + ''. + 'phabricator/ $ ./scripts/repository/test_connection.php '. + phutil_escape_html($repository->getCallsign()). + ''. + 'This will verify that your configuration is correct and the '. + 'daemons can connect to the remote repository and pull changes '. + 'from it.'. + '
'); + + $form->appendChild('
'); + + $form->appendChild( + '

Importing Repository Information

'. + '
'); + if ($is_git) { $form->appendChild( '

Select a path on local disk '. @@ -415,6 +517,12 @@ class PhabricatorRepositoryEditController 'Number of seconds daemon should sleep between requests. Larger '. 'numbers reduce load but also decrease responsiveness.')); + $form->appendChild('

'); + + $form->appendChild( + '

Application Configuration

'. + '
'); + if ($is_git) { $form ->appendChild( @@ -452,8 +560,8 @@ class PhabricatorRepositoryEditController 1 => 'Disabled - Do Not Send Email', )) ->setCaption( - 'You can temporarily disable Herald notifications when reparsing '. - 'a repository or importing a new repository.')); + 'You can temporarily disable Herald commit notifications when '. + 'reparsing a repository or importing a new repository.')); $parsers = id(new PhutilSymbolLoader()) ->setAncestorClass('PhabricatorRepositoryCommitMessageDetailParser') @@ -487,10 +595,12 @@ class PhabricatorRepositoryEditController ->setCaption('Repository UUID from svn info.')); } + $form->appendChild('
'); + $form ->appendChild( id(new AphrontFormSubmitControl()) - ->setValue('Save')); + ->setValue('Save Configuration')); $panel = new AphrontPanelView(); $panel->setHeader('Repository Tracking'); diff --git a/src/applications/repository/daemon/commitdiscovery/svn/PhabricatorRepositorySvnCommitDiscoveryDaemon.php b/src/applications/repository/daemon/commitdiscovery/svn/PhabricatorRepositorySvnCommitDiscoveryDaemon.php index 1492caa547..fc68dad836 100644 --- a/src/applications/repository/daemon/commitdiscovery/svn/PhabricatorRepositorySvnCommitDiscoveryDaemon.php +++ b/src/applications/repository/daemon/commitdiscovery/svn/PhabricatorRepositorySvnCommitDiscoveryDaemon.php @@ -28,9 +28,9 @@ class PhabricatorRepositorySvnCommitDiscoveryDaemon } $uri = $this->getBaseSVNLogURI(); - list($xml) = execx( - 'svn log --xml --non-interactive --quiet --limit 1 %s@HEAD', - $uri); + list($xml) = $repository->execxRemoteCommand( + ' log --xml --quiet --limit 1 %s@HEAD', + $uri); $results = $this->parseSVNLogXML($xml); $commit = key($results); @@ -47,6 +47,7 @@ class PhabricatorRepositorySvnCommitDiscoveryDaemon private function discoverCommit($commit, $epoch) { $uri = $this->getBaseSVNLogURI(); + $repository = $this->getRepository(); $discover = array( $commit => $epoch, @@ -58,8 +59,8 @@ class PhabricatorRepositorySvnCommitDiscoveryDaemon // Find all the unknown commits on this path. Note that we permit // importing an SVN subdirectory rather than the entire repository, so // commits may be nonsequential. - list($err, $xml, $stderr) = exec_manual( - 'svn log --xml --non-interactive --quiet --limit %d %s@%d', + list($err, $xml, $stderr) = $repository->execRemoteCommand( + ' log --xml --quiet --limit %d %s@%d', $limit, $uri, $upper_bound - 1); diff --git a/src/applications/repository/daemon/commitdiscovery/svn/__init__.php b/src/applications/repository/daemon/commitdiscovery/svn/__init__.php index df2258faf8..e8b0dd6f29 100644 --- a/src/applications/repository/daemon/commitdiscovery/svn/__init__.php +++ b/src/applications/repository/daemon/commitdiscovery/svn/__init__.php @@ -9,7 +9,6 @@ phutil_require_module('phabricator', 'applications/repository/constants/repositorytype'); phutil_require_module('phabricator', 'applications/repository/daemon/commitdiscovery/base'); -phutil_require_module('phutil', 'future/exec'); phutil_require_module('phutil', 'utils'); diff --git a/src/applications/repository/storage/repository/PhabricatorRepository.php b/src/applications/repository/storage/repository/PhabricatorRepository.php index 0235e1a435..1775f7ba0f 100644 --- a/src/applications/repository/storage/repository/PhabricatorRepository.php +++ b/src/applications/repository/storage/repository/PhabricatorRepository.php @@ -32,6 +32,8 @@ class PhabricatorRepository extends PhabricatorRepositoryDAO { protected $versionControlSystem; protected $details = array(); + private $sshKeyfile; + public function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, @@ -55,4 +57,240 @@ class PhabricatorRepository extends PhabricatorRepositoryDAO { return $this; } + public function getRemoteURI() { + $raw_uri = $this->getDetail('remote-uri'); + + $vcs = $this->getVersionControlSystem(); + $is_git = ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT); + + // If there's no protocol (git implicit SSH) reformat the URI to be a + // normal URI. These git URIs look like "user@domain.com:path" instead of + // "ssh://user@domain/path". + $uri = new PhutilURI($raw_uri); + if ($is_git && !$uri->getProtocol()) { + list($domain, $path) = explode(':', $raw_uri, 2); + $uri = new PhutilURI('ssh://'.$domain.'/'.$path); + } + + if ($this->isSSHProtocol($uri->getProtocol())) { + if ($this->getSSHLogin()) { + $uri->setUser($this->getSSHLogin()); + } + } + + return (string)$uri; + } + + public function getLocalPath() { + return $this->getDetail('local-path'); + } + + public function execRemoteCommand($pattern /*, $arg, ... */) { + $args = func_get_args(); + $args = $this->formatRemoteCommand($args); + return call_user_func_array('exec_manual', $args); + } + + public function execxRemoteCommand($pattern /*, $arg, ... */) { + $args = func_get_args(); + $args = $this->formatRemoteCommand($args); + return call_user_func_array('execx', $args); + } + + public function getRemoteCommandFuture($pattern /*, $arg, ... */) { + $args = func_get_args(); + $args = $this->formatRemoteCommand($args); + return newv('ExecFuture', $args); + } + + public function passthruRemoteCommand($pattern /*, $arg, ... */) { + $args = func_get_args(); + $args = $this->formatRemoteCommand($args); + return call_user_func_array('phutil_passthru', $args); + } + + public function execLocalCommand($pattern /*, $arg, ... */) { + $args = func_get_args(); + $args = $this->formatLocalCommand($args); + return call_user_func_array('exec_manual', $args); + } + + public function execxLocalCommand($pattern /*, $arg, ... */) { + $args = func_get_args(); + $args = $this->formatLocalCommand($args); + return call_user_func_array('execx', $args); + } + + public function getLocalCommandFuture($pattern /*, $arg, ... */) { + $args = func_get_args(); + $args = $this->formatLocalCommand($args); + return newv('ExecFuture', $args); + + } + public function passthruLocalCommand($pattern /*, $arg, ... */) { + $args = func_get_args(); + $args = $this->formatLocalCommand($args); + return call_user_func_array('phutil_passthru', $args); + } + + + private function formatRemoteCommand(array $args) { + $pattern = $args[0]; + $args = array_slice($args, 1); + + if ($this->shouldUseSSH()) { + switch ($this->getVersionControlSystem()) { + case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: + $pattern = "SVN_SSH=%s svn --non-interactive {$pattern}"; + array_unshift( + $args, + csprintf( + 'ssh -l %s -i %s', + $this->getSSHLogin(), + $this->getSSHKeyfile())); + break; + case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: + $command = call_user_func_array( + 'csprintf', + array_merge( + array( + "(ssh-add %s && git {$pattern})", + $this->getSSHKeyfile(), + ), + $args)); + $pattern = "ssh-agent sh -c %s"; + $args = array($command); + break; + case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: + $pattern = "hg --config ui.ssh=%s {$pattern}"; + array_unshift( + $args, + csprintf( + 'ssh -l %s -i %s', + $this->getSSHLogin(), + $this->getSSHKeyfile())); + break; + default: + throw new Exception("Unrecognized version control system."); + } + } else if ($this->shouldUseHTTP()) { + switch ($this->getVersionControlSystem()) { + case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: + $pattern = + "svn ". + "--non-interactive ". + "--no-auth-cache ". + "--trust-server-cert ". + "--username %s ". + "--password %s ". + $pattern; + array_unshift( + $args, + $this->getDetail('http-login'), + $this->getDetail('http-pass')); + break; + default: + throw new Exception( + "No support for HTTP Basic Auth in this version control system."); + } + } else { + switch ($this->getVersionControlSystem()) { + case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: + $pattern = "svn --non-interactive {$pattern}"; + break; + case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: + $pattern = "git {$pattern}"; + break; + case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: + $pattern = "hg {$pattern}"; + break; + default: + throw new Exception("Unrecognized version control system."); + } + } + + array_unshift($args, $pattern); + + return $args; + } + + private function formatLocalCommand(array $args) { + $pattern = $args[0]; + $args = array_slice($args, 1); + + switch ($this->getVersionControlSystem()) { + case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: + $pattern = "(cd %s && svn --non-interactive {$pattern})"; + array_unshift($args, $this->getLocalPath()); + break; + case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: + $pattern = "(cd %s && git {$pattern})"; + array_unshift($args, $this->getLocalPath()); + break; + case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: + $pattern = "(cd %s && hg {$pattern})"; + array_unshift($args, $this->getLocalPath()); + break; + default: + throw new Exception("Unrecognized version control system."); + } + + array_unshift($args, $pattern); + + return $args; + } + + private function getSSHLogin() { + return $this->getDetail('ssh-login'); + } + + private function getSSHKeyfile() { + if ($this->sshKeyfile === null) { + $key = $this->getDetail('ssh-key'); + $keyfile = $this->getDetail('ssh-keyfile'); + if ($keyfile) { + // Make sure we can read the file, that it exists, etc. + Filesystem::readFile($keyfile); + $this->sshKeyfile = $keyfile; + } else if ($key) { + $keyfile = new TempFile('phabricator-repository-ssh-key'); + chmod($keyfile, 0600); + Filesystem::writeFile($keyfile, $key); + $this->sshKeyfile = $keyfile; + } else { + $this->sshKeyfile = ''; + } + } + + return (string)$this->sshKeyfile; + } + + public function shouldUseSSH() { + $uri = new PhutilURI($this->getRemoteURI()); + $protocol = $uri->getProtocol(); + if ($this->isSSHProtocol($protocol)) { + return (bool)$this->getSSHKeyfile(); + } else { + return false; + } + } + + public function shouldUseHTTP() { + $uri = new PhutilURI($this->getRemoteURI()); + $protocol = $uri->getProtocol(); + if ($this->isHTTPProtocol($protocol)) { + return (bool)$this->getDetail('http-login'); + } else { + return false; + } + } + + private function isSSHProtocol($protocol) { + return ($protocol == 'ssh' || $protocol == 'svn+ssh'); + } + + private function isHTTPProtocol($protocol) { + return ($protocol == 'http' || $protocol == 'https'); + } + } diff --git a/src/applications/repository/storage/repository/__init__.php b/src/applications/repository/storage/repository/__init__.php index 6480f38dac..e08a467030 100644 --- a/src/applications/repository/storage/repository/__init__.php +++ b/src/applications/repository/storage/repository/__init__.php @@ -8,9 +8,15 @@ phutil_require_module('phabricator', 'applications/phid/constants'); phutil_require_module('phabricator', 'applications/phid/storage/phid'); +phutil_require_module('phabricator', 'applications/repository/constants/repositorytype'); phutil_require_module('phabricator', 'applications/repository/storage/base'); +phutil_require_module('phutil', 'filesystem'); +phutil_require_module('phutil', 'filesystem/tempfile'); +phutil_require_module('phutil', 'future/exec'); +phutil_require_module('phutil', 'parser/uri'); phutil_require_module('phutil', 'utils'); +phutil_require_module('phutil', 'xsprintf/csprintf'); phutil_require_source('PhabricatorRepository.php'); diff --git a/src/applications/repository/worker/base/PhabricatorRepositoryCommitParserWorker.php b/src/applications/repository/worker/base/PhabricatorRepositoryCommitParserWorker.php index 7596b9cd23..938c9eb199 100644 --- a/src/applications/repository/worker/base/PhabricatorRepositoryCommitParserWorker.php +++ b/src/applications/repository/worker/base/PhabricatorRepositoryCommitParserWorker.php @@ -68,8 +68,8 @@ abstract class PhabricatorRepositoryCommitParserWorker } try { - list($xml) = execx( - "svn log --xml {$verbose} --limit 1 --non-interactive %s@%d", + list($xml) = $this->repository->execxRemoteCommand( + "log --xml {$verbose} --limit 1 %s@%d", $uri, $revision); } catch (CommandException $ex) { diff --git a/src/applications/repository/worker/commitchangeparser/svn/PhabricatorRepositorySvnCommitChangeParserWorker.php b/src/applications/repository/worker/commitchangeparser/svn/PhabricatorRepositorySvnCommitChangeParserWorker.php index f365552475..266dff941a 100644 --- a/src/applications/repository/worker/commitchangeparser/svn/PhabricatorRepositorySvnCommitChangeParserWorker.php +++ b/src/applications/repository/worker/commitchangeparser/svn/PhabricatorRepositorySvnCommitChangeParserWorker.php @@ -496,11 +496,59 @@ class PhabricatorRepositorySvnCommitChangeParserWorker $commit_table = new PhabricatorRepositoryCommit(); $commit_data = queryfx_all( $commit_table->establishConnection('w'), - 'SELECT id, commitIdentifier FROM %T WHERE commitIdentifier in (%Ld)', + 'SELECT id, commitIdentifier FROM %T + WHERE repositoryID = %d AND commitIdentifier in (%Ld)', $commit_table->getTableName(), + $repository->getID(), $commits); - return ipull($commit_data, 'id', 'commitIdentifier'); + $commit_map = ipull($commit_data, 'id', 'commitIdentifier'); + + $need = array(); + foreach ($commits as $commit) { + if (empty($commit_map[$commit])) { + $need[] = $commit; + } + } + + // If we are parsing a Subversion repository and have been configured to + // import only some subdirectory of it, we may find commits which reference + // other foreign commits outside of the directory (for instance, because of + // a move or copy). Rather than trying to execute full parses on them, just + // create stub commits and identify the stubs as foreign commits. + if ($need) { + $subpath = $repository->getDetail('svn-subpath'); + if (!$subpath) { + $commits = implode(', ', $need); + throw new Exception( + "Missing commits ({$need}) in a SVN repository which is not ". + "configured for subdirectory-only parsing!"); + } + foreach ($need as $foreign_commit) { + $commit = new PhabricatorRepositoryCommit(); + $commit->setRepositoryID($repository->getID()); + $commit->setCommitIdentifier($foreign_commit); + $commit->setEpoch(0); + $commit->save(); + + $data = new PhabricatorRepositoryCommitData(); + $data->setCommitID($commit->getID()); + $data->setAuthorName(''); + $data->setCommitMessage(''); + $data->setCommitDetails( + array( + 'foreign-svn-stub' => true, + // Denormalize this to make it easier to debug cases where someone + // did half a parse and then changed the subdirectory or something + // like that. + 'svn-subpath' => $subpath, + )); + $data->save(); + $commit_map[$foreign_commit] = $commit->getID(); + } + } + + return $commit_map; } private function lookupPathFileType( @@ -545,10 +593,9 @@ class PhabricatorRepositorySvnCommitChangeParserWorker // position in the document. $all_paths = array_reverse(array_keys($parents)); foreach (array_chunk($all_paths, 64) as $path_chunk) { - list($raw_xml) = execx( - 'svn --non-interactive --xml ls %C', - implode(' ', $path_chunk)); - + list($raw_xml) = $repository->execxRemoteCommand( + '--xml ls %C', + implode(' ', $path_chunk)); $xml = new SimpleXMLElement($raw_xml); foreach ($xml->list as $list) { $list_path = (string)$list['path']; @@ -621,8 +668,8 @@ class PhabricatorRepositorySvnCommitChangeParserWorker $cache_loc = sys_get_temp_dir().'/diffusion.'.$hashkey.'.svnls'; if (!Filesystem::pathExists($cache_loc)) { $tmp = new TempFile(); - execx( - 'svn --non-interactive --xml ls -R %s%s@%d > %s', + $repository->execxRemoteCommand( + '--xml ls -R %s%s@%d > %s', $repository->getDetail('remote-uri'), $path, $rev, diff --git a/src/applications/repository/worker/commitchangeparser/svn/__init__.php b/src/applications/repository/worker/commitchangeparser/svn/__init__.php index 27bd1250dc..88eb272294 100644 --- a/src/applications/repository/worker/commitchangeparser/svn/__init__.php +++ b/src/applications/repository/worker/commitchangeparser/svn/__init__.php @@ -8,6 +8,7 @@ phutil_require_module('phabricator', 'applications/differential/constants/changetype'); phutil_require_module('phabricator', 'applications/repository/storage/commit'); +phutil_require_module('phabricator', 'applications/repository/storage/commitdata'); phutil_require_module('phabricator', 'applications/repository/storage/repository'); phutil_require_module('phabricator', 'applications/repository/worker/commitchangeparser/base'); phutil_require_module('phabricator', 'storage/qsprintf'); diff --git a/src/infrastructure/setup/PhabricatorSetup.php b/src/infrastructure/setup/PhabricatorSetup.php index 5a27eb4e50..59648608f6 100644 --- a/src/infrastructure/setup/PhabricatorSetup.php +++ b/src/infrastructure/setup/PhabricatorSetup.php @@ -145,6 +145,21 @@ class PhabricatorSetup { } } + list($err, $stdout, $stderr) = exec_manual( + '/usr/bin/env php -r %s', + 'exit;'); + if ($err) { + self::writeFailure(); + self::write("Unable to execute 'php' on the command line from the web ". + "server. Verify that 'php' is in the webserver's PATH.\n". + " err: {$err}\n". + "stdout: {$stdout}\n". + "stderr: {$stderr}\n"); + return; + } else { + self::write(" okay PHP is available from the command line.\n"); + } + $root = dirname(phutil_get_library_root('phabricator')); // On RHEL6, doing a distro install of pcntl makes it available from the diff --git a/src/storage/connection/mysql/AphrontMySQLDatabaseConnection.php b/src/storage/connection/mysql/AphrontMySQLDatabaseConnection.php index 4cce4ed84e..6a9b7639c9 100644 --- a/src/storage/connection/mysql/AphrontMySQLDatabaseConnection.php +++ b/src/storage/connection/mysql/AphrontMySQLDatabaseConnection.php @@ -209,7 +209,7 @@ class AphrontMySQLDatabaseConnection extends AphrontDatabaseConnection { $this->requireConnection(); // TODO: Do we need to include transactional statements here? - $is_write = !preg_match('/^(SELECT|SHOW)\s/', $raw_query); + $is_write = !preg_match('/^(SELECT|SHOW|EXPLAIN)\s/', $raw_query); if ($is_write) { AphrontWriteGuard::willWrite(); } diff --git a/src/view/layout/filepreview/AphrontFilePreviewView.php b/src/view/layout/filepreview/AphrontFilePreviewView.php index 274d640e77..d1f017e4dd 100644 --- a/src/view/layout/filepreview/AphrontFilePreviewView.php +++ b/src/view/layout/filepreview/AphrontFilePreviewView.php @@ -41,7 +41,7 @@ final class AphrontFilePreviewView extends AphrontView { $link = phutil_render_tag( 'a', array( - 'href' => $file->getViewURI(), + 'href' => $file->getBestURI(), 'target' => '_blank', ), $img); diff --git a/webroot/index.php b/webroot/index.php index 44159da28b..2c2154fadb 100644 --- a/webroot/index.php +++ b/webroot/index.php @@ -250,7 +250,7 @@ function phabricator_shutdown() { return; } - if ($event['type'] != E_ERROR) { + if ($event['type'] != E_ERROR && $event['type'] != E_PARSE) { return; } diff --git a/webroot/rsrc/css/aphront/form-view.css b/webroot/rsrc/css/aphront/form-view.css index 6c228cc51a..f05e957d3f 100644 --- a/webroot/rsrc/css/aphront/form-view.css +++ b/webroot/rsrc/css/aphront/form-view.css @@ -87,6 +87,18 @@ margin: 0.75em 3% 1.25em; } +.aphront-form-important { + margin: .5em 0; + background: #ffffdd; + padding: .5em 1em; +} +.aphront-form-important code { + display: block; + padding: .25em; + margin: .5em 2em; +} + + .aphront-form-control-static .aphront-form-input { padding-top: 4px; font-size: 13px; diff --git a/webroot/rsrc/css/application/contentsource/content-source-view.css b/webroot/rsrc/css/application/contentsource/content-source-view.css index 9ab0da3c07..09ee5aac3d 100644 --- a/webroot/rsrc/css/application/contentsource/content-source-view.css +++ b/webroot/rsrc/css/application/contentsource/content-source-view.css @@ -3,30 +3,5 @@ */ .phabricator-content-source-view { - padding: 2px 20px 2px 0; - font-size: 11px; color: #888888; - font-weight: normal; - background: no-repeat right center; - position: relative; -} - -.phabricator-content-source-web { - background-image: url(/rsrc/image/icon/fatcow/source/web.png); -} - -.phabricator-content-source-email { - background-image: url(/rsrc/image/icon/fatcow/source/email.png); -} - -.phabricator-content-source-conduit { - background-image: url(/rsrc/image/icon/fatcow/source/conduit.png); -} - -.phabricator-content-source-mobile { - background-image: url(/rsrc/image/icon/fatcow/source/mobile.png); -} - -.phabricator-content-source-tablet { - background-image: url(/rsrc/image/icon/fatcow/source/tablet.png); } diff --git a/webroot/rsrc/css/application/differential/core.css b/webroot/rsrc/css/application/differential/core.css index 643991eaa9..12c8b24a79 100644 --- a/webroot/rsrc/css/application/differential/core.css +++ b/webroot/rsrc/css/application/differential/core.css @@ -15,7 +15,7 @@ .differential-panel { margin: 25px 0; - max-width: 1118px; + max-width: 1120px; border: 1px solid #666622; background: #efefdf; padding: 15px 20px; diff --git a/webroot/rsrc/image/icon/fatcow/README b/webroot/rsrc/image/icon/fatcow/README index 5d3ea5ed31..c294834741 100644 --- a/webroot/rsrc/image/icon/fatcow/README +++ b/webroot/rsrc/image/icon/fatcow/README @@ -13,4 +13,5 @@ Some icons have been adapted from the FatCow set for use in Phabricator: source/email.png (from email.png) source/conduit.png (from satellite_dish.png) source/mobile.png (from phone.png) - source/tablet.png (from tablet.png) \ No newline at end of file + source/tablet.png (from tablet.png) + source/fax.png (from fax.png) \ No newline at end of file diff --git a/webroot/rsrc/image/icon/fatcow/source/fax.png b/webroot/rsrc/image/icon/fatcow/source/fax.png new file mode 100644 index 0000000000..399c895aaa Binary files /dev/null and b/webroot/rsrc/image/icon/fatcow/source/fax.png differ