1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-12-04 20:52:42 +01:00
phorge-arcanist/src/workflow/patch/ArcanistPatchWorkflow.php
2011-01-09 15:22:25 -08:00

300 lines
9 KiB
PHP

<?php
/*
* Copyright 2011 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 ArcanistPatchWorkflow extends ArcanistBaseWorkflow {
const SOURCE_BUNDLE = 'bundle';
const SOURCE_PATCH = 'patch';
const SOURCE_REVISION = 'revision';
const SOURCE_DIFF = 'diff';
private $source;
private $sourceParam;
public function getCommandHelp() {
return phutil_console_format(<<<EOTEXT
**patch** __source__
Supports: git, svn
Apply the changes in a Differential revision, patchfile, or arc
bundle to the working copy.
EOTEXT
);
}
public function getArguments() {
return array(
'revision' => array(
'param' => 'revision_id',
'help' =>
"Apply changes from a Differential revision, using the most recent ".
"diff that has been attached to it.",
),
'diff' => array(
'param' => 'diff_id',
'help' =>
"Apply changes from a Differential diff. Normally you want to use ".
"--revision to get the most recent changes, but you can ".
"specifically apply an out-of-date diff or a diff which was never ".
"attached to a revision by using this flag.",
),
'arcbundle' => array(
'param' => 'bundlefile',
'help' =>
"Apply changes from an arc bundle generated with 'arc export'.",
),
'patch' => array(
'param' => 'patchfile',
'help' =>
"Apply changes from a git patchfile or unified patchfile.",
),
);
}
protected function didParseArguments() {
$source = null;
$requested = 0;
if ($this->getArgument('revision')) {
$source = self::SOURCE_REVISION;
$requested++;
}
if ($this->getArgument('diff')) {
$source = self::SOURCE_DIFF;
$requested++;
}
if ($this->getArgument('arcbundle')) {
$source = self::SOURCE_BUNDLE;
$requested++;
}
if ($this->getArgument('patch')) {
$source = self::SOURCE_PATCH;
$requested++;
}
if ($requested === 0) {
throw new ArcanistUsageException(
"Specify one of '--revision <revision_id>' (to select the current ".
"changes attached to a Differential revision), '--diff <diff_id>' ".
"(to select a specific, out-of-date diff or a diff which is not ".
"attached to a revision), '--arcbundle <file>' or '--patch <file>' ".
"to choose a patch source.");
} else if ($requested > 1) {
throw new ArcanistUsageException(
"Options '--revision', '--diff', '--arcbundle' and '--patch' are ".
"not compatible. Choose exactly one patch source.");
}
$this->source = $source;
$this->sourceParam = $this->getArgument($source);
}
public function requiresConduit() {
return ($this->getSource() == self::SOURCE_REVISION) ||
($this->getSource() == self::SOURCE_DIFF);
}
public function requiresAuthentication() {
return $this->requiresConduit();
}
public function requiresRepositoryAPI() {
return true;
}
public function requiresWorkingCopy() {
return true;
}
private function getSource() {
return $this->source;
}
private function getSourceParam() {
return $this->sourceParam;
}
public function run() {
$source = $this->getSource();
$param = $this->getSourceParam();
switch ($source) {
case self::SOURCE_PATCH:
if ($param == '-') {
$patch = @file_get_contents('php://stdin');
if (!strlen($patch)) {
throw new ArcanistUsageException(
"Failed to read patch from stdin!");
}
} else {
$patch = Filesystem::readFile($param);
}
$bundle = ArcanistBundle::newFromDiff($patch);
break;
case self::SOURCE_BUNDLE:
$path = $this->getArgument('arcbundle');
$bundle = ArcanistBundle::newFromArcBundle($path);
break;
case self::SOURCE_REVISION:
$bundle = $this->loadRevisionBundleFromConduit(
$this->getConduit(),
$param);
break;
case self::SOURCE_DIFF:
$bundle = $this->loadDiffBundleFromConduit(
$this->getConduit(),
$param);
break;
}
$repository_api = $this->getRepositoryAPI();
if ($repository_api instanceof ArcanistSubversionAPI) {
$copies = array();
$deletes = array();
$patches = array();
$propset = array();
$adds = array();
$changes = $bundle->getChanges();
foreach ($changes as $change) {
$type = $change->getType();
$should_patch = true;
switch ($type) {
case ArcanistDiffChangeType::TYPE_MOVE_AWAY:
case ArcanistDiffChangeType::TYPE_MULTICOPY:
case ArcanistDiffChangeType::TYPE_DELETE:
$path = $change->getCurrentPath();
$fpath = $repository_api->getPath($path);
if (!@file_exists($fpath)) {
$this->confirm(
"Patch deletes file '{$path}', but the file does not exist in ".
"the working copy. Continue anyway?");
} else {
$deletes[] = $change->getCurrentPath();
}
$should_patch = false;
break;
case ArcanistDiffChangeType::TYPE_COPY_HERE:
case ArcanistDiffChangeType::TYPE_MOVE_HERE:
$path = $change->getOldPath();
$fpath = $repository_api->getPath($path);
if (!@file_exists($fpath)) {
$cpath = $change->getCurrentPath();
if ($type == ArcanistDiffChangeType::TYPE_COPY_HERE) {
$verbs = 'copies';
} else {
$verbs = 'moves';
}
$this->confirm(
"Patch {$verbs} '{$path}' to '{$cpath}', but source path ".
"does not exist in the working copy. Continue anyway?");
} else {
$copies[] = array(
$change->getOldPath(),
$change->getCurrentPath());
}
break;
case ArcanistDiffChangeType::TYPE_ADD:
$adds[] = $change->getCurrentPath();
break;
}
if ($should_patch) {
if ($change->getHunks()) {
$cbundle = ArcanistBundle::newFromChanges(array($change));
$patches[$change->getCurrentPath()] = $cbundle->toUnifiedDiff();
}
$prop_old = $change->getOldProperties();
$prop_new = $change->getNewProperties();
$props = $prop_old + $prop_new;
foreach ($props as $key => $ignored) {
if (idx($prop_old, $key) !== idx($prop_new, $key)) {
$propset[$change->getCurrentPath()][$key] = idx($prop_new[$key]);
}
}
}
}
foreach ($copies as $copy) {
list($src, $dst) = $copy;
passthru(
csprintf(
'(cd %s; svn cp %s %s)',
$repository_api->getPath(),
$src,
$dst));
}
foreach ($deletes as $delete) {
passthru(
csprintf(
'(cd %s; svn rm %s)',
$repository_api->getPath(),
$delete));
}
foreach ($patches as $path => $patch) {
$tmp = new TempFile();
Filesystem::writeFile($tmp, $patch);
passthru(
csprintf(
'(cd %s; patch -p0 < %s)',
$repository_api->getPath(),
$tmp));
}
foreach ($adds as $add) {
passthru(
csprintf(
'(cd %s; svn add %s)',
$repository_api->getPath(),
$add));
}
foreach ($propset as $path => $changes) {
foreach ($change as $prop => $value) {
// TODO: Probably need to handle svn:executable specially here by
// doing chmod +x or -x.
if ($value === null) {
passthru(
csprintf(
'(cd %s; svn propdel %s %s)',
$repository_api->getPath(),
$prop,
$path));
} else {
passthru(
csprintf(
'(cd %s; svn propset %s %s %s)',
$repository_api->getPath(),
$prop,
$value,
$path));
}
}
}
echo "Applied patch.\n";
} else {
$future = new ExecFuture(
'(cd %s; git apply --index)',
$repository_api->getPath());
$future->write($bundle->toGitPatch());
$future->resolvex();
}
return 0;
}
}