Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F89011
D7739.id17493.diff
All Users
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
12 KB
Referenced Files
None
Subscribers
None
D7739.id17493.diff
View Options
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -207,6 +207,9 @@
'ConduitAPI_phpast_Method' => 'applications/phpast/conduit/ConduitAPI_phpast_Method.php',
'ConduitAPI_phpast_getast_Method' => 'applications/phpast/conduit/ConduitAPI_phpast_getast_Method.php',
'ConduitAPI_phpast_version_Method' => 'applications/phpast/conduit/ConduitAPI_phpast_version_Method.php',
+ 'ConduitAPI_phragment_Method' => 'applications/phragment/conduit/ConduitAPI_phragment_Method.php',
+ 'ConduitAPI_phragment_getpatch_Method' => 'applications/phragment/conduit/ConduitAPI_phragment_getpatch_Method.php',
+ 'ConduitAPI_phragment_getstate_Method' => 'applications/phragment/conduit/ConduitAPI_phragment_getstate_Method.php',
'ConduitAPI_phriction_Method' => 'applications/phriction/conduit/ConduitAPI_phriction_Method.php',
'ConduitAPI_phriction_edit_Method' => 'applications/phriction/conduit/ConduitAPI_phriction_edit_Method.php',
'ConduitAPI_phriction_history_Method' => 'applications/phriction/conduit/ConduitAPI_phriction_history_Method.php',
@@ -2573,6 +2576,9 @@
'ConduitAPI_phpast_Method' => 'ConduitAPIMethod',
'ConduitAPI_phpast_getast_Method' => 'ConduitAPI_phpast_Method',
'ConduitAPI_phpast_version_Method' => 'ConduitAPI_phpast_Method',
+ 'ConduitAPI_phragment_Method' => 'ConduitAPIMethod',
+ 'ConduitAPI_phragment_getpatch_Method' => 'ConduitAPI_phragment_Method',
+ 'ConduitAPI_phragment_getstate_Method' => 'ConduitAPI_phragment_Method',
'ConduitAPI_phriction_Method' => 'ConduitAPIMethod',
'ConduitAPI_phriction_edit_Method' => 'ConduitAPI_phriction_Method',
'ConduitAPI_phriction_history_Method' => 'ConduitAPI_phriction_Method',
diff --git a/src/applications/phragment/conduit/ConduitAPI_phragment_Method.php b/src/applications/phragment/conduit/ConduitAPI_phragment_Method.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phragment/conduit/ConduitAPI_phragment_Method.php
@@ -0,0 +1,13 @@
+<?php
+
+/**
+ * @group conduit
+ */
+abstract class ConduitAPI_phragment_Method extends ConduitAPIMethod {
+
+ public function getApplication() {
+ return PhabricatorApplication::getByClass(
+ 'PhabricatorApplicationPhragment');
+ }
+
+}
diff --git a/src/applications/phragment/conduit/ConduitAPI_phragment_getpatch_Method.php b/src/applications/phragment/conduit/ConduitAPI_phragment_getpatch_Method.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phragment/conduit/ConduitAPI_phragment_getpatch_Method.php
@@ -0,0 +1,196 @@
+<?php
+
+/**
+ * @group conduit
+ */
+final class ConduitAPI_phragment_getpatch_Method
+ extends ConduitAPI_phragment_Method {
+
+ public function getMethodDescription() {
+ return "Retrieve the patches to apply for a given set of files.";
+ }
+
+ public function defineParamTypes() {
+ return array(
+ 'path' => 'required string',
+ 'state' => 'required dict<string, string>',
+ );
+ }
+
+ public function defineReturnType() {
+ return 'nonempty dict';
+ }
+
+ public function defineErrorTypes() {
+ return array(
+ 'ERR_BAD_FRAGMENT' => 'No such fragment exists',
+ );
+ }
+
+ protected function execute(ConduitAPIRequest $request) {
+ $path = $request->getValue('path');
+ $state = $request->getValue('state');
+ // The state is an array mapping file paths to hashes.
+
+ $patches = array();
+
+ // We need to get all of the mappings (like phragment.getstate) first
+ // so that we can detect deletions and creations of files.
+ $fragment = id(new PhragmentFragmentQuery())
+ ->setViewer($request->getUser())
+ ->withPaths(array($path))
+ ->executeOne();
+ if ($fragment === null) {
+ throw new ConduitException('ERR_BAD_FRAGMENT');
+ }
+
+ $mappings = $this->getFragmentMappings(
+ $request->getUser(),
+ $fragment,
+ $fragment->getPath());
+
+ $file_phids = mpull(mpull($mappings, 'getLatestVersion'), 'getFilePHID');
+ $files = id(new PhabricatorFileQuery())
+ ->setViewer($request->getUser())
+ ->withPHIDs($file_phids)
+ ->execute();
+ $files = mpull($files, null, 'getPHID');
+
+ // Scan all of the files that the caller currently has and iterate
+ // over that.
+ foreach ($state as $path => $hash) {
+ // If $mappings[$path] exists, then the user has the file and it's
+ // also a fragment.
+ if (array_key_exists($path, $mappings)) {
+ $file_phid = $mappings[$path]->getLatestVersion()->getFilePHID();
+ if ($file_phid !== null) {
+ // If the file PHID is present, then we need to check the
+ // hashes to see if they are the same.
+ $hash_caller = strtolower($state[$path]);
+ $hash_current = $files[$file_phid]->getContentHash();
+ if ($hash_caller === $hash_current) {
+ // The user's version is identical to our version, so
+ // there is no update needed.
+ } else {
+ // The hash differs, and the user needs to update.
+ $patches[] = array(
+ 'path' => $path,
+ 'file_old' => null,
+ 'file_new' => $files[$file_phid],
+ 'hash_old' => $hash_caller,
+ 'hash_new' => $hash_current,
+ 'patch' => null);
+ }
+ } else {
+ // We have a record of this as a file, but there is no file
+ // attached to the latest version, so we consider this to be
+ // a deletion.
+ $patches[] = array(
+ 'path' => $path,
+ 'file_old' => null,
+ 'file_new' => null,
+ 'hash_old' => $hash_caller,
+ 'hash_new' => PhragmentPatchUtil::EMPTY_HASH,
+ 'patch' => null);
+ }
+ } else {
+ // If $mappings[$path] does not exist, then the user has a file,
+ // and we have absolutely no record of it what-so-ever (we haven't
+ // even recorded a deletion). Assuming most applications will store
+ // some form of data near their own files, this is probably a data
+ // file relevant for the application that is not versioned, so we
+ // don't tell the client to do anything with it.
+ }
+ }
+
+ // Check the remaining files that we know about but the caller has
+ // not reported.
+ foreach ($mappings as $path => $child) {
+ if (array_key_exists($path, $state)) {
+ // We have already evaluated this above.
+ } else {
+ $file_phid = $mappings[$path]->getLatestVersion()->getFilePHID();
+ if ($file_phid !== null) {
+ // If the file PHID is present, then this is a new file that
+ // we know about, but the caller does not. We need to tell
+ // the caller to create the file.
+ $hash_current = $files[$file_phid]->getContentHash();
+ $patches[] = array(
+ 'path' => $path,
+ 'file_old' => null,
+ 'file_new' => $files[$file_phid],
+ 'hash_old' => PhragmentPatchUtil::EMPTY_HASH,
+ 'hash_new' => $hash_current,
+ 'patch' => null);
+ } else {
+ // We have a record of deleting this file, and the caller hasn't
+ // reported it, so they've probably deleted it in a previous
+ // update.
+ }
+ }
+ }
+
+ // Before we can calculate patches, we need to resolve the old versions
+ // of files so we can draw diffs on them.
+ $hashes = array();
+ foreach ($patches as $patch) {
+ if ($patch["hash_old"] !== PhragmentPatchUtil::EMPTY_HASH) {
+ $hashes[] = $patch["hash_old"];
+ }
+ }
+ $old_files = array();
+ if (count($hashes) !== 0) {
+ $old_files = id(new PhabricatorFile())->loadAllWhere(
+ 'contentHash IN (%Ls) LIMIT 1', $hashes);
+ }
+ $old_files = mpull($old_files, null, 'getContentHash');
+ foreach ($patches as $patch) {
+ if ($patch["hash_old"] !== PhragmentPatchUtil::EMPTY_HASH) {
+ $patch['file_old'] = $old_files[$patch['hash_old']];
+ }
+ }
+
+ // Now run through all of the patch entries, calculate the patches
+ // and return the results.
+ foreach ($patches as $key => $patch) {
+ $patches[$key]['patch'] = PhragmentPatchUtil::calculatePatch(
+ $patches[$key]['file_old'],
+ $patches[$key]['file_new']);
+ unset($patches[$key]['file_old']);
+ unset($patches[$key]['file_new']);
+ }
+
+ return $patches;
+ }
+
+ private function getFragmentMappings(
+ PhabricatorUser $viewer,
+ PhragmentFragment $current,
+ $base_path) {
+
+ $children = id(new PhragmentFragmentQuery())
+ ->setViewer($viewer)
+ ->needLatestVersion(true)
+ ->withLeadingPath($current->getPath().'/')
+ ->withDepths(array($current->getDepth() + 1))
+ ->execute();
+
+ if (count($children) === 0) {
+ $path = substr($current->getPath(), strlen($base_path) + 1);
+ return array($path => $current);
+ } else {
+ $mappings = array();
+ foreach ($children as $child) {
+ $child_mappings = $this->getFragmentMappings(
+ $viewer,
+ $child,
+ $base_path);
+ foreach ($child_mappings as $key => $value) {
+ $mappings[$key] = $value;
+ }
+ }
+ return $mappings;
+ }
+ }
+
+}
diff --git a/src/applications/phragment/conduit/ConduitAPI_phragment_getstate_Method.php b/src/applications/phragment/conduit/ConduitAPI_phragment_getstate_Method.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phragment/conduit/ConduitAPI_phragment_getstate_Method.php
@@ -0,0 +1,103 @@
+<?php
+
+/**
+ * @group conduit
+ */
+final class ConduitAPI_phragment_getstate_Method
+ extends ConduitAPI_phragment_Method {
+
+ public function getMethodDescription() {
+ return "Retrieve the current state of a fragment by its path.";
+ }
+
+ public function defineParamTypes() {
+ return array(
+ 'path' => 'required string',
+ );
+ }
+
+ public function defineReturnType() {
+ return 'nonempty dict';
+ }
+
+ public function defineErrorTypes() {
+ return array(
+ 'ERR_BAD_FRAGMENT' => 'No such fragment exists',
+ );
+ }
+
+ protected function execute(ConduitAPIRequest $request) {
+ $path = $request->getValue('path');
+
+ $fragment = id(new PhragmentFragmentQuery())
+ ->setViewer($request->getUser())
+ ->withPaths(array($path))
+ ->executeOne();
+ if ($fragment === null) {
+ throw new ConduitException('ERR_BAD_FRAGMENT');
+ }
+
+ $mappings = $this->getFragmentMappings(
+ $request->getUser(),
+ $fragment,
+ $fragment->getPath());
+
+ $file_phids = mpull(mpull($mappings, 'getLatestVersion'), 'getFilePHID');
+ $files = id(new PhabricatorFileQuery())
+ ->setViewer($request->getUser())
+ ->withPHIDs($file_phids)
+ ->execute();
+ $files = mpull($files, null, 'getPHID');
+
+ $results = array();
+ foreach ($mappings as $path => $child) {
+ $file_phid = $child->getLatestVersion()->getFilePHID();
+ if (!isset($files[$file_phid])) {
+ // Skip any files we don't have permission to access.
+ continue;
+ }
+
+ $file = $files[$file_phid];
+ $path = substr($child->getPath(), strlen($fragment->getPath()) + 1);
+ $results[] = array(
+ 'phid' => $child->getPHID(),
+ 'phid_version' => $child->getLatestVersionPHID(),
+ 'path' => $path,
+ 'hash' => $file->getContentHash(),
+ 'version' => $child->getLatestVersion()->getSequence(),
+ 'uri' => $file->getViewURI());
+ }
+ return $results;
+ }
+
+ private function getFragmentMappings(
+ PhabricatorUser $viewer,
+ PhragmentFragment $current,
+ $base_path) {
+
+ $children = id(new PhragmentFragmentQuery())
+ ->setViewer($viewer)
+ ->needLatestVersion(true)
+ ->withLeadingPath($current->getPath().'/')
+ ->withDepths(array($current->getDepth() + 1))
+ ->execute();
+
+ if (count($children) === 0) {
+ $path = substr($current->getPath(), strlen($base_path) + 1);
+ return array($path => $current);
+ } else {
+ $mappings = array();
+ foreach ($children as $child) {
+ $child_mappings = $this->getFragmentMappings(
+ $viewer,
+ $child,
+ $base_path);
+ foreach ($child_mappings as $key => $value) {
+ $mappings[$key] = $value;
+ }
+ }
+ return $mappings;
+ }
+ }
+
+}
diff --git a/src/applications/phragment/util/PhragmentPatchUtil.php b/src/applications/phragment/util/PhragmentPatchUtil.php
--- a/src/applications/phragment/util/PhragmentPatchUtil.php
+++ b/src/applications/phragment/util/PhragmentPatchUtil.php
@@ -9,7 +9,7 @@
*
* @phutil-external-symbol class diff_match_patch
*/
- public function calculatePatch(
+ public static function calculatePatch(
PhabricatorFile $old = null,
PhabricatorFile $new = null) {
File Metadata
Details
Attached
Mime Type
text/x-diff
Storage Engine
amazon-s3
Storage Format
Raw Data
Storage Handle
phabricator/sf/b5/7unttbaeofrdcysn
Default Alt Text
D7739.id17493.diff (12 KB)
Attached To
Mode
D7739: Provide `phragment.getstate` and `phragment.getpatch` Conduit methods
Attached
Detach File
Event Timeline
Log In to Comment