@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator

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

+1151 -9
+21
resources/sql/patches/20131208.phragmentsnapshot.sql
··· 1 + CREATE TABLE {$NAMESPACE}_phragment.phragment_snapshot ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + phid VARCHAR(64) NOT NULL COLLATE utf8_bin, 4 + primaryFragmentPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, 5 + name VARCHAR(192) NOT NULL COLLATE utf8_bin, 6 + description LONGTEXT NULL COLLATE utf8_bin, 7 + dateCreated INT UNSIGNED NOT NULL, 8 + dateModified INT UNSIGNED NOT NULL, 9 + UNIQUE KEY `key_phid` (phid), 10 + UNIQUE KEY `key_name` (primaryFragmentPHID, name) 11 + ) ENGINE=InnoDB, COLLATE utf8_general_ci; 12 + 13 + CREATE TABLE {$NAMESPACE}_phragment.phragment_snapshotchild ( 14 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 15 + snapshotPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, 16 + fragmentPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, 17 + fragmentVersionPHID VARCHAR(64) NULL COLLATE utf8_bin, 18 + dateCreated INT UNSIGNED NOT NULL, 19 + dateModified INT UNSIGNED NOT NULL, 20 + UNIQUE KEY `key_child` (snapshotPHID, fragmentPHID, fragmentVersionPHID) 21 + ) ENGINE=InnoDB, COLLATE utf8_general_ci;
+26
src/__phutil_library_map__.php
··· 2186 2186 'PhragmentHistoryController' => 'applications/phragment/controller/PhragmentHistoryController.php', 2187 2187 'PhragmentPHIDTypeFragment' => 'applications/phragment/phid/PhragmentPHIDTypeFragment.php', 2188 2188 'PhragmentPHIDTypeFragmentVersion' => 'applications/phragment/phid/PhragmentPHIDTypeFragmentVersion.php', 2189 + 'PhragmentPHIDTypeSnapshot' => 'applications/phragment/phid/PhragmentPHIDTypeSnapshot.php', 2189 2190 'PhragmentPatchController' => 'applications/phragment/controller/PhragmentPatchController.php', 2190 2191 'PhragmentPatchUtil' => 'applications/phragment/util/PhragmentPatchUtil.php', 2191 2192 'PhragmentRevertController' => 'applications/phragment/controller/PhragmentRevertController.php', 2193 + 'PhragmentSnapshot' => 'applications/phragment/storage/PhragmentSnapshot.php', 2194 + 'PhragmentSnapshotChild' => 'applications/phragment/storage/PhragmentSnapshotChild.php', 2195 + 'PhragmentSnapshotChildQuery' => 'applications/phragment/query/PhragmentSnapshotChildQuery.php', 2196 + 'PhragmentSnapshotCreateController' => 'applications/phragment/controller/PhragmentSnapshotCreateController.php', 2197 + 'PhragmentSnapshotDeleteController' => 'applications/phragment/controller/PhragmentSnapshotDeleteController.php', 2198 + 'PhragmentSnapshotPromoteController' => 'applications/phragment/controller/PhragmentSnapshotPromoteController.php', 2199 + 'PhragmentSnapshotQuery' => 'applications/phragment/query/PhragmentSnapshotQuery.php', 2200 + 'PhragmentSnapshotViewController' => 'applications/phragment/controller/PhragmentSnapshotViewController.php', 2192 2201 'PhragmentUpdateController' => 'applications/phragment/controller/PhragmentUpdateController.php', 2193 2202 'PhragmentVersionController' => 'applications/phragment/controller/PhragmentVersionController.php', 2194 2203 'PhragmentZIPController' => 'applications/phragment/controller/PhragmentZIPController.php', ··· 4789 4798 'PhragmentHistoryController' => 'PhragmentController', 4790 4799 'PhragmentPHIDTypeFragment' => 'PhabricatorPHIDType', 4791 4800 'PhragmentPHIDTypeFragmentVersion' => 'PhabricatorPHIDType', 4801 + 'PhragmentPHIDTypeSnapshot' => 'PhabricatorPHIDType', 4792 4802 'PhragmentPatchController' => 'PhragmentController', 4793 4803 'PhragmentPatchUtil' => 'Phobject', 4794 4804 'PhragmentRevertController' => 'PhragmentController', 4805 + 'PhragmentSnapshot' => 4806 + array( 4807 + 0 => 'PhragmentDAO', 4808 + 1 => 'PhabricatorPolicyInterface', 4809 + ), 4810 + 'PhragmentSnapshotChild' => 4811 + array( 4812 + 0 => 'PhragmentDAO', 4813 + 1 => 'PhabricatorPolicyInterface', 4814 + ), 4815 + 'PhragmentSnapshotChildQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 4816 + 'PhragmentSnapshotCreateController' => 'PhragmentController', 4817 + 'PhragmentSnapshotDeleteController' => 'PhragmentController', 4818 + 'PhragmentSnapshotPromoteController' => 'PhragmentController', 4819 + 'PhragmentSnapshotQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 4820 + 'PhragmentSnapshotViewController' => 'PhragmentController', 4795 4821 'PhragmentUpdateController' => 'PhragmentController', 4796 4822 'PhragmentVersionController' => 'PhragmentController', 4797 4823 'PhragmentZIPController' => 'PhragmentController',
+10
src/applications/phragment/application/PhabricatorApplicationPhragment.php
··· 39 39 'update/(?P<dblob>.*)' => 'PhragmentUpdateController', 40 40 'history/(?P<dblob>.*)' => 'PhragmentHistoryController', 41 41 'zip/(?P<dblob>.*)' => 'PhragmentZIPController', 42 + 'zip@(?P<snapshot>[^/]+)/(?P<dblob>.*)' => 'PhragmentZIPController', 42 43 'version/(?P<id>[0-9]*)/' => 'PhragmentVersionController', 43 44 'patch/(?P<aid>[0-9x]*)/(?P<bid>[0-9]*)/' => 'PhragmentPatchController', 44 45 'revert/(?P<id>[0-9]*)/(?P<dblob>.*)' => 'PhragmentRevertController', 46 + 'snapshot/' => array( 47 + 'create/(?P<dblob>.*)' => 'PhragmentSnapshotCreateController', 48 + 'view/(?P<id>[0-9]*)/' => 'PhragmentSnapshotViewController', 49 + 'delete/(?P<id>[0-9]*)/' => 'PhragmentSnapshotDeleteController', 50 + 'promote/' => array( 51 + 'latest/(?P<dblob>.*)' => 'PhragmentSnapshotPromoteController', 52 + '(?P<id>[0-9]*)/' => 'PhragmentSnapshotPromoteController', 53 + ), 54 + ), 45 55 ), 46 56 ); 47 57 }
+1 -1
src/applications/phragment/controller/PhragmentBrowseController.php
··· 56 56 foreach ($fragments as $fragment) { 57 57 $item = id(new PHUIObjectItemView()); 58 58 $item->setHeader($fragment->getName()); 59 - $item->setHref($this->getApplicationURI('/browse/'.$fragment->getPath())); 59 + $item->setHref($fragment->getURI()); 60 60 if (!$fragment->isDirectory()) { 61 61 $item->addAttribute(pht( 62 62 'Last Updated %s',
+31
src/applications/phragment/controller/PhragmentController.php
··· 64 64 $phids = array(); 65 65 $phids[] = $fragment->getLatestVersionPHID(); 66 66 67 + $snapshot_phids = array(); 68 + $snapshots = id(new PhragmentSnapshotQuery()) 69 + ->setViewer($viewer) 70 + ->withPrimaryFragmentPHIDs(array($fragment->getPHID())) 71 + ->execute(); 72 + foreach ($snapshots as $snapshot) { 73 + $phids[] = $snapshot->getPHID(); 74 + $snapshot_phids[] = $snapshot->getPHID(); 75 + } 76 + 67 77 $this->loadHandles($phids); 68 78 69 79 $file = null; ··· 127 137 ->setHref($this->getApplicationURI("history/".$fragment->getPath())) 128 138 ->setIcon('history')); 129 139 } 140 + $actions->addAction( 141 + id(new PhabricatorActionView()) 142 + ->setName(pht('Create Snapshot')) 143 + ->setHref($this->getApplicationURI( 144 + "snapshot/create/".$fragment->getPath())) 145 + ->setDisabled(false) // TODO: Policy 146 + ->setIcon('snapshot')); 147 + $actions->addAction( 148 + id(new PhabricatorActionView()) 149 + ->setName(pht('Promote Snapshot to Here')) 150 + ->setHref($this->getApplicationURI( 151 + "snapshot/promote/latest/".$fragment->getPath())) 152 + ->setWorkflow(true) 153 + ->setDisabled(false) // TODO: Policy 154 + ->setIcon('promote')); 130 155 131 156 $properties = id(new PHUIPropertyListView()) 132 157 ->setUser($viewer) ··· 150 175 $properties->addProperty( 151 176 pht('Type'), 152 177 pht('Directory')); 178 + } 179 + 180 + if (count($snapshot_phids) > 0) { 181 + $properties->addProperty( 182 + pht('Snapshots'), 183 + $this->renderHandlesForPHIDs($snapshot_phids)); 153 184 } 154 185 155 186 return id(new PHUIObjectBoxView())
+170
src/applications/phragment/controller/PhragmentSnapshotCreateController.php
··· 1 + <?php 2 + 3 + final class PhragmentSnapshotCreateController extends PhragmentController { 4 + 5 + private $dblob; 6 + 7 + public function willProcessRequest(array $data) { 8 + $this->dblob = idx($data, "dblob", ""); 9 + } 10 + 11 + public function processRequest() { 12 + $request = $this->getRequest(); 13 + $viewer = $request->getUser(); 14 + 15 + $parents = $this->loadParentFragments($this->dblob); 16 + if ($parents === null) { 17 + return new Aphront404Response(); 18 + } 19 + $fragment = nonempty(last($parents), null); 20 + if ($fragment === null) { 21 + return new Aphront404Response(); 22 + } 23 + 24 + $children = id(new PhragmentFragmentQuery()) 25 + ->setViewer($viewer) 26 + ->needLatestVersion(true) 27 + ->withLeadingPath($fragment->getPath().'/') 28 + ->execute(); 29 + 30 + $error_view = null; 31 + 32 + if ($request->isFormPost()) { 33 + $errors = array(); 34 + 35 + $v_name = $request->getStr('name'); 36 + if (strlen($v_name) === 0) { 37 + $errors[] = pht('You must specify a name.'); 38 + } 39 + if (strpos($v_name, '/') !== false) { 40 + $errors[] = pht('Snapshot names can not contain "/".'); 41 + } 42 + 43 + if (!count($errors)) { 44 + $snapshot = null; 45 + 46 + try { 47 + // Create the snapshot. 48 + $snapshot = id(new PhragmentSnapshot()) 49 + ->setPrimaryFragmentPHID($fragment->getPHID()) 50 + ->setName($v_name) 51 + ->save(); 52 + } catch (AphrontQueryDuplicateKeyException $e) { 53 + $errors[] = pht('A snapshot with this name already exists.'); 54 + } 55 + 56 + if (!count($errors)) { 57 + // Add the primary fragment. 58 + id(new PhragmentSnapshotChild()) 59 + ->setSnapshotPHID($snapshot->getPHID()) 60 + ->setFragmentPHID($fragment->getPHID()) 61 + ->setFragmentVersionPHID($fragment->getLatestVersionPHID()) 62 + ->save(); 63 + 64 + // Add all of the child fragments. 65 + foreach ($children as $child) { 66 + id(new PhragmentSnapshotChild()) 67 + ->setSnapshotPHID($snapshot->getPHID()) 68 + ->setFragmentPHID($child->getPHID()) 69 + ->setFragmentVersionPHID($child->getLatestVersionPHID()) 70 + ->save(); 71 + } 72 + 73 + return id(new AphrontRedirectResponse()) 74 + ->setURI('/phragment/snapshot/view/'.$snapshot->getID()); 75 + } 76 + } 77 + 78 + $error_view = id(new AphrontErrorView()) 79 + ->setErrors($errors) 80 + ->setTitle(pht('Errors while creating snapshot')); 81 + } 82 + 83 + $fragment_sequence = "-"; 84 + if ($fragment->getLatestVersion() !== null) { 85 + $fragment_sequence = $fragment->getLatestVersion()->getSequence(); 86 + } 87 + 88 + $rows = array(); 89 + $rows[] = phutil_tag( 90 + 'tr', 91 + array(), 92 + array( 93 + phutil_tag('th', array(), 'Fragment'), 94 + phutil_tag('th', array(), 'Version'))); 95 + $rows[] = phutil_tag( 96 + 'tr', 97 + array(), 98 + array( 99 + phutil_tag('td', array(), $fragment->getPath()), 100 + phutil_tag('td', array(), $fragment_sequence))); 101 + foreach ($children as $child) { 102 + $sequence = "-"; 103 + if ($child->getLatestVersion() !== null) { 104 + $sequence = $child->getLatestVersion()->getSequence(); 105 + } 106 + $rows[] = phutil_tag( 107 + 'tr', 108 + array(), 109 + array( 110 + phutil_tag('td', array(), $child->getPath()), 111 + phutil_tag('td', array(), $sequence))); 112 + } 113 + 114 + $table = phutil_tag( 115 + 'table', 116 + array('class' => 'remarkup-table'), 117 + $rows); 118 + 119 + $container = phutil_tag( 120 + 'div', 121 + array('class' => 'phabricator-remarkup'), 122 + array( 123 + phutil_tag( 124 + 'p', 125 + array(), 126 + pht( 127 + "The snapshot will contain the following fragments at ". 128 + "the specified versions: ")), 129 + $table)); 130 + 131 + $form = id(new AphrontFormView()) 132 + ->setUser($viewer) 133 + ->appendChild( 134 + id(new AphrontFormTextControl()) 135 + ->setLabel(pht('Fragment Path')) 136 + ->setDisabled(true) 137 + ->setValue('/'.$fragment->getPath())) 138 + ->appendChild( 139 + id(new AphrontFormTextControl()) 140 + ->setLabel(pht('Snapshot Name')) 141 + ->setName('name')) 142 + ->appendChild( 143 + id(new AphrontFormSubmitControl()) 144 + ->setValue(pht('Create Snapshot')) 145 + ->addCancelButton( 146 + $this->getApplicationURI('browse/'.$fragment->getPath()))) 147 + ->appendChild( 148 + id(new PHUIFormDividerControl())) 149 + ->appendInstructions($container); 150 + 151 + $crumbs = $this->buildApplicationCrumbsWithPath($parents); 152 + $crumbs->addCrumb( 153 + id(new PhabricatorCrumbView()) 154 + ->setName(pht('Create Snapshot'))); 155 + 156 + $box = id(new PHUIObjectBoxView()) 157 + ->setHeaderText(pht('Create Snapshot of %s', $fragment->getName())) 158 + ->setFormError($error_view) 159 + ->setForm($form); 160 + 161 + return $this->buildApplicationPage( 162 + array( 163 + $crumbs, 164 + $box), 165 + array( 166 + 'title' => pht('Create Fragment'), 167 + 'device' => true)); 168 + } 169 + 170 + }
+50
src/applications/phragment/controller/PhragmentSnapshotDeleteController.php
··· 1 + <?php 2 + 3 + final class PhragmentSnapshotDeleteController extends PhragmentController { 4 + 5 + private $id; 6 + 7 + public function willProcessRequest(array $data) { 8 + $this->id = $data['id']; 9 + } 10 + 11 + public function processRequest() { 12 + $request = $this->getRequest(); 13 + $viewer = $request->getUser(); 14 + 15 + $snapshot = id(new PhragmentSnapshotQuery()) 16 + ->setViewer($viewer) 17 + ->withIDs(array($this->id)) 18 + ->executeOne(); 19 + if ($snapshot === null) { 20 + return new Aphront404Response(); 21 + } 22 + 23 + if ($request->isDialogFormPost()) { 24 + $fragment_uri = $snapshot->getPrimaryFragment()->getURI(); 25 + 26 + $snapshot->delete(); 27 + 28 + return id(new AphrontRedirectResponse()) 29 + ->setURI($fragment_uri); 30 + } 31 + 32 + return $this->createDialog(); 33 + } 34 + 35 + function createDialog() { 36 + $request = $this->getRequest(); 37 + $viewer = $request->getUser(); 38 + 39 + $dialog = id(new AphrontDialogView()) 40 + ->setTitle(pht('Really delete this snapshot?')) 41 + ->setUser($request->getUser()) 42 + ->addSubmitButton(pht('Delete')) 43 + ->addCancelButton(pht('Cancel')) 44 + ->appendParagraph(pht( 45 + "Deleting this snapshot is a permanent operation. You can not ". 46 + "recover the state of the snapshot.")); 47 + return id(new AphrontDialogResponse())->setDialog($dialog); 48 + } 49 + 50 + }
+180
src/applications/phragment/controller/PhragmentSnapshotPromoteController.php
··· 1 + <?php 2 + 3 + final class PhragmentSnapshotPromoteController extends PhragmentController { 4 + 5 + private $dblob; 6 + private $id; 7 + private $targetSnapshot; 8 + private $targetFragment; 9 + private $snapshots; 10 + private $options; 11 + 12 + public function willProcessRequest(array $data) { 13 + $this->dblob = idx($data, 'dblob', null); 14 + $this->id = idx($data, 'id', null); 15 + } 16 + 17 + public function processRequest() { 18 + $request = $this->getRequest(); 19 + $viewer = $request->getUser(); 20 + 21 + // When the user is promoting a snapshot to the latest version, the 22 + // identifier is a fragment path. 23 + if ($this->dblob !== null) { 24 + $this->targetFragment = id(new PhragmentFragmentQuery()) 25 + ->setViewer($viewer) 26 + ->withPaths(array($this->dblob)) 27 + ->executeOne(); 28 + if ($this->targetFragment === null) { 29 + return new Aphront404Response(); 30 + } 31 + 32 + $this->snapshots = id(new PhragmentSnapshotQuery()) 33 + ->setViewer($viewer) 34 + ->withPrimaryFragmentPHIDs(array($this->targetFragment->getPHID())) 35 + ->execute(); 36 + } 37 + 38 + // When the user is promoting a snapshot to another snapshot, the 39 + // identifier is another snapshot ID. 40 + if ($this->id !== null) { 41 + $this->targetSnapshot = id(new PhragmentSnapshotQuery()) 42 + ->setViewer($viewer) 43 + ->withIDs(array($this->id)) 44 + ->executeOne(); 45 + if ($this->targetSnapshot === null) { 46 + return new Aphront404Response(); 47 + } 48 + 49 + $this->snapshots = id(new PhragmentSnapshotQuery()) 50 + ->setViewer($viewer) 51 + ->withPrimaryFragmentPHIDs(array( 52 + $this->targetSnapshot->getPrimaryFragmentPHID())) 53 + ->execute(); 54 + } 55 + 56 + // If there's no identifier, just 404. 57 + if ($this->snapshots === null) { 58 + return new Aphront404Response(); 59 + } 60 + 61 + // Work out what options the user has. 62 + $this->options = mpull( 63 + $this->snapshots, 64 + 'getName', 65 + 'getID'); 66 + if ($this->id !== null) { 67 + unset($this->options[$this->id]); 68 + } 69 + 70 + // If there's no options, show a dialog telling the 71 + // user there are no snapshots to promote. 72 + if (count($this->options) === 0) { 73 + return id(new AphrontDialogResponse())->setDialog( 74 + id(new AphrontDialogView()) 75 + ->setTitle(pht('No snapshots to promote')) 76 + ->appendParagraph(pht( 77 + "There are no snapshots available to promote.")) 78 + ->setUser($request->getUser()) 79 + ->addCancelButton(pht('Cancel'))); 80 + } 81 + 82 + // Handle snapshot promotion. 83 + if ($request->isDialogFormPost()) { 84 + $snapshot = id(new PhragmentSnapshotQuery()) 85 + ->setViewer($viewer) 86 + ->withIDs(array($request->getStr('snapshot'))) 87 + ->executeOne(); 88 + if ($snapshot === null) { 89 + return new Aphront404Response(); 90 + } 91 + 92 + $snapshot->openTransaction(); 93 + // Delete all existing child entries. 94 + $children = id(new PhragmentSnapshotChildQuery()) 95 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 96 + ->withSnapshotPHIDs(array($snapshot->getPHID())) 97 + ->execute(); 98 + foreach ($children as $child) { 99 + $child->delete(); 100 + } 101 + 102 + if ($this->id === null) { 103 + // The user is promoting the snapshot to the latest version. 104 + $children = id(new PhragmentFragmentQuery()) 105 + ->setViewer($viewer) 106 + ->needLatestVersion(true) 107 + ->withLeadingPath($this->targetFragment->getPath().'/') 108 + ->execute(); 109 + 110 + // Add the primary fragment. 111 + id(new PhragmentSnapshotChild()) 112 + ->setSnapshotPHID($snapshot->getPHID()) 113 + ->setFragmentPHID($this->targetFragment->getPHID()) 114 + ->setFragmentVersionPHID( 115 + $this->targetFragment->getLatestVersionPHID()) 116 + ->save(); 117 + 118 + // Add all of the child fragments. 119 + foreach ($children as $child) { 120 + id(new PhragmentSnapshotChild()) 121 + ->setSnapshotPHID($snapshot->getPHID()) 122 + ->setFragmentPHID($child->getPHID()) 123 + ->setFragmentVersionPHID($child->getLatestVersionPHID()) 124 + ->save(); 125 + } 126 + } else { 127 + // The user is promoting the snapshot to another snapshot. We just 128 + // copy the other snapshot's child entries and change the snapshot 129 + // PHID to make it identical. 130 + $children = id(new PhragmentSnapshotChildQuery()) 131 + ->setViewer($viewer) 132 + ->withSnapshotPHIDs(array($this->targetSnapshot->getPHID())) 133 + ->execute(); 134 + foreach ($children as $child) { 135 + id(new PhragmentSnapshotChild()) 136 + ->setSnapshotPHID($snapshot->getPHID()) 137 + ->setFragmentPHID($child->getFragmentPHID()) 138 + ->setFragmentVersionPHID($child->getFragmentVersionPHID()) 139 + ->save(); 140 + } 141 + } 142 + $snapshot->saveTransaction(); 143 + 144 + return id(new AphrontRedirectResponse()); 145 + } 146 + 147 + return $this->createDialog(); 148 + } 149 + 150 + function createDialog() { 151 + $request = $this->getRequest(); 152 + $viewer = $request->getUser(); 153 + 154 + $dialog = id(new AphrontDialogView()) 155 + ->setTitle(pht('Promote which snapshot?')) 156 + ->setUser($request->getUser()) 157 + ->addSubmitButton(pht('Promote')) 158 + ->addCancelButton(pht('Cancel')); 159 + 160 + if ($this->id === null) { 161 + // The user is promoting a snapshot to the latest version. 162 + $dialog->appendParagraph(pht( 163 + "Select the snapshot you want to promote to the latest version:")); 164 + } else { 165 + // The user is promoting a snapshot to another snapshot. 166 + $dialog->appendParagraph(pht( 167 + "Select the snapshot you want to promote to '%s':", 168 + $this->targetSnapshot->getName())); 169 + } 170 + 171 + $dialog->appendChild( 172 + id(new AphrontFormSelectControl()) 173 + ->setUser($viewer) 174 + ->setName('snapshot') 175 + ->setOptions($this->options)); 176 + 177 + return id(new AphrontDialogResponse())->setDialog($dialog); 178 + } 179 + 180 + }
+146
src/applications/phragment/controller/PhragmentSnapshotViewController.php
··· 1 + <?php 2 + 3 + final class PhragmentSnapshotViewController extends PhragmentController { 4 + 5 + private $id; 6 + 7 + public function willProcessRequest(array $data) { 8 + $this->id = idx($data, "id", ""); 9 + } 10 + 11 + public function processRequest() { 12 + $request = $this->getRequest(); 13 + $viewer = $request->getUser(); 14 + 15 + $snapshot = id(new PhragmentSnapshotQuery()) 16 + ->setViewer($viewer) 17 + ->withIDs(array($this->id)) 18 + ->executeOne(); 19 + if ($snapshot === null) { 20 + return new Aphront404Response(); 21 + } 22 + 23 + $box = $this->createSnapshotView($snapshot); 24 + 25 + $fragment = id(new PhragmentFragmentQuery()) 26 + ->setViewer($viewer) 27 + ->withPHIDs(array($snapshot->getPrimaryFragmentPHID())) 28 + ->executeOne(); 29 + if ($fragment === null) { 30 + return new Aphront404Response(); 31 + } 32 + 33 + $parents = $this->loadParentFragments($fragment->getPath()); 34 + if ($parents === null) { 35 + return new Aphront404Response(); 36 + } 37 + 38 + $crumbs = $this->buildApplicationCrumbsWithPath($parents); 39 + $crumbs->addCrumb( 40 + id(new PhabricatorCrumbView()) 41 + ->setName(pht('"%s" Snapshot', $snapshot->getName()))); 42 + 43 + $children = id(new PhragmentSnapshotChildQuery()) 44 + ->setViewer($viewer) 45 + ->needFragments(true) 46 + ->needFragmentVersions(true) 47 + ->withSnapshotPHIDs(array($snapshot->getPHID())) 48 + ->execute(); 49 + 50 + $list = id(new PHUIObjectItemListView()) 51 + ->setUser($viewer); 52 + 53 + foreach ($children as $child) { 54 + $item = id(new PHUIObjectItemView()) 55 + ->setHeader($child->getFragment()->getPath()); 56 + 57 + if ($child->getFragmentVersion() !== null) { 58 + $item 59 + ->setHref($child->getFragmentVersion()->getURI()) 60 + ->addAttribute(pht( 61 + 'Version %s', 62 + $child->getFragmentVersion()->getSequence())); 63 + } else { 64 + $item 65 + ->setHref($child->getFragment()->getURI()) 66 + ->addAttribute(pht('Directory')); 67 + } 68 + 69 + $list->addItem($item); 70 + } 71 + 72 + return $this->buildApplicationPage( 73 + array( 74 + $crumbs, 75 + $box, 76 + $list), 77 + array( 78 + 'title' => pht('View Snapshot'), 79 + 'device' => true)); 80 + } 81 + 82 + protected function createSnapshotView($snapshot) { 83 + if ($snapshot === null) { 84 + return null; 85 + } 86 + 87 + $viewer = $this->getRequest()->getUser(); 88 + 89 + $phids = array(); 90 + $phids[] = $snapshot->getPrimaryFragmentPHID(); 91 + 92 + $this->loadHandles($phids); 93 + 94 + $header = id(new PHUIHeaderView()) 95 + ->setHeader(pht('"%s" Snapshot', $snapshot->getName())) 96 + ->setPolicyObject($snapshot) 97 + ->setUser($viewer); 98 + 99 + $zip_uri = $this->getApplicationURI( 100 + "zip@".$snapshot->getName(). 101 + "/".$snapshot->getPrimaryFragment()->getPath()); 102 + 103 + $actions = id(new PhabricatorActionListView()) 104 + ->setUser($viewer) 105 + ->setObject($snapshot) 106 + ->setObjectURI($snapshot->getURI()); 107 + $actions->addAction( 108 + id(new PhabricatorActionView()) 109 + ->setName(pht('Download Snapshot as ZIP')) 110 + ->setHref($zip_uri) 111 + ->setDisabled(false) // TODO: Policy 112 + ->setIcon('zip')); 113 + $actions->addAction( 114 + id(new PhabricatorActionView()) 115 + ->setName(pht('Delete Snapshot')) 116 + ->setHref($this->getApplicationURI( 117 + "snapshot/delete/".$snapshot->getID()."/")) 118 + ->setWorkflow(true) 119 + ->setDisabled(false) // TODO: Policy 120 + ->setIcon('delete')); 121 + $actions->addAction( 122 + id(new PhabricatorActionView()) 123 + ->setName(pht('Promote Another Snapshot to Here')) 124 + ->setHref($this->getApplicationURI( 125 + "snapshot/promote/".$snapshot->getID()."/")) 126 + ->setWorkflow(true) 127 + ->setDisabled(false) // TODO: Policy 128 + ->setIcon('promote')); 129 + 130 + $properties = id(new PHUIPropertyListView()) 131 + ->setUser($viewer) 132 + ->setObject($snapshot) 133 + ->setActionList($actions); 134 + 135 + $properties->addProperty( 136 + pht('Name'), 137 + $snapshot->getName()); 138 + $properties->addProperty( 139 + pht('Fragment'), 140 + $this->renderHandlesForPHIDs(array($snapshot->getPrimaryFragmentPHID()))); 141 + 142 + return id(new PHUIObjectBoxView()) 143 + ->setHeader($header) 144 + ->addPropertyList($properties); 145 + } 146 + }
+35 -2
src/applications/phragment/controller/PhragmentZIPController.php
··· 3 3 final class PhragmentZIPController extends PhragmentController { 4 4 5 5 private $dblob; 6 + private $snapshot; 7 + 8 + private $snapshotCache; 6 9 7 10 public function willProcessRequest(array $data) { 8 11 $this->dblob = idx($data, "dblob", ""); 12 + $this->snapshot = idx($data, "snapshot", null); 9 13 } 10 14 11 15 public function processRequest() { ··· 17 21 return new Aphront404Response(); 18 22 } 19 23 $fragment = idx($parents, count($parents) - 1, null); 24 + 25 + if ($this->snapshot !== null) { 26 + $snapshot = id(new PhragmentSnapshotQuery()) 27 + ->setViewer($viewer) 28 + ->withPrimaryFragmentPHIDs(array($fragment->getPHID())) 29 + ->withNames(array($this->snapshot)) 30 + ->executeOne(); 31 + if ($snapshot === null) { 32 + return new Aphront404Response(); 33 + } 34 + 35 + $cache = id(new PhragmentSnapshotChildQuery()) 36 + ->setViewer($viewer) 37 + ->needFragmentVersions(true) 38 + ->withSnapshotPHIDs(array($snapshot->getPHID())) 39 + ->execute(); 40 + $this->snapshotCache = mpull( 41 + $cache, 42 + 'getFragmentVersion', 43 + 'getFragmentPHID'); 44 + } 20 45 21 46 $temp = new TempFile(); 22 47 ··· 95 120 96 121 if (count($children) === 0) { 97 122 $path = substr($current->getPath(), strlen($base_path) + 1); 98 - if ($current->getLatestVersion() === null) { 123 + if ($this->getVersion($current) === null) { 99 124 return array(); 100 125 } 101 - return array($path => $current->getLatestVersion()->getFilePHID()); 126 + return array($path => $this->getVersion($current)->getFilePHID()); 102 127 } else { 103 128 $mappings = array(); 104 129 foreach ($children as $child) { ··· 108 133 } 109 134 } 110 135 return $mappings; 136 + } 137 + } 138 + 139 + private function getVersion($fragment) { 140 + if ($this->snapshot === null) { 141 + return $fragment->getLatestVersion(); 142 + } else { 143 + return idx($this->snapshotCache, $fragment->getPHID(), null); 111 144 } 112 145 } 113 146
+4 -1
src/applications/phragment/phid/PhragmentPHIDTypeFragment.php
··· 34 34 foreach ($handles as $phid => $handle) { 35 35 $fragment = $objects[$phid]; 36 36 37 - $handle->setName($fragment->getID()); 37 + $handle->setName(pht( 38 + "Fragment %s: %s", 39 + $fragment->getID(), 40 + $fragment->getName())); 38 41 $handle->setURI($fragment->getURI()); 39 42 } 40 43 }
+4 -1
src/applications/phragment/phid/PhragmentPHIDTypeFragmentVersion.php
··· 34 34 foreach ($handles as $phid => $handle) { 35 35 $version = $objects[$phid]; 36 36 37 - $handle->setName($version->getSequence()); 37 + $handle->setName(pht( 38 + "Fragment Version %d: %s", 39 + $version->getSequence(), 40 + $version->getFragment()->getName())); 38 41 $handle->setURI($version->getURI()); 39 42 } 40 43 }
+48
src/applications/phragment/phid/PhragmentPHIDTypeSnapshot.php
··· 1 + <?php 2 + 3 + final class PhragmentPHIDTypeSnapshot 4 + extends PhabricatorPHIDType { 5 + 6 + const TYPECONST = 'PHRS'; 7 + 8 + public function getTypeConstant() { 9 + return self::TYPECONST; 10 + } 11 + 12 + public function getTypeName() { 13 + return pht('Snapshot'); 14 + } 15 + 16 + public function newObject() { 17 + return new PhragmentSnapshot(); 18 + } 19 + 20 + protected function buildQueryForObjects( 21 + PhabricatorObjectQuery $query, 22 + array $phids) { 23 + 24 + return id(new PhragmentSnapshotQuery()) 25 + ->withPHIDs($phids); 26 + } 27 + 28 + public function loadHandles( 29 + PhabricatorHandleQuery $query, 30 + array $handles, 31 + array $objects) { 32 + 33 + $viewer = $query->getViewer(); 34 + foreach ($handles as $phid => $handle) { 35 + $snapshot = $objects[$phid]; 36 + 37 + $handle->setName(pht( 38 + 'Snapshot: %s', 39 + $snapshot->getName())); 40 + $handle->setURI($snapshot->getURI()); 41 + } 42 + } 43 + 44 + public function canLoadNamedObject($name) { 45 + return false; 46 + } 47 + 48 + }
+3 -3
src/applications/phragment/query/PhragmentFragmentQuery.php
··· 8 8 private $paths; 9 9 private $leadingPath; 10 10 private $depths; 11 - private $needsLatestVersion; 11 + private $needLatestVersion; 12 12 13 13 public function withIDs(array $ids) { 14 14 $this->ids = $ids; ··· 36 36 } 37 37 38 38 public function needLatestVersion($need_latest_version) { 39 - $this->needsLatestVersion = $need_latest_version; 39 + $this->needLatestVersion = $need_latest_version; 40 40 return $this; 41 41 } 42 42 ··· 99 99 } 100 100 101 101 protected function didFilterPage(array $page) { 102 - if ($this->needsLatestVersion) { 102 + if ($this->needLatestVersion) { 103 103 $versions = array(); 104 104 105 105 $version_phids = array_filter(mpull($page, 'getLatestVersionPHID'));
+174
src/applications/phragment/query/PhragmentSnapshotChildQuery.php
··· 1 + <?php 2 + 3 + final class PhragmentSnapshotChildQuery 4 + extends PhabricatorCursorPagedPolicyAwareQuery { 5 + 6 + private $ids; 7 + private $snapshotPHIDs; 8 + private $fragmentPHIDs; 9 + private $fragmentVersionPHIDs; 10 + private $needFragments; 11 + private $needFragmentVersions; 12 + 13 + public function withIDs(array $ids) { 14 + $this->ids = $ids; 15 + return $this; 16 + } 17 + 18 + public function withSnapshotPHIDs(array $snapshot_phids) { 19 + $this->snapshotPHIDs = $snapshot_phids; 20 + return $this; 21 + } 22 + 23 + public function withFragmentPHIDs(array $fragment_phids) { 24 + $this->fragmentPHIDs = $fragment_phids; 25 + return $this; 26 + } 27 + 28 + public function withFragmentVersionPHIDs(array $fragment_version_phids) { 29 + $this->fragmentVersionPHIDs = $fragment_version_phids; 30 + return $this; 31 + } 32 + 33 + public function needFragments($need_fragments) { 34 + $this->needFragments = $need_fragments; 35 + return $this; 36 + } 37 + 38 + public function needFragmentVersions($need_fragment_versions) { 39 + $this->needFragmentVersions = $need_fragment_versions; 40 + return $this; 41 + } 42 + 43 + public function loadPage() { 44 + $table = new PhragmentSnapshotChild(); 45 + $conn_r = $table->establishConnection('r'); 46 + 47 + $data = queryfx_all( 48 + $conn_r, 49 + 'SELECT * FROM %T %Q %Q %Q', 50 + $table->getTableName(), 51 + $this->buildWhereClause($conn_r), 52 + $this->buildOrderClause($conn_r), 53 + $this->buildLimitClause($conn_r)); 54 + 55 + return $table->loadAllFromArray($data); 56 + } 57 + 58 + protected function buildWhereClause($conn_r) { 59 + $where = array(); 60 + 61 + if ($this->ids) { 62 + $where[] = qsprintf( 63 + $conn_r, 64 + 'id IN (%Ld)', 65 + $this->ids); 66 + } 67 + 68 + if ($this->snapshotPHIDs) { 69 + $where[] = qsprintf( 70 + $conn_r, 71 + 'snapshotPHID IN (%Ls)', 72 + $this->snapshotPHIDs); 73 + } 74 + 75 + if ($this->fragmentPHIDs) { 76 + $where[] = qsprintf( 77 + $conn_r, 78 + 'fragmentPHID IN (%Ls)', 79 + $this->fragmentPHIDs); 80 + } 81 + 82 + if ($this->fragmentVersionPHIDs) { 83 + $where[] = qsprintf( 84 + $conn_r, 85 + 'fragmentVersionPHID IN (%Ls)', 86 + $this->fragmentVersionPHIDs); 87 + } 88 + 89 + $where[] = $this->buildPagingClause($conn_r); 90 + 91 + return $this->formatWhereClause($where); 92 + } 93 + 94 + protected function willFilterPage(array $page) { 95 + $snapshots = array(); 96 + 97 + $snapshot_phids = array_filter(mpull($page, 'getSnapshotPHID')); 98 + if ($snapshot_phids) { 99 + $snapshots = id(new PhabricatorObjectQuery()) 100 + ->setViewer($this->getViewer()) 101 + ->withPHIDs($snapshot_phids) 102 + ->setParentQuery($this) 103 + ->execute(); 104 + $snapshots = mpull($snapshots, null, 'getPHID'); 105 + } 106 + 107 + foreach ($page as $key => $child) { 108 + $snapshot_phid = $child->getSnapshotPHID(); 109 + if (empty($snapshots[$snapshot_phid])) { 110 + unset($page[$key]); 111 + continue; 112 + } 113 + $child->attachSnapshot($snapshots[$snapshot_phid]); 114 + } 115 + 116 + return $page; 117 + } 118 + 119 + protected function didFilterPage(array $page) { 120 + if ($this->needFragments) { 121 + $fragments = array(); 122 + 123 + $fragment_phids = array_filter(mpull($page, 'getFragmentPHID')); 124 + if ($fragment_phids) { 125 + $fragments = id(new PhabricatorObjectQuery()) 126 + ->setViewer($this->getViewer()) 127 + ->withPHIDs($fragment_phids) 128 + ->setParentQuery($this) 129 + ->execute(); 130 + $fragments = mpull($fragments, null, 'getPHID'); 131 + } 132 + 133 + foreach ($page as $key => $child) { 134 + $fragment_phid = $child->getFragmentPHID(); 135 + if (empty($fragments[$fragment_phid])) { 136 + unset($page[$key]); 137 + continue; 138 + } 139 + $child->attachFragment($fragments[$fragment_phid]); 140 + } 141 + } 142 + 143 + if ($this->needFragmentVersions) { 144 + $fragment_versions = array(); 145 + 146 + $fragment_version_phids = array_filter(mpull( 147 + $page, 148 + 'getFragmentVersionPHID')); 149 + if ($fragment_version_phids) { 150 + $fragment_versions = id(new PhabricatorObjectQuery()) 151 + ->setViewer($this->getViewer()) 152 + ->withPHIDs($fragment_version_phids) 153 + ->setParentQuery($this) 154 + ->execute(); 155 + $fragment_versions = mpull($fragment_versions, null, 'getPHID'); 156 + } 157 + 158 + foreach ($page as $key => $child) { 159 + $fragment_version_phid = $child->getFragmentVersionPHID(); 160 + if (empty($fragment_versions[$fragment_version_phid])) { 161 + continue; 162 + } 163 + $child->attachFragmentVersion( 164 + $fragment_versions[$fragment_version_phid]); 165 + } 166 + } 167 + 168 + return $page; 169 + } 170 + 171 + public function getQueryApplicationClass() { 172 + return 'PhabricatorApplicationPhragment'; 173 + } 174 + }
+110
src/applications/phragment/query/PhragmentSnapshotQuery.php
··· 1 + <?php 2 + 3 + final class PhragmentSnapshotQuery 4 + extends PhabricatorCursorPagedPolicyAwareQuery { 5 + 6 + private $ids; 7 + private $phids; 8 + private $primaryFragmentPHIDs; 9 + private $names; 10 + 11 + public function withIDs(array $ids) { 12 + $this->ids = $ids; 13 + return $this; 14 + } 15 + 16 + public function withPHIDs(array $phids) { 17 + $this->phids = $phids; 18 + return $this; 19 + } 20 + 21 + public function withPrimaryFragmentPHIDs(array $primary_fragment_phids) { 22 + $this->primaryFragmentPHIDs = $primary_fragment_phids; 23 + return $this; 24 + } 25 + 26 + public function withNames(array $names) { 27 + $this->names = $names; 28 + return $this; 29 + } 30 + 31 + public function loadPage() { 32 + $table = new PhragmentSnapshot(); 33 + $conn_r = $table->establishConnection('r'); 34 + 35 + $data = queryfx_all( 36 + $conn_r, 37 + 'SELECT * FROM %T %Q %Q %Q', 38 + $table->getTableName(), 39 + $this->buildWhereClause($conn_r), 40 + $this->buildOrderClause($conn_r), 41 + $this->buildLimitClause($conn_r)); 42 + 43 + return $table->loadAllFromArray($data); 44 + } 45 + 46 + protected function buildWhereClause($conn_r) { 47 + $where = array(); 48 + 49 + if ($this->ids) { 50 + $where[] = qsprintf( 51 + $conn_r, 52 + 'id IN (%Ld)', 53 + $this->ids); 54 + } 55 + 56 + if ($this->phids) { 57 + $where[] = qsprintf( 58 + $conn_r, 59 + 'phid IN (%Ls)', 60 + $this->phids); 61 + } 62 + 63 + if ($this->primaryFragmentPHIDs) { 64 + $where[] = qsprintf( 65 + $conn_r, 66 + 'primaryFragmentPHID IN (%Ls)', 67 + $this->primaryFragmentPHIDs); 68 + } 69 + 70 + if ($this->names) { 71 + $where[] = qsprintf( 72 + $conn_r, 73 + 'name IN (%Ls)', 74 + $this->names); 75 + } 76 + 77 + $where[] = $this->buildPagingClause($conn_r); 78 + 79 + return $this->formatWhereClause($where); 80 + } 81 + 82 + protected function willFilterPage(array $page) { 83 + $fragments = array(); 84 + 85 + $fragment_phids = array_filter(mpull($page, 'getPrimaryFragmentPHID')); 86 + if ($fragment_phids) { 87 + $fragments = id(new PhabricatorObjectQuery()) 88 + ->setViewer($this->getViewer()) 89 + ->withPHIDs($fragment_phids) 90 + ->setParentQuery($this) 91 + ->execute(); 92 + $fragments = mpull($fragments, null, 'getPHID'); 93 + } 94 + 95 + foreach ($page as $key => $snapshot) { 96 + $fragment_phid = $snapshot->getPrimaryFragmentPHID(); 97 + if (empty($fragments[$fragment_phid])) { 98 + unset($page[$key]); 99 + continue; 100 + } 101 + $snapshot->attachPrimaryFragment($fragments[$fragment_phid]); 102 + } 103 + 104 + return $page; 105 + } 106 + 107 + public function getQueryApplicationClass() { 108 + return 'PhabricatorApplicationPhragment'; 109 + } 110 + }
+1 -1
src/applications/phragment/storage/PhragmentFragment.php
··· 23 23 } 24 24 25 25 public function getURI() { 26 - return '/phragment/fragment/'.$this->getID().'/'; 26 + return '/phragment/browse/'.$this->getPath(); 27 27 } 28 28 29 29 public function getName() {
+69
src/applications/phragment/storage/PhragmentSnapshot.php
··· 1 + <?php 2 + 3 + final class PhragmentSnapshot extends PhragmentDAO 4 + implements PhabricatorPolicyInterface { 5 + 6 + protected $primaryFragmentPHID; 7 + protected $name; 8 + 9 + private $primaryFragment = self::ATTACHABLE; 10 + 11 + public function getConfiguration() { 12 + return array( 13 + self::CONFIG_AUX_PHID => true, 14 + ) + parent::getConfiguration(); 15 + } 16 + 17 + public function generatePHID() { 18 + return PhabricatorPHID::generateNewPHID( 19 + PhragmentPHIDTypeSnapshot::TYPECONST); 20 + } 21 + 22 + public function getURI() { 23 + return '/phragment/snapshot/view/'.$this->getID().'/'; 24 + } 25 + 26 + public function getPrimaryFragment() { 27 + return $this->assertAttached($this->primaryFragment); 28 + } 29 + 30 + public function attachPrimaryFragment(PhragmentFragment $fragment) { 31 + return $this->primaryFragment = $fragment; 32 + } 33 + 34 + public function delete() { 35 + $children = id(new PhragmentSnapshotChild()) 36 + ->loadAllWhere('snapshotPHID = %s', $this->getPHID()); 37 + $this->openTransaction(); 38 + foreach ($children as $child) { 39 + $child->delete(); 40 + } 41 + $result = parent::delete(); 42 + $this->saveTransaction(); 43 + return $result; 44 + } 45 + 46 + 47 + /* -( Policy Interface )--------------------------------------------------- */ 48 + 49 + 50 + public function getCapabilities() { 51 + return array( 52 + PhabricatorPolicyCapability::CAN_VIEW 53 + ); 54 + } 55 + 56 + public function getPolicy($capability) { 57 + return $this->getPrimaryFragment()->getPolicy($capability); 58 + } 59 + 60 + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 61 + return $this->getPrimaryFragment() 62 + ->hasAutomaticCapability($capability, $viewer); 63 + } 64 + 65 + public function describeAutomaticCapability($capability) { 66 + return $this->getPrimaryFragment() 67 + ->describeAutomaticCapability($capability); 68 + } 69 + }
+64
src/applications/phragment/storage/PhragmentSnapshotChild.php
··· 1 + <?php 2 + 3 + final class PhragmentSnapshotChild extends PhragmentDAO 4 + implements PhabricatorPolicyInterface { 5 + 6 + protected $snapshotPHID; 7 + protected $fragmentPHID; 8 + protected $fragmentVersionPHID; 9 + 10 + private $snapshot = self::ATTACHABLE; 11 + private $fragment = self::ATTACHABLE; 12 + private $fragmentVersion = self::ATTACHABLE; 13 + 14 + public function getSnapshot() { 15 + return $this->assertAttached($this->snapshot); 16 + } 17 + 18 + public function attachSnapshot(PhragmentSnapshot $snapshot) { 19 + return $this->snapshot = $snapshot; 20 + } 21 + 22 + public function getFragment() { 23 + return $this->assertAttached($this->fragment); 24 + } 25 + 26 + public function attachFragment(PhragmentFragment $fragment) { 27 + return $this->fragment = $fragment; 28 + } 29 + 30 + public function getFragmentVersion() { 31 + if ($this->fragmentVersionPHID === null) { 32 + return null; 33 + } 34 + return $this->assertAttached($this->fragmentVersion); 35 + } 36 + 37 + public function attachFragmentVersion(PhragmentFragmentVersion $version) { 38 + return $this->fragmentVersion = $version; 39 + } 40 + 41 + 42 + /* -( Policy Interface )--------------------------------------------------- */ 43 + 44 + 45 + public function getCapabilities() { 46 + return array( 47 + PhabricatorPolicyCapability::CAN_VIEW 48 + ); 49 + } 50 + 51 + public function getPolicy($capability) { 52 + return $this->getSnapshot()->getPolicy($capability); 53 + } 54 + 55 + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 56 + return $this->getSnapshot() 57 + ->hasAutomaticCapability($capability, $viewer); 58 + } 59 + 60 + public function describeAutomaticCapability($capability) { 61 + return $this->getSnapshot() 62 + ->describeAutomaticCapability($capability); 63 + } 64 + }
+4
src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
··· 1824 1824 'type' => 'sql', 1825 1825 'name' => $this->getPatchPath('20131206.phragmentnull.sql'), 1826 1826 ), 1827 + '20131208.phragmentsnapshot.sql' => array( 1828 + 'type' => 'sql', 1829 + 'name' => $this->getPatchPath('20131208.phragmentsnapshot.sql'), 1830 + ), 1827 1831 ); 1828 1832 } 1829 1833 }