protected function getCommitFileList(array $revision)
 {
     $repository_api = $this->getRepositoryAPI();
     $revision_id = $revision['id'];
     $commit_paths = $this->getConduit()->callMethodSynchronous('differential.getcommitpaths', array('revision_id' => $revision_id));
     $dir_paths = array();
     foreach ($commit_paths as $path) {
         $path = dirname($path);
         while ($path != '.') {
             $dir_paths[$path] = true;
             $path = dirname($path);
         }
     }
     $commit_paths = array_fill_keys($commit_paths, true);
     $status = $repository_api->getSVNStatus();
     $modified_but_not_included = array();
     foreach ($status as $path => $mask) {
         if (!empty($dir_paths[$path])) {
             $commit_paths[$path] = true;
         }
         if (!empty($commit_paths[$path])) {
             continue;
         }
         foreach ($commit_paths as $will_commit => $ignored) {
             if (Filesystem::isDescendant($path, $will_commit)) {
                 throw new ArcanistUsageException(pht("This commit includes the directory '%s', but it contains a " . "modified path ('%s') which is NOT included in the commit. " . "Subversion can not handle this operation and will commit the " . "path anyway. You need to sort out the working copy changes to " . "'%s' before you may proceed with the commit.", $will_commit, $path, $path));
             }
         }
         $modified_but_not_included[] = $path;
     }
     if ($modified_but_not_included) {
         $prefix = pht('%s locally modified path(s) are not included in this revision:', phutil_count($modified_but_not_included));
         $prompt = pht('These %s path(s) will NOT be committed. Commit this revision anyway?', phutil_count($modified_but_not_included));
         $this->promptFileWarning($prefix, $prompt, $modified_but_not_included);
     }
     $do_not_exist = array();
     foreach ($commit_paths as $path => $ignored) {
         $disk_path = $repository_api->getPath($path);
         if (file_exists($disk_path)) {
             continue;
         }
         if (is_link($disk_path)) {
             continue;
         }
         if (idx($status, $path) & ArcanistRepositoryAPI::FLAG_DELETED) {
             continue;
         }
         $do_not_exist[] = $path;
         unset($commit_paths[$path]);
     }
     if ($do_not_exist) {
         $prefix = pht('Revision includes changes to %s path(s) that do not exist:', phutil_count($do_not_exist));
         $prompt = pht('Commit this revision anyway?');
         $this->promptFileWarning($prefix, $prompt, $do_not_exist);
     }
     $files = array_keys($commit_paths);
     $files = ArcanistSubversionAPI::escapeFileNamesForSVN($files);
     if (empty($files)) {
         throw new ArcanistUsageException(pht('There is nothing left to commit. ' . 'None of the modified paths exist.'));
     }
     return $files;
 }
 public function run()
 {
     $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);
                 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;
         }
     }
     $try_encoding = nonempty($this->getArgument('encoding'), null);
     if (!$try_encoding) {
         if ($this->requiresConduit()) {
             try {
                 $try_encoding = $this->getRepositoryEncoding();
             } catch (ConduitClientException $e) {
                 $try_encoding = null;
             }
         }
     }
     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 ($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);
     }
     if (!$has_base_revision && $this->shouldApplyDependencies()) {
         $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 --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!'));
             }
             // in case there were any submodule changes involved
             $repository_api->execpassthru('submodule update --init --recursive');
             if ($this->shouldCommit()) {
                 if ($bundle->getFullAuthor()) {
                     $author_cmd = csprintf('--author=%s', $bundle->getFullAuthor());
                 } else {
                     $author_cmd = '';
                 }
                 $commit_message = $this->getCommitMessage($bundle);
                 $future = $repository_api->execFutureLocal('commit -a %C -F - --no-verify', $author_cmd);
                 $future->write($commit_message);
                 $future->resolvex();
                 $verb = pht('committed');
             } else {
                 $verb = pht('applied');
             }
             if ($this->canBranch() && !$this->shouldBranch() && $this->shouldCommit() && $has_base_revision) {
                 $repository_api->execxLocal('checkout %s', $original_branch);
                 $ex = null;
                 try {
                     $repository_api->execxLocal('cherry-pick %s', $new_branch);
                 } 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;
                 }
             }
             echo phutil_console_format("<bg:green>** %s **</bg> %s\n", pht('OKAY'), pht('Successfully %s patch.', $verb));
         } 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;
 }
 public function testSVNFileEscapes()
 {
     $input = array('.', 'x', '*****@*****.**');
     $expect = array('.', 'x', 'x@2x.png@');
     $this->assertEqual($expect, ArcanistSubversionAPI::escapeFileNamesForSVN($input));
 }