1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-11-26 00:32:41 +01:00

Improve arc handling of various binary file operations in Git

Summary:
See T866, D3521. Additional things this fixes:

  - `arc export` now exports binary data correctly.
  - `ArcanistBundle` unit tests now load and apply binary data correctly.
  - `arc patch` no longer relies on `base` configuration.
  - Adds tests to the tarball:

  commit df340e88d8aba12e8f2b8827f01f0cd9f35eb758
  Author: epriestley <git@epriestley.com>
  Date:   Wed Oct 17 15:46:11 2012 -0700

      Remove binary image.

  commit 3f5c6d735e64c25a04f83be48ef184b25b5282f0
  Author: epriestley <git@epriestley.com>
  Date:   Wed Oct 17 15:45:58 2012 -0700

      Copy binary image.

  commit b454edb3bb29890ee5b3af5ef66ce6a24d15d882
  Author: epriestley <git@epriestley.com>
  Date:   Wed Oct 17 15:45:35 2012 -0700

      Move binary image.

  commit 5de5f3dfda1b7db2eb054e57699f05aaf1f4483e
  Author: epriestley <git@epriestley.com>
  Date:   Wed Oct 17 15:45:09 2012 -0700

      Add a binary image.

Test Plan: Ran unit tests, `arc patch`, `arc export`, `arc diff`, `arc upgrade`.

Reviewers: vrana, btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T866

Differential Revision: https://secure.phabricator.com/D3732
This commit is contained in:
epriestley 2012-10-19 10:10:25 -07:00
parent 4c476c0201
commit ffac8d5c4c
13 changed files with 164 additions and 58 deletions

View file

@ -312,15 +312,14 @@ final class ArcanistBundle {
$old_file_phids = array();
foreach ($changes as $change) {
if (!$this->isGitBinaryChange($change)) {
continue;
}
$type = $change->getType();
if ($type == ArcanistDiffChangeType::TYPE_MOVE_AWAY) {
$file_type = $change->getFileType();
$is_binary = ($file_type == ArcanistDiffChangeType::FILE_BINARY ||
$file_type == ArcanistDiffChangeType::FILE_IMAGE);
if ($is_binary) {
foreach ($change->getAwayPaths() as $path) {
$old_file_phids[$path] = $change->getMetadata('old:binary-phid');
}
foreach ($change->getAwayPaths() as $path) {
$old_file_phids[$path] = $change->getMetadata('old:binary-phid');
}
}
}
@ -348,8 +347,7 @@ final class ArcanistBundle {
$old_mode = idx($change->getOldProperties(), 'unix:filemode', '100644');
$new_mode = idx($change->getNewProperties(), 'unix:filemode', '100644');
$is_binary = ($file_type == ArcanistDiffChangeType::FILE_BINARY ||
$file_type == ArcanistDiffChangeType::FILE_IMAGE);
$is_binary = $this->isGitBinaryChange($change);
if ($is_binary) {
$old_phid = idx($old_file_phids, $this->getCurrentPath($change));
@ -427,6 +425,12 @@ final class ArcanistBundle {
return $this->convertNonUTF8Diff($diff);
}
private function isGitBinaryChange(ArcanistDiffChange $change) {
$file_type = $change->getFileType();
return ($file_type == ArcanistDiffChangeType::FILE_BINARY ||
$file_type == ArcanistDiffChangeType::FILE_IMAGE);
}
private function convertNonUTF8Diff($diff) {
if ($this->encoding) {
$diff = phutil_utf8_convert($diff, $this->encoding, 'UTF-8');
@ -615,7 +619,7 @@ final class ArcanistBundle {
return $this;
}
private function getBlob($phid) {
private function getBlob($phid, $name = null) {
if ($this->loadFileDataCallback) {
return call_user_func($this->loadFileDataCallback, $phid);
}
@ -625,8 +629,14 @@ final class ArcanistBundle {
return $blob_data;
}
$console = PhutilConsole::getConsole();
if ($this->conduit) {
echo "Downloading binary data...\n";
if ($name) {
$console->writeErr("Downloading binary data for '%s'...\n", $name);
} else {
$console->writeErr("Downloading binary data...\n");
}
$data_base64 = $this->conduit->callMethodSynchronous(
'file.download',
array(
@ -642,23 +652,37 @@ final class ArcanistBundle {
$old_phid = idx($change->getAllMetadata(), 'old:binary-phid', $old_phid);
$new_phid = $change->getMetadata('new:binary-phid');
if (!$old_phid) {
$old_data = null;
if ($change->getOriginalFileData() !== null) {
$old_data = $change->getOriginalFileData();
} else if ($old_phid) {
$name = basename($change->getOldPath());
$old_data = $this->getBlob($old_phid, $name);
}
$old_length = strlen($old_data);
if ($old_data === null) {
$old_data = '';
$old_length = 0;
$old_sha1 = str_repeat('0', 40);
} else {
$old_data = $this->getBlob($old_phid);
$old_length = strlen($old_data);
$old_sha1 = sha1("blob {$old_length}\0{$old_data}");
}
if (!$new_phid) {
$new_data = null;
if ($change->getCurrentFileData() !== null) {
$new_data = $change->getCurrentFileData();
} else if ($new_phid) {
$name = basename($change->getCurrentPath());
$new_data = $this->getBlob($new_phid, $name);
}
$new_length = strlen($new_data);
if ($new_data === null) {
$new_data = '';
$new_length = 0;
$new_sha1 = str_repeat('0', 40);
} else {
$new_data = $this->getBlob($new_phid);
$new_length = strlen($new_data);
$new_sha1 = sha1("blob {$new_length}\0{$new_data}");
}

View file

@ -1140,4 +1140,49 @@ final class ArcanistDiffParser {
return $name;
}
}
public function loadSyntheticData(
array $changes,
ArcanistRepositoryAPI $repository_api) {
assert_instances_of($changes, 'ArcanistDiffChange');
foreach ($changes as $change) {
$path = $change->getCurrentPath();
// Certain types of changes (moves and copies) don't contain change data
// when expressed in raw "git diff" form. Augment any such diffs with
// textual data.
if ($change->getNeedsSyntheticGitHunks()) {
$diff = $repository_api->getRawDiffText($path, $moves = false);
$raw_changes = $this->parseDiff($diff);
foreach ($raw_changes as $raw_change) {
if ($raw_change->getCurrentPath() == $path) {
$change->setFileType($raw_change->getFileType());
foreach ($raw_change->getHunks() as $hunk) {
// Git thinks that this file has been added. But we know that it
// has been moved or copied without a change.
$hunk->setCorpus(
preg_replace('/^\+/m', ' ', $hunk->getCorpus()));
$change->addHunk($hunk);
}
break;
}
}
$change->setNeedsSyntheticGitHunks(false);
}
if ($change->getFileType() != ArcanistDiffChangeType::FILE_BINARY &&
$change->getFileType() != ArcanistDiffChangeType::FILE_IMAGE) {
continue;
}
$change->setOriginalFileData($repository_api->getOriginalFileData($path));
$change->setCurrentFileData($repository_api->getCurrentFileData($path));
}
return $changes;
}
}

View file

@ -71,6 +71,8 @@ final class ArcanistBundleTestCase extends ArcanistTestCase {
foreach ($commits as $commit) {
list($commit_hash, $tree_hash, $subject) = explode(' ', $commit, 3);
execx('git reset --hard %s --', $commit_hash);
list($diff) = execx(
'git diff %s^ %s --',
$commit_hash,
@ -78,12 +80,20 @@ final class ArcanistBundleTestCase extends ArcanistTestCase {
$parser = new ArcanistDiffParser();
$changes = $parser->parseDiff($diff);
$repository_api = new ArcanistGitAPI($fixture->getPath());
$repository_api->setRelativeCommit($commit_hash.'^');
$parser->loadSyntheticData($changes, $repository_api);
$bundle = ArcanistBundle::newFromChanges($changes);
execx('git reset --hard %s^ --', $commit_hash);
$patch = $bundle->toGitPatch();
id(new ExecFuture('git apply --index --reject'))
->write($bundle->toGitPatch())
->write($patch)
->resolvex();
execx('git commit -m %s', $subject);

Binary file not shown.

View file

@ -40,6 +40,27 @@ final class ArcanistDiffChange {
private $needsSyntheticGitHunks;
private $currentFileData;
private $originalFileData;
public function setOriginalFileData($original_file_data) {
$this->originalFileData = $original_file_data;
return $this;
}
public function getOriginalFileData() {
return $this->originalFileData;
}
public function setCurrentFileData($current_file_data) {
$this->currentFileData = $current_file_data;
return $this;
}
public function getCurrentFileData() {
return $this->currentFileData;
}
public function toDictionary() {
$hunks = array();
foreach ($this->hunks as $hunk) {

View file

@ -689,6 +689,11 @@ final class ArcanistGitAPI extends ArcanistRepositoryAPI {
return true;
}
public function setDefaultBaseCommit() {
$this->setRelativeCommit('HEAD^');
return $this;
}
public function hasLocalCommit($commit) {
try {
if (!$this->getCanonicalRevisionName($commit)) {

View file

@ -427,6 +427,11 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
return true;
}
public function setDefaultBaseCommit() {
$this->setRelativeCommit('.^');
return $this;
}
public function hasLocalCommit($commit) {
try {
$this->getCanonicalRevisionName($commit);

View file

@ -182,6 +182,21 @@ abstract class ArcanistRepositoryAPI {
ConduitClient $conduit,
array $query);
/**
* Set the base commit to a reasonable default value so that working copy
* status checks can do something meaningful and won't invoke configured
* 'base' rules.
*
* This is primarily useful for workflows which do not operate on commit
* ranges but need to verify the working copy is not dirty, like "amend",
* "upgrade" and "patch".
*
* @return this
*/
public function setDefaultBaseCommit() {
throw new ArcanistCapabilityNotSupportedException($this);
}
public function amendCommit($message) {
throw new ArcanistCapabilityNotSupportedException($this);
}

View file

@ -106,10 +106,8 @@ EOTEXT
$revision_id = $this->normalizeRevisionID($this->getArgument('revision'));
}
if ($repository_api instanceof ArcanistGitAPI) {
$repository_api->setRelativeCommit('HEAD^');
} else if ($repository_api instanceof ArcanistMercurialAPI) {
$repository_api->setRelativeCommit('.^');
if ($repository_api->supportsRelativeLocalCommits()) {
$repository_api->setDefaultBaseCommit();
}
$in_working_copy = $repository_api->loadWorkingCopyDifferentialRevisions(

View file

@ -1019,41 +1019,18 @@ EOTEXT
}
}
$changes = $parser->loadSyntheticData($changes, $repository_api);
foreach ($changes as $change) {
$path = $change->getCurrentPath();
// Certain types of changes (moves and copies) don't contain change data
// when expressed in raw "git diff" form. Augment any such diffs with
// textual data.
if ($change->getNeedsSyntheticGitHunks()) {
$diff = $repository_api->getRawDiffText($path, $moves = false);
$parser = $this->newDiffParser();
$raw_changes = $parser->parseDiff($diff);
foreach ($raw_changes as $raw_change) {
if ($raw_change->getCurrentPath() == $path) {
$change->setFileType($raw_change->getFileType());
foreach ($raw_change->getHunks() as $hunk) {
// Git thinks that this file has been added. But we know that it
// has been moved or copied without a change.
$hunk->setCorpus(
preg_replace('/^\+/m', ' ', $hunk->getCorpus()));
$change->addHunk($hunk);
}
break;
}
}
$change->setNeedsSyntheticGitHunks(false);
}
if ($change->getFileType() != ArcanistDiffChangeType::FILE_BINARY) {
continue;
}
$path = $change->getCurrentPath();
$name = basename($path);
$old_file = $repository_api->getOriginalFileData($path);
$old_file = $change->getOriginalFileData();
$old_dict = $this->uploadFile($old_file, $name, 'old binary');
if ($old_dict['guid']) {
$change->setMetadata('old:binary-phid', $old_dict['guid']);
@ -1061,7 +1038,7 @@ EOTEXT
$change->setMetadata('old:file:size', $old_dict['size']);
$change->setMetadata('old:file:mime-type', $old_dict['mime']);
$new_file = $repository_api->getCurrentFileData($path);
$new_file = $change->getCurrentFileData();
$new_dict = $this->uploadFile($new_file, $name, 'new binary');
if ($new_dict['guid']) {
$change->setMetadata('new:binary-phid', $new_dict['guid']);

View file

@ -204,6 +204,8 @@ EOTEXT
$paths);
}
$changes = $parser->loadSyntheticData($changes, $repository_api);
$bundle = ArcanistBundle::newFromChanges($changes);
$bundle->setProjectID($this->getWorkingCopy()->getProjectID());
$bundle->setBaseRevision(

View file

@ -837,6 +837,11 @@ EOTEXT
* Do the best we can to prevent PEBKAC and id10t issues.
*/
private function sanityCheck(ArcanistBundle $bundle) {
$repository_api = $this->getRepositoryAPI();
if ($repository_api->supportsRelativeLocalCommits()) {
$repository_api->setDefaultBaseCommit();
}
// Require clean working copy
$this->requireCleanWorkingCopy();
@ -870,7 +875,6 @@ EOTEXT
// Check to see if the bundle's base revision matches the working copy
// base revision
$repository_api = $this->getRepositoryAPI();
if ($repository_api->supportsRelativeLocalCommits()) {
$bundle_base_rev = $bundle->getBaseRevision();
if (empty($bundle_base_rev)) {

View file

@ -66,10 +66,10 @@ EOTEXT
$repository_api = ArcanistRepositoryAPI::newAPIFromWorkingCopyIdentity(
$working_copy);
// Force the range to HEAD^..HEAD, which is meaningless but keeps us
// from triggering "base" rules or other commit range resolution rules
// that might prompt the user when we pull the working copy status.
$repository_api->setRelativeCommit('HEAD^');
if ($repository_api->supportsRelativeLocalCommits()) {
$repository_api->setDefaultBaseCommit();
}
$this->setRepositoryAPI($repository_api);
// Require no local changes.