in src/workflow/ArcanistPatchWorkflow.php [405:977]
public function run() {
// UBER CODE
$this->uberRefProvider = new UberRefProvider(
$this->getConfigurationManager()->getConfigFromAnySource('uber.arcanist.use_non_tag_refs', false)
);
// UBER CODE END
$source = $this->getSource();
$param = $this->getSourceParam();
try {
switch ($source) {
case self::SOURCE_PATCH:
if ($param == '-') {
$patch = @file_get_contents('php://stdin');
if (!strlen($patch)) {
throw new ArcanistUsageException(
pht('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);
if ($this->shouldMergeUsingStagingGitTag()) {
$this->mergeBranchFromStagingArea($param, $bundle);
return 0;
} elseif ($this->shouldUseStagingGitTags()) {
$repository_api = $this->getRepositoryAPI();
if (!$repository_api->hasLocalCommit($bundle->getBaseRevision())) {
$this->pullBaseTagFromStagingArea($param);
}
}
break;
}
} catch (ConduitClientException $ex) {
if ($ex->getErrorCode() == 'ERR-INVALID-SESSION') {
// Phabricator is not configured to allow anonymous access to
// Differential.
$this->authenticateConduit();
return $this->run();
} else {
throw $ex;
}
// UBER CODE
} catch (HTTPFutureHTTPResponseStatus $ex) {
if ($ex->getStatusCode() == 401) {
$this->authenticateConduit();
return $this->run();
} else {
throw $ex;
}
}
// UBER CODE END
$try_encoding = nonempty($this->getArgument('encoding'), null);
if (!$try_encoding) {
if ($this->requiresConduit()) {
try {
$try_encoding = $this->getRepositoryEncoding();
} catch (ConduitClientException $e) {
$try_encoding = null;
// UBER CODE
} catch (HTTPFutureHTTPResponseStatus $ex) {
if ($ex->getStatusCode() != 401) {
throw $ex;
}
$try_encoding = null;
// UBER CODE END
}
}
}
if ($try_encoding) {
$bundle->setEncoding($try_encoding);
}
$sanity_check = !$this->getArgument('force', false);
// we should update the working copy before we do ANYTHING else to
// the working copy
if ($this->shouldUpdateWorkingCopy()) {
$this->updateWorkingCopy();
}
if ($sanity_check) {
$this->requireCleanWorkingCopy();
}
$repository_api = $this->getRepositoryAPI();
$has_base_revision = $repository_api->hasLocalCommit(
$bundle->getBaseRevision());
if (!$has_base_revision) {
if ($repository_api instanceof ArcanistGitAPI) {
echo phutil_console_format(
"<bg:blue>** %s **</bg> %s\n",
pht('INFO'),
pht('Base commit is not in local repository; trying to fetch.'));
// UBER CODE
$this->authenticateConduit();
if ($source == self::SOURCE_DIFF) {
$this->pullBaseTagFromStagingArea($param);
$has_base_revision = $repository_api->hasLocalCommit(
$bundle->getBaseRevision());
}
if (!$has_base_revision) {
$this->pullFromAllRemotesUntilFound($bundle->getBaseRevision());
$has_base_revision = $repository_api->hasLocalCommit(
$bundle->getBaseRevision());
if (!$has_base_revision) {
$repository_api->execManualLocal('fetch --quiet --all');
$has_base_revision = $repository_api->hasLocalCommit(
$bundle->getBaseRevision());
}
}
// UBER CODE END
}
}
if ($this->canBranch() &&
($this->shouldBranch() ||
($this->shouldCommit() && $has_base_revision))) {
if ($repository_api instanceof ArcanistGitAPI) {
$original_branch = $repository_api->getBranchName();
} else if ($repository_api instanceof ArcanistMercurialAPI) {
$original_branch = $repository_api->getActiveBookmark();
}
// If we weren't on a branch, then record the ref we'll return to
// instead.
if ($original_branch === null) {
if ($repository_api instanceof ArcanistGitAPI) {
$original_branch = $repository_api->getCanonicalRevisionName('HEAD');
} else if ($repository_api instanceof ArcanistMercurialAPI) {
$original_branch = $repository_api->getCanonicalRevisionName('.');
}
}
$new_branch = $this->createBranch($bundle, $has_base_revision);
// UBER CODE
} elseif ($has_base_revision) {
// try reseting to a base revision to properly apply change
if ($repository_api instanceof ArcanistGitAPI) {
$base_revision = $bundle->getBaseRevision();
$repository_api->execManualLocal("reset --hard %s", $base_revision);
}
}
// UBER CODE END
if (!$has_base_revision && $this->shouldApplyDependencies()) {
$this->authenticateConduit(); // UBER CODE
$this->applyDependencies($bundle);
}
if ($sanity_check) {
$this->sanityCheck($bundle);
}
if ($repository_api instanceof ArcanistSubversionAPI) {
$patch_err = 0;
$copies = array();
$deletes = array();
$patches = array();
$propset = array();
$adds = array();
$symlinks = array();
$changes = $bundle->getChanges();
foreach ($changes as $change) {
$type = $change->getType();
$should_patch = true;
$filetype = $change->getFileType();
switch ($filetype) {
case ArcanistDiffChangeType::FILE_SYMLINK:
$should_patch = false;
$symlinks[] = $change;
break;
}
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)) {
$ok = phutil_console_confirm(
pht(
"Patch deletes file '%s', but the file does not exist in ".
"the working copy. Continue anyway?",
$path));
if (!$ok) {
throw new ArcanistUserAbortException();
}
} 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 = pht('copies');
} else {
$verbs = pht('moves');
}
$ok = phutil_console_confirm(
pht(
"Patch %s '%s' to '%s', but source path does not exist ".
"in the working copy. Continue anyway?",
$verbs,
$path,
$cpath));
if (!$ok) {
throw new ArcanistUserAbortException();
}
} else {
$copies[] = array(
$change->getOldPath(),
$change->getCurrentPath(),
);
}
break;
case ArcanistDiffChangeType::TYPE_ADD:
$adds[] = $change->getCurrentPath();
break;
}
if ($should_patch) {
$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);
}
}
}
}
// Before we start doing anything, create all the directories we're going
// to add files to if they don't already exist.
foreach ($copies as $copy) {
list($src, $dst) = $copy;
$this->createParentDirectoryOf($dst);
}
foreach ($patches as $path => $patch) {
$this->createParentDirectoryOf($path);
}
foreach ($adds as $add) {
$this->createParentDirectoryOf($add);
}
// TODO: The SVN patch workflow likely does not work on windows because
// of the (cd ...) stuff.
foreach ($copies as $copy) {
list($src, $dst) = $copy;
passthru(
csprintf(
'(cd %s; svn cp %s %s)',
$repository_api->getPath(),
ArcanistSubversionAPI::escapeFileNameForSVN($src),
ArcanistSubversionAPI::escapeFileNameForSVN($dst)));
}
foreach ($deletes as $delete) {
passthru(
csprintf(
'(cd %s; svn rm %s)',
$repository_api->getPath(),
ArcanistSubversionAPI::escapeFileNameForSVN($delete)));
}
foreach ($symlinks as $symlink) {
$link_target = $symlink->getSymlinkTarget();
$link_path = $symlink->getCurrentPath();
switch ($symlink->getType()) {
case ArcanistDiffChangeType::TYPE_ADD:
case ArcanistDiffChangeType::TYPE_CHANGE:
case ArcanistDiffChangeType::TYPE_MOVE_HERE:
case ArcanistDiffChangeType::TYPE_COPY_HERE:
execx(
'(cd %s && ln -sf %s %s)',
$repository_api->getPath(),
$link_target,
$link_path);
break;
}
}
foreach ($patches as $path => $patch) {
$err = null;
if ($patch) {
$tmp = new TempFile();
Filesystem::writeFile($tmp, $patch);
passthru(
csprintf(
'(cd %s; patch -p0 < %s)',
$repository_api->getPath(),
$tmp),
$err);
} else {
passthru(
csprintf(
'(cd %s; touch %s)',
$repository_api->getPath(),
$path),
$err);
}
if ($err) {
$patch_err = max($patch_err, $err);
}
}
foreach ($adds as $add) {
passthru(
csprintf(
'(cd %s; svn add %s)',
$repository_api->getPath(),
ArcanistSubversionAPI::escapeFileNameForSVN($add)));
}
foreach ($propset as $path => $changes) {
foreach ($changes as $prop => $value) {
if ($prop == 'unix:filemode') {
// Setting this property also changes the file mode.
$prop = 'svn:executable';
$value = (octdec($value) & 0111 ? 'on' : null);
}
if ($value === null) {
passthru(
csprintf(
'(cd %s; svn propdel %s %s)',
$repository_api->getPath(),
$prop,
ArcanistSubversionAPI::escapeFileNameForSVN($path)));
} else {
passthru(
csprintf(
'(cd %s; svn propset %s %s %s)',
$repository_api->getPath(),
$prop,
$value,
ArcanistSubversionAPI::escapeFileNameForSVN($path)));
}
}
}
if ($patch_err == 0) {
echo phutil_console_format(
"<bg:green>** %s **</bg> %s\n",
pht('OKAY'),
pht('Successfully applied patch to the working copy.'));
} else {
echo phutil_console_format(
"\n\n<bg:yellow>** %s **</bg> %s\n",
pht('WARNING'),
pht(
"Some hunks could not be applied cleanly by the unix '%s' ".
"utility. Your working copy may be different from the revision's ".
"base, or you may be in the wrong subdirectory. You can export ".
"the raw patch file using '%s', and then try to apply it by ".
"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;
} else if ($repository_api instanceof ArcanistGitAPI) {
$patchfile = new TempFile();
Filesystem::writeFile($patchfile, $bundle->toGitPatch());
$passthru = new PhutilExecPassthru(
'git apply --whitespace nowarn --index --reject -- %s',
$patchfile);
$passthru->setCWD($repository_api->getPath());
$err = $passthru->execute();
if ($err) {
echo phutil_console_format(
"\n<bg:red>** %s **</bg>\n",
pht('Patch Failed!'));
// 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
// can not apply these patches on case-insensitive filesystems and
// there is no way to build a patch which works.
throw new ArcanistUsageException(pht('Unable to apply patch!'));
}
// See PHI1083 and PHI648. If the patch applied changes to submodules,
// it only updates the submodule pointer, not the actual submodule. We're
// left with the pointer update staged in the index, and the unmodified
// submodule on disk.
// If we then "git commit --all" or "git add --all", the unmodified
// submodule on disk is added to the index as a change, which effectively
// undoes the patch we just applied and reverts the submodule back to
// the previous state.
// To avoid this, do a submodule update before we continue.
// We could also possibly skip the "--all" flag so we don't have to do
// this submodule update, but we want to leave the working copy in a
// clean state anyway, so we're going to have to do an update at some
// point. This usually doesn't cost us anything.
$repository_api->execPassthru('submodule update --init --recursive');
if ($this->shouldCommit()) {
$flags = array();
if ($bundle->getFullAuthor()) {
$flags[] = csprintf('--author=%s', $bundle->getFullAuthor());
}
$commit_message = $this->getCommitMessage($bundle);
$future = $repository_api->execFutureLocal(
'commit -a %Ls -F - --no-verify',
$flags);
$future->write($commit_message);
$future->resolvex();
$this->writeOkay(
pht('COMMITTED'),
pht('Successfully committed patch.'));
} else {
$this->writeOkay(
pht('APPLIED'),
pht('Successfully applied patch.'));
}
if ($this->canBranch() &&
!$this->shouldBranch() &&
$this->shouldCommit() && $has_base_revision) {
// See PHI1083 and PHI648. Synchronize submodule state after mutating
// the working copy.
$repository_api->execxLocal('checkout %s --', $original_branch);
$repository_api->execPassthru('submodule update --init --recursive');
$ex = null;
try {
if ($this->shouldUseMerge()) {
$repository_api->execxLocal('cherry-pick --keep-redundant-commits %s', $new_branch);
} else {
/* $repository_api->execxLocal('cherry-pick %s', $new_branch); */
// TODO Check if this works.
$repository_api->execxLocal('cherry-pick -- %s', $new_branch);
}
$repository_api->execPassthru('submodule update --init --recursive');
} catch (Exception $ex) {
// do nothing
}
$repository_api->execxLocal('branch -D -- %s', $new_branch);
if ($ex) {
echo phutil_console_format(
"\n<bg:red>** %s**</bg>\n",
pht('Cherry Pick Failed!'));
throw $ex;
}
}
} else if ($repository_api instanceof ArcanistMercurialAPI) {
$future = $repository_api->execFutureLocal('import --no-commit -');
$future->write($bundle->toGitPatch());
try {
$future->resolvex();
} catch (CommandException $ex) {
echo phutil_console_format(
"\n<bg:red>** %s **</bg>\n",
pht('Patch Failed!'));
$stderr = $ex->getStderr();
if (preg_match('/case-folding collision/', $stderr)) {
echo phutil_console_wrap(
phutil_console_format(
"\n<bg:yellow>** %s **</bg> %s\n",
pht('WARNING'),
pht(
"This patch may have failed because it attempts to change ".
"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;
}
if ($this->shouldCommit()) {
$author = coalesce($bundle->getFullAuthor(), $bundle->getAuthorName());
if ($author !== null) {
$author_cmd = csprintf('-u %s', $author);
} else {
$author_cmd = '';
}
$commit_message = $this->getCommitMessage($bundle);
$future = $repository_api->execFutureLocal(
'commit %C -l -',
$author_cmd);
$future->write($commit_message);
$future->resolvex();
if (!$this->shouldBranch() && $has_base_revision) {
$original_rev = $repository_api->getCanonicalRevisionName(
$original_branch);
$current_parent = $repository_api->getCanonicalRevisionName(
hgsprintf('%s^', $new_branch));
$err = 0;
if ($original_rev != $current_parent) {
list($err) = $repository_api->execManualLocal(
'rebase --dest %s --rev %s',
hgsprintf('%s', $original_branch),
hgsprintf('%s', $new_branch));
}
$repository_api->execxLocal('bookmark --delete %s', $new_branch);
if ($err) {
$repository_api->execManualLocal('rebase --abort');
throw new ArcanistUsageException(
phutil_console_format(
"\n<bg:red>** %s**</bg>\n",
pht('Rebase onto %s failed!', $original_branch)));
}
}
$verb = pht('committed');
} else {
$verb = pht('applied');
}
echo phutil_console_format(
"<bg:green>** %s **</bg> %s\n",
pht('OKAY'),
pht('Successfully %s patch.', $verb));
} else {
throw new Exception(pht('Unknown version control system.'));
}
return 0;
}