diff --git a/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php b/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php --- a/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php +++ b/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php @@ -234,6 +234,15 @@ '[Differential]') ->setDescription(pht('Subject prefix for Differential mail.')), $this->newOption( + 'metamta.differential.subject-template', + 'string', + 'D{$id}: {$title}') + ->setSummary(pht('Subject template for Differential mail.')) + ->setDescription(pht('Available keys: id, title, repository, branch')) + ->addExample( + '[${repository}/${branch}] D${id}: ${title}', + pht('Use repository and branch')), + $this->newOption( 'metamta.differential.attach-patches', 'bool', false) diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -1081,6 +1081,11 @@ return PhabricatorEnv::getEnvConfig('metamta.differential.subject-prefix'); } + private function getMailSubjectTemplate() { + return PhabricatorEnv::getEnvConfig( + 'metamta.differential.subject-template'); + } + protected function getMailThreadID(PhabricatorLiskDAO $object) { // This is nonstandard, but retains threading with older messages. $phid = $object->getPHID(); @@ -1097,15 +1102,66 @@ $title = $object->getTitle(); $original_title = $object->getOriginalTitle(); + $branch = $object->getActiveDiff()->getBranch(); + $repository_name = $object->getRepository()->getName(); + + $base_variables = array( + 'id' => $id, + 'branch' => $branch, + 'repository' => $repository_name + ); - $subject = "D{$id}: {$title}"; - $thread_topic = "D{$id}: {$original_title}"; + $subject = $this->mergeVariables($this->getMailSubjectTemplate(), + array_merge($base_variables, array( + 'title' => $title + ))); + + $thread_topic = $this->mergeVariables($this->getMailSubjectTemplate(), + array_merge($base_variables, array( + 'title' => $original_title + ))); return id(new PhabricatorMetaMTAMail()) ->setSubject($subject) ->addHeader('Thread-Topic', $thread_topic); } + /** + * Convert a user-provided string with variables in it, like: + * + * ${project}/${branch} + * + * ...into a string with variables merged into it safely: + * + * myproject/mybranch + * + * Maybe move to libphutil? + * @param string User-provided pattern string containing `${variables}`. + * @param dict List of available replacement variables. + * @return string String with variables replaced safely into it. + * + */ + protected function mergeVariables($pattern, array $variables) { + $regexp = '/\\$\\{(?P[a-z\\.]+)\\}/'; + + $matches = null; + preg_match_all($regexp, $pattern, $matches); + + $argv = array(); + foreach ($matches['name'] as $name) { + if (!array_key_exists($name, $variables)) { + throw new Exception(pht("No such variable '%s'!", $name)); + } + $argv[] = $variables[$name]; + } + + $pattern = str_replace('%', '%%', $pattern); + $pattern = preg_replace($regexp, '%s', $pattern); + + return vsprintf($pattern, $argv); + } + + protected function buildMailBody( PhabricatorLiskDAO $object, array $xactions) {