@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
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Implement viewing versions and downloading patches in Phragment

Summary:
This adds support for viewing individual versions on a fragment as well as comparing versions and downloading diff_match_patch-based patches.

It does not use the side-by-side diff format as while it works for small changes, it quickly becomes impossible to distingush what changes have been made due to the diff_match_patch format.

Test Plan: Clicked on versions and downloaded patches.

Reviewers: epriestley, #blessed_reviewers

Reviewed By: epriestley

CC: Korvin, epriestley, aran

Maniphest Tasks: T4205

Differential Revision: https://secure.phabricator.com/D7734

+302 -2
+4
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 + 'PhragmentPatchController' => 'applications/phragment/controller/PhragmentPatchController.php', 2189 2190 'PhragmentPatchUtil' => 'applications/phragment/util/PhragmentPatchUtil.php', 2190 2191 'PhragmentUpdateController' => 'applications/phragment/controller/PhragmentUpdateController.php', 2192 + 'PhragmentVersionController' => 'applications/phragment/controller/PhragmentVersionController.php', 2191 2193 'PhragmentZIPController' => 'applications/phragment/controller/PhragmentZIPController.php', 2192 2194 'PhrequentController' => 'applications/phrequent/controller/PhrequentController.php', 2193 2195 'PhrequentDAO' => 'applications/phrequent/storage/PhrequentDAO.php', ··· 4786 4788 'PhragmentHistoryController' => 'PhragmentController', 4787 4789 'PhragmentPHIDTypeFragment' => 'PhabricatorPHIDType', 4788 4790 'PhragmentPHIDTypeFragmentVersion' => 'PhabricatorPHIDType', 4791 + 'PhragmentPatchController' => 'PhragmentController', 4789 4792 'PhragmentPatchUtil' => 'Phobject', 4790 4793 'PhragmentUpdateController' => 'PhragmentController', 4794 + 'PhragmentVersionController' => 'PhragmentController', 4791 4795 'PhragmentZIPController' => 'PhragmentController', 4792 4796 'PhrequentController' => 'PhabricatorController', 4793 4797 'PhrequentDAO' => 'PhabricatorLiskDAO',
+2
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 + 'version/(?P<id>[0-9]*)/' => 'PhragmentVersionController', 43 + 'patch/(?P<aid>[0-9x]*)/(?P<bid>[0-9]*)/' => 'PhragmentPatchController', 42 44 ), 43 45 ); 44 46 }
+1
src/applications/phragment/controller/PhragmentHistoryController.php
··· 47 47 foreach ($versions as $version) { 48 48 $item = id(new PHUIObjectItemView()); 49 49 $item->setHeader('Version '.$version->getSequence()); 50 + $item->setHref($version->getURI()); 50 51 $item->addAttribute(phabricator_datetime( 51 52 $version->getDateCreated(), 52 53 $viewer));
+88
src/applications/phragment/controller/PhragmentPatchController.php
··· 1 + <?php 2 + 3 + final class PhragmentPatchController extends PhragmentController { 4 + 5 + private $aid; 6 + private $bid; 7 + 8 + public function willProcessRequest(array $data) { 9 + $this->aid = idx($data, "aid", 0); 10 + $this->bid = idx($data, "bid", 0); 11 + } 12 + 13 + public function processRequest() { 14 + $request = $this->getRequest(); 15 + $viewer = $request->getUser(); 16 + 17 + // If "aid" is "x", then it means the user wants to generate 18 + // a patch of an empty file to the version specified by "bid". 19 + 20 + $ids = array($this->aid, $this->bid); 21 + if ($this->aid === "x") { 22 + $ids = array($this->bid); 23 + } 24 + 25 + $versions = id(new PhragmentFragmentVersionQuery()) 26 + ->setViewer($viewer) 27 + ->withIDs($ids) 28 + ->execute(); 29 + 30 + $version_a = null; 31 + if ($this->aid !== "x") { 32 + $version_a = idx($versions, $this->aid, null); 33 + if ($version_a === null) { 34 + return new Aphront404Response(); 35 + } 36 + } 37 + 38 + $version_b = idx($versions, $this->bid, null); 39 + if ($version_b === null) { 40 + return new Aphront404Response(); 41 + } 42 + 43 + $file_phids = array(); 44 + if ($version_a !== null) { 45 + $file_phids[] = $version_a->getFilePHID(); 46 + } 47 + $file_phids[] = $version_b->getFilePHID(); 48 + 49 + $files = id(new PhabricatorFileQuery()) 50 + ->setViewer($viewer) 51 + ->withPHIDs($file_phids) 52 + ->execute(); 53 + $files = mpull($files, null, 'getPHID'); 54 + 55 + $file_a = null; 56 + if ($version_a != null) { 57 + $file_a = idx($files, $version_a->getFilePHID(), null); 58 + } 59 + $file_b = idx($files, $version_b->getFilePHID(), null); 60 + 61 + $patch = PhragmentPatchUtil::calculatePatch($file_a, $file_b); 62 + 63 + if ($patch === null) { 64 + throw new Exception("Unable to compute patch!"); 65 + } 66 + 67 + $a_sequence = 'x'; 68 + if ($version_a !== null) { 69 + $a_sequence = $version_a->getSequence(); 70 + } 71 + 72 + $name = 73 + $version_b->getFragment()->getName().'.'. 74 + $a_sequence.'.'. 75 + $version_b->getSequence().'.patch'; 76 + 77 + $result = PhabricatorFile::buildFromFileDataOrHash( 78 + $patch, 79 + array( 80 + 'name' => $name, 81 + 'mime-type' => 'text/plain', 82 + 'ttl' => time() + 60 * 60 * 24, 83 + )); 84 + return id(new AphrontRedirectResponse()) 85 + ->setURI($result->getBestURI()); 86 + } 87 + 88 + }
+179
src/applications/phragment/controller/PhragmentVersionController.php
··· 1 + <?php 2 + 3 + final class PhragmentVersionController extends PhragmentController { 4 + 5 + private $id; 6 + 7 + public function willProcessRequest(array $data) { 8 + $this->id = idx($data, "id", 0); 9 + } 10 + 11 + public function processRequest() { 12 + $request = $this->getRequest(); 13 + $viewer = $request->getUser(); 14 + 15 + $version = id(new PhragmentFragmentVersionQuery()) 16 + ->setViewer($viewer) 17 + ->withIDs(array($this->id)) 18 + ->executeOne(); 19 + if ($version === null) { 20 + return new Aphront404Response(); 21 + } 22 + 23 + $parents = $this->loadParentFragments($version->getFragment()->getPath()); 24 + if ($parents === null) { 25 + return new Aphront404Response(); 26 + } 27 + $current = idx($parents, count($parents) - 1, null); 28 + 29 + $crumbs = $this->buildApplicationCrumbsWithPath($parents); 30 + $crumbs->addCrumb( 31 + id(new PhabricatorCrumbView()) 32 + ->setName(pht('View Version %d', $version->getSequence()))); 33 + 34 + $phids = array(); 35 + $phids[] = $version->getFilePHID(); 36 + 37 + $this->loadHandles($phids); 38 + 39 + $file = id(new PhabricatorFileQuery()) 40 + ->setViewer($viewer) 41 + ->withPHIDs(array($version->getFilePHID())) 42 + ->executeOne(); 43 + if ($file !== null) { 44 + $file_uri = $file->getBestURI(); 45 + } 46 + 47 + $header = id(new PHUIHeaderView()) 48 + ->setHeader(pht( 49 + "%s at version %d", 50 + $version->getFragment()->getName(), 51 + $version->getSequence())) 52 + ->setPolicyObject($version) 53 + ->setUser($viewer); 54 + 55 + $actions = id(new PhabricatorActionListView()) 56 + ->setUser($viewer) 57 + ->setObject($version) 58 + ->setObjectURI($version->getURI()); 59 + $actions->addAction( 60 + id(new PhabricatorActionView()) 61 + ->setName(pht('Download Version')) 62 + ->setHref($file_uri) 63 + ->setDisabled($file === null) 64 + ->setIcon('download')); 65 + 66 + $properties = id(new PHUIPropertyListView()) 67 + ->setUser($viewer) 68 + ->setObject($version) 69 + ->setActionList($actions); 70 + $properties->addProperty( 71 + pht('File'), 72 + $this->renderHandlesForPHIDs(array($version->getFilePHID()))); 73 + 74 + $box = id(new PHUIObjectBoxView()) 75 + ->setHeader($header) 76 + ->addPropertyList($properties); 77 + 78 + return $this->buildApplicationPage( 79 + array( 80 + $crumbs, 81 + $box, 82 + $this->renderPatchFromPreviousVersion($version, $file), 83 + $this->renderPreviousVersionList($version)), 84 + array( 85 + 'title' => pht('View Version'), 86 + 'device' => true)); 87 + } 88 + 89 + private function renderPatchFromPreviousVersion( 90 + PhragmentFragmentVersion $version, 91 + PhabricatorFile $file) { 92 + 93 + $request = $this->getRequest(); 94 + $viewer = $request->getUser(); 95 + 96 + $previous_file = null; 97 + $previous = id(new PhragmentFragmentVersionQuery()) 98 + ->setViewer($viewer) 99 + ->withFragmentPHIDs(array($version->getFragmentPHID())) 100 + ->withSequences(array($version->getSequence() - 1)) 101 + ->executeOne(); 102 + if ($previous !== null) { 103 + $previous_file = id(new PhabricatorFileQuery()) 104 + ->setViewer($viewer) 105 + ->withPHIDs(array($previous->getFilePHID())) 106 + ->executeOne(); 107 + } 108 + 109 + $patch = PhragmentPatchUtil::calculatePatch($previous_file, $file); 110 + 111 + if ($patch === null) { 112 + return id(new AphrontErrorView()) 113 + ->setSeverity(AphrontErrorView::SEVERITY_NOTICE) 114 + ->setTitle(pht("Identical Version")) 115 + ->appendChild(phutil_tag( 116 + 'p', 117 + array(), 118 + pht("This version is identical to the previous version."))); 119 + } 120 + 121 + if (strlen($patch) > 20480) { 122 + // Patch is longer than 20480 characters. Trim it and let the user know. 123 + $patch = substr($patch, 0, 20480)."\n...\n"; 124 + $patch .= pht( 125 + "This patch is longer than 20480 characters. Use the link ". 126 + "in the action list to download the full patch."); 127 + } 128 + 129 + return id(new PHUIObjectBoxView()) 130 + ->setHeader(id(new PHUIHeaderView()) 131 + ->setHeader(pht('Differences since previous version'))) 132 + ->appendChild(id(new PhabricatorSourceCodeView()) 133 + ->setLines(phutil_split_lines($patch))); 134 + } 135 + 136 + private function renderPreviousVersionList( 137 + PhragmentFragmentVersion $version) { 138 + 139 + $request = $this->getRequest(); 140 + $viewer = $request->getUser(); 141 + 142 + $previous_versions = id(new PhragmentFragmentVersionQuery()) 143 + ->setViewer($viewer) 144 + ->withFragmentPHIDs(array($version->getFragmentPHID())) 145 + ->withSequenceBefore($version->getSequence()) 146 + ->execute(); 147 + 148 + $list = id(new PHUIObjectItemListView()) 149 + ->setUser($viewer); 150 + 151 + foreach ($previous_versions as $previous_version) { 152 + $item = id(new PHUIObjectItemView()); 153 + $item->setHeader('Version '.$previous_version->getSequence()); 154 + $item->setHref($previous_version->getURI()); 155 + $item->addAttribute(phabricator_datetime( 156 + $previous_version->getDateCreated(), 157 + $viewer)); 158 + $item->addAction(id(new PHUIListItemView()) 159 + ->setIcon('patch') 160 + ->setName(pht("Get Patch")) 161 + ->setHref($this->getApplicationURI( 162 + 'patch/'.$previous_version->getID().'/'.$version->getID()))); 163 + $list->addItem($item); 164 + } 165 + 166 + $item = id(new PHUIObjectItemView()); 167 + $item->setHeader('Prior to Version 0'); 168 + $item->addAttribute('Prior to any content (empty file)'); 169 + $item->addAction(id(new PHUIListItemView()) 170 + ->setIcon('patch') 171 + ->setName(pht("Get Patch")) 172 + ->setHref($this->getApplicationURI( 173 + 'patch/x/'.$version->getID()))); 174 + $list->addItem($item); 175 + 176 + return $list; 177 + } 178 + 179 + }
+26
src/applications/phragment/query/PhragmentFragmentVersionQuery.php
··· 6 6 private $ids; 7 7 private $phids; 8 8 private $fragmentPHIDs; 9 + private $sequences; 10 + private $sequenceBefore; 9 11 10 12 public function withIDs(array $ids) { 11 13 $this->ids = $ids; ··· 19 21 20 22 public function withFragmentPHIDs(array $fragment_phids) { 21 23 $this->fragmentPHIDs = $fragment_phids; 24 + return $this; 25 + } 26 + 27 + public function withSequences(array $sequences) { 28 + $this->sequences = $sequences; 29 + return $this; 30 + } 31 + 32 + public function withSequenceBefore($current) { 33 + $this->sequenceBefore = $current; 22 34 return $this; 23 35 } 24 36 ··· 59 71 $conn_r, 60 72 'fragmentPHID IN (%Ls)', 61 73 $this->fragmentPHIDs); 74 + } 75 + 76 + if ($this->sequences) { 77 + $where[] = qsprintf( 78 + $conn_r, 79 + 'sequence IN (%Ld)', 80 + $this->sequences); 81 + } 82 + 83 + if ($this->sequenceBefore !== null) { 84 + $where[] = qsprintf( 85 + $conn_r, 86 + 'sequence < %d', 87 + $this->sequenceBefore); 62 88 } 63 89 64 90 $where[] = $this->buildPagingClause($conn_r);
+1 -1
src/applications/phragment/storage/PhragmentFragmentVersion.php
··· 22 22 } 23 23 24 24 public function getURI() { 25 - return '/phragment/patch/'.$this->getID().'/'; 25 + return '/phragment/version/'.$this->getID().'/'; 26 26 } 27 27 28 28 public function getFragment() {
+1 -1
src/applications/phragment/util/PhragmentPatchUtil.php
··· 9 9 * 10 10 * @phutil-external-symbol class diff_match_patch 11 11 */ 12 - public function calculatePatch( 12 + public static function calculatePatch( 13 13 PhabricatorFile $old = null, 14 14 PhabricatorFile $new = null) { 15 15