Changeset View
Changeset View
Standalone View
Standalone View
src/workflow/ArcanistPatchWorkflow.php
| Show All 36 Lines | EOTEXT | ||||
| ); | ); | ||||
| } | } | ||||
| public function getArguments() { | public function getArguments() { | ||||
| return array( | return array( | ||||
| 'revision' => array( | 'revision' => array( | ||||
| 'param' => 'revision_id', | 'param' => 'revision_id', | ||||
| 'paramtype' => 'complete', | 'paramtype' => 'complete', | ||||
| 'help' => | 'help' => pht( | ||||
| "Apply changes from a Differential revision, using the most recent ". | "Apply changes from a Differential revision, using the most recent ". | ||||
| "diff that has been attached to it. You can run 'arc patch D12345' ". | "diff that has been attached to it. You can run '%s' as a shorthand.", | ||||
| "as a shorthand.", | 'arc patch D12345'), | ||||
| ), | ), | ||||
| 'diff' => array( | 'diff' => array( | ||||
| 'param' => 'diff_id', | 'param' => 'diff_id', | ||||
| 'help' => | 'help' => pht( | ||||
| 'Apply changes from a Differential diff. Normally you want to use '. | 'Apply changes from a Differential diff. Normally you want to use '. | ||||
| '--revision to get the most recent changes, but you can '. | '%s to get the most recent changes, but you can specifically apply '. | ||||
| 'specifically apply an out-of-date diff or a diff which was never '. | 'an out-of-date diff or a diff which was never attached to a '. | ||||
| 'attached to a revision by using this flag.', | 'revision by using this flag.', | ||||
| '--revision'), | |||||
| ), | ), | ||||
| 'arcbundle' => array( | 'arcbundle' => array( | ||||
| 'param' => 'bundlefile', | 'param' => 'bundlefile', | ||||
| 'paramtype' => 'file', | 'paramtype' => 'file', | ||||
| 'help' => | 'help' => pht( | ||||
| "Apply changes from an arc bundle generated with 'arc export'.", | "Apply changes from an arc bundle generated with '%s'.", | ||||
| 'arc export'), | |||||
| ), | ), | ||||
| 'patch' => array( | 'patch' => array( | ||||
| 'param' => 'patchfile', | 'param' => 'patchfile', | ||||
| 'paramtype' => 'file', | 'paramtype' => 'file', | ||||
| 'help' => 'Apply changes from a git patchfile or unified patchfile.', | 'help' => pht( | ||||
| 'Apply changes from a git patchfile or unified patchfile.'), | |||||
| ), | ), | ||||
| 'encoding' => array( | 'encoding' => array( | ||||
| 'param' => 'encoding', | 'param' => 'encoding', | ||||
| 'help' => 'Attempt to convert non UTF-8 patch into specified encoding.', | 'help' => pht( | ||||
| 'Attempt to convert non UTF-8 patch into specified encoding.'), | |||||
| ), | ), | ||||
| 'update' => array( | 'update' => array( | ||||
| 'supports' => array('git', 'svn', 'hg'), | 'supports' => array('git', 'svn', 'hg'), | ||||
| 'help' => 'Update the local working copy before applying the patch.', | 'help' => pht( | ||||
| 'Update the local working copy before applying the patch.'), | |||||
| 'conflicts' => array( | 'conflicts' => array( | ||||
| 'nobranch' => true, | 'nobranch' => true, | ||||
| 'bookmark' => true, | 'bookmark' => true, | ||||
| ), | ), | ||||
| ), | ), | ||||
| 'nocommit' => array( | 'nocommit' => array( | ||||
| 'supports' => array('git', 'hg'), | 'supports' => array('git', 'hg'), | ||||
| 'help' => | 'help' => pht( | ||||
| 'Normally under git/hg, if the patch is successful, the changes '. | 'Normally under git/hg, if the patch is successful, the changes '. | ||||
| 'are committed to the working copy. This flag prevents the commit.', | 'are committed to the working copy. This flag prevents the commit.'), | ||||
| ), | ), | ||||
| 'skip-dependencies' => array( | 'skip-dependencies' => array( | ||||
| 'supports' => array('git', 'hg'), | 'supports' => array('git', 'hg'), | ||||
| 'help' => | 'help' => pht( | ||||
| 'Normally, if a patch has dependencies that are not present in the '. | 'Normally, if a patch has dependencies that are not present in the '. | ||||
| 'working copy, arc tries to apply them as well. This flag prevents '. | 'working copy, arc tries to apply them as well. This flag prevents '. | ||||
| 'such work.', | 'such work.'), | ||||
| ), | ), | ||||
| 'nobranch' => array( | 'nobranch' => array( | ||||
| 'supports' => array('git', 'hg'), | 'supports' => array('git', 'hg'), | ||||
| 'help' => | 'help' => pht( | ||||
| 'Normally, a new branch (git) or bookmark (hg) is created and then '. | 'Normally, a new branch (git) or bookmark (hg) is created and then '. | ||||
| 'the patch is applied and committed in the new branch/bookmark. '. | 'the patch is applied and committed in the new branch/bookmark. '. | ||||
| 'This flag cherry-picks the resultant commit onto the original '. | 'This flag cherry-picks the resultant commit onto the original '. | ||||
| 'branch and deletes the temporary branch.', | 'branch and deletes the temporary branch.'), | ||||
| 'conflicts' => array( | 'conflicts' => array( | ||||
| 'update' => true, | 'update' => true, | ||||
| ), | ), | ||||
| ), | ), | ||||
| 'force' => array( | 'force' => array( | ||||
| 'help' => 'Do not run any sanity checks.', | 'help' => pht('Do not run any sanity checks.'), | ||||
| ), | ), | ||||
| '*' => 'name', | '*' => 'name', | ||||
| ); | ); | ||||
| } | } | ||||
| protected function didParseArguments() { | protected function didParseArguments() { | ||||
| $source = null; | $source = null; | ||||
| $requested = 0; | $requested = 0; | ||||
| Show All 13 Lines | if ($this->getArgument('patch')) { | ||||
| $source = self::SOURCE_PATCH; | $source = self::SOURCE_PATCH; | ||||
| $requested++; | $requested++; | ||||
| } | } | ||||
| $use_revision_id = null; | $use_revision_id = null; | ||||
| if ($this->getArgument('name')) { | if ($this->getArgument('name')) { | ||||
| $namev = $this->getArgument('name'); | $namev = $this->getArgument('name'); | ||||
| if (count($namev) > 1) { | if (count($namev) > 1) { | ||||
| throw new ArcanistUsageException('Specify at most one revision name.'); | throw new ArcanistUsageException( | ||||
| pht('Specify at most one revision name.')); | |||||
| } | } | ||||
| $source = self::SOURCE_REVISION; | $source = self::SOURCE_REVISION; | ||||
| $requested++; | $requested++; | ||||
| $use_revision_id = $this->normalizeRevisionID(head($namev)); | $use_revision_id = $this->normalizeRevisionID(head($namev)); | ||||
| } | } | ||||
| if ($requested === 0) { | if ($requested === 0) { | ||||
| throw new ArcanistUsageException( | throw new ArcanistUsageException( | ||||
| "Specify one of 'D12345', '--revision <revision_id>' (to select the ". | pht( | ||||
| "current changes attached to a Differential revision), ". | "Specify one of '%s', '%s' (to select the current changes attached ". | ||||
| "'--diff <diff_id>' (to select a specific, out-of-date diff or a ". | "to a Differential revision), '%s' (to select a specific, ". | ||||
| "diff which is not attached to a revision), '--arcbundle <file>' ". | "out-of-date diff or a diff which is not attached to a revision), ". | ||||
| "or '--patch <file>' to choose a patch source."); | "'%s' or '%s' to choose a patch source.", | ||||
| 'D12345', | |||||
| '--revision <revision_id>', | |||||
| '--diff <diff_id>', | |||||
| '--arcbundle <file>', | |||||
| '--patch <file>')); | |||||
| } else if ($requested > 1) { | } else if ($requested > 1) { | ||||
| throw new ArcanistUsageException( | throw new ArcanistUsageException( | ||||
| "Options 'D12345', '--revision', '--diff', '--arcbundle' and ". | pht( | ||||
| "'--patch' are not compatible. Choose exactly one patch source."); | "Options '%s', '%s', '%s', '%s' and '%s' are not compatible. ". | ||||
| "Choose exactly one patch source.", | |||||
| 'D12345', | |||||
| '--revision', | |||||
| '--diff', | |||||
| '--arcbundle', | |||||
| '--patch')); | |||||
| } | } | ||||
| $this->source = $source; | $this->source = $source; | ||||
| $this->sourceParam = nonempty( | $this->sourceParam = nonempty( | ||||
| $use_revision_id, | $use_revision_id, | ||||
| $this->getArgument($source)); | $this->getArgument($source)); | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 50 Lines • ▼ Show 20 Lines | foreach ($suffixes as $suffix) { | ||||
| list($err) = $repository_api->execManualLocal( | list($err) = $repository_api->execManualLocal( | ||||
| 'rev-parse --verify %s', | 'rev-parse --verify %s', | ||||
| $proposed_name); | $proposed_name); | ||||
| // no error means git rev-parse found a branch | // no error means git rev-parse found a branch | ||||
| if (!$err) { | if (!$err) { | ||||
| echo phutil_console_format( | echo phutil_console_format( | ||||
| "Branch name {$proposed_name} already exists; trying a new name.\n"); | "%s\n", | ||||
| pht( | |||||
| 'Branch name %s already exists; trying a new name.', | |||||
| $proposed_name)); | |||||
| continue; | continue; | ||||
| } else { | } else { | ||||
| $branch_name = $proposed_name; | $branch_name = $proposed_name; | ||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| if (!$branch_name) { | if (!$branch_name) { | ||||
| throw new Exception( | throw new Exception( | ||||
| pht( | |||||
| 'Arc was unable to automagically make a name for this patch. '. | 'Arc was unable to automagically make a name for this patch. '. | ||||
| 'Please clean up your working copy and try again.' | 'Please clean up your working copy and try again.')); | ||||
| ); | |||||
| } | } | ||||
| return $branch_name; | return $branch_name; | ||||
| } | } | ||||
| private function getBookmarkName(ArcanistBundle $bundle) { | private function getBookmarkName(ArcanistBundle $bundle) { | ||||
| $bookmark_name = null; | $bookmark_name = null; | ||||
| $repository_api = $this->getRepositoryAPI(); | $repository_api = $this->getRepositoryAPI(); | ||||
| Show All 9 Lines | foreach ($suffixes as $suffix) { | ||||
| list($err) = $repository_api->execManualLocal( | list($err) = $repository_api->execManualLocal( | ||||
| 'log -r %s', | 'log -r %s', | ||||
| hgsprintf('%s', $proposed_name)); | hgsprintf('%s', $proposed_name)); | ||||
| // no error means hg log found a bookmark | // no error means hg log found a bookmark | ||||
| if (!$err) { | if (!$err) { | ||||
| echo phutil_console_format( | echo phutil_console_format( | ||||
| "Bookmark name %s already exists; trying a new name.\n", | "%s\n", | ||||
| $proposed_name); | pht( | ||||
| 'Bookmark name %s already exists; trying a new name.', | |||||
| $proposed_name)); | |||||
| continue; | continue; | ||||
| } else { | } else { | ||||
| $bookmark_name = $proposed_name; | $bookmark_name = $proposed_name; | ||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| if (!$bookmark_name) { | if (!$bookmark_name) { | ||||
| throw new Exception( | throw new Exception( | ||||
| pht( | |||||
| 'Arc was unable to automagically make a name for this patch. '. | 'Arc was unable to automagically make a name for this patch. '. | ||||
| 'Please clean up your working copy and try again.' | 'Please clean up your working copy and try again.')); | ||||
| ); | |||||
| } | } | ||||
| return $bookmark_name; | return $bookmark_name; | ||||
| } | } | ||||
| private function createBranch(ArcanistBundle $bundle, $has_base_revision) { | private function createBranch(ArcanistBundle $bundle, $has_base_revision) { | ||||
| $repository_api = $this->getRepositoryAPI(); | $repository_api = $this->getRepositoryAPI(); | ||||
| if ($repository_api instanceof ArcanistGitAPI) { | if ($repository_api instanceof ArcanistGitAPI) { | ||||
| $branch_name = $this->getBranchName($bundle); | $branch_name = $this->getBranchName($bundle); | ||||
| $base_revision = $bundle->getBaseRevision(); | $base_revision = $bundle->getBaseRevision(); | ||||
| if ($base_revision && $has_base_revision) { | if ($base_revision && $has_base_revision) { | ||||
| $base_revision = $repository_api->getCanonicalRevisionName( | $base_revision = $repository_api->getCanonicalRevisionName( | ||||
| $base_revision); | $base_revision); | ||||
| $repository_api->execxLocal( | $repository_api->execxLocal( | ||||
| 'checkout -b %s %s', | 'checkout -b %s %s', | ||||
| $branch_name, | $branch_name, | ||||
| $base_revision); | $base_revision); | ||||
| } else { | } else { | ||||
| $repository_api->execxLocal( | $repository_api->execxLocal('checkout -b %s', $branch_name); | ||||
| 'checkout -b %s', | |||||
| $branch_name); | |||||
| } | } | ||||
| echo phutil_console_format( | echo phutil_console_format( | ||||
| "Created and checked out branch %s.\n", | "%s\n", | ||||
| $branch_name); | pht( | ||||
| 'Created and checked out branch %s.', | |||||
| $branch_name)); | |||||
| } else if ($repository_api instanceof ArcanistMercurialAPI) { | } else if ($repository_api instanceof ArcanistMercurialAPI) { | ||||
| $branch_name = $this->getBookmarkName($bundle); | $branch_name = $this->getBookmarkName($bundle); | ||||
| $base_revision = $bundle->getBaseRevision(); | $base_revision = $bundle->getBaseRevision(); | ||||
| if ($base_revision && $has_base_revision) { | if ($base_revision && $has_base_revision) { | ||||
| $base_revision = $repository_api->getCanonicalRevisionName( | $base_revision = $repository_api->getCanonicalRevisionName( | ||||
| $base_revision); | $base_revision); | ||||
| echo "Updating to the revision's base commit\n"; | echo pht("Updating to the revision's base commit")."\n"; | ||||
| $repository_api->execPassthru( | $repository_api->execPassthru('update %s', $base_revision); | ||||
| 'update %s', | |||||
| $base_revision); | |||||
| } | } | ||||
| $repository_api->execxLocal('bookmark %s', $branch_name); | $repository_api->execxLocal('bookmark %s', $branch_name); | ||||
| echo phutil_console_format( | echo phutil_console_format( | ||||
| "Created and checked out bookmark %s.\n", | "%s\n", | ||||
| $branch_name); | pht( | ||||
| 'Created and checked out bookmark %s.', | |||||
| $branch_name)); | |||||
| } | } | ||||
| return $branch_name; | return $branch_name; | ||||
| } | } | ||||
| private function shouldApplyDependencies() { | private function shouldApplyDependencies() { | ||||
| return !$this->getArgument('skip-dependencies', false); | return !$this->getArgument('skip-dependencies', false); | ||||
| } | } | ||||
| private function shouldUpdateWorkingCopy() { | private function shouldUpdateWorkingCopy() { | ||||
| return $this->getArgument('update', false); | return $this->getArgument('update', false); | ||||
| } | } | ||||
| private function updateWorkingCopy() { | private function updateWorkingCopy() { | ||||
| echo "Updating working copy...\n"; | echo pht('Updating working copy...')."\n"; | ||||
| $this->getRepositoryAPI()->updateWorkingCopy(); | $this->getRepositoryAPI()->updateWorkingCopy(); | ||||
| echo "Done.\n"; | echo pht('Done.')."\n"; | ||||
| } | } | ||||
| public function run() { | public function run() { | ||||
| $source = $this->getSource(); | $source = $this->getSource(); | ||||
| $param = $this->getSourceParam(); | $param = $this->getSourceParam(); | ||||
| try { | try { | ||||
| switch ($source) { | switch ($source) { | ||||
| case self::SOURCE_PATCH: | case self::SOURCE_PATCH: | ||||
| if ($param == '-') { | if ($param == '-') { | ||||
| $patch = @file_get_contents('php://stdin'); | $patch = @file_get_contents('php://stdin'); | ||||
| if (!strlen($patch)) { | if (!strlen($patch)) { | ||||
| throw new ArcanistUsageException( | throw new ArcanistUsageException( | ||||
| 'Failed to read patch from stdin!'); | pht('Failed to read patch from stdin!')); | ||||
| } | } | ||||
| } else { | } else { | ||||
| $patch = Filesystem::readFile($param); | $patch = Filesystem::readFile($param); | ||||
| } | } | ||||
| $bundle = ArcanistBundle::newFromDiff($patch); | $bundle = ArcanistBundle::newFromDiff($patch); | ||||
| break; | break; | ||||
| case self::SOURCE_BUNDLE: | case self::SOURCE_BUNDLE: | ||||
| $path = $this->getArgument('arcbundle'); | $path = $this->getArgument('arcbundle'); | ||||
| ▲ Show 20 Lines • Show All 107 Lines • ▼ Show 20 Lines | if ($repository_api instanceof ArcanistSubversionAPI) { | ||||
| switch ($type) { | switch ($type) { | ||||
| case ArcanistDiffChangeType::TYPE_MOVE_AWAY: | case ArcanistDiffChangeType::TYPE_MOVE_AWAY: | ||||
| case ArcanistDiffChangeType::TYPE_MULTICOPY: | case ArcanistDiffChangeType::TYPE_MULTICOPY: | ||||
| case ArcanistDiffChangeType::TYPE_DELETE: | case ArcanistDiffChangeType::TYPE_DELETE: | ||||
| $path = $change->getCurrentPath(); | $path = $change->getCurrentPath(); | ||||
| $fpath = $repository_api->getPath($path); | $fpath = $repository_api->getPath($path); | ||||
| if (!@file_exists($fpath)) { | if (!@file_exists($fpath)) { | ||||
| $ok = phutil_console_confirm( | $ok = phutil_console_confirm( | ||||
| "Patch deletes file '{$path}', but the file does not exist in ". | pht( | ||||
| "the working copy. Continue anyway?"); | "Patch deletes file '%s', but the file does not exist in ". | ||||
| "the working copy. Continue anyway?", | |||||
| $path)); | |||||
| if (!$ok) { | if (!$ok) { | ||||
| throw new ArcanistUserAbortException(); | throw new ArcanistUserAbortException(); | ||||
| } | } | ||||
| } else { | } else { | ||||
| $deletes[] = $change->getCurrentPath(); | $deletes[] = $change->getCurrentPath(); | ||||
| } | } | ||||
| $should_patch = false; | $should_patch = false; | ||||
| break; | break; | ||||
| case ArcanistDiffChangeType::TYPE_COPY_HERE: | case ArcanistDiffChangeType::TYPE_COPY_HERE: | ||||
| case ArcanistDiffChangeType::TYPE_MOVE_HERE: | case ArcanistDiffChangeType::TYPE_MOVE_HERE: | ||||
| $path = $change->getOldPath(); | $path = $change->getOldPath(); | ||||
| $fpath = $repository_api->getPath($path); | $fpath = $repository_api->getPath($path); | ||||
| if (!@file_exists($fpath)) { | if (!@file_exists($fpath)) { | ||||
| $cpath = $change->getCurrentPath(); | $cpath = $change->getCurrentPath(); | ||||
| if ($type == ArcanistDiffChangeType::TYPE_COPY_HERE) { | if ($type == ArcanistDiffChangeType::TYPE_COPY_HERE) { | ||||
| $verbs = 'copies'; | $verbs = pht('copies'); | ||||
| } else { | } else { | ||||
| $verbs = 'moves'; | $verbs = pht('moves'); | ||||
| } | } | ||||
| $ok = phutil_console_confirm( | $ok = phutil_console_confirm( | ||||
| "Patch {$verbs} '{$path}' to '{$cpath}', but source path ". | pht( | ||||
| "does not exist in the working copy. Continue anyway?"); | "Patch %s '%s' to '%s', but source path does not exist ". | ||||
| "in the working copy. Continue anyway?", | |||||
| $verbs, | |||||
| $path, | |||||
| $cpath)); | |||||
| if (!$ok) { | if (!$ok) { | ||||
| throw new ArcanistUserAbortException(); | throw new ArcanistUserAbortException(); | ||||
| } | } | ||||
| } else { | } else { | ||||
| $copies[] = array( | $copies[] = array( | ||||
| $change->getOldPath(), | $change->getOldPath(), | ||||
| $change->getCurrentPath(), | $change->getCurrentPath(), | ||||
| ); | ); | ||||
| ▲ Show 20 Lines • Show All 125 Lines • ▼ Show 20 Lines | if ($repository_api instanceof ArcanistSubversionAPI) { | ||||
| $value, | $value, | ||||
| ArcanistSubversionAPI::escapeFileNameForSVN($path))); | ArcanistSubversionAPI::escapeFileNameForSVN($path))); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| if ($patch_err == 0) { | if ($patch_err == 0) { | ||||
| echo phutil_console_format( | echo phutil_console_format( | ||||
| "<bg:green>** OKAY **</bg> Successfully applied patch ". | "<bg:green>** %s **</bg> %s\n", | ||||
| "to the working copy.\n"); | pht('OKAY'), | ||||
| pht('Successfully applied patch to the working copy.')); | |||||
| } else { | } else { | ||||
| echo phutil_console_format( | echo phutil_console_format( | ||||
| "\n\n<bg:yellow>** WARNING **</bg> Some hunks could not be applied ". | "\n\n<bg:yellow>** %s **</bg> %s\n", | ||||
| "cleanly by the unix 'patch' utility. Your working copy may be ". | pht('WARNING'), | ||||
| "different from the revision's base, or you may be in the wrong ". | pht( | ||||
| "subdirectory. You can export the raw patch file using ". | "Some hunks could not be applied cleanly by the unix '%s' ". | ||||
| "'arc export --unified', and then try to apply it by fiddling with ". | "utility. Your working copy may be different from the revision's ". | ||||
| "options to 'patch' (particularly, -p), or manually. The output ". | "base, or you may be in the wrong subdirectory. You can export ". | ||||
| "above, from 'patch', may be helpful in figuring out what went ". | "the raw patch file using '%s', and then try to apply it by ". | ||||
| "wrong.\n"); | "fiddling with options to '%s' (particularly, %s), or manually. ". | ||||
| "The output above, from '%s', may be helpful in ". | |||||
| "figuring out what went wrong.", | |||||
| 'patch', | |||||
| 'arc export --unified', | |||||
| 'patch', | |||||
| '-p', | |||||
| 'patch')); | |||||
| } | } | ||||
| return $patch_err; | return $patch_err; | ||||
| } else if ($repository_api instanceof ArcanistGitAPI) { | } else if ($repository_api instanceof ArcanistGitAPI) { | ||||
| $patchfile = new TempFile(); | $patchfile = new TempFile(); | ||||
| Filesystem::writeFile($patchfile, $bundle->toGitPatch()); | Filesystem::writeFile($patchfile, $bundle->toGitPatch()); | ||||
| $passthru = new PhutilExecPassthru( | $passthru = new PhutilExecPassthru( | ||||
| 'git apply --index --reject -- %s', | 'git apply --index --reject -- %s', | ||||
| $patchfile); | $patchfile); | ||||
| $passthru->setCWD($repository_api->getPath()); | $passthru->setCWD($repository_api->getPath()); | ||||
| $err = $passthru->execute(); | $err = $passthru->execute(); | ||||
| if ($err) { | if ($err) { | ||||
| echo phutil_console_format( | echo phutil_console_format( | ||||
| "\n<bg:red>** Patch Failed! **</bg>\n"); | "\n<bg:red>** %s **</bg>\n", | ||||
| pht('Patch Failed!')); | |||||
| // NOTE: Git patches may fail if they change the case of a filename | // NOTE: Git patches may fail if they change the case of a filename | ||||
| // (for instance, from 'example.c' to 'Example.c'). As of now, Git | // (for instance, from 'example.c' to 'Example.c'). As of now, Git | ||||
| // can not apply these patches on case-insensitive filesystems and | // can not apply these patches on case-insensitive filesystems and | ||||
| // there is no way to build a patch which works. | // there is no way to build a patch which works. | ||||
| throw new ArcanistUsageException('Unable to apply patch!'); | throw new ArcanistUsageException(pht('Unable to apply patch!')); | ||||
| } | } | ||||
| // in case there were any submodule changes involved | // in case there were any submodule changes involved | ||||
| $repository_api->execpassthru( | $repository_api->execpassthru('submodule update --init --recursive'); | ||||
| 'submodule update --init --recursive'); | |||||
| if ($this->shouldCommit()) { | if ($this->shouldCommit()) { | ||||
| if ($bundle->getFullAuthor()) { | if ($bundle->getFullAuthor()) { | ||||
| $author_cmd = csprintf('--author=%s', $bundle->getFullAuthor()); | $author_cmd = csprintf('--author=%s', $bundle->getFullAuthor()); | ||||
| } else { | } else { | ||||
| $author_cmd = ''; | $author_cmd = ''; | ||||
| } | } | ||||
| $commit_message = $this->getCommitMessage($bundle); | $commit_message = $this->getCommitMessage($bundle); | ||||
| $future = $repository_api->execFutureLocal( | $future = $repository_api->execFutureLocal( | ||||
| 'commit -a %C -F - --no-verify', | 'commit -a %C -F - --no-verify', | ||||
| $author_cmd); | $author_cmd); | ||||
| $future->write($commit_message); | $future->write($commit_message); | ||||
| $future->resolvex(); | $future->resolvex(); | ||||
| $verb = 'committed'; | $verb = pht('committed'); | ||||
| } else { | } else { | ||||
| $verb = 'applied'; | $verb = pht('applied'); | ||||
| } | } | ||||
| if ($this->canBranch() && | if ($this->canBranch() && | ||||
| !$this->shouldBranch() && | !$this->shouldBranch() && | ||||
| $this->shouldCommit() && $has_base_revision) { | $this->shouldCommit() && $has_base_revision) { | ||||
| $repository_api->execxLocal('checkout %s', $original_branch); | $repository_api->execxLocal('checkout %s', $original_branch); | ||||
| $ex = null; | $ex = null; | ||||
| try { | try { | ||||
| $repository_api->execxLocal('cherry-pick %s', $new_branch); | $repository_api->execxLocal('cherry-pick %s', $new_branch); | ||||
| } catch (Exception $ex) { | } catch (Exception $ex) { | ||||
| // do nothing | // do nothing | ||||
| } | } | ||||
| $repository_api->execxLocal('branch -D %s', $new_branch); | $repository_api->execxLocal('branch -D %s', $new_branch); | ||||
| if ($ex) { | if ($ex) { | ||||
| echo phutil_console_format( | echo phutil_console_format( | ||||
| "\n<bg:red>** Cherry Pick Failed!**</bg>\n"); | "\n<bg:red>** %s**</bg>\n", | ||||
| pht('Cherry Pick Failed!')); | |||||
| throw $ex; | throw $ex; | ||||
| } | } | ||||
| } | } | ||||
| echo phutil_console_format( | echo phutil_console_format( | ||||
| "<bg:green>** OKAY **</bg> Successfully {$verb} patch.\n"); | "<bg:green>** %s **</bg> %s\n", | ||||
| pht('OKAY'), | |||||
| pht('Successfully %s patch.', $verb)); | |||||
| } else if ($repository_api instanceof ArcanistMercurialAPI) { | } else if ($repository_api instanceof ArcanistMercurialAPI) { | ||||
| $future = $repository_api->execFutureLocal('import --no-commit -'); | |||||
| $future = $repository_api->execFutureLocal( | |||||
| 'import --no-commit -'); | |||||
| $future->write($bundle->toGitPatch()); | $future->write($bundle->toGitPatch()); | ||||
| try { | try { | ||||
| $future->resolvex(); | $future->resolvex(); | ||||
| } catch (CommandException $ex) { | } catch (CommandException $ex) { | ||||
| echo phutil_console_format( | echo phutil_console_format( | ||||
| "\n<bg:red>** Patch Failed! **</bg>\n"); | "\n<bg:red>** %s **</bg>\n", | ||||
| pht('Patch Failed!')); | |||||
| $stderr = $ex->getStdErr(); | $stderr = $ex->getStdErr(); | ||||
| if (preg_match('/case-folding collision/', $stderr)) { | if (preg_match('/case-folding collision/', $stderr)) { | ||||
| echo phutil_console_wrap( | echo phutil_console_wrap( | ||||
| phutil_console_format( | phutil_console_format( | ||||
| "\n<bg:yellow>** WARNING **</bg> This patch may have failed ". | "\n<bg:yellow>** %s **</bg> %s\n", | ||||
| "because it attempts to change the case of a filename (for ". | pht('WARNING'), | ||||
| "instance, from 'example.c' to 'Example.c'). Mercurial cannot ". | pht( | ||||
| "apply patches like this on case-insensitive filesystems. You ". | "This patch may have failed because it attempts to change ". | ||||
| "must apply this patch manually.\n")); | "the case of a filename (for instance, from '%s' to '%s'). ". | ||||
| "Mercurial cannot apply patches like this on case-insensitive ". | |||||
| "filesystems. You must apply this patch manually.", | |||||
| 'example.c', | |||||
| 'Example.c'))); | |||||
| } | } | ||||
| throw $ex; | throw $ex; | ||||
| } | } | ||||
| if ($this->shouldCommit()) { | if ($this->shouldCommit()) { | ||||
| $author = coalesce($bundle->getFullAuthor(), $bundle->getAuthorName()); | $author = coalesce($bundle->getFullAuthor(), $bundle->getAuthorName()); | ||||
| if ($author !== null) { | if ($author !== null) { | ||||
| $author_cmd = csprintf('-u %s', $author); | $author_cmd = csprintf('-u %s', $author); | ||||
| Show All 20 Lines | if ($repository_api instanceof ArcanistSubversionAPI) { | ||||
| 'rebase --dest %s --rev %s', | 'rebase --dest %s --rev %s', | ||||
| hgsprintf('%s', $original_branch), | hgsprintf('%s', $original_branch), | ||||
| hgsprintf('%s', $new_branch)); | hgsprintf('%s', $new_branch)); | ||||
| } | } | ||||
| $repository_api->execxLocal('bookmark --delete %s', $new_branch); | $repository_api->execxLocal('bookmark --delete %s', $new_branch); | ||||
| if ($err) { | if ($err) { | ||||
| $repository_api->execManualLocal('rebase --abort'); | $repository_api->execManualLocal('rebase --abort'); | ||||
| throw new ArcanistUsageException(phutil_console_format( | throw new ArcanistUsageException( | ||||
| "\n<bg:red>** Rebase onto $original_branch failed!**</bg>\n")); | phutil_console_format( | ||||
| "\n<bg:red>** %s**</bg>\n", | |||||
| pht('Rebase onto %s failed!', $original_branch))); | |||||
| } | } | ||||
| } | } | ||||
| $verb = 'committed'; | $verb = pht('committed'); | ||||
| } else { | } else { | ||||
| $verb = 'applied'; | $verb = pht('applied'); | ||||
| } | } | ||||
| echo phutil_console_format( | echo phutil_console_format( | ||||
| "<bg:green>** OKAY **</bg> Successfully {$verb} patch.\n"); | "<bg:green>** %s **</bg> %s\n", | ||||
| pht('OKAY'), | |||||
| pht('Successfully %s patch.', $verb)); | |||||
| } else { | } else { | ||||
| throw new Exception('Unknown version control system.'); | throw new Exception(pht('Unknown version control system.')); | ||||
| } | } | ||||
| return 0; | return 0; | ||||
| } | } | ||||
| private function getCommitMessage(ArcanistBundle $bundle) { | private function getCommitMessage(ArcanistBundle $bundle) { | ||||
| $revision_id = $bundle->getRevisionID(); | $revision_id = $bundle->getRevisionID(); | ||||
| $commit_message = null; | $commit_message = null; | ||||
| $prompt_message = null; | $prompt_message = null; | ||||
| // if we have a revision id the commit message is in differential | // if we have a revision id the commit message is in differential | ||||
| // TODO: See T848 for the authenticated stuff. | // TODO: See T848 for the authenticated stuff. | ||||
| if ($revision_id && $this->isConduitAuthenticated()) { | if ($revision_id && $this->isConduitAuthenticated()) { | ||||
| $conduit = $this->getConduit(); | $conduit = $this->getConduit(); | ||||
| $commit_message = $conduit->callMethodSynchronous( | $commit_message = $conduit->callMethodSynchronous( | ||||
| 'differential.getcommitmessage', | 'differential.getcommitmessage', | ||||
| array( | array( | ||||
| 'revision_id' => $revision_id, | 'revision_id' => $revision_id, | ||||
| )); | )); | ||||
| $prompt_message = " Note arcanist failed to load the commit message ". | $prompt_message = pht( | ||||
| "from differential for revision D{$revision_id}."; | ' Note arcanist failed to load the commit message '. | ||||
| 'from differential for revision %s.', | |||||
| "D{$revision_id}"); | |||||
| } | } | ||||
| // no revision id or failed to fetch commit message so get it from the | // no revision id or failed to fetch commit message so get it from the | ||||
| // user on the command line | // user on the command line | ||||
| if (!$commit_message) { | if (!$commit_message) { | ||||
| $template = | $template = sprintf( | ||||
| "\n\n". | "\n\n# %s%s\n", | ||||
| "# Enter a commit message for this patch. If you just want to apply ". | pht( | ||||
| "the patch to the working copy without committing, re-run arc patch ". | 'Enter a commit message for this patch. If you just want to apply '. | ||||
| "with the --nocommit flag.". | 'the patch to the working copy without committing, re-run arc patch '. | ||||
| $prompt_message. | 'with the %s flag.', | ||||
| "\n"; | '--nocommit'), | ||||
| $prompt_message); | |||||
| $commit_message = $this->newInteractiveEditor($template) | $commit_message = $this->newInteractiveEditor($template) | ||||
| ->setName('arcanist-patch-commit-message') | ->setName('arcanist-patch-commit-message') | ||||
| ->editInteractively(); | ->editInteractively(); | ||||
| $commit_message = ArcanistCommentRemover::removeComments($commit_message); | $commit_message = ArcanistCommentRemover::removeComments($commit_message); | ||||
| if (!strlen(trim($commit_message))) { | if (!strlen(trim($commit_message))) { | ||||
| throw new ArcanistUserAbortException(); | throw new ArcanistUserAbortException(); | ||||
| Show All 12 Lines | private function applyDependencies(ArcanistBundle $bundle) { | ||||
| // check for (and automagically apply on the user's be-hest) any revisions | // check for (and automagically apply on the user's be-hest) any revisions | ||||
| // this patch depends on | // this patch depends on | ||||
| $graph = $this->buildDependencyGraph($bundle); | $graph = $this->buildDependencyGraph($bundle); | ||||
| if ($graph) { | if ($graph) { | ||||
| $start_phid = $graph->getStartPHID(); | $start_phid = $graph->getStartPHID(); | ||||
| $cycle_phids = $graph->detectCycles($start_phid); | $cycle_phids = $graph->detectCycles($start_phid); | ||||
| if ($cycle_phids) { | if ($cycle_phids) { | ||||
| $phids = array_keys($graph->getNodes()); | $phids = array_keys($graph->getNodes()); | ||||
| $issue = 'The dependencies for this patch have a cycle. Applying them '. | $issue = pht( | ||||
| 'is not guaranteed to work. Continue anyway?'; | 'The dependencies for this patch have a cycle. Applying them '. | ||||
| 'is not guaranteed to work. Continue anyway?'); | |||||
| $okay = phutil_console_confirm($issue, true); | $okay = phutil_console_confirm($issue, true); | ||||
| } else { | } else { | ||||
| $phids = $graph->getTopographicallySortedNodes(); | $phids = $graph->getTopographicallySortedNodes(); | ||||
| $phids = array_reverse($phids); | $phids = array_reverse($phids); | ||||
| $okay = true; | $okay = true; | ||||
| } | } | ||||
| if (!$okay) { | if (!$okay) { | ||||
| ▲ Show 20 Lines • Show All 49 Lines • ▼ Show 20 Lines | private function sanityCheck(ArcanistBundle $bundle) { | ||||
| // project id | // project id | ||||
| $bundle_project_id = $bundle->getProjectID(); | $bundle_project_id = $bundle->getProjectID(); | ||||
| $working_copy_project_id = $this->getWorkingCopy()->getProjectID(); | $working_copy_project_id = $this->getWorkingCopy()->getProjectID(); | ||||
| if (empty($bundle_project_id)) { | if (empty($bundle_project_id)) { | ||||
| // this means $source is SOURCE_PATCH || SOURCE_BUNDLE w/ $version = 0 | // this means $source is SOURCE_PATCH || SOURCE_BUNDLE w/ $version = 0 | ||||
| // they don't come with a project id so just do nothing | // they don't come with a project id so just do nothing | ||||
| } else if ($bundle_project_id != $working_copy_project_id) { | } else if ($bundle_project_id != $working_copy_project_id) { | ||||
| if ($working_copy_project_id) { | if ($working_copy_project_id) { | ||||
| $issue = | $issue = pht( | ||||
| "This patch is for the '{$bundle_project_id}' project, but the ". | "This patch is for the '%s' project, but the working copy ". | ||||
| "working copy belongs to the '{$working_copy_project_id}' project."; | "belongs to the '%s' project.", | ||||
| } else { | $bundle_project_id, | ||||
| $issue = | $working_copy_project_id); | ||||
| "This patch is for the '{$bundle_project_id}' project, but the ". | } else { | ||||
| "working copy does not have an '.arcconfig' file to identify which ". | $issue = pht( | ||||
| "project it belongs to."; | "This patch is for the '%s' project, but the working copy does ". | ||||
| "not have an '%s' file to identify which project it belongs to.", | |||||
| $bundle_project_id, | |||||
| '.arcconfig'); | |||||
| } | } | ||||
| $ok = phutil_console_confirm( | $ok = phutil_console_confirm( | ||||
| "{$issue} Still try to apply the patch?", | pht('%s Still try to apply the patch?', $issue), | ||||
| $default_no = false); | $default_no = false); | ||||
| if (!$ok) { | if (!$ok) { | ||||
| throw new ArcanistUserAbortException(); | throw new ArcanistUserAbortException(); | ||||
| } | } | ||||
| } | } | ||||
| // Check to see if the bundle's base revision matches the working copy | // Check to see if the bundle's base revision matches the working copy | ||||
| // base revision | // base revision | ||||
| ▲ Show 20 Lines • Show All 43 Lines • ▼ Show 20 Lines | if ($repository_api->supportsLocalCommits()) { | ||||
| $bundle_base_rev_str = nonempty( | $bundle_base_rev_str = nonempty( | ||||
| $bundle_base_rev_str, | $bundle_base_rev_str, | ||||
| $bundle_base_rev); | $bundle_base_rev); | ||||
| $source_base_rev_str = nonempty( | $source_base_rev_str = nonempty( | ||||
| $source_base_rev_str, | $source_base_rev_str, | ||||
| $source_base_rev); | $source_base_rev); | ||||
| $ok = phutil_console_confirm( | $ok = phutil_console_confirm( | ||||
| "This diff is against commit {$bundle_base_rev_str}, but the ". | pht( | ||||
| "commit is nowhere in the working copy. Try to apply it against ". | 'This diff is against commit %s, but the commit is nowhere '. | ||||
| "the current working copy state? ({$source_base_rev_str})", | 'in the working copy. Try to apply it against the current '. | ||||
| 'working copy state? (%s)', | |||||
| $bundle_base_rev_str, | |||||
| $source_base_rev_str), | |||||
| $default_no = false); | $default_no = false); | ||||
| if (!$ok) { | if (!$ok) { | ||||
| throw new ArcanistUserAbortException(); | throw new ArcanistUserAbortException(); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 83 Lines • Show Last 20 Lines | |||||