1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-20 20:40:56 +01:00

Add an "importing" state to repositories and clean up the UI

Summary:
Fixes T3217. Ref T776. Ref T1493. Broadly, this introduces a mechanism which works like this:

  - When a repository is created, we set an "importing" flag.
  - After discovery completes, we check if a repository has no importing commits. Basically, this is the first time we catch up to HEAD.
  - If we're caught up, clear the "importing" flag.

This flag lets us fix some issues:

  - T3217. Currently, when you import a new repository and users have rules like "Email me on every commit ever" or "trigger an audit on every commit", we take a bunch of publish actions. Instead, implicitly disable publishing during import.
  - An imported but un-pulled repository currently has an incomprehensible error on `/diffusion/X/`. Fix that.
  - Show more cues in the UI about importing.
  - Made some exceptions more specific.

Test Plan:
This is the new screen for a completely new repo, replacing a giant exception:

{F75443}

  - Created a repository, saw it "importing".
  - Pulled and discovered it.
  - Processed its commits.
  - Ran discovery again, saw import flag clear.
  - Also this repository was empty, which hit some of the other code.

This is the new "parsed empty repository" UI, which isn't good, but is less broken:

{F75446}

Reviewers: btrahan

Reviewed By: btrahan

CC: aran, hach-que

Maniphest Tasks: T3607, T1493, T776, T3217

Differential Revision: https://secure.phabricator.com/D7429
This commit is contained in:
epriestley 2013-10-26 19:59:57 -07:00
parent 9125095587
commit d8bda7c66e
8 changed files with 318 additions and 100 deletions

View file

@ -1080,11 +1080,5 @@ final class DiffusionCommitController extends DiffusionController {
return $parser->processCorpus($corpus);
}
private function renderStatusMessage($title, $body) {
return id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_WARNING)
->setTitle($title)
->appendChild($body);
}
}

View file

@ -442,5 +442,11 @@ abstract class DiffusionController extends PhabricatorController {
return preg_replace('@^/diffusion/[A-Z]+@', '', $request_path);
}
protected function renderStatusMessage($title, $body) {
return id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_WARNING)
->setTitle($title)
->appendChild($body);
}
}

View file

@ -7,7 +7,8 @@ final class DiffusionRepositoryController extends DiffusionController {
}
public function processRequest() {
$drequest = $this->diffusionRequest;
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
$content = array();
@ -15,106 +16,108 @@ final class DiffusionRepositoryController extends DiffusionController {
$content[] = $crumbs;
$content[] = $this->buildPropertiesTable($drequest->getRepository());
$history_results = $this->callConduitWithDiffusionRequest(
'diffusion.historyquery',
array(
'commit' => $drequest->getCommit(),
'path' => $drequest->getPath(),
'offset' => 0,
'limit' => 15));
$history = DiffusionPathChange::newFromConduit(
$history_results['pathChanges']);
$browse_results = DiffusionBrowseResultSet::newFromConduit(
$this->callConduitWithDiffusionRequest(
'diffusion.browsequery',
array(
'path' => $drequest->getPath(),
'commit' => $drequest->getCommit(),
)));
$browse_paths = $browse_results->getPaths();
$phids = array();
foreach ($history as $item) {
$data = $item->getCommitData();
if ($data) {
if ($data->getCommitDetail('authorPHID')) {
$phids[$data->getCommitDetail('authorPHID')] = true;
}
if ($data->getCommitDetail('committerPHID')) {
$phids[$data->getCommitDetail('committerPHID')] = true;
try {
$history_results = $this->callConduitWithDiffusionRequest(
'diffusion.historyquery',
array(
'commit' => $drequest->getCommit(),
'path' => $drequest->getPath(),
'offset' => 0,
'limit' => 15));
$history = DiffusionPathChange::newFromConduit(
$history_results['pathChanges']);
foreach ($history as $item) {
$data = $item->getCommitData();
if ($data) {
if ($data->getCommitDetail('authorPHID')) {
$phids[$data->getCommitDetail('authorPHID')] = true;
}
if ($data->getCommitDetail('committerPHID')) {
$phids[$data->getCommitDetail('committerPHID')] = true;
}
}
}
} catch (Exception $ex) {
$history_results = null;
$history = null;
$history_exception = $ex;
}
foreach ($browse_paths as $item) {
$data = $item->getLastCommitData();
if ($data) {
if ($data->getCommitDetail('authorPHID')) {
$phids[$data->getCommitDetail('authorPHID')] = true;
}
if ($data->getCommitDetail('committerPHID')) {
$phids[$data->getCommitDetail('committerPHID')] = true;
try {
$browse_results = DiffusionBrowseResultSet::newFromConduit(
$this->callConduitWithDiffusionRequest(
'diffusion.browsequery',
array(
'path' => $drequest->getPath(),
'commit' => $drequest->getCommit(),
)));
$browse_paths = $browse_results->getPaths();
foreach ($browse_paths as $item) {
$data = $item->getLastCommitData();
if ($data) {
if ($data->getCommitDetail('authorPHID')) {
$phids[$data->getCommitDetail('authorPHID')] = true;
}
if ($data->getCommitDetail('committerPHID')) {
$phids[$data->getCommitDetail('committerPHID')] = true;
}
}
}
} catch (Exception $ex) {
$browse_results = null;
$browse_paths = null;
$browse_exception = $ex;
}
$phids = array_keys($phids);
$handles = $this->loadViewerHandles($phids);
$readme = $this->callConduitWithDiffusionRequest(
'diffusion.readmequery',
array(
'paths' => $browse_results->getPathDicts()
if ($browse_results) {
$readme = $this->callConduitWithDiffusionRequest(
'diffusion.readmequery',
array(
'paths' => $browse_results->getPathDicts()
));
} else {
$readme = null;
}
$history_table = new DiffusionHistoryTableView();
$history_table->setUser($this->getRequest()->getUser());
$history_table->setDiffusionRequest($drequest);
$history_table->setHandles($handles);
$history_table->setHistory($history);
$history_table->loadRevisions();
$history_table->setParents($history_results['parents']);
$history_table->setIsHead(true);
$content[] = $this->buildHistoryTable(
$history_results,
$history,
$history_exception,
$handles);
$callsign = $drequest->getRepository()->getCallsign();
$all = phutil_tag(
'a',
array(
'href' => $drequest->generateURI(
array(
'action' => 'history',
)),
),
pht('View Full Commit History'));
$content[] = $this->buildBrowseTable(
$browse_results,
$browse_paths,
$browse_exception,
$handles);
$panel = new AphrontPanelView();
$panel->setHeader(pht("Recent Commits · %s", $all));
$panel->appendChild($history_table);
$panel->setNoBackground();
try {
$content[] = $this->buildTagListTable($drequest);
} catch (Exception $ex) {
if (!$repository->isImporting()) {
$content[] = $this->renderStatusMessage(
pht('Unable to Load Tags'),
$ex->getMessage());
}
}
$content[] = $panel;
$browse_table = new DiffusionBrowseTableView();
$browse_table->setDiffusionRequest($drequest);
$browse_table->setHandles($handles);
$browse_table->setPaths($browse_paths);
$browse_table->setUser($this->getRequest()->getUser());
$browse_panel = new AphrontPanelView();
$browse_panel->setHeader(phutil_tag(
'a',
array('href' => $drequest->generateURI(array('action' => 'browse'))),
pht('Browse Repository')));
$browse_panel->appendChild($browse_table);
$browse_panel->setNoBackground();
$content[] = $browse_panel;
$content[] = $this->buildTagListTable($drequest);
$content[] = $this->buildBranchListTable($drequest);
try {
$content[] = $this->buildBranchListTable($drequest);
} catch (Exception $ex) {
if (!$repository->isImporting()) {
$content[] = $this->renderStatusMessage(
pht('Unable to Load Branches'),
$ex->getMessage());
}
}
if ($readme) {
$box = new PHUIBoxView();
@ -145,6 +148,15 @@ final class DiffusionRepositoryController extends DiffusionController {
->setUser($user)
->setPolicyObject($repository);
if (!$repository->isTracked()) {
$header->setStatus('policy-noone', '', pht('Inactive'));
} else if ($repository->isImporting()) {
$header->setStatus('time', 'red', pht('Importing...'));
} else {
$header->setStatus('oh-ok', '', pht('Active'));
}
$actions = $this->buildActionList($repository);
$view = id(new PHUIPropertyListView())
@ -333,4 +345,109 @@ final class DiffusionRepositoryController extends DiffusionController {
return $view;
}
private function buildHistoryTable(
$history_results,
$history,
$history_exception,
array $handles) {
$request = $this->getRequest();
$viewer = $request->getUser();
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
if ($history_exception) {
if ($repository->isImporting()) {
return $this->renderStatusMessage(
pht('Still Importing...'),
pht(
'This repository is still importing. History is not yet '.
'available.'));
} else {
return $this->renderStatusMessage(
pht('Unable to Retrieve History'),
$history_exception->getMessage());
}
}
$history_table = id(new DiffusionHistoryTableView())
->setUser($viewer)
->setDiffusionRequest($drequest)
->setHandles($handles)
->setHistory($history);
// TODO: Super sketchy.
$history_table->loadRevisions();
if ($history_results) {
$history_table->setParents($history_results['parents']);
}
$history_table->setIsHead(true);
$callsign = $drequest->getRepository()->getCallsign();
$all = phutil_tag(
'a',
array(
'href' => $drequest->generateURI(
array(
'action' => 'history',
)),
),
pht('View Full Commit History'));
$panel = new AphrontPanelView();
$panel->setHeader(pht("Recent Commits · %s", $all));
$panel->appendChild($history_table);
$panel->setNoBackground();
return $panel;
}
private function buildBrowseTable(
$browse_results,
$browse_paths,
$browse_exception,
array $handles) {
$request = $this->getRequest();
$viewer = $request->getUser();
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
if ($browse_exception) {
if ($repository->isImporting()) {
// The history table renders a useful message.
return null;
} else {
return $this->renderStatusMessage(
pht('Unable to Retrieve Paths'),
$browse_exception->getMessage());
}
}
$browse_table = id(new DiffusionBrowseTableView())
->setUser($viewer)
->setDiffusionRequest($drequest)
->setHandles($handles);
if ($browse_paths) {
$browse_table->setPaths($browse_paths);
} else {
$browse_table->setPaths(array());
}
$browse_uri = $drequest->generateURI(array('action' => 'browse'));
$browse_panel = new AphrontPanelView();
$browse_panel->setHeader(
phutil_tag(
'a',
array('href' => $browse_uri),
pht('Browse Repository')));
$browse_panel->appendChild($browse_table);
$browse_panel->setNoBackground();
return $browse_panel;
}
}

View file

@ -95,6 +95,10 @@ final class DiffusionRepositoryCreateController
// transactions to apply this change.
$repository->setCallsign($callsign);
// Put the repository in "Importing" mode until we finish
// parsing it.
$repository->setDetail('importing', true);
$xactions[] = id(clone $template)
->setTransactionType($type_name)
->setNewValue(

View file

@ -21,6 +21,8 @@ final class DiffusionHistoryTableView extends DiffusionView {
$commit_phids[] = $item->getCommit()->getPHID();
}
}
// TODO: Get rid of this.
$this->revisions = id(new DifferentialRevision())
->loadIDsByCommitPHIDs($commit_phids);
return $this;

View file

@ -201,28 +201,41 @@ final class PhabricatorRepositoryPullLocalDaemon
public function discoverRepository(PhabricatorRepository $repository) {
$vcs = $repository->getVersionControlSystem();
$result = null;
$refs = null;
switch ($vcs) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
return $this->executeGitDiscover($repository);
$result = $this->executeGitDiscover($repository);
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$refs = $this->getDiscoveryEngine($repository)
->discoverCommits();
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
return $this->executeHgDiscover($repository);
$result = $this->executeHgDiscover($repository);
break;
default:
throw new Exception("Unknown VCS '{$vcs}'!");
}
foreach ($refs as $ref) {
$this->recordCommit(
$repository,
$ref->getIdentifier(),
$ref->getEpoch(),
$ref->getBranch());
if ($refs !== null) {
foreach ($refs as $ref) {
$this->recordCommit(
$repository,
$ref->getIdentifier(),
$ref->getEpoch(),
$ref->getBranch());
}
}
return (bool)count($refs);
$this->checkIfRepositoryIsFullyImported($repository);
if ($refs !== null) {
return (bool)count($refs);
} else {
return $result;
}
}
private function getDiscoveryEngine(PhabricatorRepository $repository) {
@ -456,7 +469,39 @@ final class PhabricatorRepositoryPullLocalDaemon
return $repository->getID().':'.$commit_identifier;
}
private function checkIfRepositoryIsFullyImported(
PhabricatorRepository $repository) {
// Check if the repository has the "Importing" flag set. We want to clear
// the flag if we can.
$importing = $repository->getDetail('importing');
if (!$importing) {
// This repository isn't marked as "Importing", so we're done.
return;
}
// Look for any commit which hasn't imported.
$unparsed_commit = queryfx_one(
$repository->establishConnection('r'),
'SELECT * FROM %T WHERE repositoryID = %d AND importStatus != %d',
id(new PhabricatorRepositoryCommit())->getTableName(),
$repository->getID(),
PhabricatorRepositoryCommit::IMPORTED_ALL);
if ($unparsed_commit) {
// We found a commit which still needs to import, so we can't clear the
// flag.
return;
}
// Clear the "importing" flag.
$repository->openTransaction();
$repository->beginReadLocking();
$repository = $repository->reload();
$repository->setDetail('importing', false);
$repository->save();
$repository->endReadLocking();
$repository->saveTransaction();
}
/* -( Git Implementation )------------------------------------------------- */
@ -488,6 +533,12 @@ final class PhabricatorRepositoryPullLocalDaemon
$stdout,
$only_this_remote = DiffusionBranchInformation::DEFAULT_GIT_REMOTE);
if (!$branches) {
// This repository has no branches at all, so we don't need to do
// anything. Generally, this means the repository is empty.
return;
}
$callsign = $repository->getCallsign();
$tracked_something = false;

View file

@ -192,24 +192,32 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
}
public function execLocalCommand($pattern /* , $arg, ... */) {
$this->assertLocalExists();
$args = func_get_args();
$args = $this->formatLocalCommand($args);
return call_user_func_array('exec_manual', $args);
}
public function execxLocalCommand($pattern /* , $arg, ... */) {
$this->assertLocalExists();
$args = func_get_args();
$args = $this->formatLocalCommand($args);
return call_user_func_array('execx', $args);
}
public function getLocalCommandFuture($pattern /* , $arg, ... */) {
$this->assertLocalExists();
$args = func_get_args();
$args = $this->formatLocalCommand($args);
return newv('ExecFuture', $args);
}
public function passthruLocalCommand($pattern /* , $arg, ... */) {
$this->assertLocalExists();
$args = func_get_args();
$args = $this->formatLocalCommand($args);
return call_user_func_array('phutil_passthru', $args);
@ -433,6 +441,10 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
}
public function shouldAutocloseBranch($branch) {
if ($this->isImporting()) {
return false;
}
if ($this->getDetail('disable-autoclose', false)) {
return false;
}
@ -486,6 +498,10 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
return 'r'.$this->getCallsign().$short_identifier;
}
public function isImporting() {
return (bool)$this->getDetail('importing', false);
}
/* -( Repository URI Management )------------------------------------------ */
@ -750,6 +766,27 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
}
/**
* Raise more useful errors when there are basic filesystem problems.
*/
private function assertLocalExists() {
switch ($this->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
if (!$this->isHosted()) {
// For non-hosted SVN repositories, we don't expect a local directory
// to exist.
return;
}
break;
}
$local = $this->getLocalPath();
Filesystem::assertExists($local);
Filesystem::assertIsDirectory($local);
Filesystem::assertReadable($local);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */

View file

@ -24,6 +24,13 @@ final class PhabricatorRepositoryCommitHeraldWorker
PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit) {
// Don't take any actions on an importing repository. Principally, this
// avoids generating thousands of audits or emails when you import an
// established repository on an existing install.
if ($repository->isImporting()) {
return;
}
$data = id(new PhabricatorRepositoryCommitData())->loadOneWhere(
'commitID = %d',
$commit->getID());