mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-30 01:10:58 +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:
parent
9125095587
commit
d8bda7c66e
8 changed files with 318 additions and 100 deletions
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,8 @@ final class DiffusionRepositoryController extends DiffusionController {
|
|||
}
|
||||
|
||||
public function processRequest() {
|
||||
$drequest = $this->diffusionRequest;
|
||||
$drequest = $this->getDiffusionRequest();
|
||||
$repository = $drequest->getRepository();
|
||||
|
||||
$content = array();
|
||||
|
||||
|
@ -15,7 +16,9 @@ final class DiffusionRepositoryController extends DiffusionController {
|
|||
$content[] = $crumbs;
|
||||
|
||||
$content[] = $this->buildPropertiesTable($drequest->getRepository());
|
||||
$phids = array();
|
||||
|
||||
try {
|
||||
$history_results = $this->callConduitWithDiffusionRequest(
|
||||
'diffusion.historyquery',
|
||||
array(
|
||||
|
@ -26,16 +29,6 @@ final class DiffusionRepositoryController extends DiffusionController {
|
|||
$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) {
|
||||
|
@ -47,6 +40,21 @@ final class DiffusionRepositoryController extends DiffusionController {
|
|||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
$history_results = null;
|
||||
$history = null;
|
||||
$history_exception = $ex;
|
||||
}
|
||||
|
||||
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();
|
||||
|
@ -59,62 +67,57 @@ final class DiffusionRepositoryController extends DiffusionController {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception $ex) {
|
||||
$browse_results = null;
|
||||
$browse_paths = null;
|
||||
$browse_exception = $ex;
|
||||
}
|
||||
|
||||
$phids = array_keys($phids);
|
||||
$handles = $this->loadViewerHandles($phids);
|
||||
|
||||
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'));
|
||||
|
||||
$panel = new AphrontPanelView();
|
||||
$panel->setHeader(pht("Recent Commits · %s", $all));
|
||||
$panel->appendChild($history_table);
|
||||
$panel->setNoBackground();
|
||||
|
||||
$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->buildBrowseTable(
|
||||
$browse_results,
|
||||
$browse_paths,
|
||||
$browse_exception,
|
||||
$handles);
|
||||
|
||||
try {
|
||||
$content[] = $this->buildTagListTable($drequest);
|
||||
} catch (Exception $ex) {
|
||||
if (!$repository->isImporting()) {
|
||||
$content[] = $this->renderStatusMessage(
|
||||
pht('Unable to Load Tags'),
|
||||
$ex->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -201,19 +201,25 @@ 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}'!");
|
||||
}
|
||||
|
||||
if ($refs !== null) {
|
||||
foreach ($refs as $ref) {
|
||||
$this->recordCommit(
|
||||
$repository,
|
||||
|
@ -221,8 +227,15 @@ final class PhabricatorRepositoryPullLocalDaemon
|
|||
$ref->getEpoch(),
|
||||
$ref->getBranch());
|
||||
}
|
||||
}
|
||||
|
||||
$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;
|
||||
|
|
|
@ -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 )----------------------------------------- */
|
||||
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
Loading…
Reference in a new issue