public function resolveBaseCommitRule()

in src/repository/api/ArcanistGitAPI.php [1339:1505]


  public function resolveBaseCommitRule($rule, $source) {
    list($type, $name) = explode(':', $rule, 2);

    switch ($type) {
      case 'git':
        $matches = null;
        if (preg_match('/^merge-base\((.+)\)$/', $name, $matches)) {
          list($err, $merge_base) = $this->execManualLocal(
            'merge-base %s HEAD',
            $matches[1]);
          if (!$err) {
            $this->setBaseCommitExplanation(
              pht(
                "it is the merge-base of '%s' and HEAD, as specified by ".
                "'%s' in your %s 'base' configuration.",
                $matches[1],
                $rule,
                $source));
            return trim($merge_base);
          }
        } else if (preg_match('/^branch-unique\((.+)\)$/', $name, $matches)) {
          list($err, $merge_base) = $this->execManualLocal(
            'merge-base %s HEAD',
            $matches[1]);
          if ($err) {
            return null;
          }
          $merge_base = trim($merge_base);

          list($commits) = $this->execxLocal(
            'log --format=%C %s..HEAD --',
            '%H',
            $merge_base);
          $commits = array_filter(explode("\n", $commits));

          if (!$commits) {
            return null;
          }

          $commits[] = $merge_base;

          $head_branch_count = null;
          $all_branch_names = ipull($this->getAllBranches(), 'name');
          foreach ($commits as $commit) {
            // Ideally, we would use something like "for-each-ref --contains"
            // to get a filtered list of branches ready for script consumption.
            // Instead, try to get predictable output from "branch --contains".

            $flags = array();
            $flags[] = '--no-color';

            // NOTE: The "--no-column" flag was introduced in Git 1.7.11, so
            // don't pass it if we're running an older version. See T9953.
            $version = $this->getGitVersion();
            if (version_compare($version, '1.7.11', '>=')) {
              $flags[] = '--no-column';
            }

            list($branches) = $this->execxLocal(
              'branch %Ls --contains %s',
              $flags,
              $commit);
            $branches = array_filter(explode("\n", $branches));

            // Filter the list, removing the "current" marker (*) and ignoring
            // anything other than known branch names (mainly, any possible
            // "detached HEAD" or "no branch" line).
            foreach ($branches as $key => $branch) {
              $branch = trim($branch, ' *');
              if (in_array($branch, $all_branch_names)) {
                $branches[$key] = $branch;
              } else {
                unset($branches[$key]);
              }
            }

            if ($head_branch_count === null) {
              // If this is the first commit, it's HEAD. Count how many
              // branches it is on; we want to include commits on the same
              // number of branches. This covers a case where this branch
              // has sub-branches and we're running "arc diff" here again
              // for whatever reason.
              $head_branch_count = count($branches);
            } else if (count($branches) > $head_branch_count) {
              $branches = implode(', ', $branches);
              $this->setBaseCommitExplanation(
                pht(
                  "it is the first commit between '%s' (the merge-base of ".
                  "'%s' and HEAD) which is also contained by another branch ".
                  "(%s).",
                  $merge_base,
                  $matches[1],
                  $branches));
              return $commit;
            }
          }
        } else {
          list($err) = $this->execManualLocal(
            'cat-file -t %s',
            $name);
          if (!$err) {
            $this->setBaseCommitExplanation(
              pht(
                "it is specified by '%s' in your %s 'base' configuration.",
                $rule,
                $source));
            return $name;
          }
        }
        break;
      case 'arc':
        switch ($name) {
          case 'empty':
            $this->setBaseCommitExplanation(
              pht(
                "you specified '%s' in your %s 'base' configuration.",
                $rule,
                $source));
            return self::GIT_MAGIC_ROOT_COMMIT;
          case 'amended':
            $text = $this->getCommitMessage('HEAD');
            $message = ArcanistDifferentialCommitMessage::newFromRawCorpus(
              $text);
            if ($message->getRevisionID()) {
              $this->setBaseCommitExplanation(
                pht(
                  "HEAD has been amended with 'Differential Revision:', ".
                  "as specified by '%s' in your %s 'base' configuration.",
                  $rule,
                  $source));
              return 'HEAD^';
            }
            break;
          case 'upstream':
            list($err, $upstream) = $this->execManualLocal(
              'rev-parse --abbrev-ref --symbolic-full-name %s',
              '@{upstream}');
            if (!$err) {
              $upstream = rtrim($upstream);
              list($upstream_merge_base) = $this->execxLocal(
                'merge-base %s HEAD',
                $upstream);
              $upstream_merge_base = rtrim($upstream_merge_base);
              $this->setBaseCommitExplanation(
                pht(
                  "it is the merge-base of the upstream of the current branch ".
                  "and HEAD, and matched the rule '%s' in your %s ".
                  "'base' configuration.",
                  $rule,
                  $source));
              return $upstream_merge_base;
            }
            break;
          case 'this':
            $this->setBaseCommitExplanation(
              pht(
                "you specified '%s' in your %s 'base' configuration.",
                $rule,
                $source));
            return 'HEAD^';
        }
      default:
        return null;
    }

    return null;
  }