public function run()

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;
  }