Use a template to set the subject line.
Add an admin editable ChangeSubject.vm template used to format the subject header in change emails. Change-Id: Iea58807b9a947bf0a4eba31c04977582430137ed
This commit is contained in:
120
Documentation/config-mail.txt
Normal file
120
Documentation/config-mail.txt
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
Gerrit Code Review - Mail Templates
|
||||||
|
===================================
|
||||||
|
|
||||||
|
Gerrit uses velocity templates for the bulk of the standard mails it sends out.
|
||||||
|
There are builtin default templates which are used if they are not overridden.
|
||||||
|
These defaults are also provided as examples so that administrators may copy
|
||||||
|
them and easily modify them to tweak their contents.
|
||||||
|
|
||||||
|
|
||||||
|
Template Locations and Extensions:
|
||||||
|
----------------------------------
|
||||||
|
|
||||||
|
The default example templates reside under: `'$site_path'/etc/mail` and are
|
||||||
|
terminated with the double extension `.vm.example`. Modifying these example
|
||||||
|
files will have no effect on the behavior of Gerrit. However, copying an
|
||||||
|
example template to an equivalently named file without the `.example` extension
|
||||||
|
and modifying it will allow an administrator to customize the template.
|
||||||
|
|
||||||
|
|
||||||
|
Supported Mail Templates:
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Each mail that Gerrit sends out is controlled by at least one template, these
|
||||||
|
are listed below. Change emails are influenced by two additional templates,
|
||||||
|
one to set the subject line, and one to set the footer which gets appended to
|
||||||
|
all the change emails (see `ChangeSubject.vm` and `ChangeFooter.vm` below.)
|
||||||
|
|
||||||
|
ChangeSubject.vm
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The `ChangeSubject.vm` template will determine the contents of the email
|
||||||
|
subject line for ALL emails related to changes.
|
||||||
|
|
||||||
|
|
||||||
|
Mail Variables and Methods
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
Mail templates can access and display objects currently made available to them
|
||||||
|
via the velocity context. While the base objects are documented here, it is
|
||||||
|
possible to call public methods on these objects from templates. Those methods
|
||||||
|
are not documented here since they could change with every release. As these
|
||||||
|
templates are meant to be modified only by a qualified sysadmin, it is accepted
|
||||||
|
that writing templates for Gerrit emails is likely to require some basic
|
||||||
|
knowledge of the class structure to be useful. Browsing the source code might
|
||||||
|
be necessary for anything more than a minor formatting change.
|
||||||
|
|
||||||
|
Warning
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
|
Be aware that modifying templates can cause them to fail to parse and therefor
|
||||||
|
not send out the actual email, or worse, calling methods on the available
|
||||||
|
objects could have internal side effects which would adversely affect the
|
||||||
|
health of your Gerrit server and/or data.
|
||||||
|
|
||||||
|
All OutgoingEmails
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
All outgoing emails have the following variables available to them:
|
||||||
|
|
||||||
|
$email::
|
||||||
|
+
|
||||||
|
A reference to the class constructing the current `OutgoingEmail`. With this
|
||||||
|
reference it is possible to call any public method on the OutgoingEmail class
|
||||||
|
or the current child class inherited from it.
|
||||||
|
|
||||||
|
$messageClass::
|
||||||
|
+
|
||||||
|
A String containing the messageClass
|
||||||
|
|
||||||
|
$StringUtils::
|
||||||
|
+
|
||||||
|
A reference to the Apache `StringUtils` class. This can be very useful for
|
||||||
|
formatting strings.
|
||||||
|
|
||||||
|
Change Emails
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
All change related emails have the following additional variables available to them:
|
||||||
|
|
||||||
|
$change::
|
||||||
|
+
|
||||||
|
A reference to the current `Change` object
|
||||||
|
|
||||||
|
$changeId::
|
||||||
|
+
|
||||||
|
Id of the current change (a `Change.Key`)
|
||||||
|
|
||||||
|
$coverLetter::
|
||||||
|
+
|
||||||
|
The text of the `ChangeMessage`
|
||||||
|
|
||||||
|
$branch::
|
||||||
|
+
|
||||||
|
A reference to the branch of this change (a `Branch.NameKey`)
|
||||||
|
|
||||||
|
$fromName::
|
||||||
|
+
|
||||||
|
The name of the from user
|
||||||
|
|
||||||
|
$projectName::
|
||||||
|
+
|
||||||
|
The name of this change's project
|
||||||
|
|
||||||
|
$patchSet::
|
||||||
|
+
|
||||||
|
A reference to the current `PatchSet`
|
||||||
|
|
||||||
|
$patchSetInfo::
|
||||||
|
+
|
||||||
|
A reference to the current `PatchSetInfo`
|
||||||
|
|
||||||
|
|
||||||
|
See Also
|
||||||
|
--------
|
||||||
|
|
||||||
|
* link:http://velocity.apache.org/[velocity]
|
||||||
|
|
||||||
|
GERRIT
|
||||||
|
------
|
||||||
|
Part of link:index.html[Gerrit Code Review]
|
@@ -31,6 +31,7 @@ Configuration
|
|||||||
* link:config-sso.html[Single Sign-On Systems]
|
* link:config-sso.html[Single Sign-On Systems]
|
||||||
* link:config-apache2.html[Apache 2 Reverse Proxy]
|
* link:config-apache2.html[Apache 2 Reverse Proxy]
|
||||||
* link:config-hooks.html[Hooks]
|
* link:config-hooks.html[Hooks]
|
||||||
|
* link:config-mail.html[Mail Templates]
|
||||||
|
|
||||||
Developer Documentation
|
Developer Documentation
|
||||||
-----------------------
|
-----------------------
|
||||||
|
@@ -25,11 +25,13 @@ import static com.google.gerrit.pgm.init.InitUtil.version;
|
|||||||
import com.google.gerrit.pgm.Init;
|
import com.google.gerrit.pgm.Init;
|
||||||
import com.google.gerrit.pgm.util.ConsoleUI;
|
import com.google.gerrit.pgm.util.ConsoleUI;
|
||||||
import com.google.gerrit.server.config.SitePaths;
|
import com.google.gerrit.server.config.SitePaths;
|
||||||
|
import com.google.gerrit.server.mail.OutgoingEmail;
|
||||||
import com.google.inject.Binding;
|
import com.google.inject.Binding;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Injector;
|
import com.google.inject.Injector;
|
||||||
import com.google.inject.TypeLiteral;
|
import com.google.inject.TypeLiteral;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -66,6 +68,7 @@ public class SitePathInitializer {
|
|||||||
mkdir(site.etc_dir);
|
mkdir(site.etc_dir);
|
||||||
mkdir(site.lib_dir);
|
mkdir(site.lib_dir);
|
||||||
mkdir(site.logs_dir);
|
mkdir(site.logs_dir);
|
||||||
|
mkdir(site.mail_dir);
|
||||||
mkdir(site.static_dir);
|
mkdir(site.static_dir);
|
||||||
|
|
||||||
for (InitStep step : steps) {
|
for (InitStep step : steps) {
|
||||||
@@ -82,11 +85,19 @@ public class SitePathInitializer {
|
|||||||
extract(site.gerrit_sh, Init.class, "gerrit.sh");
|
extract(site.gerrit_sh, Init.class, "gerrit.sh");
|
||||||
chmod(0755, site.gerrit_sh);
|
chmod(0755, site.gerrit_sh);
|
||||||
|
|
||||||
|
extractMailExample("ChangeSubject.vm");
|
||||||
|
|
||||||
if (!ui.isBatch()) {
|
if (!ui.isBatch()) {
|
||||||
System.err.println();
|
System.err.println();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void extractMailExample(String orig) throws Exception {
|
||||||
|
File ex = new File(site.mail_dir, orig + ".example");
|
||||||
|
extract(ex, OutgoingEmail.class, orig);
|
||||||
|
chmod(0444, ex);
|
||||||
|
}
|
||||||
|
|
||||||
private static List<InitStep> stepsOf(final Injector injector) {
|
private static List<InitStep> stepsOf(final Injector injector) {
|
||||||
final ArrayList<InitStep> r = new ArrayList<InitStep>();
|
final ArrayList<InitStep> r = new ArrayList<InitStep>();
|
||||||
for (Binding<InitStep> b : all(injector)) {
|
for (Binding<InitStep> b : all(injector)) {
|
||||||
|
@@ -30,7 +30,7 @@ public class AbandonedSender extends ReplyToChangeSender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void init() {
|
protected void init() throws EmailException {
|
||||||
super.init();
|
super.init();
|
||||||
|
|
||||||
ccAllApprovals();
|
ccAllApprovals();
|
||||||
@@ -39,7 +39,7 @@ public class AbandonedSender extends ReplyToChangeSender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void formatChange() {
|
protected void formatChange() throws EmailException {
|
||||||
appendText(getNameFor(fromId));
|
appendText(getNameFor(fromId));
|
||||||
appendText(" has abandoned change " + change.getKey().abbreviate() + ":\n");
|
appendText(" has abandoned change " + change.getKey().abbreviate() + ":\n");
|
||||||
appendText("\n");
|
appendText("\n");
|
||||||
|
@@ -32,7 +32,7 @@ public class AddReviewerSender extends NewChangeSender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void init() {
|
protected void init() throws EmailException {
|
||||||
super.init();
|
super.init();
|
||||||
|
|
||||||
ccExistingReviewers();
|
ccExistingReviewers();
|
||||||
|
@@ -122,7 +122,7 @@ public abstract class ChangeEmail extends OutgoingEmail {
|
|||||||
protected abstract void formatChange() throws EmailException;
|
protected abstract void formatChange() throws EmailException;
|
||||||
|
|
||||||
/** Setup the message headers and envelope (TO, CC, BCC). */
|
/** Setup the message headers and envelope (TO, CC, BCC). */
|
||||||
protected void init() {
|
protected void init() throws EmailException {
|
||||||
if (args.projectCache != null) {
|
if (args.projectCache != null) {
|
||||||
projectState = args.projectCache.get(change.getProject());
|
projectState = args.projectCache.get(change.getProject());
|
||||||
projectName =
|
projectName =
|
||||||
@@ -194,23 +194,8 @@ public abstract class ChangeEmail extends OutgoingEmail {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setChangeSubjectHeader() {
|
private void setChangeSubjectHeader() throws EmailException {
|
||||||
final StringBuilder subj = new StringBuilder();
|
setHeader("Subject", velocifyFile("ChangeSubject.vm"));
|
||||||
subj.append("[");
|
|
||||||
subj.append(change.getDest().getShortName());
|
|
||||||
subj.append("] ");
|
|
||||||
subj.append("Change ");
|
|
||||||
subj.append(change.getKey().abbreviate());
|
|
||||||
subj.append(": (");
|
|
||||||
subj.append(projectName);
|
|
||||||
subj.append(") ");
|
|
||||||
if (change.getSubject().length() > 60) {
|
|
||||||
subj.append(change.getSubject().substring(0, 60));
|
|
||||||
subj.append("...");
|
|
||||||
} else {
|
|
||||||
subj.append(change.getSubject());
|
|
||||||
}
|
|
||||||
setHeader("Subject", subj.toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get a link to the change; null if the server doesn't know its own address. */
|
/** Get a link to the change; null if the server doesn't know its own address. */
|
||||||
@@ -434,7 +419,10 @@ public abstract class ChangeEmail extends OutgoingEmail {
|
|||||||
protected void setupVelocityContext() {
|
protected void setupVelocityContext() {
|
||||||
super.setupVelocityContext();
|
super.setupVelocityContext();
|
||||||
velocityContext.put("change", change);
|
velocityContext.put("change", change);
|
||||||
|
velocityContext.put("changeId", change.getKey());
|
||||||
|
velocityContext.put("coverLetter", getCoverLetter());
|
||||||
velocityContext.put("branch", change.getDest());
|
velocityContext.put("branch", change.getDest());
|
||||||
|
velocityContext.put("fromName", getNameFor(fromId));
|
||||||
velocityContext.put("projectName", projectName);
|
velocityContext.put("projectName", projectName);
|
||||||
velocityContext.put("patchSet", patchSet);
|
velocityContext.put("patchSet", patchSet);
|
||||||
velocityContext.put("patchSetInfo", patchSetInfo);
|
velocityContext.put("patchSetInfo", patchSetInfo);
|
||||||
|
@@ -56,7 +56,7 @@ public class CommentSender extends ReplyToChangeSender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void init() {
|
protected void init() throws EmailException {
|
||||||
super.init();
|
super.init();
|
||||||
|
|
||||||
ccAllApprovals();
|
ccAllApprovals();
|
||||||
@@ -65,7 +65,7 @@ public class CommentSender extends ReplyToChangeSender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void formatChange() {
|
protected void formatChange() throws EmailException {
|
||||||
if (!"".equals(getCoverLetter()) || !inlineComments.isEmpty()) {
|
if (!"".equals(getCoverLetter()) || !inlineComments.isEmpty()) {
|
||||||
appendText("Comments on Patch Set " + patchSet.getPatchSetId() + ":\n");
|
appendText("Comments on Patch Set " + patchSet.getPatchSetId() + ":\n");
|
||||||
appendText("\n");
|
appendText("\n");
|
||||||
|
@@ -40,7 +40,7 @@ public class CreateChangeSender extends NewChangeSender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void init() {
|
protected void init() throws EmailException {
|
||||||
super.init();
|
super.init();
|
||||||
|
|
||||||
bccWatchers();
|
bccWatchers();
|
||||||
|
@@ -30,14 +30,14 @@ public class MergeFailSender extends ReplyToChangeSender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void init() {
|
protected void init() throws EmailException {
|
||||||
super.init();
|
super.init();
|
||||||
|
|
||||||
ccExistingReviewers();
|
ccExistingReviewers();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void formatChange() {
|
protected void formatChange() throws EmailException {
|
||||||
appendText("Change " + change.getKey().abbreviate());
|
appendText("Change " + change.getKey().abbreviate());
|
||||||
if (patchSetInfo != null && patchSetInfo.getAuthor() != null
|
if (patchSetInfo != null && patchSetInfo.getAuthor() != null
|
||||||
&& patchSetInfo.getAuthor().getName() != null) {
|
&& patchSetInfo.getAuthor().getName() != null) {
|
||||||
|
@@ -51,7 +51,7 @@ public class MergedSender extends ReplyToChangeSender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void init() {
|
protected void init() throws EmailException {
|
||||||
super.init();
|
super.init();
|
||||||
|
|
||||||
ccAllApprovals();
|
ccAllApprovals();
|
||||||
@@ -61,7 +61,7 @@ public class MergedSender extends ReplyToChangeSender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void formatChange() {
|
protected void formatChange() throws EmailException {
|
||||||
appendText("Change " + change.getKey().abbreviate());
|
appendText("Change " + change.getKey().abbreviate());
|
||||||
if (patchSetInfo != null && patchSetInfo.getAuthor() != null
|
if (patchSetInfo != null && patchSetInfo.getAuthor() != null
|
||||||
&& patchSetInfo.getAuthor().getName() != null) {
|
&& patchSetInfo.getAuthor().getName() != null) {
|
||||||
|
@@ -46,7 +46,7 @@ public abstract class NewChangeSender extends ChangeEmail {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void init() {
|
protected void init() throws EmailException {
|
||||||
super.init();
|
super.init();
|
||||||
|
|
||||||
setHeader("Message-ID", getChangeMessageThreadId());
|
setHeader("Message-ID", getChangeMessageThreadId());
|
||||||
@@ -57,7 +57,7 @@ public abstract class NewChangeSender extends ChangeEmail {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void formatChange() {
|
protected void formatChange() throws EmailException {
|
||||||
formatSalutation();
|
formatSalutation();
|
||||||
formatChangeDetail();
|
formatChangeDetail();
|
||||||
|
|
||||||
|
@@ -124,7 +124,7 @@ public abstract class OutgoingEmail {
|
|||||||
protected abstract void format() throws EmailException;
|
protected abstract void format() throws EmailException;
|
||||||
|
|
||||||
/** Setup the message headers and envelope (TO, CC, BCC). */
|
/** Setup the message headers and envelope (TO, CC, BCC). */
|
||||||
protected void init() {
|
protected void init() throws EmailException {
|
||||||
setupVelocityContext();
|
setupVelocityContext();
|
||||||
|
|
||||||
smtpFromAddress = args.fromAddressGenerator.from(fromId);
|
smtpFromAddress = args.fromAddressGenerator.from(fromId);
|
||||||
|
@@ -40,7 +40,7 @@ public class RegisterNewEmailSender extends OutgoingEmail {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void init() {
|
protected void init() throws EmailException {
|
||||||
super.init();
|
super.init();
|
||||||
setHeader("Subject", "[Gerrit Code Review] Email Verification");
|
setHeader("Subject", "[Gerrit Code Review] Email Verification");
|
||||||
add(RecipientType.TO, new Address(addr));
|
add(RecipientType.TO, new Address(addr));
|
||||||
@@ -52,7 +52,7 @@ public class RegisterNewEmailSender extends OutgoingEmail {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void format() {
|
protected void format() throws EmailException {
|
||||||
final StringBuilder url = new StringBuilder();
|
final StringBuilder url = new StringBuilder();
|
||||||
url.append(getGerritUrl());
|
url.append(getGerritUrl());
|
||||||
url.append("#VE,");
|
url.append("#VE,");
|
||||||
|
@@ -53,7 +53,7 @@ public class ReplacePatchSetSender extends ReplyToChangeSender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void init() {
|
protected void init() throws EmailException {
|
||||||
super.init();
|
super.init();
|
||||||
|
|
||||||
if (fromId != null) {
|
if (fromId != null) {
|
||||||
@@ -67,7 +67,7 @@ public class ReplacePatchSetSender extends ReplyToChangeSender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void formatChange() {
|
protected void formatChange() throws EmailException {
|
||||||
formatSalutation();
|
formatSalutation();
|
||||||
formatChangeDetail();
|
formatChangeDetail();
|
||||||
|
|
||||||
|
@@ -23,7 +23,7 @@ public abstract class ReplyToChangeSender extends ChangeEmail {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void init() {
|
protected void init() throws EmailException {
|
||||||
super.init();
|
super.init();
|
||||||
|
|
||||||
final String threadId = getChangeMessageThreadId();
|
final String threadId = getChangeMessageThreadId();
|
||||||
|
@@ -0,0 +1,37 @@
|
|||||||
|
## Copyright (C) 2010 The Android Open Source Project
|
||||||
|
##
|
||||||
|
## Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
## you may not use this file except in compliance with the License.
|
||||||
|
## You may obtain a copy of the License at
|
||||||
|
##
|
||||||
|
## http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
##
|
||||||
|
## Unless required by applicable law or agreed to in writing, software
|
||||||
|
## distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
## See the License for the specific language governing permissions and
|
||||||
|
## limitations under the License.
|
||||||
|
##
|
||||||
|
##
|
||||||
|
## Template Type:
|
||||||
|
## -------------
|
||||||
|
## This is a velocity mail template, see: http://velocity.apache.org and the
|
||||||
|
## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates.
|
||||||
|
##
|
||||||
|
## Template File Names and extensions:
|
||||||
|
## ----------------------------------
|
||||||
|
## Gerrit will use templates ending in ".vm" but will ignore templates ending
|
||||||
|
## in ".vm.example". If a .vm template does not exist, the default internal
|
||||||
|
## gerrit template which is the same as the .vm.example will be used. If you
|
||||||
|
## want to override the default template, copy the .vm.exmaple file to a .vm
|
||||||
|
## file and edit it appropriately.
|
||||||
|
##
|
||||||
|
## This Template:
|
||||||
|
## --------------
|
||||||
|
## The ChangeSubject.vm template will determine the contents of the email
|
||||||
|
## subject line for ALL emails related to changes.
|
||||||
|
##
|
||||||
|
#macro(elipses $length $str)
|
||||||
|
#if($str.length() > $length)${str.substring(0,$length)}...#else$str#end
|
||||||
|
#end
|
||||||
|
[$branch.shortName] Change $changeId.abbreviate(): ($projectName) #elipses(60, $change.subject)
|
Reference in New Issue
Block a user