private function parseCommitMessagesIntoFields()

in src/workflow/ArcanistDiffWorkflow.php [2463:2614]


  private function parseCommitMessagesIntoFields(array $local) {
    $conduit = $this->getConduit();
    $local = ipull($local, null, 'commit');

    // If the user provided "--reviewers" or "--ccs", add a faux message to
    // the list with the implied fields.

    $faux_message = array();
    if ($this->getArgument('reviewers')) {
      $faux_message[] = pht('Reviewers: %s', $this->getArgument('reviewers'));
    }
    if ($this->getArgument('cc')) {
      $faux_message[] = pht('CC: %s', $this->getArgument('cc'));
    }

    // See T12069. After T10312, the first line of a message is always parsed
    // as a title. Add a placeholder so "Reviewers" and "CC" are never the
    // first line.
    $placeholder_title = pht('<placeholder>');

    if ($faux_message) {
      array_unshift($faux_message, $placeholder_title);
      $faux_message = implode("\n\n", $faux_message);
      $local = array(
        '(Flags)     ' => array(
          'message' => $faux_message,
          'summary' => pht('Command-Line Flags'),
        ),
      ) + $local;
    }

    // Build a human-readable list of the commits, so we can show the user which
    // commits are included in the diff.
    $included = array();
    foreach ($local as $hash => $info) {
      $included[] = substr($hash, 0, 12).' '.$info['summary'];
    }

    // Parse all of the messages into fields.
    $messages = array();
    foreach ($local as $hash => $info) {
      $text = $info['message'];
      $obj = ArcanistDifferentialCommitMessage::newFromRawCorpus($text);
      $messages[$hash] = $obj;
    }

    $notes = array();
    $fields = array();
    foreach ($messages as $hash => $message) {
      try {
        $message->pullDataFromConduit($conduit, $partial = true);
        $fields[$hash] = $message->getFields();
      } catch (ArcanistDifferentialCommitMessageParserException $ex) {
        if ($this->getArgument('verbatim')) {
          // In verbatim mode, just bail when we hit an error. The user can
          // rerun without --verbatim if they want to fix it manually. Most
          // users will probably `git commit --amend` instead.
          throw $ex;
        }
        $fields[$hash] = $message->getFields();

        $frev = substr($hash, 0, 12);
        $notes[] = pht(
          'NOTE: commit %s could not be completely parsed:',
          $frev);
        foreach ($ex->getParserErrors() as $error) {
          $notes[] = "  - {$error}";
        }
      }
    }

    // Merge commit message fields. We do this somewhat-intelligently so that
    // multiple "Reviewers" or "CC" fields will merge into the concatenation
    // of all values.

    // We have special parsing rules for 'title' because we can't merge
    // multiple titles, and one-line commit messages like "fix stuff" will
    // parse as titles. Instead, pick the first title we encounter. When we
    // encounter subsequent titles, treat them as part of the summary. Then
    // we merge all the summaries together below.

    $result = array();

    // Process fields in oldest-first order, so earlier commits get to set the
    // title of record and reviewers/ccs are listed in chronological order.
    $fields = array_reverse($fields);

    foreach ($fields as $hash => $dict) {
      $title = idx($dict, 'title');
      if (!strlen($title)) {
        continue;
      }

      if ($title === $placeholder_title) {
        continue;
      }

      if (!isset($result['title'])) {
        // We don't have a title yet, so use this one.
        $result['title'] = $title;
      } else {
        // We already have a title, so merge this new title into the summary.
        $summary = idx($dict, 'summary');
        if ($summary) {
          $summary = $title."\n\n".$summary;
        } else {
          $summary = $title;
        }
        $fields[$hash]['summary'] = $summary;
      }
    }

    // Now, merge all the other fields in a general sort of way.

    foreach ($fields as $hash => $dict) {
      foreach ($dict as $key => $value) {
        if ($key == 'title') {
          // This has been handled above, and either assigned directly or
          // merged into the summary.
          continue;
        }

        if (is_array($value)) {
          // For array values, merge the arrays, appending the new values.
          // Examples are "Reviewers" and "Cc", where this produces a list of
          // all users specified as reviewers.
          $cur = idx($result, $key, array());
          $new = array_merge($cur, $value);
          $result[$key] = $new;
          continue;
        } else {
          if (!strlen(trim($value))) {
            // Ignore empty fields.
            continue;
          }

          // For string values, append the new field to the old field with
          // a blank line separating them. Examples are "Test Plan" and
          // "Summary".
          $cur = idx($result, $key, '');
          if (strlen($cur)) {
            $new = $cur."\n\n".$value;
          } else {
            $new = $value;
          }
          $result[$key] = $new;
        }
      }
    }

    return array($result, $notes, $included);
  }