From 38e029205bf26f9302092efd1861abe1705ff984 Mon Sep 17 00:00:00 2001 From: Bob Trahan Date: Thu, 28 Jun 2012 10:12:20 -0700 Subject: [PATCH] Add a "Download Raw Diff" action to Differential Summary: this lets people download diffs easily in case arc export, arc patch, etc isn't to their liking or whatever. Test Plan: "Download Raw Diff" a few times with various things toggled in the "Revision Update History" widget Reviewers: epriestley Reviewed By: epriestley CC: aran, Korvin Maniphest Tasks: T1294 Differential Revision: https://secure.phabricator.com/D2855 --- .../DifferentialRevisionViewController.php | 200 +++++++++++++++--- 1 file changed, 174 insertions(+), 26 deletions(-) diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index 0fa578f189..2e09f8812b 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -66,6 +66,22 @@ final class DifferentialRevisionViewController extends DifferentialController { $diff_vs = null; } + $arc_project = $target->loadArcanistProject(); + $repository = ($arc_project ? $arc_project->loadRepository() : null); + + list($changesets, $vs_map, $vs_changesets, $rendering_references) = + $this->loadChangesetsAndVsMap( + $target, + idx($diffs, $diff_vs), + $repository); + + if ($request->getExists('download')) { + return $this->buildRawDiffResponse($changesets, + $vs_changesets, + $vs_map, + $repository); + } + list($aux_fields, $props) = $this->loadAuxiliaryFieldsAndProperties( $revision, $target_manual, @@ -75,14 +91,6 @@ final class DifferentialRevisionViewController extends DifferentialController { 'arc:unit', )); - $arc_project = $target->loadArcanistProject(); - $repository = ($arc_project ? $arc_project->loadRepository() : null); - - list($changesets, $vs_map, $rendering_references) = - $this->loadChangesetsAndVsMap( - $target, - idx($diffs, $diff_vs), - $repository); $comments = $revision->loadComments(); $comments = array_merge( @@ -505,6 +513,13 @@ final class DifferentialRevisionViewController extends DifferentialController { ); } + $request_uri = $this->getRequest()->getRequestURI(); + $links[] = array( + 'class' => 'action-download', + 'name' => 'Download Raw Diff', + 'href' => $request_uri->alter('download', 'true') + ); + return $links; } @@ -642,40 +657,43 @@ final class DifferentialRevisionViewController extends DifferentialController { $changesets = idx($changeset_groups, $target->getID(), array()); $changesets = mpull($changesets, null, 'getID'); - $refs = array(); - foreach ($changesets as $changeset) { - $refs[$changeset->getID()] = $changeset->getID(); - } - - $vs_map = array(); + $refs = array(); + $vs_map = array(); + $vs_changesets = array(); if ($diff_vs) { - $vs_changesets = array(); - $vs_id = $diff_vs->getID(); + $vs_id = $diff_vs->getID(); + $vs_changesets_path_map = array(); foreach (idx($changeset_groups, $vs_id, array()) as $changeset) { $path = $changeset->getAbsoluteRepositoryPath($repository, $diff_vs); - $vs_changesets[$path] = $changeset; + $vs_changesets_path_map[$path] = $changeset; + $vs_changesets[$changeset->getID()] = $changeset; } foreach ($changesets as $key => $changeset) { - $file = $changeset->getAbsoluteRepositoryPath($repository, $target); - if (isset($vs_changesets[$file])) { - $vs_map[$changeset->getID()] = $vs_changesets[$file]->getID(); + $path = $changeset->getAbsoluteRepositoryPath($repository, $target); + if (isset($vs_changesets_path_map[$path])) { + $vs_map[$changeset->getID()] = + $vs_changesets_path_map[$path]->getID(); $refs[$changeset->getID()] = - $changeset->getID().'/'.$vs_changesets[$file]->getID(); - unset($vs_changesets[$file]); + $changeset->getID().'/'.$vs_changesets_path_map[$path]->getID(); + unset($vs_changesets_path_map[$path]); } else { $refs[$changeset->getID()] = $changeset->getID(); } } - foreach ($vs_changesets as $changeset) { + foreach ($vs_changesets_path_map as $path => $changeset) { $changesets[$changeset->getID()] = $changeset; - $vs_map[$changeset->getID()] = -1; - $refs[$changeset->getID()] = $changeset->getID().'/-1'; + $vs_map[$changeset->getID()] = -1; + $refs[$changeset->getID()] = $changeset->getID().'/-1'; + } + } else { + foreach ($changesets as $changeset) { + $refs[$changeset->getID()] = $changeset->getID(); } } $changesets = msort($changesets, 'getSortKey'); - return array($changesets, $vs_map, $refs); + return array($changesets, $vs_map, $vs_changesets, $refs); } private function loadAuxiliaryFieldsAndProperties( @@ -834,4 +852,134 @@ final class DifferentialRevisionViewController extends DifferentialController { ''; } + /** + * Straight copy of the loadFileByPhid method in + * @{class:DifferentialReviewRequestMail}. + * + * This is because of the code similarity between the buildPatch method in + * @{class:DifferentialReviewRequestMail} and @{method:buildRawDiffResponse} + * in this class. Both of these methods end up using call_user_func and this + * piece of code is the lucky function. + * + * @return mixed (@{class:PhabricatorFile} if found, null if not) + */ + public function loadFileByPHID($phid) { + $file = id(new PhabricatorFile())->loadOneWhere( + 'phid = %s', + $phid); + if (!$file) { + return null; + } + return $file->loadFileData(); + } + + /** + * Note this code is somewhat similar to the buildPatch method in + * @{class:DifferentialReviewRequestMail}. + * + * @return @{class:AphrontRedirectResponse} + */ + private function buildRawDiffResponse( + array $changesets, + array $vs_changesets, + array $vs_map, + PhabricatorRepository $repository = null) { + + assert_instances_of($changesets, 'DifferentialChangeset'); + assert_instances_of($vs_changesets, 'DifferentialChangeset'); + + $engine = new PhabricatorDifferenceEngine(); + $generated_changesets = array(); + foreach ($changesets as $changeset) { + $changeset->attachHunks($changeset->loadHunks()); + $vs = idx($vs_map, $changeset->getID()); + if ($vs) { + $choice = $vs_changeset = $vs_changesets[$vs]; + $vs_changeset->attachHunks($vs_changeset->loadHunks()); + $right = $vs_changeset->makeNewFile(); + } else { + $choice = $changeset; + $right = $changeset->makeOldFile(); + } + $left = $changeset->makeNewFile(); + + $synthetic = $engine->generateChangesetFromFileContent( + $left, + $right); + + if (!$synthetic->getAffectedLineCount()) { + $filetype = $choice->getFileType(); + if ($filetype == DifferentialChangeType::FILE_TEXT || + $filetype == DifferentialChangeType::FILE_SYMLINK) { + continue; + } + } + + $choice->attachHunks($synthetic->getHunks()); + + $generated_changesets[] = $choice; + } + + $diff = new DifferentialDiff(); + $diff->attachChangesets($generated_changesets); + $diff_dict = $diff->getDiffDict(); + + $changes = array(); + foreach ($diff_dict['changes'] as $changedict) { + $changes[] = ArcanistDiffChange::newFromDictionary($changedict); + } + $bundle = ArcanistBundle::newFromChanges($changes); + + $bundle->setLoadFileDataCallback(array($this, 'loadFileByPHID')); + + $vcs = $repository ? $repository->getVersionControlSystem() : null; + switch ($vcs) { + case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: + $raw_diff = $bundle->toGitPatch(); + break; + case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: + case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: + default: + $raw_diff = $bundle->toUnifiedDiff(); + break; + } + + $hash = PhabricatorHash::digest($raw_diff); + + $file = id(new PhabricatorFile())->loadOneWhere( + 'contentHash = %s LIMIT 1', + $hash); + + if (!$file) { + $request_uri = $this->getRequest()->getRequestURI(); + + // this ends up being something like + // D123.diff + // or the verbose + // D123.vs123.id123.whitespaceignore-all.diff + // lame but nice to include these options + $file_name = ltrim($request_uri->getPath(), '/') . '.'; + foreach ($request_uri->getQueryParams() as $key => $value) { + if ($key == 'download') { + continue; + } + $file_name .= $key . $value . '.'; + } + $file_name .= 'diff'; + + // We're just caching the data; this is always safe. + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + + $file = PhabricatorFile::newFromFileData( + $raw_diff, + array( + 'name' => $file_name, + )); + + unset($unguarded); + } + + return id(new AphrontRedirectResponse())->setURI($file->getBestURI()); + + } }