Page MenuHomePhabricator

D7741.diff
No OneTemporary

D7741.diff

Index: resources/sql/patches/20131208.phragmentsnapshot.sql
===================================================================
--- /dev/null
+++ resources/sql/patches/20131208.phragmentsnapshot.sql
@@ -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;
Index: src/__phutil_library_map__.php
===================================================================
--- src/__phutil_library_map__.php
+++ src/__phutil_library_map__.php
@@ -2186,9 +2186,18 @@
'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 @@
'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',
Index: src/applications/phragment/application/PhabricatorApplicationPhragment.php
===================================================================
--- src/applications/phragment/application/PhabricatorApplicationPhragment.php
+++ src/applications/phragment/application/PhabricatorApplicationPhragment.php
@@ -39,9 +39,19 @@
'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',
+ ),
+ ),
),
);
}
Index: src/applications/phragment/controller/PhragmentBrowseController.php
===================================================================
--- src/applications/phragment/controller/PhragmentBrowseController.php
+++ src/applications/phragment/controller/PhragmentBrowseController.php
@@ -56,7 +56,7 @@
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',
Index: src/applications/phragment/controller/PhragmentController.php
===================================================================
--- src/applications/phragment/controller/PhragmentController.php
+++ src/applications/phragment/controller/PhragmentController.php
@@ -64,6 +64,16 @@
$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 @@
->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 @@
pht('Directory'));
}
+ if (count($snapshot_phids) > 0) {
+ $properties->addProperty(
+ pht('Snapshots'),
+ $this->renderHandlesForPHIDs($snapshot_phids));
+ }
+
return id(new PHUIObjectBoxView())
->setHeader($header)
->addPropertyList($properties);
Index: src/applications/phragment/controller/PhragmentSnapshotCreateController.php
===================================================================
--- /dev/null
+++ src/applications/phragment/controller/PhragmentSnapshotCreateController.php
@@ -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));
+ }
+
+}
Index: src/applications/phragment/controller/PhragmentSnapshotDeleteController.php
===================================================================
--- /dev/null
+++ src/applications/phragment/controller/PhragmentSnapshotDeleteController.php
@@ -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);
+ }
+
+}
Index: src/applications/phragment/controller/PhragmentSnapshotPromoteController.php
===================================================================
--- /dev/null
+++ src/applications/phragment/controller/PhragmentSnapshotPromoteController.php
@@ -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);
+ }
+
+}
Index: src/applications/phragment/controller/PhragmentSnapshotViewController.php
===================================================================
--- /dev/null
+++ src/applications/phragment/controller/PhragmentSnapshotViewController.php
@@ -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);
+ }
+}
Index: src/applications/phragment/controller/PhragmentZIPController.php
===================================================================
--- src/applications/phragment/controller/PhragmentZIPController.php
+++ src/applications/phragment/controller/PhragmentZIPController.php
@@ -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 @@
}
$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 @@
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 @@
}
}
+ private function getVersion($fragment) {
+ if ($this->snapshot === null) {
+ return $fragment->getLatestVersion();
+ } else {
+ return idx($this->snapshotCache, $fragment->getPHID(), null);
+ }
+ }
+
}
Index: src/applications/phragment/phid/PhragmentPHIDTypeFragment.php
===================================================================
--- src/applications/phragment/phid/PhragmentPHIDTypeFragment.php
+++ src/applications/phragment/phid/PhragmentPHIDTypeFragment.php
@@ -34,7 +34,10 @@
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());
}
}
Index: src/applications/phragment/phid/PhragmentPHIDTypeFragmentVersion.php
===================================================================
--- src/applications/phragment/phid/PhragmentPHIDTypeFragmentVersion.php
+++ src/applications/phragment/phid/PhragmentPHIDTypeFragmentVersion.php
@@ -34,7 +34,10 @@
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());
}
}
Index: src/applications/phragment/phid/PhragmentPHIDTypeSnapshot.php
===================================================================
--- src/applications/phragment/phid/PhragmentPHIDTypeSnapshot.php
+++ src/applications/phragment/phid/PhragmentPHIDTypeSnapshot.php
@@ -1,27 +1,27 @@
<?php
-final class PhragmentPHIDTypeFragment
+final class PhragmentPHIDTypeSnapshot
extends PhabricatorPHIDType {
- const TYPECONST = 'PHRF';
+ const TYPECONST = 'PHRS';
public function getTypeConstant() {
return self::TYPECONST;
}
public function getTypeName() {
- return pht('Fragment');
+ return pht('Snapshot');
}
public function newObject() {
- return new PhragmentFragment();
+ return new PhragmentSnapshot();
}
protected function buildQueryForObjects(
PhabricatorObjectQuery $query,
array $phids) {
- return id(new PhragmentFragmentQuery())
+ return id(new PhragmentSnapshotQuery())
->withPHIDs($phids);
}
@@ -32,10 +32,12 @@
$viewer = $query->getViewer();
foreach ($handles as $phid => $handle) {
- $fragment = $objects[$phid];
+ $snapshot = $objects[$phid];
- $handle->setName($fragment->getID());
- $handle->setURI($fragment->getURI());
+ $handle->setName(pht(
+ 'Snapshot: %s',
+ $snapshot->getName()));
+ $handle->setURI($snapshot->getURI());
}
}
Index: src/applications/phragment/query/PhragmentFragmentQuery.php
===================================================================
--- src/applications/phragment/query/PhragmentFragmentQuery.php
+++ src/applications/phragment/query/PhragmentFragmentQuery.php
@@ -8,7 +8,7 @@
private $paths;
private $leadingPath;
private $depths;
- private $needsLatestVersion;
+ private $needLatestVersion;
public function withIDs(array $ids) {
$this->ids = $ids;
@@ -36,7 +36,7 @@
}
public function needLatestVersion($need_latest_version) {
- $this->needsLatestVersion = $need_latest_version;
+ $this->needLatestVersion = $need_latest_version;
return $this;
}
@@ -99,7 +99,7 @@
}
protected function didFilterPage(array $page) {
- if ($this->needsLatestVersion) {
+ if ($this->needLatestVersion) {
$versions = array();
$version_phids = array_filter(mpull($page, 'getLatestVersionPHID'));
Index: src/applications/phragment/query/PhragmentSnapshotChildQuery.php
===================================================================
--- /dev/null
+++ src/applications/phragment/query/PhragmentSnapshotChildQuery.php
@@ -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';
+ }
+}
Index: src/applications/phragment/query/PhragmentSnapshotQuery.php
===================================================================
--- /dev/null
+++ src/applications/phragment/query/PhragmentSnapshotQuery.php
@@ -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';
+ }
+}
Index: src/applications/phragment/storage/PhragmentFragment.php
===================================================================
--- src/applications/phragment/storage/PhragmentFragment.php
+++ src/applications/phragment/storage/PhragmentFragment.php
@@ -23,7 +23,7 @@
}
public function getURI() {
- return '/phragment/fragment/'.$this->getID().'/';
+ return '/phragment/browse/'.$this->getPath();
}
public function getName() {
Index: src/applications/phragment/storage/PhragmentSnapshot.php
===================================================================
--- /dev/null
+++ src/applications/phragment/storage/PhragmentSnapshot.php
@@ -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);
+ }
+}
Index: src/applications/phragment/storage/PhragmentSnapshotChild.php
===================================================================
--- /dev/null
+++ src/applications/phragment/storage/PhragmentSnapshotChild.php
@@ -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);
+ }
+}
Index: src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
===================================================================
--- src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
+++ src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
@@ -1824,6 +1824,10 @@
'type' => 'sql',
'name' => $this->getPatchPath('20131206.phragmentnull.sql'),
),
+ '20131208.phragmentsnapshot.sql' => array(
+ 'type' => 'sql',
+ 'name' => $this->getPatchPath('20131208.phragmentsnapshot.sql'),
+ ),
);
}
}

File Metadata

Mime Type
text/plain
Expires
Wed, Feb 19, 2:42 PM (19 h, 42 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7169611
Default Alt Text
D7741.diff (43 KB)

Event Timeline