Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/diffusion/request/base/DiffusionRequest.php b/src/applications/diffusion/request/base/DiffusionRequest.php
index c10146a981..5d4d0cafd8 100644
--- a/src/applications/diffusion/request/base/DiffusionRequest.php
+++ b/src/applications/diffusion/request/base/DiffusionRequest.php
@@ -1,502 +1,513 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Contains logic to parse Diffusion requests, which have a complicated URI
* structure.
*
*
* @task new Creating Requests
* @task uri Managing Diffusion URIs
*
* @group diffusion
*/
abstract class DiffusionRequest {
protected $callsign;
protected $path;
protected $line;
protected $symbolicCommit;
protected $commit;
protected $branch;
protected $commitType = 'commit';
protected $tagContent;
protected $repository;
protected $repositoryCommit;
protected $repositoryCommitData;
protected $stableCommitName;
abstract protected function getSupportsBranches();
abstract protected function didInitialize();
/* -( Creating Requests )-------------------------------------------------- */
/**
* Create a new synthetic request from a parameter dictionary. If you need
* a @{class:DiffusionRequest} object in order to issue a DiffusionQuery, you
* can use this method to build one.
*
* Parameters are:
*
* - `callsign` Repository callsign. Provide this or `repository`.
* - `repository` Repository object. Provide this or `callsign`.
* - `branch` Optional, branch name.
* - `path` Optional, file path.
* - `commit` Optional, commit identifier.
* - `line` Optional, line range.
*
* @param map See documentation.
* @return DiffusionRequest New request object.
* @task new
*/
final public static function newFromDictionary(array $data) {
if (isset($data['repository']) && isset($data['callsign'])) {
throw new Exception(
"Specify 'repository' or 'callsign', but not both.");
} else if (!isset($data['repository']) && !isset($data['callsign'])) {
throw new Exception(
"One of 'repository' and 'callsign' is required.");
}
if (isset($data['repository'])) {
$object = self::newFromRepository($data['repository']);
} else {
$object = self::newFromCallsign($data['callsign']);
}
$object->initializeFromDictionary($data);
return $object;
}
/**
* Create a new request from an Aphront request dictionary. This is an
* internal method that you generally should not call directly; instead,
* call @{method:newFromDictionary}.
*
* @param map Map of Aphront request data.
* @return DiffusionRequest New request object.
* @task new
*/
final public static function newFromAphrontRequestDictionary(array $data) {
$callsign = phutil_unescape_uri_path_component(idx($data, 'callsign'));
$object = self::newFromCallsign($callsign);
$use_branches = $object->getSupportsBranches();
$parsed = self::parseRequestBlob(idx($data, 'dblob'), $use_branches);
$object->initializeFromDictionary($parsed);
return $object;
}
/**
* Internal.
*
* @task new
*/
final private function __construct() {
// <private>
}
/**
* Internal. Use @{method:newFromDictionary}, not this method.
*
* @param string Repository callsign.
* @return DiffusionRequest New request object.
* @task new
*/
final private static function newFromCallsign($callsign) {
$repository = id(new PhabricatorRepository())->loadOneWhere(
'callsign = %s',
$callsign);
if (!$repository) {
throw new Exception("No such repository '{$callsign}'.");
}
return self::newFromRepository($repository);
}
/**
* Internal. Use @{method:newFromDictionary}, not this method.
*
* @param PhabricatorRepository Repository object.
* @return DiffusionRequest New request object.
* @task new
*/
final private static function newFromRepository(
PhabricatorRepository $repository) {
$map = array(
PhabricatorRepositoryType::REPOSITORY_TYPE_GIT => 'DiffusionGitRequest',
PhabricatorRepositoryType::REPOSITORY_TYPE_SVN => 'DiffusionSvnRequest',
PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL =>
'DiffusionMercurialRequest',
);
$class = idx($map, $repository->getVersionControlSystem());
if (!$class) {
throw new Exception("Unknown version control system!");
}
$object = new $class();
$object->repository = $repository;
$object->callsign = $repository->getCallsign();
return $object;
}
/**
* Internal. Use @{method:newFromDictionary}, not this method.
*
* @param map Map of parsed data.
* @return void
* @task new
*/
final private function initializeFromDictionary(array $data) {
$this->path = idx($data, 'path');
$this->symbolicCommit = idx($data, 'commit');
$this->commit = idx($data, 'commit');
$this->line = idx($data, 'line');
if ($this->getSupportsBranches()) {
$this->branch = idx($data, 'branch');
}
$this->didInitialize();
}
public function getRepository() {
return $this->repository;
}
public function getCallsign() {
return $this->callsign;
}
public function getPath() {
return $this->path;
}
public function getLine() {
return $this->line;
}
public function getCommit() {
return $this->commit;
}
public function getSymbolicCommit() {
return $this->symbolicCommit;
}
public function getBranch() {
return $this->branch;
}
public function getTagContent() {
return $this->tagContent;
}
public function loadCommit() {
if (empty($this->repositoryCommit)) {
$repository = $this->getRepository();
$commit = id(new PhabricatorRepositoryCommit())->loadOneWhere(
'repositoryID = %d AND commitIdentifier = %s',
$repository->getID(),
$this->getCommit());
$this->repositoryCommit = $commit;
}
return $this->repositoryCommit;
}
public function loadCommitData() {
if (empty($this->repositoryCommitData)) {
$commit = $this->loadCommit();
$data = id(new PhabricatorRepositoryCommitData())->loadOneWhere(
'commitID = %d',
$commit->getID());
if (!$data) {
$data = new PhabricatorRepositoryCommitData();
$data->setCommitMessage('(This commit has not fully parsed yet.)');
}
$this->repositoryCommitData = $data;
}
return $this->repositoryCommitData;
}
/**
* Retrieve a stable, permanent commit name. This returns a non-symbolic
* identifier for the current commit: e.g., a specific commit hash in git
* (NOT a symbolic name like "origin/master") or a specific revision number
* in SVN (NOT a symbolic name like "HEAD").
*
* @return string Stable commit name, like a git hash or SVN revision. Not
* a symbolic commit reference.
*/
public function getStableCommitName() {
return $this->stableCommitName;
}
final public function getRawCommit() {
return $this->commit;
}
public function setCommit($commit) {
$this->commit = $commit;
return $this;
}
/* -( Managing Diffusion URIs )-------------------------------------------- */
/**
* Generate a Diffusion URI using this request to provide defaults. See
* @{method:generateDiffusionURI} for details. This method is the same, but
* preserves the request parameters if they are not overridden.
*
* @param map See @{method:generateDiffusionURI}.
* @return PhutilURI Generated URI.
* @task uri
*/
public function generateURI(array $params) {
if (empty($params['stable'])) {
$default_commit = $this->getRawCommit();
} else {
$default_commit = $this->getStableCommitName();
}
$defaults = array(
'callsign' => $this->getCallsign(),
'path' => $this->getPath(),
'branch' => $this->getBranch(),
'commit' => $default_commit,
);
foreach ($defaults as $key => $val) {
if (!isset($params[$key])) { // Overwrite NULL.
$params[$key] = $val;
}
}
return self::generateDiffusionURI($params);
}
/**
* Generate a Diffusion URI from a parameter map. Applies the correct encoding
* and formatting to the URI. Parameters are:
*
* - `action` One of `history`, `browse`, `change`, `lastmodified`,
* `branch` or `revision-ref`. The action specified by the URI.
* - `callsign` Repository callsign.
* - `branch` Optional if action is not `branch`, branch name.
* - `path` Optional, path to file.
* - `commit` Optional, commit identifier.
* - `line` Optional, line range.
* - `params` Optional, query parameters.
*
* The function generates the specified URI and returns it.
*
* @param map See documentation.
* @return PhutilURI Generated URI.
* @task uri
*/
public static function generateDiffusionURI(array $params) {
$action = idx($params, 'action');
$callsign = idx($params, 'callsign');
$path = idx($params, 'path');
$branch = idx($params, 'branch');
$commit = idx($params, 'commit');
$line = idx($params, 'line');
if (strlen($callsign)) {
$callsign = phutil_escape_uri_path_component($callsign).'/';
}
if (strlen($branch)) {
$branch = phutil_escape_uri_path_component($branch).'/';
}
if (strlen($path)) {
$path = ltrim($path, '/');
$path = str_replace(array(';', '$'), array(';;', '$$'), $path);
$path = phutil_escape_uri($path);
}
$path = "{$branch}{$path}";
if (strlen($commit)) {
+ $commit = str_replace('$', '$$', $commit);
$commit = ';'.phutil_escape_uri($commit);
}
if (strlen($line)) {
$line = '$'.phutil_escape_uri($line);
}
$req_callsign = false;
$req_branch = false;
$req_commit = false;
switch ($action) {
case 'history':
case 'browse':
case 'change':
case 'lastmodified':
case 'tags':
$req_callsign = true;
break;
case 'branch':
$req_callsign = true;
$req_branch = true;
break;
case 'commit':
$req_callsign = true;
$req_commit = true;
break;
}
if ($req_callsign && !strlen($callsign)) {
throw new Exception(
"Diffusion URI action '{$action}' requires callsign!");
}
if ($req_branch && !strlen($branch)) {
throw new Exception(
"Diffusion URI action '{$action}' requires branch!");
}
if ($req_commit && !strlen($commit)) {
throw new Exception(
"Diffusion URI action '{$action}' requires commit!");
}
switch ($action) {
case 'change':
case 'history':
case 'browse':
case 'lastmodified':
case 'tags':
$uri = "/diffusion/{$callsign}{$action}/{$path}{$commit}{$line}";
break;
case 'branch':
$uri = "/diffusion/{$callsign}repository/{$path}";
break;
case 'external':
$commit = ltrim($commit, ';');
$uri = "/diffusion/external/{$commit}/";
break;
case 'rendering-ref':
// This isn't a real URI per se, it's passed as a query parameter to
// the ajax changeset stuff but then we parse it back out as though
// it came from a URI.
$uri = "{$path}{$commit}";
break;
case 'commit':
$commit = ltrim($commit, ';');
$callsign = rtrim($callsign, '/');
$uri = "/r{$callsign}{$commit}";
break;
default:
throw new Exception("Unknown Diffusion URI action '{$action}'!");
}
if ($action == 'rendering-ref') {
return $uri;
}
$uri = new PhutilURI($uri);
if (idx($params, 'params')) {
$uri->setQueryParams($params['params']);
}
return $uri;
}
/**
* Internal. Public only for unit tests.
*
* Parse the request URI into components.
*
* @param string URI blob.
* @param bool True if this VCS supports branches.
* @return map Parsed URI.
*
* @task uri
*/
public static function parseRequestBlob($blob, $supports_branches) {
$result = array(
'branch' => null,
'path' => null,
'commit' => null,
'line' => null,
);
$matches = null;
if ($supports_branches) {
// Consume the front part of the URI, up to the first "/". This is the
// path-component encoded branch name.
if (preg_match('@^([^/]+)/@', $blob, $matches)) {
$result['branch'] = phutil_unescape_uri_path_component($matches[1]);
$blob = substr($blob, strlen($matches[1]) + 1);
}
}
// Consume the back part of the URI, up to the first "$". Use a negative
// lookbehind to prevent matching '$$'. We double the '$' symbol when
// encoding so that files with names like "money/$100" will survive.
- if (preg_match('@(?<![$])[$]([\d-]+)$@', $blob, $matches)) {
+ $pattern = '@(?:(?:^|[^$])(?:[$][$])*)[$]([\d-]+)$@';
+ if (preg_match($pattern, $blob, $matches)) {
$result['line'] = $matches[1];
$blob = substr($blob, 0, -(strlen($matches[1]) + 1));
}
- // Consume the commit name, stopping on ';;'.
- if (preg_match('@(?<!;);([a-z0-9]+)$@', $blob, $matches)) {
+ // We've consumed the line number if it exists, so unescape "$" in the
+ // rest of the string.
+ $blob = str_replace('$$', '$', $blob);
+
+ // Consume the commit name, stopping on ';;'. We allow any character to
+ // appear in commits names, as they can sometimes be symbolic names (like
+ // tag names or refs).
+ if (preg_match('@(?:(?:^|[^;])(?:;;)*);([^;].*)$@', $blob, $matches)) {
$result['commit'] = $matches[1];
$blob = substr($blob, 0, -(strlen($matches[1]) + 1));
}
- // Un-double our delimiter characters.
+ // We've consumed the commit if it exists, so unescape ";" in the rest
+ // of the string.
+ $blob = str_replace(';;', ';', $blob);
+
if (strlen($blob)) {
- $result['path'] = str_replace(array(';;', '$$'), array(';', '$'), $blob);
+ $result['path'] = $blob;
}
$parts = explode('/', $result['path']);
foreach ($parts as $part) {
// Prevent any hyjinx since we're ultimately shipping this to the
// filesystem under a lot of workflows.
if ($part == '..') {
throw new Exception("Invalid path URI.");
}
}
return $result;
}
}
diff --git a/src/applications/diffusion/request/base/__tests__/DiffusionURITestCase.php b/src/applications/diffusion/request/base/__tests__/DiffusionURITestCase.php
index 14d3e8040c..84e18a3d33 100644
--- a/src/applications/diffusion/request/base/__tests__/DiffusionURITestCase.php
+++ b/src/applications/diffusion/request/base/__tests__/DiffusionURITestCase.php
@@ -1,137 +1,153 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class DiffusionURITestCase extends ArcanistPhutilTestCase {
public function testBlobDecode() {
$map = array(
// This is a basic blob.
'branch/path.ext;abc$3' => array(
'branch' => 'branch',
'path' => 'path.ext',
'commit' => 'abc',
'line' => '3',
),
'branch/path.ext$3' => array(
'branch' => 'branch',
'path' => 'path.ext',
'line' => '3',
),
'branch/money;;/$$100' => array(
'branch' => 'branch',
'path' => 'money;/$100',
),
'a%252Fb/' => array(
'branch' => 'a/b',
),
+ 'branch/path/;Version-1_0_0' => array(
+ 'branch' => 'branch',
+ 'path' => 'path/',
+ 'commit' => 'Version-1_0_0',
+ ),
+ 'branch/path/;$$moneytag$$' => array(
+ 'branch' => 'branch',
+ 'path' => 'path/',
+ 'commit' => '$moneytag$',
+ ),
+ 'branch/path/semicolon;;;;;$$;;semicolon;;$$$$$100' => array(
+ 'branch' => 'branch',
+ 'path' => 'path/semicolon;;',
+ 'commit' => '$;;semicolon;;$$',
+ 'line' => '100',
+ ),
);
foreach ($map as $input => $expect) {
// Simulate decode effect of the webserver.
$input = rawurldecode($input);
$expect = $expect + array(
'branch' => null,
'path' => null,
'commit' => null,
'line' => null,
);
$expect = array_select_keys(
$expect,
array('branch', 'path', 'commit', 'line'));
$actual = $this->parseBlob($input);
$this->assertEqual(
$expect,
$actual,
"Parsing '{$input}'");
}
}
public function testBlobDecodeFail() {
$this->tryTestCaseMap(
array(
'branch/path/../../../secrets/secrets.key' => false,
),
array($this, 'parseBlob'));
}
public function parseBlob($blob) {
return DiffusionRequest::parseRequestBlob(
$blob,
$supports_branches = true);
}
public function testURIGeneration() {
$map = array(
'/diffusion/A/browse/branch/path.ext;abc$1' => array(
'action' => 'browse',
'callsign' => 'A',
'branch' => 'branch',
'path' => 'path.ext',
'commit' => 'abc',
'line' => '1',
),
'/diffusion/A/browse/a%252Fb/path.ext' => array(
'action' => 'browse',
'callsign' => 'A',
'branch' => 'a/b',
'path' => 'path.ext',
),
'/diffusion/A/browse/%2B/%20%21' => array(
'action' => 'browse',
'callsign' => 'A',
'path' => '+/ !',
),
'/diffusion/A/browse/money/%24%24100$2' => array(
'action' => 'browse',
'callsign' => 'A',
'path' => 'money/$100',
'line' => '2',
),
'/diffusion/A/browse/path/to/file.ext?view=things' => array(
'action' => 'browse',
'callsign' => 'A',
'path' => 'path/to/file.ext',
'params' => array(
'view' => 'things',
),
),
'/diffusion/A/repository/master/' => array(
'action' => 'branch',
'callsign' => 'A',
'branch' => 'master',
),
'path/to/file.ext;abc' => array(
'action' => 'rendering-ref',
'path' => 'path/to/file.ext',
'commit' => 'abc',
),
);
foreach ($map as $expect => $input) {
$actual = DiffusionRequest::generateDiffusionURI($input);
$this->assertEqual(
$expect,
(string)$actual);
}
}
}
diff --git a/src/applications/diffusion/view/taglist/DiffusionTagListView.php b/src/applications/diffusion/view/taglist/DiffusionTagListView.php
index b26c147050..080f4a841d 100644
--- a/src/applications/diffusion/view/taglist/DiffusionTagListView.php
+++ b/src/applications/diffusion/view/taglist/DiffusionTagListView.php
@@ -1,134 +1,134 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class DiffusionTagListView extends DiffusionView {
private $tags;
private $user;
private $commits = array();
private $handles = array();
public function setUser($user) {
$this->user = $user;
return $this;
}
public function setTags($tags) {
$this->tags = $tags;
return $this;
}
public function setCommits(array $commits) {
$this->commits = mpull($commits, null, 'getCommitIdentifier');
return $this;
}
public function setHandles(array $handles) {
$this->handles = $handles;
return $this;
}
public function getRequiredHandlePHIDs() {
return array_filter(mpull($this->commits, 'getAuthorPHID'));
}
public function render() {
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
$rows = array();
foreach ($this->tags as $tag) {
$commit = idx($this->commits, $tag->getCommitIdentifier());
$tag_link = phutil_render_tag(
'a',
array(
'href' => $drequest->generateURI(
array(
'action' => 'browse',
- 'commit' => $tag->getCommitIdentifier(),
+ 'commit' => $tag->getName(),
)),
),
phutil_escape_html($tag->getName()));
$commit_link = phutil_render_tag(
'a',
array(
'href' => $drequest->generateURI(
array(
'action' => 'commit',
'commit' => $tag->getCommitIdentifier(),
)),
),
phutil_escape_html(
$repository->formatCommitName(
$tag->getCommitIdentifier())));
$author = null;
if ($commit && $commit->getAuthorPHID()) {
$author = $this->handles[$commit->getAuthorPHID()]->renderLink();
} else if ($commit && $commit->getCommitData()) {
$author = phutil_escape_html($commit->getCommitData()->getAuthorName());
} else {
$author = phutil_escape_html($tag->getAuthor());
}
$description = null;
if ($tag->getType() == 'git/tag') {
// In Git, a tag may be a "real" tag, or just a reference to a commit.
// If it's a real tag, use the message on the tag, since this may be
// unique data which isn't otherwise available.
$description = $tag->getDescription();
} else {
if ($commit && $commit->getCommitData()) {
$description = $commit->getCommitData()->getSummary();
} else {
$description = $tag->getDescription();
}
}
$description = phutil_escape_html($description);
$rows[] = array(
$tag_link,
$commit_link,
$description,
$author,
phabricator_datetime($tag->getEpoch(), $this->user),
);
}
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
'Tag',
'Commit',
'Description',
'Author',
'Created',
));
$table->setColumnClasses(
array(
'pri',
'',
'wide',
));
return $table->render();
}
}

File Metadata

Mime Type
text/x-diff
Expires
Fri, Nov 14, 10:12 AM (1 d, 13 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
336929
Default Alt Text
(24 KB)

Event Timeline