1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-18 21:02:41 +01:00

Support pushing data into Git LFS

Summary:
Ref T7789. Ref T10604. This implements the `upload` action, which streams file data into Files.

This makes Git LFS actually work, at least roughly.

Test Plan:
  - Tracked files in an LFS repository.
  - Pushed LFS data (`git lfs track '*.png'; git add something.png; git commit -m ...; git push`).
  - Pulled LFS data (`git checkout master^; rm -rf .git/lfs; git checkout master; open something.png`).
  - Verified LFS refs show up in the gitlfsref table.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T7789, T10604

Differential Revision: https://secure.phabricator.com/D15492
This commit is contained in:
epriestley 2016-03-18 06:22:37 -07:00
parent f07d0ae7c3
commit a24f001b08
4 changed files with 129 additions and 5 deletions

View file

@ -2501,6 +2501,7 @@ phutil_register_library_map(array(
'PhabricatorInvalidConfigSetupCheck' => 'applications/config/check/PhabricatorInvalidConfigSetupCheck.php',
'PhabricatorIteratedMD5PasswordHasher' => 'infrastructure/util/password/PhabricatorIteratedMD5PasswordHasher.php',
'PhabricatorIteratedMD5PasswordHasherTestCase' => 'infrastructure/util/password/__tests__/PhabricatorIteratedMD5PasswordHasherTestCase.php',
'PhabricatorIteratorFileUploadSource' => 'applications/files/uploadsource/PhabricatorIteratorFileUploadSource.php',
'PhabricatorJIRAAuthProvider' => 'applications/auth/provider/PhabricatorJIRAAuthProvider.php',
'PhabricatorJavelinLinter' => 'infrastructure/lint/linter/PhabricatorJavelinLinter.php',
'PhabricatorJiraIssueHasObjectEdgeType' => 'applications/doorkeeper/edge/PhabricatorJiraIssueHasObjectEdgeType.php',
@ -6947,6 +6948,7 @@ phutil_register_library_map(array(
'PhabricatorInvalidConfigSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorIteratedMD5PasswordHasher' => 'PhabricatorPasswordHasher',
'PhabricatorIteratedMD5PasswordHasherTestCase' => 'PhabricatorTestCase',
'PhabricatorIteratorFileUploadSource' => 'PhabricatorFileUploadSource',
'PhabricatorJIRAAuthProvider' => 'PhabricatorOAuth1AuthProvider',
'PhabricatorJavelinLinter' => 'ArcanistLinter',
'PhabricatorJiraIssueHasObjectEdgeType' => 'PhabricatorEdgeType',

View file

@ -891,7 +891,12 @@ final class DiffusionServeController extends DiffusionController {
}
$path = $this->getGitLFSRequestPath($repository);
if ($path == 'objects/batch') {
$matches = null;
if (preg_match('(^upload/(.*)\z)', $path, $matches)) {
$oid = $matches[1];
return $this->serveGitLFSUploadRequest($repository, $viewer, $oid);
} else if ($path == 'objects/batch') {
return $this->serveGitLFSBatchRequest($repository, $viewer);
} else {
return DiffusionGitLFSResponse::newErrorResponse(
@ -947,7 +952,7 @@ final class DiffusionServeController extends DiffusionController {
if ($file_phids) {
$files = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs(array($file_phids))
->withPHIDs($file_phids)
->execute();
$files = mpull($files, null, 'getPHID');
} else {
@ -960,6 +965,7 @@ final class DiffusionServeController extends DiffusionController {
$oid = idx($object, 'oid');
$size = idx($object, 'size');
$ref = idx($refs, $oid);
$error = null;
// NOTE: If we already have a ref for this object, we only emit a
// "download" action. The client should not upload the file again.
@ -968,9 +974,26 @@ final class DiffusionServeController extends DiffusionController {
if ($ref) {
$file = idx($files, $ref->getFilePHID());
if ($file) {
// Git LFS may prompt users for authentication if the action does
// not provide an "Authorization" header and does not have a query
// parameter named "token". See here for discussion:
// <https://github.com/github/git-lfs/issues/1088>
$no_authorization = 'Basic '.base64_encode('none');
$get_uri = $file->getCDNURIWithToken();
$actions['download'] = array(
'href' => $get_uri,
'header' => array(
'Authorization' => $no_authorization,
),
);
} else {
$error = array(
'code' => 404,
'message' => pht(
'Object "%s" was previously uploaded, but no longer exists '.
'on this server.',
$oid),
);
}
} else if ($want_upload) {
@ -995,11 +1018,20 @@ final class DiffusionServeController extends DiffusionController {
);
}
$output[] = array(
$object = array(
'oid' => $oid,
'size' => $size,
'actions' => $actions,
);
if ($actions) {
$object['actions'] = $actions;
}
if ($error) {
$object['error'] = $error;
}
$output[] = $object;
}
$output = array(
@ -1010,6 +1042,69 @@ final class DiffusionServeController extends DiffusionController {
->setContent($output);
}
private function serveGitLFSUploadRequest(
PhabricatorRepository $repository,
PhabricatorUser $viewer,
$oid) {
$ref = id(new PhabricatorRepositoryGitLFSRefQuery())
->setViewer($viewer)
->withRepositoryPHIDs(array($repository->getPHID()))
->withObjectHashes(array($oid))
->executeOne();
if ($ref) {
return DiffusionGitLFSResponse::newErrorResponse(
405,
pht(
'Content for object "%s" is already known to this server. It can '.
'not be uploaded again.',
$oid));
}
$request_stream = new AphrontRequestStream();
$request_iterator = $request_stream->getIterator();
$hashing_iterator = id(new PhutilHashingIterator($request_iterator))
->setAlgorithm('sha256');
$source = id(new PhabricatorIteratorFileUploadSource())
->setName('lfs-'.$oid)
->setViewPolicy(PhabricatorPolicies::POLICY_NOONE)
->setIterator($hashing_iterator);
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$file = $source->uploadFile();
unset($unguarded);
$hash = $hashing_iterator->getHash();
if ($hash !== $oid) {
return DiffusionGitLFSResponse::newErrorResponse(
400,
pht(
'Uploaded data is corrupt or invalid. Expected hash "%s", actual '.
'hash "%s".',
$oid,
$hash));
}
$ref = id(new PhabricatorRepositoryGitLFSRef())
->setRepositoryPHID($repository->getPHID())
->setObjectHash($hash)
->setByteSize($file->getByteSize())
->setAuthorPHID($viewer->getPHID())
->setFilePHID($file->getPHID());
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
// Attach the file to the repository to give users permission
// to access it.
$file->attachToObject($repository->getPHID());
$ref->save();
unset($unguarded);
// This is just a plain HTTP 200 with no content, which is what `git lfs`
// expects.
return new DiffusionGitLFSResponse();
}
private function newGitLFSHTTPAuthorization(
PhabricatorRepository $repository,
PhabricatorUser $viewer,

View file

@ -72,7 +72,9 @@ abstract class PhabricatorFileUploadSource
$data->rewind();
$this->didRewind = true;
} else {
$data->next();
if ($data->valid()) {
$data->next();
}
}
if (!$data->valid()) {

View file

@ -0,0 +1,25 @@
<?php
final class PhabricatorIteratorFileUploadSource
extends PhabricatorFileUploadSource {
private $iterator;
public function setIterator(Iterator $iterator) {
$this->iterator = $iterator;
return $this;
}
public function getIterator() {
return $this->iterator;
}
protected function newDataIterator() {
return $this->getIterator();
}
protected function getDataLength() {
return null;
}
}