1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-09 16:32:39 +01:00

Implement snapshots in Phragment

Summary:
Ref T4212.  This implements snapshots in Phragment, which allows you to take a snapshot of a fragment at a given point in time, and download a ZIP of the snapshot as it was in this state.

There's also functionality for deleting and promoting snapshots.  You can promote a snapshot to either the latest version or any other snapshot of the fragment.

Test Plan: Clicked around, took some snapshots, promoted them to different points and deleted snapshots.  Also downloaded ZIPs of the snapshots and saw the right versions coming through for all the files downloaded.

Reviewers: epriestley, #blessed_reviewers

Reviewed By: epriestley

CC: Korvin, epriestley, aran

Maniphest Tasks: T4205, T4212

Differential Revision: https://secure.phabricator.com/D7741
This commit is contained in:
James Rhodes 2013-12-09 08:24:50 +11:00
parent e165787725
commit 8bb6e807f0
20 changed files with 1151 additions and 9 deletions

View file

@ -0,0 +1,21 @@
CREATE TABLE {$NAMESPACE}_phragment.phragment_snapshot (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
primaryFragmentPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
name VARCHAR(192) NOT NULL COLLATE utf8_bin,
description LONGTEXT NULL COLLATE utf8_bin,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (phid),
UNIQUE KEY `key_name` (primaryFragmentPHID, name)
) ENGINE=InnoDB, COLLATE utf8_general_ci;
CREATE TABLE {$NAMESPACE}_phragment.phragment_snapshotchild (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
snapshotPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
fragmentPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
fragmentVersionPHID VARCHAR(64) NULL COLLATE utf8_bin,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_child` (snapshotPHID, fragmentPHID, fragmentVersionPHID)
) ENGINE=InnoDB, COLLATE utf8_general_ci;

View file

@ -2186,9 +2186,18 @@ phutil_register_library_map(array(
'PhragmentHistoryController' => 'applications/phragment/controller/PhragmentHistoryController.php',
'PhragmentPHIDTypeFragment' => 'applications/phragment/phid/PhragmentPHIDTypeFragment.php',
'PhragmentPHIDTypeFragmentVersion' => 'applications/phragment/phid/PhragmentPHIDTypeFragmentVersion.php',
'PhragmentPHIDTypeSnapshot' => 'applications/phragment/phid/PhragmentPHIDTypeSnapshot.php',
'PhragmentPatchController' => 'applications/phragment/controller/PhragmentPatchController.php',
'PhragmentPatchUtil' => 'applications/phragment/util/PhragmentPatchUtil.php',
'PhragmentRevertController' => 'applications/phragment/controller/PhragmentRevertController.php',
'PhragmentSnapshot' => 'applications/phragment/storage/PhragmentSnapshot.php',
'PhragmentSnapshotChild' => 'applications/phragment/storage/PhragmentSnapshotChild.php',
'PhragmentSnapshotChildQuery' => 'applications/phragment/query/PhragmentSnapshotChildQuery.php',
'PhragmentSnapshotCreateController' => 'applications/phragment/controller/PhragmentSnapshotCreateController.php',
'PhragmentSnapshotDeleteController' => 'applications/phragment/controller/PhragmentSnapshotDeleteController.php',
'PhragmentSnapshotPromoteController' => 'applications/phragment/controller/PhragmentSnapshotPromoteController.php',
'PhragmentSnapshotQuery' => 'applications/phragment/query/PhragmentSnapshotQuery.php',
'PhragmentSnapshotViewController' => 'applications/phragment/controller/PhragmentSnapshotViewController.php',
'PhragmentUpdateController' => 'applications/phragment/controller/PhragmentUpdateController.php',
'PhragmentVersionController' => 'applications/phragment/controller/PhragmentVersionController.php',
'PhragmentZIPController' => 'applications/phragment/controller/PhragmentZIPController.php',
@ -4789,9 +4798,26 @@ phutil_register_library_map(array(
'PhragmentHistoryController' => 'PhragmentController',
'PhragmentPHIDTypeFragment' => 'PhabricatorPHIDType',
'PhragmentPHIDTypeFragmentVersion' => 'PhabricatorPHIDType',
'PhragmentPHIDTypeSnapshot' => 'PhabricatorPHIDType',
'PhragmentPatchController' => 'PhragmentController',
'PhragmentPatchUtil' => 'Phobject',
'PhragmentRevertController' => 'PhragmentController',
'PhragmentSnapshot' =>
array(
0 => 'PhragmentDAO',
1 => 'PhabricatorPolicyInterface',
),
'PhragmentSnapshotChild' =>
array(
0 => 'PhragmentDAO',
1 => 'PhabricatorPolicyInterface',
),
'PhragmentSnapshotChildQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhragmentSnapshotCreateController' => 'PhragmentController',
'PhragmentSnapshotDeleteController' => 'PhragmentController',
'PhragmentSnapshotPromoteController' => 'PhragmentController',
'PhragmentSnapshotQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhragmentSnapshotViewController' => 'PhragmentController',
'PhragmentUpdateController' => 'PhragmentController',
'PhragmentVersionController' => 'PhragmentController',
'PhragmentZIPController' => 'PhragmentController',

View file

@ -39,9 +39,19 @@ final class PhabricatorApplicationPhragment extends PhabricatorApplication {
'update/(?P<dblob>.*)' => 'PhragmentUpdateController',
'history/(?P<dblob>.*)' => 'PhragmentHistoryController',
'zip/(?P<dblob>.*)' => 'PhragmentZIPController',
'zip@(?P<snapshot>[^/]+)/(?P<dblob>.*)' => 'PhragmentZIPController',
'version/(?P<id>[0-9]*)/' => 'PhragmentVersionController',
'patch/(?P<aid>[0-9x]*)/(?P<bid>[0-9]*)/' => 'PhragmentPatchController',
'revert/(?P<id>[0-9]*)/(?P<dblob>.*)' => 'PhragmentRevertController',
'snapshot/' => array(
'create/(?P<dblob>.*)' => 'PhragmentSnapshotCreateController',
'view/(?P<id>[0-9]*)/' => 'PhragmentSnapshotViewController',
'delete/(?P<id>[0-9]*)/' => 'PhragmentSnapshotDeleteController',
'promote/' => array(
'latest/(?P<dblob>.*)' => 'PhragmentSnapshotPromoteController',
'(?P<id>[0-9]*)/' => 'PhragmentSnapshotPromoteController',
),
),
),
);
}

View file

@ -56,7 +56,7 @@ final class PhragmentBrowseController extends PhragmentController {
foreach ($fragments as $fragment) {
$item = id(new PHUIObjectItemView());
$item->setHeader($fragment->getName());
$item->setHref($this->getApplicationURI('/browse/'.$fragment->getPath()));
$item->setHref($fragment->getURI());
if (!$fragment->isDirectory()) {
$item->addAttribute(pht(
'Last Updated %s',

View file

@ -64,6 +64,16 @@ abstract class PhragmentController extends PhabricatorController {
$phids = array();
$phids[] = $fragment->getLatestVersionPHID();
$snapshot_phids = array();
$snapshots = id(new PhragmentSnapshotQuery())
->setViewer($viewer)
->withPrimaryFragmentPHIDs(array($fragment->getPHID()))
->execute();
foreach ($snapshots as $snapshot) {
$phids[] = $snapshot->getPHID();
$snapshot_phids[] = $snapshot->getPHID();
}
$this->loadHandles($phids);
$file = null;
@ -127,6 +137,21 @@ abstract class PhragmentController extends PhabricatorController {
->setHref($this->getApplicationURI("history/".$fragment->getPath()))
->setIcon('history'));
}
$actions->addAction(
id(new PhabricatorActionView())
->setName(pht('Create Snapshot'))
->setHref($this->getApplicationURI(
"snapshot/create/".$fragment->getPath()))
->setDisabled(false) // TODO: Policy
->setIcon('snapshot'));
$actions->addAction(
id(new PhabricatorActionView())
->setName(pht('Promote Snapshot to Here'))
->setHref($this->getApplicationURI(
"snapshot/promote/latest/".$fragment->getPath()))
->setWorkflow(true)
->setDisabled(false) // TODO: Policy
->setIcon('promote'));
$properties = id(new PHUIPropertyListView())
->setUser($viewer)
@ -152,6 +177,12 @@ abstract class PhragmentController extends PhabricatorController {
pht('Directory'));
}
if (count($snapshot_phids) > 0) {
$properties->addProperty(
pht('Snapshots'),
$this->renderHandlesForPHIDs($snapshot_phids));
}
return id(new PHUIObjectBoxView())
->setHeader($header)
->addPropertyList($properties);

View file

@ -0,0 +1,170 @@
<?php
final class PhragmentSnapshotCreateController extends PhragmentController {
private $dblob;
public function willProcessRequest(array $data) {
$this->dblob = idx($data, "dblob", "");
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$parents = $this->loadParentFragments($this->dblob);
if ($parents === null) {
return new Aphront404Response();
}
$fragment = nonempty(last($parents), null);
if ($fragment === null) {
return new Aphront404Response();
}
$children = id(new PhragmentFragmentQuery())
->setViewer($viewer)
->needLatestVersion(true)
->withLeadingPath($fragment->getPath().'/')
->execute();
$error_view = null;
if ($request->isFormPost()) {
$errors = array();
$v_name = $request->getStr('name');
if (strlen($v_name) === 0) {
$errors[] = pht('You must specify a name.');
}
if (strpos($v_name, '/') !== false) {
$errors[] = pht('Snapshot names can not contain "/".');
}
if (!count($errors)) {
$snapshot = null;
try {
// Create the snapshot.
$snapshot = id(new PhragmentSnapshot())
->setPrimaryFragmentPHID($fragment->getPHID())
->setName($v_name)
->save();
} catch (AphrontQueryDuplicateKeyException $e) {
$errors[] = pht('A snapshot with this name already exists.');
}
if (!count($errors)) {
// Add the primary fragment.
id(new PhragmentSnapshotChild())
->setSnapshotPHID($snapshot->getPHID())
->setFragmentPHID($fragment->getPHID())
->setFragmentVersionPHID($fragment->getLatestVersionPHID())
->save();
// Add all of the child fragments.
foreach ($children as $child) {
id(new PhragmentSnapshotChild())
->setSnapshotPHID($snapshot->getPHID())
->setFragmentPHID($child->getPHID())
->setFragmentVersionPHID($child->getLatestVersionPHID())
->save();
}
return id(new AphrontRedirectResponse())
->setURI('/phragment/snapshot/view/'.$snapshot->getID());
}
}
$error_view = id(new AphrontErrorView())
->setErrors($errors)
->setTitle(pht('Errors while creating snapshot'));
}
$fragment_sequence = "-";
if ($fragment->getLatestVersion() !== null) {
$fragment_sequence = $fragment->getLatestVersion()->getSequence();
}
$rows = array();
$rows[] = phutil_tag(
'tr',
array(),
array(
phutil_tag('th', array(), 'Fragment'),
phutil_tag('th', array(), 'Version')));
$rows[] = phutil_tag(
'tr',
array(),
array(
phutil_tag('td', array(), $fragment->getPath()),
phutil_tag('td', array(), $fragment_sequence)));
foreach ($children as $child) {
$sequence = "-";
if ($child->getLatestVersion() !== null) {
$sequence = $child->getLatestVersion()->getSequence();
}
$rows[] = phutil_tag(
'tr',
array(),
array(
phutil_tag('td', array(), $child->getPath()),
phutil_tag('td', array(), $sequence)));
}
$table = phutil_tag(
'table',
array('class' => 'remarkup-table'),
$rows);
$container = phutil_tag(
'div',
array('class' => 'phabricator-remarkup'),
array(
phutil_tag(
'p',
array(),
pht(
"The snapshot will contain the following fragments at ".
"the specified versions: ")),
$table));
$form = id(new AphrontFormView())
->setUser($viewer)
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Fragment Path'))
->setDisabled(true)
->setValue('/'.$fragment->getPath()))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Snapshot Name'))
->setName('name'))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Create Snapshot'))
->addCancelButton(
$this->getApplicationURI('browse/'.$fragment->getPath())))
->appendChild(
id(new PHUIFormDividerControl()))
->appendInstructions($container);
$crumbs = $this->buildApplicationCrumbsWithPath($parents);
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Create Snapshot')));
$box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Create Snapshot of %s', $fragment->getName()))
->setFormError($error_view)
->setForm($form);
return $this->buildApplicationPage(
array(
$crumbs,
$box),
array(
'title' => pht('Create Fragment'),
'device' => true));
}
}

View file

@ -0,0 +1,50 @@
<?php
final class PhragmentSnapshotDeleteController extends PhragmentController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$snapshot = id(new PhragmentSnapshotQuery())
->setViewer($viewer)
->withIDs(array($this->id))
->executeOne();
if ($snapshot === null) {
return new Aphront404Response();
}
if ($request->isDialogFormPost()) {
$fragment_uri = $snapshot->getPrimaryFragment()->getURI();
$snapshot->delete();
return id(new AphrontRedirectResponse())
->setURI($fragment_uri);
}
return $this->createDialog();
}
function createDialog() {
$request = $this->getRequest();
$viewer = $request->getUser();
$dialog = id(new AphrontDialogView())
->setTitle(pht('Really delete this snapshot?'))
->setUser($request->getUser())
->addSubmitButton(pht('Delete'))
->addCancelButton(pht('Cancel'))
->appendParagraph(pht(
"Deleting this snapshot is a permanent operation. You can not ".
"recover the state of the snapshot."));
return id(new AphrontDialogResponse())->setDialog($dialog);
}
}

View file

@ -0,0 +1,180 @@
<?php
final class PhragmentSnapshotPromoteController extends PhragmentController {
private $dblob;
private $id;
private $targetSnapshot;
private $targetFragment;
private $snapshots;
private $options;
public function willProcessRequest(array $data) {
$this->dblob = idx($data, 'dblob', null);
$this->id = idx($data, 'id', null);
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
// When the user is promoting a snapshot to the latest version, the
// identifier is a fragment path.
if ($this->dblob !== null) {
$this->targetFragment = id(new PhragmentFragmentQuery())
->setViewer($viewer)
->withPaths(array($this->dblob))
->executeOne();
if ($this->targetFragment === null) {
return new Aphront404Response();
}
$this->snapshots = id(new PhragmentSnapshotQuery())
->setViewer($viewer)
->withPrimaryFragmentPHIDs(array($this->targetFragment->getPHID()))
->execute();
}
// When the user is promoting a snapshot to another snapshot, the
// identifier is another snapshot ID.
if ($this->id !== null) {
$this->targetSnapshot = id(new PhragmentSnapshotQuery())
->setViewer($viewer)
->withIDs(array($this->id))
->executeOne();
if ($this->targetSnapshot === null) {
return new Aphront404Response();
}
$this->snapshots = id(new PhragmentSnapshotQuery())
->setViewer($viewer)
->withPrimaryFragmentPHIDs(array(
$this->targetSnapshot->getPrimaryFragmentPHID()))
->execute();
}
// If there's no identifier, just 404.
if ($this->snapshots === null) {
return new Aphront404Response();
}
// Work out what options the user has.
$this->options = mpull(
$this->snapshots,
'getName',
'getID');
if ($this->id !== null) {
unset($this->options[$this->id]);
}
// If there's no options, show a dialog telling the
// user there are no snapshots to promote.
if (count($this->options) === 0) {
return id(new AphrontDialogResponse())->setDialog(
id(new AphrontDialogView())
->setTitle(pht('No snapshots to promote'))
->appendParagraph(pht(
"There are no snapshots available to promote."))
->setUser($request->getUser())
->addCancelButton(pht('Cancel')));
}
// Handle snapshot promotion.
if ($request->isDialogFormPost()) {
$snapshot = id(new PhragmentSnapshotQuery())
->setViewer($viewer)
->withIDs(array($request->getStr('snapshot')))
->executeOne();
if ($snapshot === null) {
return new Aphront404Response();
}
$snapshot->openTransaction();
// Delete all existing child entries.
$children = id(new PhragmentSnapshotChildQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withSnapshotPHIDs(array($snapshot->getPHID()))
->execute();
foreach ($children as $child) {
$child->delete();
}
if ($this->id === null) {
// The user is promoting the snapshot to the latest version.
$children = id(new PhragmentFragmentQuery())
->setViewer($viewer)
->needLatestVersion(true)
->withLeadingPath($this->targetFragment->getPath().'/')
->execute();
// Add the primary fragment.
id(new PhragmentSnapshotChild())
->setSnapshotPHID($snapshot->getPHID())
->setFragmentPHID($this->targetFragment->getPHID())
->setFragmentVersionPHID(
$this->targetFragment->getLatestVersionPHID())
->save();
// Add all of the child fragments.
foreach ($children as $child) {
id(new PhragmentSnapshotChild())
->setSnapshotPHID($snapshot->getPHID())
->setFragmentPHID($child->getPHID())
->setFragmentVersionPHID($child->getLatestVersionPHID())
->save();
}
} else {
// The user is promoting the snapshot to another snapshot. We just
// copy the other snapshot's child entries and change the snapshot
// PHID to make it identical.
$children = id(new PhragmentSnapshotChildQuery())
->setViewer($viewer)
->withSnapshotPHIDs(array($this->targetSnapshot->getPHID()))
->execute();
foreach ($children as $child) {
id(new PhragmentSnapshotChild())
->setSnapshotPHID($snapshot->getPHID())
->setFragmentPHID($child->getFragmentPHID())
->setFragmentVersionPHID($child->getFragmentVersionPHID())
->save();
}
}
$snapshot->saveTransaction();
return id(new AphrontRedirectResponse());
}
return $this->createDialog();
}
function createDialog() {
$request = $this->getRequest();
$viewer = $request->getUser();
$dialog = id(new AphrontDialogView())
->setTitle(pht('Promote which snapshot?'))
->setUser($request->getUser())
->addSubmitButton(pht('Promote'))
->addCancelButton(pht('Cancel'));
if ($this->id === null) {
// The user is promoting a snapshot to the latest version.
$dialog->appendParagraph(pht(
"Select the snapshot you want to promote to the latest version:"));
} else {
// The user is promoting a snapshot to another snapshot.
$dialog->appendParagraph(pht(
"Select the snapshot you want to promote to '%s':",
$this->targetSnapshot->getName()));
}
$dialog->appendChild(
id(new AphrontFormSelectControl())
->setUser($viewer)
->setName('snapshot')
->setOptions($this->options));
return id(new AphrontDialogResponse())->setDialog($dialog);
}
}

View file

@ -0,0 +1,146 @@
<?php
final class PhragmentSnapshotViewController extends PhragmentController {
private $id;
public function willProcessRequest(array $data) {
$this->id = idx($data, "id", "");
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$snapshot = id(new PhragmentSnapshotQuery())
->setViewer($viewer)
->withIDs(array($this->id))
->executeOne();
if ($snapshot === null) {
return new Aphront404Response();
}
$box = $this->createSnapshotView($snapshot);
$fragment = id(new PhragmentFragmentQuery())
->setViewer($viewer)
->withPHIDs(array($snapshot->getPrimaryFragmentPHID()))
->executeOne();
if ($fragment === null) {
return new Aphront404Response();
}
$parents = $this->loadParentFragments($fragment->getPath());
if ($parents === null) {
return new Aphront404Response();
}
$crumbs = $this->buildApplicationCrumbsWithPath($parents);
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('"%s" Snapshot', $snapshot->getName())));
$children = id(new PhragmentSnapshotChildQuery())
->setViewer($viewer)
->needFragments(true)
->needFragmentVersions(true)
->withSnapshotPHIDs(array($snapshot->getPHID()))
->execute();
$list = id(new PHUIObjectItemListView())
->setUser($viewer);
foreach ($children as $child) {
$item = id(new PHUIObjectItemView())
->setHeader($child->getFragment()->getPath());
if ($child->getFragmentVersion() !== null) {
$item
->setHref($child->getFragmentVersion()->getURI())
->addAttribute(pht(
'Version %s',
$child->getFragmentVersion()->getSequence()));
} else {
$item
->setHref($child->getFragment()->getURI())
->addAttribute(pht('Directory'));
}
$list->addItem($item);
}
return $this->buildApplicationPage(
array(
$crumbs,
$box,
$list),
array(
'title' => pht('View Snapshot'),
'device' => true));
}
protected function createSnapshotView($snapshot) {
if ($snapshot === null) {
return null;
}
$viewer = $this->getRequest()->getUser();
$phids = array();
$phids[] = $snapshot->getPrimaryFragmentPHID();
$this->loadHandles($phids);
$header = id(new PHUIHeaderView())
->setHeader(pht('"%s" Snapshot', $snapshot->getName()))
->setPolicyObject($snapshot)
->setUser($viewer);
$zip_uri = $this->getApplicationURI(
"zip@".$snapshot->getName().
"/".$snapshot->getPrimaryFragment()->getPath());
$actions = id(new PhabricatorActionListView())
->setUser($viewer)
->setObject($snapshot)
->setObjectURI($snapshot->getURI());
$actions->addAction(
id(new PhabricatorActionView())
->setName(pht('Download Snapshot as ZIP'))
->setHref($zip_uri)
->setDisabled(false) // TODO: Policy
->setIcon('zip'));
$actions->addAction(
id(new PhabricatorActionView())
->setName(pht('Delete Snapshot'))
->setHref($this->getApplicationURI(
"snapshot/delete/".$snapshot->getID()."/"))
->setWorkflow(true)
->setDisabled(false) // TODO: Policy
->setIcon('delete'));
$actions->addAction(
id(new PhabricatorActionView())
->setName(pht('Promote Another Snapshot to Here'))
->setHref($this->getApplicationURI(
"snapshot/promote/".$snapshot->getID()."/"))
->setWorkflow(true)
->setDisabled(false) // TODO: Policy
->setIcon('promote'));
$properties = id(new PHUIPropertyListView())
->setUser($viewer)
->setObject($snapshot)
->setActionList($actions);
$properties->addProperty(
pht('Name'),
$snapshot->getName());
$properties->addProperty(
pht('Fragment'),
$this->renderHandlesForPHIDs(array($snapshot->getPrimaryFragmentPHID())));
return id(new PHUIObjectBoxView())
->setHeader($header)
->addPropertyList($properties);
}
}

View file

@ -3,9 +3,13 @@
final class PhragmentZIPController extends PhragmentController {
private $dblob;
private $snapshot;
private $snapshotCache;
public function willProcessRequest(array $data) {
$this->dblob = idx($data, "dblob", "");
$this->snapshot = idx($data, "snapshot", null);
}
public function processRequest() {
@ -18,6 +22,27 @@ final class PhragmentZIPController extends PhragmentController {
}
$fragment = idx($parents, count($parents) - 1, null);
if ($this->snapshot !== null) {
$snapshot = id(new PhragmentSnapshotQuery())
->setViewer($viewer)
->withPrimaryFragmentPHIDs(array($fragment->getPHID()))
->withNames(array($this->snapshot))
->executeOne();
if ($snapshot === null) {
return new Aphront404Response();
}
$cache = id(new PhragmentSnapshotChildQuery())
->setViewer($viewer)
->needFragmentVersions(true)
->withSnapshotPHIDs(array($snapshot->getPHID()))
->execute();
$this->snapshotCache = mpull(
$cache,
'getFragmentVersion',
'getFragmentPHID');
}
$temp = new TempFile();
$zip = null;
@ -95,10 +120,10 @@ final class PhragmentZIPController extends PhragmentController {
if (count($children) === 0) {
$path = substr($current->getPath(), strlen($base_path) + 1);
if ($current->getLatestVersion() === null) {
if ($this->getVersion($current) === null) {
return array();
}
return array($path => $current->getLatestVersion()->getFilePHID());
return array($path => $this->getVersion($current)->getFilePHID());
} else {
$mappings = array();
foreach ($children as $child) {
@ -111,4 +136,12 @@ final class PhragmentZIPController extends PhragmentController {
}
}
private function getVersion($fragment) {
if ($this->snapshot === null) {
return $fragment->getLatestVersion();
} else {
return idx($this->snapshotCache, $fragment->getPHID(), null);
}
}
}

View file

@ -34,7 +34,10 @@ final class PhragmentPHIDTypeFragment
foreach ($handles as $phid => $handle) {
$fragment = $objects[$phid];
$handle->setName($fragment->getID());
$handle->setName(pht(
"Fragment %s: %s",
$fragment->getID(),
$fragment->getName()));
$handle->setURI($fragment->getURI());
}
}

View file

@ -34,7 +34,10 @@ final class PhragmentPHIDTypeFragmentVersion
foreach ($handles as $phid => $handle) {
$version = $objects[$phid];
$handle->setName($version->getSequence());
$handle->setName(pht(
"Fragment Version %d: %s",
$version->getSequence(),
$version->getFragment()->getName()));
$handle->setURI($version->getURI());
}
}

View file

@ -0,0 +1,48 @@
<?php
final class PhragmentPHIDTypeSnapshot
extends PhabricatorPHIDType {
const TYPECONST = 'PHRS';
public function getTypeConstant() {
return self::TYPECONST;
}
public function getTypeName() {
return pht('Snapshot');
}
public function newObject() {
return new PhragmentSnapshot();
}
protected function buildQueryForObjects(
PhabricatorObjectQuery $query,
array $phids) {
return id(new PhragmentSnapshotQuery())
->withPHIDs($phids);
}
public function loadHandles(
PhabricatorHandleQuery $query,
array $handles,
array $objects) {
$viewer = $query->getViewer();
foreach ($handles as $phid => $handle) {
$snapshot = $objects[$phid];
$handle->setName(pht(
'Snapshot: %s',
$snapshot->getName()));
$handle->setURI($snapshot->getURI());
}
}
public function canLoadNamedObject($name) {
return false;
}
}

View file

@ -8,7 +8,7 @@ final class PhragmentFragmentQuery
private $paths;
private $leadingPath;
private $depths;
private $needsLatestVersion;
private $needLatestVersion;
public function withIDs(array $ids) {
$this->ids = $ids;
@ -36,7 +36,7 @@ final class PhragmentFragmentQuery
}
public function needLatestVersion($need_latest_version) {
$this->needsLatestVersion = $need_latest_version;
$this->needLatestVersion = $need_latest_version;
return $this;
}
@ -99,7 +99,7 @@ final class PhragmentFragmentQuery
}
protected function didFilterPage(array $page) {
if ($this->needsLatestVersion) {
if ($this->needLatestVersion) {
$versions = array();
$version_phids = array_filter(mpull($page, 'getLatestVersionPHID'));

View file

@ -0,0 +1,174 @@
<?php
final class PhragmentSnapshotChildQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $snapshotPHIDs;
private $fragmentPHIDs;
private $fragmentVersionPHIDs;
private $needFragments;
private $needFragmentVersions;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withSnapshotPHIDs(array $snapshot_phids) {
$this->snapshotPHIDs = $snapshot_phids;
return $this;
}
public function withFragmentPHIDs(array $fragment_phids) {
$this->fragmentPHIDs = $fragment_phids;
return $this;
}
public function withFragmentVersionPHIDs(array $fragment_version_phids) {
$this->fragmentVersionPHIDs = $fragment_version_phids;
return $this;
}
public function needFragments($need_fragments) {
$this->needFragments = $need_fragments;
return $this;
}
public function needFragmentVersions($need_fragment_versions) {
$this->needFragmentVersions = $need_fragment_versions;
return $this;
}
public function loadPage() {
$table = new PhragmentSnapshotChild();
$conn_r = $table->establishConnection('r');
$data = queryfx_all(
$conn_r,
'SELECT * FROM %T %Q %Q %Q',
$table->getTableName(),
$this->buildWhereClause($conn_r),
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
return $table->loadAllFromArray($data);
}
protected function buildWhereClause($conn_r) {
$where = array();
if ($this->ids) {
$where[] = qsprintf(
$conn_r,
'id IN (%Ld)',
$this->ids);
}
if ($this->snapshotPHIDs) {
$where[] = qsprintf(
$conn_r,
'snapshotPHID IN (%Ls)',
$this->snapshotPHIDs);
}
if ($this->fragmentPHIDs) {
$where[] = qsprintf(
$conn_r,
'fragmentPHID IN (%Ls)',
$this->fragmentPHIDs);
}
if ($this->fragmentVersionPHIDs) {
$where[] = qsprintf(
$conn_r,
'fragmentVersionPHID IN (%Ls)',
$this->fragmentVersionPHIDs);
}
$where[] = $this->buildPagingClause($conn_r);
return $this->formatWhereClause($where);
}
protected function willFilterPage(array $page) {
$snapshots = array();
$snapshot_phids = array_filter(mpull($page, 'getSnapshotPHID'));
if ($snapshot_phids) {
$snapshots = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->withPHIDs($snapshot_phids)
->setParentQuery($this)
->execute();
$snapshots = mpull($snapshots, null, 'getPHID');
}
foreach ($page as $key => $child) {
$snapshot_phid = $child->getSnapshotPHID();
if (empty($snapshots[$snapshot_phid])) {
unset($page[$key]);
continue;
}
$child->attachSnapshot($snapshots[$snapshot_phid]);
}
return $page;
}
protected function didFilterPage(array $page) {
if ($this->needFragments) {
$fragments = array();
$fragment_phids = array_filter(mpull($page, 'getFragmentPHID'));
if ($fragment_phids) {
$fragments = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->withPHIDs($fragment_phids)
->setParentQuery($this)
->execute();
$fragments = mpull($fragments, null, 'getPHID');
}
foreach ($page as $key => $child) {
$fragment_phid = $child->getFragmentPHID();
if (empty($fragments[$fragment_phid])) {
unset($page[$key]);
continue;
}
$child->attachFragment($fragments[$fragment_phid]);
}
}
if ($this->needFragmentVersions) {
$fragment_versions = array();
$fragment_version_phids = array_filter(mpull(
$page,
'getFragmentVersionPHID'));
if ($fragment_version_phids) {
$fragment_versions = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->withPHIDs($fragment_version_phids)
->setParentQuery($this)
->execute();
$fragment_versions = mpull($fragment_versions, null, 'getPHID');
}
foreach ($page as $key => $child) {
$fragment_version_phid = $child->getFragmentVersionPHID();
if (empty($fragment_versions[$fragment_version_phid])) {
continue;
}
$child->attachFragmentVersion(
$fragment_versions[$fragment_version_phid]);
}
}
return $page;
}
public function getQueryApplicationClass() {
return 'PhabricatorApplicationPhragment';
}
}

View file

@ -0,0 +1,110 @@
<?php
final class PhragmentSnapshotQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $primaryFragmentPHIDs;
private $names;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withPrimaryFragmentPHIDs(array $primary_fragment_phids) {
$this->primaryFragmentPHIDs = $primary_fragment_phids;
return $this;
}
public function withNames(array $names) {
$this->names = $names;
return $this;
}
public function loadPage() {
$table = new PhragmentSnapshot();
$conn_r = $table->establishConnection('r');
$data = queryfx_all(
$conn_r,
'SELECT * FROM %T %Q %Q %Q',
$table->getTableName(),
$this->buildWhereClause($conn_r),
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
return $table->loadAllFromArray($data);
}
protected function buildWhereClause($conn_r) {
$where = array();
if ($this->ids) {
$where[] = qsprintf(
$conn_r,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids) {
$where[] = qsprintf(
$conn_r,
'phid IN (%Ls)',
$this->phids);
}
if ($this->primaryFragmentPHIDs) {
$where[] = qsprintf(
$conn_r,
'primaryFragmentPHID IN (%Ls)',
$this->primaryFragmentPHIDs);
}
if ($this->names) {
$where[] = qsprintf(
$conn_r,
'name IN (%Ls)',
$this->names);
}
$where[] = $this->buildPagingClause($conn_r);
return $this->formatWhereClause($where);
}
protected function willFilterPage(array $page) {
$fragments = array();
$fragment_phids = array_filter(mpull($page, 'getPrimaryFragmentPHID'));
if ($fragment_phids) {
$fragments = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->withPHIDs($fragment_phids)
->setParentQuery($this)
->execute();
$fragments = mpull($fragments, null, 'getPHID');
}
foreach ($page as $key => $snapshot) {
$fragment_phid = $snapshot->getPrimaryFragmentPHID();
if (empty($fragments[$fragment_phid])) {
unset($page[$key]);
continue;
}
$snapshot->attachPrimaryFragment($fragments[$fragment_phid]);
}
return $page;
}
public function getQueryApplicationClass() {
return 'PhabricatorApplicationPhragment';
}
}

View file

@ -23,7 +23,7 @@ final class PhragmentFragment extends PhragmentDAO
}
public function getURI() {
return '/phragment/fragment/'.$this->getID().'/';
return '/phragment/browse/'.$this->getPath();
}
public function getName() {

View file

@ -0,0 +1,69 @@
<?php
final class PhragmentSnapshot extends PhragmentDAO
implements PhabricatorPolicyInterface {
protected $primaryFragmentPHID;
protected $name;
private $primaryFragment = self::ATTACHABLE;
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhragmentPHIDTypeSnapshot::TYPECONST);
}
public function getURI() {
return '/phragment/snapshot/view/'.$this->getID().'/';
}
public function getPrimaryFragment() {
return $this->assertAttached($this->primaryFragment);
}
public function attachPrimaryFragment(PhragmentFragment $fragment) {
return $this->primaryFragment = $fragment;
}
public function delete() {
$children = id(new PhragmentSnapshotChild())
->loadAllWhere('snapshotPHID = %s', $this->getPHID());
$this->openTransaction();
foreach ($children as $child) {
$child->delete();
}
$result = parent::delete();
$this->saveTransaction();
return $result;
}
/* -( Policy Interface )--------------------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW
);
}
public function getPolicy($capability) {
return $this->getPrimaryFragment()->getPolicy($capability);
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return $this->getPrimaryFragment()
->hasAutomaticCapability($capability, $viewer);
}
public function describeAutomaticCapability($capability) {
return $this->getPrimaryFragment()
->describeAutomaticCapability($capability);
}
}

View file

@ -0,0 +1,64 @@
<?php
final class PhragmentSnapshotChild extends PhragmentDAO
implements PhabricatorPolicyInterface {
protected $snapshotPHID;
protected $fragmentPHID;
protected $fragmentVersionPHID;
private $snapshot = self::ATTACHABLE;
private $fragment = self::ATTACHABLE;
private $fragmentVersion = self::ATTACHABLE;
public function getSnapshot() {
return $this->assertAttached($this->snapshot);
}
public function attachSnapshot(PhragmentSnapshot $snapshot) {
return $this->snapshot = $snapshot;
}
public function getFragment() {
return $this->assertAttached($this->fragment);
}
public function attachFragment(PhragmentFragment $fragment) {
return $this->fragment = $fragment;
}
public function getFragmentVersion() {
if ($this->fragmentVersionPHID === null) {
return null;
}
return $this->assertAttached($this->fragmentVersion);
}
public function attachFragmentVersion(PhragmentFragmentVersion $version) {
return $this->fragmentVersion = $version;
}
/* -( Policy Interface )--------------------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW
);
}
public function getPolicy($capability) {
return $this->getSnapshot()->getPolicy($capability);
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return $this->getSnapshot()
->hasAutomaticCapability($capability, $viewer);
}
public function describeAutomaticCapability($capability) {
return $this->getSnapshot()
->describeAutomaticCapability($capability);
}
}

View file

@ -1824,6 +1824,10 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
'type' => 'sql',
'name' => $this->getPatchPath('20131206.phragmentnull.sql'),
),
'20131208.phragmentsnapshot.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20131208.phragmentsnapshot.sql'),
),
);
}
}