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 ' (to select the current ". "changes attached to a Differential revision), '--diff ' ". "(to select a specific, out-of-date diff or a diff which is not ". "attached to a revision), '--arcbundle ' or '--patch ' ". "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; } }