Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15165103
D7741.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
43 KB
Referenced Files
None
Subscribers
None
D7741.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D7741: Implement snapshots in Phragment
Attached
Detach File
Event Timeline
Log In to Comment