The new ChangePluginDefinedInfoFactory interface replaces it and also has the capability to process collection of ChangeDatas at once. This was introduced in change: https://gerrit-review.googlesource.com/c/gerrit/+/280758 Change-Id: I6bc941a22bf696efb64c9b9e1864527e6448673d
		
			
				
	
	
		
			3002 lines
		
	
	
		
			96 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			3002 lines
		
	
	
		
			96 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
:linkattrs:
 | 
						|
= Gerrit Code Review - Plugin Development
 | 
						|
 | 
						|
The Gerrit server functionality can be extended by installing plugins.
 | 
						|
This page describes how plugins for Gerrit can be developed and hosted
 | 
						|
on gerrit-review.googlesource.com.
 | 
						|
 | 
						|
For PolyGerrit-specific plugin development, consult with
 | 
						|
link:pg-plugin-dev.html[PolyGerrit Plugin Development] guide.
 | 
						|
 | 
						|
Depending on how tightly the extension code is coupled with the Gerrit
 | 
						|
server code, there is a distinction between `plugins` and `extensions`.
 | 
						|
 | 
						|
[[plugin]]
 | 
						|
A `plugin` in Gerrit is tightly coupled code that runs in the same
 | 
						|
JVM as Gerrit. It has full access to all server internals. Plugins
 | 
						|
are tightly coupled to a specific major.minor server version and
 | 
						|
may require source code changes to compile against a different
 | 
						|
server version.
 | 
						|
 | 
						|
Plugins may require a specific major.minor.patch server version
 | 
						|
and may need rebuild and revalidation across different
 | 
						|
patch levels. A different patch level may only add new
 | 
						|
API interfaces and never change or extend existing ones.
 | 
						|
 | 
						|
[[extension]]
 | 
						|
An `extension` in Gerrit runs inside of the same JVM as Gerrit
 | 
						|
in the same way as a plugin, but has limited visibility to the
 | 
						|
server's internals. The limited visibility reduces the extension's
 | 
						|
dependencies, enabling it to be compatible across a wider range
 | 
						|
of server versions.
 | 
						|
 | 
						|
Most of this documentation refers to either type as a plugin.
 | 
						|
 | 
						|
[[getting-started]]
 | 
						|
== Getting started
 | 
						|
 | 
						|
To get started with the development of a plugin, take a look at
 | 
						|
the samples in the
 | 
						|
link:https://gerrit.googlesource.com/plugins/examples[examples plugin project,role=external,window=_blank].
 | 
						|
 | 
						|
This is a project that demonstrates the various features of the
 | 
						|
plugin API. It can be taken as an example to develop an own plugin.
 | 
						|
 | 
						|
When starting from this example one should take care to adapt the
 | 
						|
`Gerrit-ApiVersion` in the `BUILD` to the version of Gerrit for which
 | 
						|
the plugin is developed.
 | 
						|
 | 
						|
[[API]]
 | 
						|
== API
 | 
						|
 | 
						|
There are two different API formats offered against which plugins can
 | 
						|
be developed:
 | 
						|
 | 
						|
gerrit-extension-api.jar::
 | 
						|
  A stable but thin interface. Suitable for extensions that need
 | 
						|
  to be notified of events, but do not require tight coupling to
 | 
						|
  the internals of Gerrit. Extensions built against this API can
 | 
						|
  expect to be binary compatible across a wide range of server
 | 
						|
  versions.
 | 
						|
 | 
						|
gerrit-plugin-api.jar::
 | 
						|
  The complete internals of the Gerrit server, permitting a
 | 
						|
  plugin to tightly couple itself and provide additional
 | 
						|
  functionality that is not possible as an extension. Plugins
 | 
						|
  built against this API are expected to break at the source
 | 
						|
  code level between every major.minor Gerrit release. A plugin
 | 
						|
  that compiles against 2.5 will probably need source code level
 | 
						|
  changes to work with 2.6, 2.7, and so on.
 | 
						|
 | 
						|
== Manifest
 | 
						|
 | 
						|
Plugins may provide optional description information with standard
 | 
						|
manifest fields:
 | 
						|
 | 
						|
----
 | 
						|
  Implementation-Title: Example plugin showing examples
 | 
						|
  Implementation-Version: 1.0
 | 
						|
  Implementation-Vendor: Example, Inc.
 | 
						|
----
 | 
						|
 | 
						|
=== ApiType
 | 
						|
 | 
						|
Plugins using the tightly coupled `gerrit-plugin-api.jar` must
 | 
						|
declare this API dependency in the manifest to gain access to server
 | 
						|
internals. If no `Gerrit-ApiType` is specified the stable `extension`
 | 
						|
API will be assumed. This may cause ClassNotFoundExceptions when
 | 
						|
loading a plugin that needs the plugin API.
 | 
						|
 | 
						|
----
 | 
						|
  Gerrit-ApiType: plugin
 | 
						|
----
 | 
						|
 | 
						|
=== Explicit Registration
 | 
						|
 | 
						|
Plugins that use explicit Guice registration must name the Guice
 | 
						|
modules in the manifest. Up to three modules can be named in the
 | 
						|
manifest. `Gerrit-Module` supplies bindings to the core server;
 | 
						|
`Gerrit-SshModule` supplies SSH commands to the SSH server (if
 | 
						|
enabled); `Gerrit-HttpModule` supplies servlets and filters to the HTTP
 | 
						|
server (if enabled). If no modules are named automatic registration
 | 
						|
will be performed by scanning all classes in the plugin JAR for
 | 
						|
`@Listen` and `@Export("")` annotations.
 | 
						|
 | 
						|
----
 | 
						|
  Gerrit-Module:     tld.example.project.CoreModuleClassName
 | 
						|
  Gerrit-SshModule:  tld.example.project.SshModuleClassName
 | 
						|
  Gerrit-HttpModule: tld.example.project.HttpModuleClassName
 | 
						|
----
 | 
						|
 | 
						|
=== Batch runtime
 | 
						|
 | 
						|
Gerrit can be run as a server, serving HTTP or SSH requests, or as an
 | 
						|
offline program. Plugins can contribute Guice modules to this batch
 | 
						|
runtime by binding `Gerrit-BatchModule` to one of their classes.
 | 
						|
The Guice injector is bound to less classes, and some Gerrit features
 | 
						|
will be absent - on purpose.
 | 
						|
 | 
						|
This feature was originally introduced to support plugins during an
 | 
						|
offline reindexing task.
 | 
						|
 | 
						|
----
 | 
						|
  Gerrit-BatchModule: tld.example.project.CoreModuleClassName
 | 
						|
----
 | 
						|
 | 
						|
In this runtime, only the module designated by `Gerrit-BatchModule` is
 | 
						|
enabled, not `Gerrit-SysModule`.
 | 
						|
 | 
						|
[[plugin_name]]
 | 
						|
=== Plugin Name
 | 
						|
 | 
						|
A plugin can optionally provide its own plugin name.
 | 
						|
 | 
						|
----
 | 
						|
  Gerrit-PluginName: replication
 | 
						|
----
 | 
						|
 | 
						|
This is useful for plugins that contribute plugin-owned capabilities that
 | 
						|
are stored in the `project.config` file. Another use case is to be able to put
 | 
						|
project specific plugin configuration section in `project.config`. In this
 | 
						|
case it is advantageous to reserve the plugin name to access the configuration
 | 
						|
section in the `project.config` file.
 | 
						|
 | 
						|
If `Gerrit-PluginName` is omitted, then the plugin's name is determined from
 | 
						|
the plugin file name.
 | 
						|
 | 
						|
If a plugin provides its own name, then that plugin cannot be deployed
 | 
						|
multiple times under different file names on one Gerrit site.
 | 
						|
 | 
						|
For Maven driven plugins, the following line must be included in the pom.xml
 | 
						|
file:
 | 
						|
 | 
						|
[source,xml]
 | 
						|
----
 | 
						|
<manifestEntries>
 | 
						|
  <Gerrit-PluginName>name</Gerrit-PluginName>
 | 
						|
</manifestEntries>
 | 
						|
----
 | 
						|
 | 
						|
For Bazel driven plugins, the following line must be included in the BUILD
 | 
						|
configuration file:
 | 
						|
 | 
						|
[source,python]
 | 
						|
----
 | 
						|
manifest_entries = [
 | 
						|
   'Gerrit-PluginName: name',
 | 
						|
]
 | 
						|
----
 | 
						|
 | 
						|
A plugin can get its own name injected at runtime:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
public class MyClass {
 | 
						|
 | 
						|
  private final String pluginName;
 | 
						|
 | 
						|
  @Inject
 | 
						|
  public MyClass(@PluginName String pluginName) {
 | 
						|
    this.pluginName = pluginName;
 | 
						|
  }
 | 
						|
 | 
						|
  [...]
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
A plugin can get its canonical web URL injected at runtime:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
public class MyClass {
 | 
						|
 | 
						|
  private final String url;
 | 
						|
 | 
						|
  @Inject
 | 
						|
  public MyClass(@PluginCanonicalWebUrl String url) {
 | 
						|
    this.url = url;
 | 
						|
  }
 | 
						|
 | 
						|
  [...]
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
The URL is composed of the server's canonical web URL and the plugin's
 | 
						|
name, i.e. `http://review.example.com:8080/plugin-name`.
 | 
						|
 | 
						|
The canonical web URL may be injected into any .jar plugin regardless of
 | 
						|
whether or not the plugin provides an HTTP servlet.
 | 
						|
 | 
						|
[[reload_method]]
 | 
						|
=== Reload Method
 | 
						|
 | 
						|
If a plugin holds an exclusive resource that must be released before
 | 
						|
loading the plugin again (for example listening on a network port or
 | 
						|
acquiring a file lock) the manifest must declare `Gerrit-ReloadMode`
 | 
						|
to be `restart`. Otherwise the preferred method of `reload` will
 | 
						|
be used, as it enables the server to hot-patch an updated plugin
 | 
						|
with no down time.
 | 
						|
 | 
						|
----
 | 
						|
  Gerrit-ReloadMode: restart
 | 
						|
----
 | 
						|
 | 
						|
In either mode ('restart' or 'reload') any plugin or extension can
 | 
						|
be updated without restarting the Gerrit server. The difference is
 | 
						|
how Gerrit handles the upgrade:
 | 
						|
 | 
						|
restart::
 | 
						|
  The old plugin is completely stopped. All registrations of SSH
 | 
						|
  commands and HTTP servlets are removed. All registrations of any
 | 
						|
  extension points are removed. All registered LifecycleListeners
 | 
						|
  have their `stop()` method invoked in reverse order. The new
 | 
						|
  plugin is started, and registrations are made from the new
 | 
						|
  plugin. There is a brief window where neither the old nor the
 | 
						|
  new plugin is connected to the server. This means SSH commands
 | 
						|
  and HTTP servlets will return not found errors, and the plugin
 | 
						|
  will not be notified of events that occurred during the restart.
 | 
						|
 | 
						|
reload::
 | 
						|
  The new plugin is started. Its LifecycleListeners are permitted
 | 
						|
  to perform their `start()` methods. All SSH and HTTP registrations
 | 
						|
  are atomically swapped out from the old plugin to the new plugin,
 | 
						|
  ensuring the server never returns a not found error. All extension
 | 
						|
  point listeners are atomically swapped out from the old plugin to
 | 
						|
  the new plugin, ensuring no events are missed (however some events
 | 
						|
  may still route to the old plugin if the swap wasn't complete yet).
 | 
						|
  The old plugin is stopped.
 | 
						|
 | 
						|
To reload/restart a plugin the link:cmd-plugin-reload.html[plugin reload]
 | 
						|
command can be used.
 | 
						|
 | 
						|
[[init_step]]
 | 
						|
=== Init step
 | 
						|
 | 
						|
Plugins can contribute their own "init step" during the Gerrit init
 | 
						|
wizard. This is useful for guiding the Gerrit administrator through
 | 
						|
the settings needed by the plugin to work properly.
 | 
						|
 | 
						|
For instance plugins to integrate Jira issues to Gerrit changes may
 | 
						|
contribute their own "init step" to allow configuring the Jira URL,
 | 
						|
credentials and possibly verify connectivity to validate them.
 | 
						|
 | 
						|
----
 | 
						|
  Gerrit-InitStep: tld.example.project.MyInitStep
 | 
						|
----
 | 
						|
 | 
						|
MyInitStep needs to follow the standard Gerrit InitStep syntax
 | 
						|
and behavior: writing to the console using the injected ConsoleUI
 | 
						|
and accessing / changing configuration settings using Section.Factory.
 | 
						|
 | 
						|
In addition to the standard Gerrit init injections, plugins receive
 | 
						|
the @PluginName String injection containing their own plugin name.
 | 
						|
 | 
						|
During their initialization plugins may get access to the
 | 
						|
`project.config` file of the `All-Projects` project and they are able
 | 
						|
to store configuration parameters in it. For this a plugin `InitStep`
 | 
						|
can get `com.google.gerrit.pgm.init.api.AllProjectsConfig` injected:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
  public class MyInitStep implements InitStep {
 | 
						|
    private final String pluginName;
 | 
						|
    private final ConsoleUI ui;
 | 
						|
    private final AllProjectsConfig allProjectsConfig;
 | 
						|
 | 
						|
    @Inject
 | 
						|
    public MyInitStep(@PluginName String pluginName, ConsoleUI ui,
 | 
						|
        AllProjectsConfig allProjectsConfig) {
 | 
						|
      this.pluginName = pluginName;
 | 
						|
      this.ui = ui;
 | 
						|
      this.allProjectsConfig = allProjectsConfig;
 | 
						|
    }
 | 
						|
 | 
						|
    @Override
 | 
						|
    public void run() throws Exception {
 | 
						|
    }
 | 
						|
 | 
						|
    @Override
 | 
						|
    public void postRun() throws Exception {
 | 
						|
      ui.message("\n");
 | 
						|
      ui.header(pluginName + " Integration");
 | 
						|
      boolean enabled = ui.yesno(true, "By default enabled for all projects");
 | 
						|
      Config cfg = allProjectsConfig.load().getConfig();
 | 
						|
      if (enabled) {
 | 
						|
        cfg.setBoolean("plugin", pluginName, "enabled", enabled);
 | 
						|
      } else {
 | 
						|
        cfg.unset("plugin", pluginName, "enabled");
 | 
						|
      }
 | 
						|
      allProjectsConfig.save(pluginName, "Initialize " + pluginName + " Integration");
 | 
						|
    }
 | 
						|
  }
 | 
						|
----
 | 
						|
 | 
						|
Bear in mind that the Plugin's InitStep class will be loaded but
 | 
						|
the standard Gerrit runtime environment is not available and the plugin's
 | 
						|
own Guice modules were not initialized.
 | 
						|
This means the InitStep for a plugin is not executed in the same way that
 | 
						|
the plugin executes within the server, and may mean a plugin author cannot
 | 
						|
trivially reuse runtime code during init.
 | 
						|
 | 
						|
For instance a plugin that wants to verify connectivity may need to statically
 | 
						|
call the constructor of their connection class, passing in values obtained
 | 
						|
from the Section.Factory rather than from an injected Config object.
 | 
						|
 | 
						|
Plugins' InitSteps are executed during the "Gerrit Plugin init" phase, after
 | 
						|
the extraction of the plugins embedded in the distribution .war file into
 | 
						|
`$GERRIT_SITE/plugins` and before the site initialization or upgrade.
 | 
						|
 | 
						|
A plugin's InitStep cannot refer to any Gerrit runtime objects injected at
 | 
						|
startup.
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
public class MyInitStep implements InitStep {
 | 
						|
  private final ConsoleUI ui;
 | 
						|
  private final Section.Factory sections;
 | 
						|
  private final String pluginName;
 | 
						|
 | 
						|
  @Inject
 | 
						|
  public GitBlitInitStep(final ConsoleUI ui, Section.Factory sections, @PluginName String pluginName) {
 | 
						|
    this.ui = ui;
 | 
						|
    this.sections = sections;
 | 
						|
    this.pluginName = pluginName;
 | 
						|
  }
 | 
						|
 | 
						|
  @Override
 | 
						|
  public void run() throws Exception {
 | 
						|
    ui.header("\nMy plugin");
 | 
						|
 | 
						|
    Section mySection = getSection("myplugin", null);
 | 
						|
    mySection.string("Link name", "linkname", "MyLink");
 | 
						|
  }
 | 
						|
 | 
						|
  @Override
 | 
						|
  public void postRun() throws Exception {
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
[[classpath]]
 | 
						|
== Classpath
 | 
						|
 | 
						|
Each plugin is loaded into its own ClassLoader, isolating plugins
 | 
						|
from each other. A plugin or extension inherits the Java runtime
 | 
						|
and the Gerrit API chosen by `Gerrit-ApiType` (extension or plugin)
 | 
						|
from the hosting server.
 | 
						|
 | 
						|
Plugins are loaded from a single JAR file. If a plugin needs
 | 
						|
additional libraries, it must include those dependencies within
 | 
						|
its own JAR. Plugins built using Maven may be able to use the
 | 
						|
link:http://maven.apache.org/plugins/maven-shade-plugin/[shade plugin,role=external,window=_blank]
 | 
						|
to package additional dependencies. Relocating (or renaming) classes
 | 
						|
should not be necessary due to the ClassLoader isolation.
 | 
						|
 | 
						|
[[events]]
 | 
						|
== Listening to Events
 | 
						|
 | 
						|
Certain operations in Gerrit trigger events. Plugins may receive
 | 
						|
notifications of these events by implementing the corresponding
 | 
						|
listeners.
 | 
						|
 | 
						|
* `com.google.gerrit.server.events.EventListener`:
 | 
						|
+
 | 
						|
Allows to listen to events without user visibility restrictions. These
 | 
						|
are the same link:cmd-stream-events.html#events[events] that are also streamed by
 | 
						|
the link:cmd-stream-events.html[gerrit stream-events] command.
 | 
						|
 | 
						|
* `com.google.gerrit.server.events.UserScopedEventListener`:
 | 
						|
+
 | 
						|
Allows to listen to events visible to the specified user. These are the
 | 
						|
same link:cmd-stream-events.html#events[events] that are also streamed
 | 
						|
by the link:cmd-stream-events.html[gerrit stream-events] command.
 | 
						|
 | 
						|
* `com.google.gerrit.extensions.events.AccountActivationListener`:
 | 
						|
+
 | 
						|
User account got activated or deactivated
 | 
						|
 | 
						|
* `com.google.gerrit.extensions.events.LifecycleListener`:
 | 
						|
+
 | 
						|
Plugin start and stop
 | 
						|
 | 
						|
* `com.google.gerrit.extensions.events.NewProjectCreatedListener`:
 | 
						|
+
 | 
						|
Project creation
 | 
						|
 | 
						|
* `com.google.gerrit.extensions.events.ProjectDeletedListener`:
 | 
						|
+
 | 
						|
Project deletion
 | 
						|
 | 
						|
* `com.google.gerrit.extensions.events.HeadUpdatedListener`:
 | 
						|
+
 | 
						|
Update of HEAD on a project
 | 
						|
 | 
						|
* `com.google.gerrit.extensions.events.UsageDataPublishedListener`:
 | 
						|
+
 | 
						|
Publication of usage data
 | 
						|
 | 
						|
* `com.google.gerrit.extensions.events.GarbageCollectorListener`:
 | 
						|
+
 | 
						|
Garbage collection ran on a project
 | 
						|
 | 
						|
* `com.google.gerrit.server.extensions.events.ChangeIndexedListener`:
 | 
						|
+
 | 
						|
Update of the change secondary index
 | 
						|
 | 
						|
* `com.google.gerrit.server.extensions.events.AccountIndexedListener`:
 | 
						|
+
 | 
						|
Update of the account secondary index
 | 
						|
 | 
						|
* `com.google.gerrit.server.extensions.events.GroupIndexedListener`:
 | 
						|
+
 | 
						|
Update of the group secondary index
 | 
						|
 | 
						|
* `com.google.gerrit.server.extensions.events.ProjectIndexedListener`:
 | 
						|
+
 | 
						|
Update of the project secondary index
 | 
						|
 | 
						|
* `com.google.gerrit.httpd.WebLoginListener`:
 | 
						|
+
 | 
						|
User login or logout interactively on the Web user interface.
 | 
						|
 | 
						|
The event listener is under the Gerrit http package to automatically
 | 
						|
inherit the javax.servlet.http dependencies and allowing to influence
 | 
						|
the login or logout flow with additional redirections.
 | 
						|
 | 
						|
[[stream-events]]
 | 
						|
== Sending Events to the Events Stream
 | 
						|
 | 
						|
Plugins may send events to the events stream where consumers of
 | 
						|
Gerrit's `stream-events` ssh command will receive them.
 | 
						|
 | 
						|
To send an event, the plugin must invoke one of the `postEvent`
 | 
						|
methods in the `EventDispatcher` interface, passing an instance of
 | 
						|
its own custom event class derived from
 | 
						|
`com.google.gerrit.server.events.Event`.
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
import com.google.gerrit.common.EventDispatcher;
 | 
						|
import com.google.gerrit.exceptions.StorageException;
 | 
						|
import com.google.gerrit.extensions.registration.DynamicItem;
 | 
						|
import com.google.inject.Inject;
 | 
						|
 | 
						|
class MyPlugin {
 | 
						|
  private final DynamicItem<EventDispatcher> eventDispatcher;
 | 
						|
 | 
						|
  @Inject
 | 
						|
  myPlugin(DynamicItem<EventDispatcher> eventDispatcher) {
 | 
						|
    this.eventDispatcher = eventDispatcher;
 | 
						|
  }
 | 
						|
 | 
						|
  private void postEvent(MyPluginEvent event) {
 | 
						|
    try {
 | 
						|
      eventDispatcher.get().postEvent(event);
 | 
						|
    } catch (StorageException e) {
 | 
						|
      // error handling
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
Plugins which define new Events should register them via the
 | 
						|
`com.google.gerrit.server.events.EventTypes.register()` method.
 | 
						|
This will make the EventType known to the system. Deserializing
 | 
						|
events with the
 | 
						|
`com.google.gerrit.server.events.EventDeserializer` class requires
 | 
						|
that the event be registered in EventTypes.
 | 
						|
 | 
						|
== Modifying the Stream Event Flow
 | 
						|
 | 
						|
It is possible to modify the stream event flow from plugins by registering
 | 
						|
an `com.google.gerrit.server.events.EventDispatcher`. A plugin may register
 | 
						|
a Dispatcher class to replace the internal Dispatcher. EventDispatcher is
 | 
						|
a DynamicItem, so Gerrit may only have one copy.
 | 
						|
 | 
						|
[[validation]]
 | 
						|
== Validation Listeners
 | 
						|
 | 
						|
Certain operations in Gerrit can be validated by plugins by
 | 
						|
implementing the corresponding link:config-validation.html[listeners].
 | 
						|
 | 
						|
[[change-message-modifier]]
 | 
						|
== Change Message Modifier
 | 
						|
 | 
						|
`com.google.gerrit.server.git.ChangeMessageModifier`:
 | 
						|
plugins implementing this can modify commit message of the change being
 | 
						|
submitted by Rebase Always and Cherry Pick submit strategies as well as
 | 
						|
change being queried with COMMIT_FOOTERS option.
 | 
						|
 | 
						|
[[merge-super-set-computation]]
 | 
						|
== Merge Super Set Computation
 | 
						|
 | 
						|
The algorithm to compute the merge super set to detect changes that
 | 
						|
should be submitted together can be customized by implementing
 | 
						|
`com.google.gerrit.server.git.MergeSuperSetComputation`.
 | 
						|
MergeSuperSetComputation is a DynamicItem, so Gerrit may only have one
 | 
						|
implementation.
 | 
						|
 | 
						|
[[receive-pack]]
 | 
						|
== Receive Pack Initializers
 | 
						|
 | 
						|
Plugins may provide ReceivePackInitializer instances, which will be
 | 
						|
invoked by Gerrit just before a ReceivePack instance will be used.
 | 
						|
Usually, plugins will make use of the setXXX methods on the ReceivePack
 | 
						|
to set additional properties on it.
 | 
						|
 | 
						|
The interactions with the core Gerrit ReceivePack initialization and
 | 
						|
between ReceivePackInitializers can be complex. Please read the
 | 
						|
ReceivePack Javadoc and Gerrit AsyncReceiveCommits implementation
 | 
						|
carefully.
 | 
						|
 | 
						|
[[post-receive-hook]]
 | 
						|
== Post Receive-Pack Hooks
 | 
						|
 | 
						|
Plugins may register PostReceiveHook instances in order to get
 | 
						|
notified when JGit successfully receives a pack. This may be useful
 | 
						|
for those plugins which would like to monitor changes in Git
 | 
						|
repositories.
 | 
						|
 | 
						|
[[upload-pack]]
 | 
						|
== Upload Pack Initializers
 | 
						|
 | 
						|
Plugins may provide UploadPackInitializer instances, which will be
 | 
						|
invoked by Gerrit just before a UploadPack instance will be used.
 | 
						|
Usually, plugins will make use of the setXXX methods on the UploadPack
 | 
						|
to set additional properties on it.
 | 
						|
 | 
						|
The interactions with the core Gerrit UploadPack initialization and
 | 
						|
between UploadPackInitializers can be complex. Please read the
 | 
						|
UploadPack Javadoc and Gerrit Upload/UploadFactory implementations
 | 
						|
carefully.
 | 
						|
 | 
						|
[[pre-upload-hook]]
 | 
						|
== Pre Upload-Pack Hooks
 | 
						|
 | 
						|
Plugins may register PreUploadHook instances in order to get
 | 
						|
notified when JGit is about to upload a pack. This may be useful
 | 
						|
for those plugins which would like to monitor usage in Git
 | 
						|
repositories.
 | 
						|
 | 
						|
[[post-upload-hook]]
 | 
						|
== Post Upload-Pack Hooks
 | 
						|
 | 
						|
Plugins may register PostUploadHook instances in order to get notified after
 | 
						|
JGit is done uploading a pack.
 | 
						|
 | 
						|
[[ssh]]
 | 
						|
== SSH Commands
 | 
						|
 | 
						|
Plugins may provide commands that can be accessed through the SSH
 | 
						|
interface (extensions do not have this option).
 | 
						|
 | 
						|
Command implementations must extend the base class SshCommand:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
import com.google.gerrit.sshd.SshCommand;
 | 
						|
import com.google.gerrit.sshd.CommandMetaData;
 | 
						|
 | 
						|
@CommandMetaData(name="print", description="Print hello command")
 | 
						|
class PrintHello extends SshCommand {
 | 
						|
  @Override
 | 
						|
  protected void run() {
 | 
						|
    stdout.print("Hello\n");
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
If no Guice modules are declared in the manifest, SSH commands may
 | 
						|
use auto-registration by providing an `@Export` annotation:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
import com.google.gerrit.extensions.annotations.Export;
 | 
						|
import com.google.gerrit.sshd.SshCommand;
 | 
						|
 | 
						|
@Export("print")
 | 
						|
class PrintHello extends SshCommand {
 | 
						|
  @Override
 | 
						|
  protected void run() {
 | 
						|
    stdout.print("Hello\n");
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
If explicit registration is being used, a Guice module must be
 | 
						|
supplied to register the SSH command and declared in the manifest
 | 
						|
with the `Gerrit-SshModule` attribute:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
import com.google.gerrit.sshd.PluginCommandModule;
 | 
						|
 | 
						|
class MyCommands extends PluginCommandModule {
 | 
						|
  @Override
 | 
						|
  protected void configureCommands() {
 | 
						|
    command(PrintHello.class);
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
For a plugin installed as name `helloworld`, the command implemented
 | 
						|
by PrintHello class will be available to users as:
 | 
						|
 | 
						|
----
 | 
						|
$ ssh -p 29418 review.example.com helloworld print
 | 
						|
----
 | 
						|
 | 
						|
[[multiple-commands]]
 | 
						|
=== Multiple Commands bound to one implementation
 | 
						|
 | 
						|
Multiple SSH commands can be bound to the same implementation class. For
 | 
						|
example a Gerrit Shell plugin can bind different shell commands to the same
 | 
						|
implementation class:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
public class SshShellModule extends PluginCommandModule {
 | 
						|
  @Override
 | 
						|
  protected void configureCommands() {
 | 
						|
    command("ls").to(ShellCommand.class);
 | 
						|
    command("ps").to(ShellCommand.class);
 | 
						|
    [...]
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
With the possible implementation:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
public class ShellCommand extends SshCommand {
 | 
						|
  @Override
 | 
						|
  protected void run() throws UnloggedFailure {
 | 
						|
    String cmd = getName().substring(getPluginName().length() + 1);
 | 
						|
    ProcessBuilder proc = new ProcessBuilder(cmd);
 | 
						|
    Process cmd = proc.start();
 | 
						|
    [...]
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
And the call:
 | 
						|
 | 
						|
----
 | 
						|
$ ssh -p 29418 review.example.com shell ls
 | 
						|
$ ssh -p 29418 review.example.com shell ps
 | 
						|
----
 | 
						|
 | 
						|
[[root-level-commands]]
 | 
						|
=== Root Level Commands
 | 
						|
 | 
						|
Single command plugins are also supported. In this scenario plugin binds
 | 
						|
SSH command to its own name. `SshModule` must inherit from
 | 
						|
`SingleCommandPluginModule` class:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
public class SshModule extends SingleCommandPluginModule {
 | 
						|
 @Override
 | 
						|
 protected void configure(LinkedBindingBuilder<Command> b) {
 | 
						|
    b.to(ShellCommand.class);
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
If the plugin above is deployed under sh.jar file in `$site/plugins`
 | 
						|
directory, generic commands can be called without specifying the
 | 
						|
actual SSH command. Note in the example below, that the called commands
 | 
						|
`ls` and `ps` was not explicitly bound:
 | 
						|
 | 
						|
----
 | 
						|
$ ssh -p 29418 review.example.com sh ls
 | 
						|
$ ssh -p 29418 review.example.com sh ps
 | 
						|
----
 | 
						|
 | 
						|
[[search_operators]]
 | 
						|
== Search Operators
 | 
						|
 | 
						|
Plugins can define new search operators to extend change searching by
 | 
						|
implementing the `ChangeQueryBuilder.ChangeOperatorFactory` interface
 | 
						|
and registering it to an operator name in the plugin module's
 | 
						|
`configure()` method.  The search operator name is defined during
 | 
						|
registration via the DynamicMap annotation mechanism.  The plugin
 | 
						|
name will get appended to the annotated name, with an underscore
 | 
						|
in between, leading to the final operator name.  An example
 | 
						|
registration looks like this:
 | 
						|
 | 
						|
    bind(ChangeOperatorFactory.class)
 | 
						|
       .annotatedWith(Exports.named("sample"))
 | 
						|
       .to(SampleOperator.class);
 | 
						|
 | 
						|
If this is registered in the `myplugin` plugin, then the resulting
 | 
						|
operator will be named `sample_myplugin`.
 | 
						|
 | 
						|
The search operator itself is implemented by ensuring that the
 | 
						|
`create()` method of the class implementing the
 | 
						|
`ChangeQueryBuilder.ChangeOperatorFactory` interface returns a
 | 
						|
`Predicate<ChangeData>`.  Here is a sample operator factory
 | 
						|
definition which creates a `MyPredicate`:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
public class SampleOperator
 | 
						|
    implements ChangeQueryBuilder.ChangeOperatorFactory {
 | 
						|
  public static class MyPredicate extends PostFilterPredicate<ChangeData> {
 | 
						|
    ...
 | 
						|
  }
 | 
						|
 | 
						|
  @Override
 | 
						|
  public Predicate<ChangeData> create(ChangeQueryBuilder builder, String value)
 | 
						|
      throws QueryParseException {
 | 
						|
    return new MyPredicate(value);
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
[[search_operands]]
 | 
						|
=== Search Operands ===
 | 
						|
 | 
						|
Plugins can define new search operands to extend change searching.
 | 
						|
Plugin methods implementing search operands (returning a
 | 
						|
`Predicate<ChangeData>`), must be defined on a class implementing
 | 
						|
one of the `ChangeQueryBuilder.ChangeOperandsFactory` interfaces
 | 
						|
(.e.g., ChangeQueryBuilder.ChangeHasOperandFactory or
 | 
						|
ChangeQueryBuilder.ChangeIsOperandFactory).  The specific
 | 
						|
`ChangeOperandFactory` class must also be bound to the `DynamicSet` from
 | 
						|
a module's `configure()` method in the plugin.
 | 
						|
 | 
						|
The new operand, when used in a search would appear as:
 | 
						|
  operatorName:operandName_pluginName
 | 
						|
 | 
						|
A sample `ChangeHasOperandFactory` class implementing, and registering, a
 | 
						|
new `has:sample_pluginName` operand is shown below:
 | 
						|
 | 
						|
====
 | 
						|
  public class SampleHasOperand implements ChangeHasOperandFactory {
 | 
						|
    public static class Module extends AbstractModule {
 | 
						|
      @Override
 | 
						|
      protected void configure() {
 | 
						|
        bind(ChangeHasOperandFactory.class)
 | 
						|
            .annotatedWith(Exports.named("sample")
 | 
						|
            .to(SampleHasOperand.class);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    @Override
 | 
						|
    public Predicate<ChangeData> create(ChangeQueryBuilder builder)
 | 
						|
        throws QueryParseException {
 | 
						|
      return new HasSamplePredicate();
 | 
						|
    }
 | 
						|
====
 | 
						|
 | 
						|
[[command_options]]
 | 
						|
=== Command Options ===
 | 
						|
 | 
						|
Plugins can provide additional options for each of the gerrit ssh and the
 | 
						|
REST API commands by implementing the DynamicBean interface and registering
 | 
						|
it to a command class name in the plugin module's `configure()` method. The
 | 
						|
plugin's name will be prepended to the name of each @Option annotation found
 | 
						|
on the DynamicBean object provided by the plugin. The example below shows a
 | 
						|
plugin that adds an option to log a value from the gerrit 'ban-commits'
 | 
						|
ssh command.
 | 
						|
 | 
						|
[source, java]
 | 
						|
----
 | 
						|
public class SshModule extends AbstractModule {
 | 
						|
  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 | 
						|
 | 
						|
  @Override
 | 
						|
  protected void configure() {
 | 
						|
    bind(DynamicOptions.DynamicBean.class)
 | 
						|
        .annotatedWith(Exports.named(
 | 
						|
        com.google.gerrit.sshd.commands.BanCommitCommand.class))
 | 
						|
        .to(BanOptions.class);
 | 
						|
  }
 | 
						|
 | 
						|
  public static class BanOptions implements DynamicOptions.DynamicBean {
 | 
						|
    @Option(name = "--log", aliases = { "-l" }, usage = "Say Hello in the Log")
 | 
						|
    private void parse(String arg) {
 | 
						|
      logger.atSevere().log("Say Hello in the Log %s", arg);
 | 
						|
    }
 | 
						|
  }
 | 
						|
----
 | 
						|
 | 
						|
To provide additional Guice bindings for options to a command in another classloader, bind a
 | 
						|
ModulesClassNamesProvider which provides the name of your Modules needed for your DynamicBean
 | 
						|
in the other classLoader.
 | 
						|
 | 
						|
Do this by binding to the name of the command you are going to bind to and providing an
 | 
						|
Iterable of Module names to instantiate and add to the Injector used to instantiate the
 | 
						|
DynamicBean in the other classLoader. This interface supports running LifecycleListeners
 | 
						|
which are defined by the Modules being provided. The duration of the lifecycle starts when
 | 
						|
a ssh or http request starts and ends when the request completes.
 | 
						|
 | 
						|
[source, java]
 | 
						|
----
 | 
						|
  bind(DynamicOptions.DynamicBean.class)
 | 
						|
      .annotatedWith(Exports.named(
 | 
						|
          "com.google.gerrit.plugins.otherplugin.command"))
 | 
						|
      .to(MyOptionsModulesClassNamesProvider.class);
 | 
						|
 | 
						|
  static class MyOptionsModulesClassNamesProvider implements DynamicOptions.ModulesClassNamesProvider {
 | 
						|
    {@literal @}Override
 | 
						|
    public String getClassName() {
 | 
						|
      return "com.googlesource.gerrit.plugins.myplugin.CommandOptions";
 | 
						|
    }
 | 
						|
    {@literal @}Override
 | 
						|
    public Iterable<String> getModulesClassNames()() {
 | 
						|
      return "com.googlesource.gerrit.plugins.myplugin.MyOptionsModule";
 | 
						|
    }
 | 
						|
  }
 | 
						|
----
 | 
						|
 | 
						|
=== Calling Command Options ===
 | 
						|
 | 
						|
Within an OptionHandler, during the processing of an option, plugins can
 | 
						|
provide and call extra parameters on the current command during parsing
 | 
						|
simulating as if they had been passed from the command line originally.
 | 
						|
 | 
						|
To call additional parameters from within an option handler, instantiate
 | 
						|
the com.google.gerrit.util.cli.CmdLineParser.Parameters class with the
 | 
						|
existing parameters, and then call callParameters() with the additional
 | 
						|
parameters to be parsed. OptionHandlers may optionally pass this class to
 | 
						|
other methods which may then both parse/consume more parameters and call
 | 
						|
additional parameters.
 | 
						|
 | 
						|
When calling command options not provided by your plugin, there is always
 | 
						|
a risk that the options may not exist, perhaps because the options being
 | 
						|
called are to be provided by another plugin, and said plugin is not
 | 
						|
currently installed. To protect againt this situation, it is possible to
 | 
						|
define an option as being dependent on other options using the
 | 
						|
@RequiresOptions() annotation. If the required options are not all not
 | 
						|
currently present, then the dependent option will not be available or
 | 
						|
visible in the help.
 | 
						|
 | 
						|
The example below shows a plugin that adds a "--special" option (perhaps
 | 
						|
for use with the Query command) that calls (and requires) the
 | 
						|
"--format json" option.
 | 
						|
 | 
						|
[source, java]
 | 
						|
----
 | 
						|
public class JsonOutputOptionHandler<T> extends OptionHandler<T> {
 | 
						|
  protected com.google.gerrit.util.cli.CmdLineParser.MyParser myParser;
 | 
						|
 | 
						|
  public JsonOutputOptionHandler(CmdLineParser parser, OptionDef option, Setter<? super T> setter) {
 | 
						|
    super(parser, option, setter);
 | 
						|
    myParser = (com.google.gerrit.util.cli.CmdLineParser.MyParser) owner;
 | 
						|
  }
 | 
						|
 | 
						|
  @Override
 | 
						|
  public int parseArguments(org.kohsuke.args4j.spi.Parameters params) throws CmdLineException {
 | 
						|
    new Parameters(params, myParser).callParameters("--format", "json");
 | 
						|
    setter.addValue(true);
 | 
						|
    return 0; // we didn't consume any additional args
 | 
						|
  }
 | 
						|
 | 
						|
  @Override
 | 
						|
  public String getDefaultMetaVariable() {
 | 
						|
   ...
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
@RequiresOptions("--format")
 | 
						|
@Option(
 | 
						|
  name = "--special",
 | 
						|
  usage = "ouptut results using json",
 | 
						|
  handler = JsonOutputOptionHandler.class
 | 
						|
)
 | 
						|
boolean json;
 | 
						|
----
 | 
						|
 | 
						|
[[query_attributes]]
 | 
						|
=== Change Attributes ===
 | 
						|
 | 
						|
==== ChangePluginDefinedInfoFactory
 | 
						|
 | 
						|
Plugins can provide additional attributes to be returned from the Get Change and
 | 
						|
Query Change APIs by implementing the `ChangePluginDefinedInfoFactory` interface
 | 
						|
and adding it to the `DynamicSet` in the plugin module's `configure()` method.
 | 
						|
The new attribute(s) will be output under a `plugin` attribute in the change
 | 
						|
output. This can be further controlled by registering a class containing @Option
 | 
						|
declarations as a `DynamicBean`, annotated with the HTTP/SSH commands on
 | 
						|
which the options should be available.
 | 
						|
 | 
						|
The example below shows a plugin that adds two attributes (`exampleName` and
 | 
						|
`changeValue`), to the change query output, when the query command is provided
 | 
						|
the `--myplugin-name--all` option.
 | 
						|
 | 
						|
[source, java]
 | 
						|
----
 | 
						|
public class Module extends AbstractModule {
 | 
						|
  @Override
 | 
						|
  protected void configure() {
 | 
						|
    // Register attribute factory.
 | 
						|
    DynamicSet.bind(binder(), ChangePluginDefinedInfoFactory.class)
 | 
						|
        .to(AttributeFactory.class);
 | 
						|
 | 
						|
    // Register options for GET /changes/X/change and /changes/X/detail.
 | 
						|
    bind(DynamicBean.class)
 | 
						|
        .annotatedWith(Exports.named(GetChange.class))
 | 
						|
        .to(MyChangeOptions.class);
 | 
						|
 | 
						|
    // Register options for GET /changes/?q=...
 | 
						|
    bind(DynamicBean.class)
 | 
						|
        .annotatedWith(Exports.named(QueryChanges.class))
 | 
						|
        .to(MyChangeOptions.class);
 | 
						|
 | 
						|
    // Register options for ssh gerrit query.
 | 
						|
    bind(DynamicBean.class)
 | 
						|
        .annotatedWith(Exports.named(Query.class))
 | 
						|
        .to(MyChangeOptions.class);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
public class MyChangeOptions implements DynamicBean {
 | 
						|
  @Option(name = "--all", usage = "Include plugin output")
 | 
						|
  public boolean all = false;
 | 
						|
}
 | 
						|
 | 
						|
public class AttributeFactory implements ChangePluginDefinedInfoFactory {
 | 
						|
  protected MyChangeOptions options;
 | 
						|
 | 
						|
  public class PluginAttribute extends PluginDefinedInfo {
 | 
						|
    public String exampleName;
 | 
						|
    public String changeValue;
 | 
						|
 | 
						|
    public PluginAttribute(ChangeData c) {
 | 
						|
      this.exampleName = "Attribute Example";
 | 
						|
      this.changeValue = Integer.toString(c.getId().get());
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  @Override
 | 
						|
  public Map<Change.Id, PluginDefinedInfo> createPluginDefinedInfos(
 | 
						|
      Collection<ChangeData> cds, BeanProvider bp, String plugin) {
 | 
						|
    if (options == null) {
 | 
						|
      options = (MyChangeOptions) bp.getDynamicBean(plugin);
 | 
						|
    }
 | 
						|
    Map<Change.Id, PluginDefinedInfo> out = new HashMap<>();
 | 
						|
    if (options.all) {
 | 
						|
      cds.forEach(cd -> out.put(cd.getId(), new PluginAttribute(cd)));
 | 
						|
      return out;
 | 
						|
    }
 | 
						|
    return ImmutableMap.of();
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
Example
 | 
						|
----
 | 
						|
 | 
						|
ssh -p 29418 localhost gerrit query --myplugin-name--all "change:1" --format json
 | 
						|
 | 
						|
Output:
 | 
						|
 | 
						|
{
 | 
						|
   "url" : "http://localhost:8080/1",
 | 
						|
   "plugins" : [
 | 
						|
      {
 | 
						|
         "name" : "myplugin-name",
 | 
						|
         "exampleName" : "Attribute Example",
 | 
						|
         "changeValue" : "1"
 | 
						|
      }
 | 
						|
   ],
 | 
						|
    ...
 | 
						|
}
 | 
						|
 | 
						|
curl http://localhost:8080/changes/1?myplugin-name--all
 | 
						|
 | 
						|
Output:
 | 
						|
 | 
						|
{
 | 
						|
  "_number": 1,
 | 
						|
  ...
 | 
						|
  "plugins": [
 | 
						|
    {
 | 
						|
      "name": "myplugin-name",
 | 
						|
      "example_name": "Attribute Example",
 | 
						|
      "change_value": "1"
 | 
						|
    }
 | 
						|
  ],
 | 
						|
  ...
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
Runtime exceptions generated by the implementors of ChangePluginDefinedInfoFactory
 | 
						|
are encapsulated in PluginDefinedInfo objects which are part of SSH/REST query output.
 | 
						|
 | 
						|
Implementors of the `ChangePluginDefinedInfoFactory` interface should check whether
 | 
						|
they need to contribute to the link:#change-etag-computation[change ETag computation]
 | 
						|
to prevent callers using ETags from potentially seeing outdated plugin attributes.
 | 
						|
 | 
						|
[[simple-configuration]]
 | 
						|
== Simple Configuration in `gerrit.config`
 | 
						|
 | 
						|
In Gerrit, global configuration is stored in the `gerrit.config` file.
 | 
						|
If a plugin needs global configuration, this configuration should be
 | 
						|
stored in a `plugin` subsection in the `gerrit.config` file.
 | 
						|
 | 
						|
This approach of storing the plugin configuration is only suitable for
 | 
						|
plugins that have a simple configuration that only consists of
 | 
						|
key-value pairs. With this approach it is not possible to have
 | 
						|
subsections in the plugin configuration. Plugins that require a complex
 | 
						|
configuration need to store their configuration in their
 | 
						|
link:#configuration[own configuration file] where they can make use of
 | 
						|
subsections. On the other hand storing the plugin configuration in a
 | 
						|
'plugin' subsection in the `gerrit.config` file has the advantage that
 | 
						|
administrators have all configuration parameters in one file, instead
 | 
						|
of having one configuration file per plugin.
 | 
						|
 | 
						|
To avoid conflicts with other plugins, it is recommended that plugins
 | 
						|
only use the `plugin` subsection with their own name. For example the
 | 
						|
`helloworld` plugin should store its configuration in the
 | 
						|
`plugin.helloworld` subsection:
 | 
						|
 | 
						|
----
 | 
						|
[plugin "helloworld"]
 | 
						|
  language = Latin
 | 
						|
----
 | 
						|
 | 
						|
Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
 | 
						|
plugin can easily access its configuration and there is no need for a
 | 
						|
plugin to parse the `gerrit.config` file on its own:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
@Inject
 | 
						|
private com.google.gerrit.server.config.PluginConfigFactory cfg;
 | 
						|
 | 
						|
[...]
 | 
						|
 | 
						|
String language = cfg.getFromGerritConfig("helloworld")
 | 
						|
                     .getString("language", "English");
 | 
						|
----
 | 
						|
 | 
						|
[[configuration]]
 | 
						|
== Configuration in own config file
 | 
						|
 | 
						|
Plugins can store their configuration in an own configuration file.
 | 
						|
This makes sense if the plugin configuration is rather complex and
 | 
						|
requires the usage of subsections. Plugins that have a simple
 | 
						|
key-value pair configuration can store their configuration in a
 | 
						|
link:#simple-configuration[`plugin` subsection of the `gerrit.config`
 | 
						|
file].
 | 
						|
 | 
						|
The plugin configuration file must be named after the plugin and must
 | 
						|
be located in the `etc` folder of the review site. For example a
 | 
						|
configuration file for a `default-reviewer` plugin could look like
 | 
						|
this:
 | 
						|
 | 
						|
.$site_path/etc/default-reviewer.config
 | 
						|
----
 | 
						|
[branch "refs/heads/master"]
 | 
						|
  reviewer = Project Owners
 | 
						|
  reviewer = john.doe@example.com
 | 
						|
[match "file:^.*\.txt"]
 | 
						|
  reviewer = My Info Developers
 | 
						|
----
 | 
						|
 | 
						|
Plugins that have sensitive configuration settings can store those settings in
 | 
						|
an own secure configuration file. The plugin's secure configuration file must be
 | 
						|
named after the plugin and must be located in the `etc` folder of the review
 | 
						|
site. For example a secure configuration file for a `default-reviewer` plugin
 | 
						|
could look like this:
 | 
						|
 | 
						|
.$site_path/etc/default-reviewer.secure.config
 | 
						|
----
 | 
						|
[auth]
 | 
						|
  password = secret
 | 
						|
----
 | 
						|
 | 
						|
Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
 | 
						|
plugin can easily access its configuration:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
@Inject
 | 
						|
private com.google.gerrit.server.config.PluginConfigFactory cfg;
 | 
						|
 | 
						|
[...]
 | 
						|
 | 
						|
String[] reviewers = cfg.getGlobalPluginConfig("default-reviewer")
 | 
						|
                        .getStringList("branch", "refs/heads/master", "reviewer");
 | 
						|
String password = cfg.getGlobalPluginConfig("default-reviewer")
 | 
						|
                     .getString("auth", null, "password");
 | 
						|
----
 | 
						|
 | 
						|
 | 
						|
[[simple-project-specific-configuration]]
 | 
						|
== Simple Project Specific Configuration in `project.config`
 | 
						|
 | 
						|
In Gerrit, project specific configuration is stored in the project's
 | 
						|
`project.config` file on the `refs/meta/config` branch.  If a plugin
 | 
						|
needs configuration on project level (e.g. to enable its functionality
 | 
						|
only for certain projects), this configuration should be stored in a
 | 
						|
`plugin` subsection in the project's `project.config` file.
 | 
						|
 | 
						|
This approach of storing the plugin configuration is only suitable for
 | 
						|
plugins that have a simple configuration that only consists of
 | 
						|
key-value pairs. With this approach it is not possible to have
 | 
						|
subsections in the plugin configuration. Plugins that require a complex
 | 
						|
configuration need to store their configuration in their
 | 
						|
link:#project-specific-configuration[own configuration file] where they
 | 
						|
can make use of subsections. On the other hand storing the plugin
 | 
						|
configuration in a 'plugin' subsection in the `project.config` file has
 | 
						|
the advantage that project owners have all configuration parameters in
 | 
						|
one file, instead of having one configuration file per plugin.
 | 
						|
 | 
						|
To avoid conflicts with other plugins, it is recommended that plugins
 | 
						|
only use the `plugin` subsection with their own name. For example the
 | 
						|
`helloworld` plugin should store its configuration in the
 | 
						|
`plugin.helloworld` subsection:
 | 
						|
 | 
						|
----
 | 
						|
  [plugin "helloworld"]
 | 
						|
    enabled = true
 | 
						|
----
 | 
						|
 | 
						|
Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
 | 
						|
plugin can easily access its project specific configuration and there
 | 
						|
is no need for a plugin to parse the `project.config` file on its own:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
@Inject
 | 
						|
private com.google.gerrit.server.config.PluginConfigFactory cfg;
 | 
						|
 | 
						|
[...]
 | 
						|
 | 
						|
boolean enabled = cfg.getFromProjectConfig(project, "helloworld")
 | 
						|
                     .getBoolean("enabled", false);
 | 
						|
----
 | 
						|
 | 
						|
It is also possible to get missing configuration parameters inherited
 | 
						|
from the parent projects:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
@Inject
 | 
						|
private com.google.gerrit.server.config.PluginConfigFactory cfg;
 | 
						|
 | 
						|
[...]
 | 
						|
 | 
						|
boolean enabled = cfg.getFromProjectConfigWithInheritance(project, "helloworld")
 | 
						|
                     .getBoolean("enabled", false);
 | 
						|
----
 | 
						|
 | 
						|
Project owners can edit the project configuration by fetching the
 | 
						|
`refs/meta/config` branch, editing the `project.config` file and
 | 
						|
pushing the commit back.
 | 
						|
 | 
						|
Plugin configuration values that are stored in the `project.config`
 | 
						|
file can be exposed in the ProjectInfoScreen to allow project owners
 | 
						|
to see and edit them from the UI.
 | 
						|
 | 
						|
For this an instance of `ProjectConfigEntry` needs to be bound for each
 | 
						|
parameter. The export name must be a valid Git variable name. The
 | 
						|
variable name is case-insensitive, allows only alphanumeric characters
 | 
						|
and '-', and must start with an alphabetic character.
 | 
						|
 | 
						|
The example below shows how the parameters `plugin.helloworld.enabled`
 | 
						|
and `plugin.helloworld.language` are bound to be editable from the
 | 
						|
Web UI. For the parameter `plugin.helloworld.enabled` "Enable Greeting"
 | 
						|
is provided as display name and the default value is set to `true`.
 | 
						|
For the parameter `plugin.helloworld.language` "Preferred Language"
 | 
						|
is provided as display name and "en" is set as default value.
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
class Module extends AbstractModule {
 | 
						|
  @Override
 | 
						|
  protected void configure() {
 | 
						|
    bind(ProjectConfigEntry.class)
 | 
						|
        .annotatedWith(Exports.named("enabled"))
 | 
						|
        .toInstance(new ProjectConfigEntry("Enable Greeting", true));
 | 
						|
    bind(ProjectConfigEntry.class)
 | 
						|
        .annotatedWith(Exports.named("language"))
 | 
						|
        .toInstance(new ProjectConfigEntry("Preferred Language", "en"));
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
By overwriting the `onUpdate` method of `ProjectConfigEntry` plugins
 | 
						|
can be notified when this configuration parameter is updated on a
 | 
						|
project.
 | 
						|
 | 
						|
[[configuring-groups]]
 | 
						|
=== Referencing groups in `project.config`
 | 
						|
 | 
						|
Plugins can refer to groups so that when they are renamed, the project
 | 
						|
config will also be updated in this section. The proper format to use is
 | 
						|
the same as for any other group reference in the `project.config`, as shown below.
 | 
						|
 | 
						|
----
 | 
						|
group group_name
 | 
						|
----
 | 
						|
 | 
						|
The file `groups` must also contains the mapping of the group name and its UUID,
 | 
						|
refer to link:config-project-config.html#file-groups[file groups]
 | 
						|
 | 
						|
[[project-specific-configuration]]
 | 
						|
== Project Specific Configuration in own config file
 | 
						|
 | 
						|
Plugins can store their project specific configuration in an own
 | 
						|
configuration file in the projects `refs/meta/config` branch.
 | 
						|
This makes sense if the plugins project specific configuration is
 | 
						|
rather complex and requires the usage of subsections. Plugins that
 | 
						|
have a simple key-value pair configuration can store their project
 | 
						|
specific configuration in a link:#simple-project-specific-configuration[
 | 
						|
`plugin` subsection of the `project.config` file].
 | 
						|
 | 
						|
The plugin configuration file in the `refs/meta/config` branch must be
 | 
						|
named after the plugin. For example a configuration file for a
 | 
						|
`default-reviewer` plugin could look like this:
 | 
						|
 | 
						|
.default-reviewer.config
 | 
						|
----
 | 
						|
[branch "refs/heads/master"]
 | 
						|
  reviewer = Project Owners
 | 
						|
  reviewer = john.doe@example.com
 | 
						|
[match "file:^.*\.txt"]
 | 
						|
  reviewer = My Info Developers
 | 
						|
----
 | 
						|
 | 
						|
Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
 | 
						|
plugin can easily access its project specific configuration:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
@Inject
 | 
						|
private com.google.gerrit.server.config.PluginConfigFactory cfg;
 | 
						|
 | 
						|
[...]
 | 
						|
 | 
						|
String[] reviewers = cfg.getProjectPluginConfig(project, "default-reviewer")
 | 
						|
                        .getStringList("branch", "refs/heads/master", "reviewer");
 | 
						|
----
 | 
						|
 | 
						|
It is also possible to get missing configuration parameters inherited
 | 
						|
from the parent projects:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
@Inject
 | 
						|
private com.google.gerrit.server.config.PluginConfigFactory cfg;
 | 
						|
 | 
						|
[...]
 | 
						|
 | 
						|
String[] reviewers = cfg.getProjectPluginConfigWithInheritance(project, "default-reviewer")
 | 
						|
                        .getStringList("branch", "refs/heads/master", "reviewer");
 | 
						|
----
 | 
						|
 | 
						|
Project owners can edit the project configuration by fetching the
 | 
						|
`refs/meta/config` branch, editing the `<plugin-name>.config` file and
 | 
						|
pushing the commit back.
 | 
						|
 | 
						|
== React on changes in project configuration
 | 
						|
 | 
						|
If a plugin wants to react on changes in the project configuration, it
 | 
						|
can implement a `GitReferenceUpdatedListener` and filter on events for
 | 
						|
the `refs/meta/config` branch:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
public class MyListener implements GitReferenceUpdatedListener {
 | 
						|
 | 
						|
  private final MetaDataUpdate.Server metaDataUpdateFactory;
 | 
						|
 | 
						|
  @Inject
 | 
						|
  MyListener(MetaDataUpdate.Server metaDataUpdateFactory) {
 | 
						|
    this.metaDataUpdateFactory = metaDataUpdateFactory;
 | 
						|
  }
 | 
						|
 | 
						|
  @Override
 | 
						|
  public void onGitReferenceUpdated(Event event) {
 | 
						|
    if (event.getRefName().equals(RefNames.REFS_CONFIG)) {
 | 
						|
      Project.NameKey p = new Project.NameKey(event.getProjectName());
 | 
						|
      try {
 | 
						|
        ProjectConfig oldCfg = parseConfig(p, event.getOldObjectId());
 | 
						|
        ProjectConfig newCfg = parseConfig(p, event.getNewObjectId());
 | 
						|
 | 
						|
        if (oldCfg != null && newCfg != null
 | 
						|
            && !oldCfg.getProject().getSubmitType().equals(newCfg.getProject().getSubmitType())) {
 | 
						|
          // submit type has changed
 | 
						|
          ...
 | 
						|
        }
 | 
						|
      } catch (IOException | ConfigInvalidException e) {
 | 
						|
        ...
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  private ProjectConfig parseConfig(Project.NameKey p, String idStr)
 | 
						|
      throws IOException, ConfigInvalidException, RepositoryNotFoundException {
 | 
						|
    ObjectId id = ObjectId.fromString(idStr);
 | 
						|
    if (ObjectId.zeroId().equals(id)) {
 | 
						|
      return null;
 | 
						|
    }
 | 
						|
    return ProjectConfig.read(metaDataUpdateFactory.create(p), id);
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
== Trace Event origin
 | 
						|
 | 
						|
When plugins are installed in a multi-master setups it can be useful to know
 | 
						|
the Gerrit `instanceId` of the server that has generated an Event.
 | 
						|
 | 
						|
E.g. A plugin that sends an instance message for every comment on a change may
 | 
						|
want to react only if the event is generated on the local Gerrit master, for
 | 
						|
avoiding duplicating the notifications.
 | 
						|
 | 
						|
If link:config-gerrit.html[instanceId] is set, each Event will contain its
 | 
						|
origin in the `instanceId` field.
 | 
						|
 | 
						|
Here and example of ref-updated JSON event payload with `instanceId`:
 | 
						|
 | 
						|
[source,json]
 | 
						|
---
 | 
						|
{
 | 
						|
  "submitter": {
 | 
						|
    "name": "Administrator",
 | 
						|
    "email": "admin@example.com",
 | 
						|
    "username": "admin"
 | 
						|
  },
 | 
						|
  "refUpdate": {
 | 
						|
    "oldRev": "a69fc95c7aad5ad41c618d31548b8af835d2959a",
 | 
						|
    "newRev": "31da6556d638a74e5370b62f83e8007f94abb7c6",
 | 
						|
    "refName": "refs/changes/01/1/meta",
 | 
						|
    "project": "test"
 | 
						|
  },
 | 
						|
  "type": "ref-updated",
 | 
						|
  "eventCreatedOn": 1588849085,
 | 
						|
  "instanceId": "instance1"
 | 
						|
}
 | 
						|
---
 | 
						|
 | 
						|
[[capabilities]]
 | 
						|
== Plugin Owned Capabilities
 | 
						|
 | 
						|
Plugins may provide their own capabilities and restrict usage of SSH
 | 
						|
commands or `UiAction` to the users who are granted those capabilities.
 | 
						|
 | 
						|
Plugins define the capabilities by overriding the `CapabilityDefinition`
 | 
						|
abstract class:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
public class PrintHelloCapability extends CapabilityDefinition {
 | 
						|
  @Override
 | 
						|
  public String getDescription() {
 | 
						|
    return "Print Hello";
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
If no Guice modules are declared in the manifest, capability may
 | 
						|
use auto-registration by providing an `@Export` annotation:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
@Export("printHello")
 | 
						|
public class PrintHelloCapability extends CapabilityDefinition {
 | 
						|
  [...]
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
Otherwise the capability must be bound in a plugin module:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
public class HelloWorldModule extends AbstractModule {
 | 
						|
  @Override
 | 
						|
  protected void configure() {
 | 
						|
    bind(CapabilityDefinition.class)
 | 
						|
      .annotatedWith(Exports.named("printHello"))
 | 
						|
      .to(PrintHelloCapability.class);
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
With a plugin-owned capability defined in this way, it is possible to restrict
 | 
						|
usage of an SSH command or `UiAction` to members of the group that were granted
 | 
						|
this capability in the usual way, using the `RequiresCapability` annotation:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
@RequiresCapability("printHello")
 | 
						|
@CommandMetaData(name="print", description="Print greeting in different languages")
 | 
						|
public final class PrintHelloWorldCommand extends SshCommand {
 | 
						|
  [...]
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
Or with `UiAction`:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
@RequiresCapability("printHello")
 | 
						|
public class SayHelloAction extends UiAction<RevisionResource>
 | 
						|
  implements RestModifyView<RevisionResource, SayHelloAction.Input> {
 | 
						|
  [...]
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
Capability scope was introduced to differentiate between plugin-owned
 | 
						|
capabilities and core capabilities. Per default the scope of the
 | 
						|
`@RequiresCapability` annotation is `CapabilityScope.CONTEXT`, that means:
 | 
						|
 | 
						|
* when `@RequiresCapability` is used within a plugin the scope of the
 | 
						|
capability is assumed to be that plugin.
 | 
						|
 | 
						|
* If `@RequiresCapability` is used within the core Gerrit Code Review server
 | 
						|
(and thus is outside of a plugin) the scope is the core server and will use
 | 
						|
the `GlobalCapability` known to Gerrit Code Review server.
 | 
						|
 | 
						|
If a plugin needs to use a core capability name (e.g. "administrateServer")
 | 
						|
this can be specified by setting `scope = CapabilityScope.CORE`:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
@RequiresCapability(value = "administrateServer", scope =
 | 
						|
    CapabilityScope.CORE)
 | 
						|
  [...]
 | 
						|
----
 | 
						|
 | 
						|
[post_review_extensions]
 | 
						|
== Post Review Extensions
 | 
						|
 | 
						|
By implementing the `com.google.gerrit.server.restapi.change.OnPostReview`
 | 
						|
interface plugins can extend the change message that is being posted when the
 | 
						|
[post review](rest-api-changes.html#set-review) REST endpoint is invoked.
 | 
						|
 | 
						|
This is useful if certain approvals have a special meaning (e.g. custom logic
 | 
						|
that is implemented in Prolog submit rules, signal for triggering an action
 | 
						|
like running CI etc.), as it allows the plugin to tell users about this meaning
 | 
						|
in the change message. This makes the effect of a given approval more
 | 
						|
transparent to the user. 
 | 
						|
 | 
						|
[[ui_extension]]
 | 
						|
== UI Extension
 | 
						|
 | 
						|
[[panels]]
 | 
						|
=== Panels
 | 
						|
 | 
						|
UI plugins can contribute panels to Gerrit screens.
 | 
						|
 | 
						|
Gerrit screens define extension points where plugins can add GWT
 | 
						|
panels with custom controls:
 | 
						|
 | 
						|
* Change Screen:
 | 
						|
** `GerritUiExtensionPoint.CHANGE_SCREEN_HEADER`:
 | 
						|
+
 | 
						|
Panel will be shown in the header bar to the right of the change
 | 
						|
status.
 | 
						|
 | 
						|
** `GerritUiExtensionPoint.CHANGE_SCREEN_HEADER_RIGHT_OF_BUTTONS`:
 | 
						|
+
 | 
						|
Panel will be shown in the header bar on the right side of the buttons.
 | 
						|
 | 
						|
** `GerritUiExtensionPoint.CHANGE_SCREEN_HEADER_RIGHT_OF_POP_DOWNS`:
 | 
						|
+
 | 
						|
Panel will be shown in the header bar on the right side of the pop down
 | 
						|
buttons.
 | 
						|
 | 
						|
** `GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_COMMIT_INFO_BLOCK`:
 | 
						|
+
 | 
						|
Panel will be shown below the commit info block.
 | 
						|
 | 
						|
** `GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK`:
 | 
						|
+
 | 
						|
Panel will be shown below the change info block.
 | 
						|
 | 
						|
** `GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_RELATED_INFO_BLOCK`:
 | 
						|
+
 | 
						|
Panel will be shown below the related info block.
 | 
						|
 | 
						|
** `GerritUiExtensionPoint.CHANGE_SCREEN_HISTORY_RIGHT_OF_BUTTONS`:
 | 
						|
+
 | 
						|
Panel will be shown in the history bar on the right side of the buttons.
 | 
						|
 | 
						|
** The following parameters are provided:
 | 
						|
*** `GerritUiExtensionPoint.Key.CHANGE_INFO`:
 | 
						|
+
 | 
						|
The link:rest-api-changes.html#change-info[ChangeInfo] entity for the
 | 
						|
current change.
 | 
						|
+
 | 
						|
The link:rest-api-changes.html#revision-info[RevisionInfo] entity for
 | 
						|
the current patch set.
 | 
						|
 | 
						|
* Project Info Screen:
 | 
						|
** `GerritUiExtensionPoint.PROJECT_INFO_SCREEN_TOP`:
 | 
						|
+
 | 
						|
Panel will be shown at the top of the screen.
 | 
						|
 | 
						|
** `GerritUiExtensionPoint.PROJECT_INFO_SCREEN_BOTTOM`:
 | 
						|
+
 | 
						|
Panel will be shown at the bottom of the screen.
 | 
						|
 | 
						|
** The following parameters are provided:
 | 
						|
*** `GerritUiExtensionPoint.Key.PROJECT_NAME`:
 | 
						|
+
 | 
						|
The name of the project.
 | 
						|
 | 
						|
* User Password Screen:
 | 
						|
** `GerritUiExtensionPoint.PASSWORD_SCREEN_BOTTOM`:
 | 
						|
+
 | 
						|
Panel will be shown at the bottom of the screen.
 | 
						|
 | 
						|
** The following parameters are provided:
 | 
						|
*** `GerritUiExtensionPoint.Key.ACCOUNT_INFO`:
 | 
						|
+
 | 
						|
The link:rest-api-accounts.html#account-info[AccountInfo] entity for
 | 
						|
the current user.
 | 
						|
 | 
						|
* User Preferences Screen:
 | 
						|
** `GerritUiExtensionPoint.PREFERENCES_SCREEN_BOTTOM`:
 | 
						|
+
 | 
						|
Panel will be shown at the bottom of the screen.
 | 
						|
 | 
						|
** The following parameters are provided:
 | 
						|
*** `GerritUiExtensionPoint.Key.ACCOUNT_INFO`:
 | 
						|
+
 | 
						|
The link:rest-api-accounts.html#account-info[AccountInfo] entity for
 | 
						|
the current user.
 | 
						|
 | 
						|
* User Profile Screen:
 | 
						|
** `GerritUiExtensionPoint.PROFILE_SCREEN_BOTTOM`:
 | 
						|
+
 | 
						|
Panel will be shown at the bottom of the screen below the grid with the
 | 
						|
profile data.
 | 
						|
 | 
						|
** The following parameters are provided:
 | 
						|
*** `GerritUiExtensionPoint.Key.ACCOUNT_INFO`:
 | 
						|
+
 | 
						|
The link:rest-api-accounts.html#account-info[AccountInfo] entity for
 | 
						|
the current user.
 | 
						|
 | 
						|
Example panel:
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
public class MyPlugin extends PluginEntryPoint {
 | 
						|
  @Override
 | 
						|
  public void onPluginLoad() {
 | 
						|
    Plugin.get().panel(GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK,
 | 
						|
        "my_panel_name",
 | 
						|
        new Panel.EntryPoint() {
 | 
						|
          @Override
 | 
						|
          public void onLoad(Panel panel) {
 | 
						|
            panel.setWidget(new InlineLabel("My Panel for change "
 | 
						|
                + panel.getInt(GerritUiExtensionPoint.Key.CHANGE_ID, -1));
 | 
						|
          }
 | 
						|
        });
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
Change Screen panel ordering may be specified in the
 | 
						|
project config. Values may be either "plugin name" or
 | 
						|
"plugin name"."panel name".
 | 
						|
Panels not specified in the config will be added
 | 
						|
to the end in load order. Panels specified in the config that
 | 
						|
are not found will be ignored.
 | 
						|
 | 
						|
Example config:
 | 
						|
----
 | 
						|
[extension-panels "CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK"]
 | 
						|
        panel = helloworld.change_id
 | 
						|
        panel = myotherplugin
 | 
						|
        panel = myplugin.my_panel_name
 | 
						|
----
 | 
						|
 | 
						|
 | 
						|
 | 
						|
[[actions]]
 | 
						|
=== Actions
 | 
						|
 | 
						|
Plugins can contribute UI actions on core Gerrit pages. This is useful
 | 
						|
for workflow customization or exposing plugin functionality through the
 | 
						|
UI in addition to SSH commands and the REST API.
 | 
						|
 | 
						|
For instance a plugin to integrate Jira with Gerrit changes may
 | 
						|
contribute a "File bug" button to allow filing a bug from the change
 | 
						|
page or plugins to integrate continuous integration systems may
 | 
						|
contribute a "Schedule" button to allow a CI build to be scheduled
 | 
						|
manually from the patch set panel.
 | 
						|
 | 
						|
Two different places on core Gerrit pages are supported:
 | 
						|
 | 
						|
* Change screen
 | 
						|
* Project info screen
 | 
						|
 | 
						|
Plugins contribute UI actions by implementing the `UiAction` interface:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
@RequiresCapability("printHello")
 | 
						|
class HelloWorldAction implements UiAction<RevisionResource>,
 | 
						|
    RestModifyView<RevisionResource, HelloWorldAction.Input> {
 | 
						|
  static class Input {
 | 
						|
    boolean french;
 | 
						|
    String message;
 | 
						|
  }
 | 
						|
 | 
						|
  private Provider<CurrentUser> user;
 | 
						|
 | 
						|
  @Inject
 | 
						|
  HelloWorldAction(Provider<CurrentUser> user) {
 | 
						|
    this.user = user;
 | 
						|
  }
 | 
						|
 | 
						|
  @Override
 | 
						|
  public String apply(RevisionResource rev, Input input) {
 | 
						|
    final String greeting = input.french
 | 
						|
        ? "Bonjour"
 | 
						|
        : "Hello";
 | 
						|
    return String.format("%s %s from change %s, patch set %d!",
 | 
						|
        greeting,
 | 
						|
        Strings.isNullOrEmpty(input.message)
 | 
						|
            ? Objects.firstNonNull(user.get().getUserName(), "world")
 | 
						|
            : input.message,
 | 
						|
        rev.getChange().getId().toString(),
 | 
						|
        rev.getPatchSet().getPatchSetId());
 | 
						|
  }
 | 
						|
 | 
						|
  @Override
 | 
						|
  public Description getDescription(
 | 
						|
      RevisionResource resource) {
 | 
						|
    return new Description()
 | 
						|
        .setLabel("Say hello")
 | 
						|
        .setTitle("Say hello in different languages");
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
Sometimes plugins may want to be able to change the state of a patch set or
 | 
						|
change in the `UiAction.apply()` method and reflect these changes on the core
 | 
						|
UI. For example a buildbot plugin which exposes a 'Schedule' button on the
 | 
						|
patch set panel may want to disable that button after the build was scheduled
 | 
						|
and update the tooltip of that button. But because of Gerrit's caching
 | 
						|
strategy the following must be taken into consideration.
 | 
						|
 | 
						|
The browser is allowed to cache the `UiAction` information until something on
 | 
						|
the change is modified. More accurately the change row needs to be modified in
 | 
						|
the database to have a more recent `lastUpdatedOn` or a new `rowVersion`, or
 | 
						|
the +refs/meta/config+ of the project or any parents needs to change to a new
 | 
						|
SHA-1. The ETag SHA-1 computation code can be found in the
 | 
						|
`ChangeResource.getETag()` method.
 | 
						|
 | 
						|
The easiest way to accomplish this is to update `lastUpdatedOn` of the change:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
@Override
 | 
						|
public Object apply(RevisionResource rcrs, Input in) {
 | 
						|
  // schedule a build
 | 
						|
  [...]
 | 
						|
  // update change
 | 
						|
  try (BatchUpdate bu = batchUpdateFactory.create(
 | 
						|
      project.getNameKey(), user, TimeUtil.nowTs())) {
 | 
						|
    bu.addOp(change.getId(), new BatchUpdate.Op() {
 | 
						|
      @Override
 | 
						|
      public boolean updateChange(ChangeContext ctx) {
 | 
						|
        return true;
 | 
						|
      }
 | 
						|
    });
 | 
						|
    bu.execute();
 | 
						|
  }
 | 
						|
  [...]
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
`UiAction` must be bound in a plugin module:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
public class Module extends AbstractModule {
 | 
						|
  @Override
 | 
						|
  protected void configure() {
 | 
						|
    install(new RestApiModule() {
 | 
						|
      @Override
 | 
						|
      protected void configure() {
 | 
						|
        post(REVISION_KIND, "say-hello")
 | 
						|
            .to(HelloWorldAction.class);
 | 
						|
      }
 | 
						|
    });
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
The module above must be declared in the `pom.xml` for Maven driven
 | 
						|
plugins:
 | 
						|
 | 
						|
[source,xml]
 | 
						|
----
 | 
						|
<manifestEntries>
 | 
						|
  <Gerrit-Module>com.googlesource.gerrit.plugins.cookbook.Module</Gerrit-Module>
 | 
						|
</manifestEntries>
 | 
						|
----
 | 
						|
 | 
						|
or in the `BUILD` configuration file for Bazel driven plugins:
 | 
						|
 | 
						|
[source,python]
 | 
						|
----
 | 
						|
manifest_entries = [
 | 
						|
  'Gerrit-Module: com.googlesource.gerrit.plugins.cookbook.Module',
 | 
						|
]
 | 
						|
----
 | 
						|
 | 
						|
In some use cases more user input must be gathered, for that `UiAction` can be
 | 
						|
combined with the JavaScript API. This would display a small popup near the
 | 
						|
activation button to gather additional input from the user. The JS file is
 | 
						|
typically put in the `static` folder within the plugin's directory:
 | 
						|
 | 
						|
[source,javascript]
 | 
						|
----
 | 
						|
Gerrit.install(function(self) {
 | 
						|
  function onSayHello(c) {
 | 
						|
    var f = c.textfield();
 | 
						|
    var t = c.checkbox();
 | 
						|
    var b = c.button('Say hello', {onclick: function(){
 | 
						|
      c.call(
 | 
						|
        {message: f.value, french: t.checked},
 | 
						|
        function(r) {
 | 
						|
          c.hide();
 | 
						|
          window.alert(r);
 | 
						|
          c.refresh();
 | 
						|
        });
 | 
						|
    }});
 | 
						|
    c.popup(c.div(
 | 
						|
      c.prependLabel('Greeting message', f),
 | 
						|
      c.br(),
 | 
						|
      c.label(t, 'french'),
 | 
						|
      c.br(),
 | 
						|
      b));
 | 
						|
    f.focus();
 | 
						|
  }
 | 
						|
  self.onAction('revision', 'say-hello', onSayHello);
 | 
						|
});
 | 
						|
----
 | 
						|
 | 
						|
The JS module must be exposed as a `WebUiPlugin` and bound as
 | 
						|
an HTTP Module:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
public class HttpModule extends ServletModule {
 | 
						|
  @Override
 | 
						|
  protected void configureServlets() {
 | 
						|
    DynamicSet.bind(binder(), WebUiPlugin.class)
 | 
						|
        .toInstance(new JavaScriptPlugin("hello.js"));
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
The HTTP module above must be declared in the `pom.xml` for Maven
 | 
						|
driven plugins:
 | 
						|
 | 
						|
[source,xml]
 | 
						|
----
 | 
						|
<manifestEntries>
 | 
						|
  <Gerrit-HttpModule>com.googlesource.gerrit.plugins.cookbook.HttpModule</Gerrit-HttpModule>
 | 
						|
</manifestEntries>
 | 
						|
----
 | 
						|
 | 
						|
or in the `BUILD` configuration file for Bazel driven plugins
 | 
						|
 | 
						|
[source,python]
 | 
						|
----
 | 
						|
manifest_entries = [
 | 
						|
  'Gerrit-HttpModule: com.googlesource.gerrit.plugins.cookbook.HttpModule',
 | 
						|
]
 | 
						|
----
 | 
						|
 | 
						|
If `UiAction` is annotated with the `@RequiresCapability` annotation, then the
 | 
						|
capability check is done during the `UiAction` gathering, so the plugin author
 | 
						|
doesn't have to set `UiAction.Description.setVisible()` explicitly in this
 | 
						|
case.
 | 
						|
 | 
						|
The following prerequisites must be met, to satisfy the capability check:
 | 
						|
 | 
						|
* user is authenticated
 | 
						|
* user is a member of a group which has the `Administrate Server` capability, or
 | 
						|
* user is a member of a group which has the required capability
 | 
						|
 | 
						|
The `apply` method is called when the button is clicked. If `UiAction` is
 | 
						|
combined with JavaScript API (its own JavaScript function is provided),
 | 
						|
then a popup dialog is normally opened to gather additional user input.
 | 
						|
A new button is placed on the popup dialog to actually send the request.
 | 
						|
 | 
						|
Every `UiAction` exposes a REST API endpoint. The endpoint from the example above
 | 
						|
can be accessed from any REST client, i. e.:
 | 
						|
 | 
						|
----
 | 
						|
  curl -X POST -H "Content-Type: application/json" \
 | 
						|
    -d '{message: "François", french: true}' \
 | 
						|
    --user joe:secret \
 | 
						|
    http://host:port/a/changes/1/revisions/1/cookbook~say-hello
 | 
						|
  "Bonjour François from change 1, patch set 1!"
 | 
						|
----
 | 
						|
 | 
						|
A special case is to bind an endpoint without a view name.  This is
 | 
						|
particularly useful for `DELETE` requests:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
public class Module extends AbstractModule {
 | 
						|
  @Override
 | 
						|
  protected void configure() {
 | 
						|
    install(new RestApiModule() {
 | 
						|
      @Override
 | 
						|
      protected void configure() {
 | 
						|
        delete(PROJECT_KIND)
 | 
						|
            .to(DeleteProject.class);
 | 
						|
      }
 | 
						|
    });
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
For a `UiAction` bound this way, a JS API function can be provided.
 | 
						|
 | 
						|
Currently only one restriction exists: per plugin only one `UiAction`
 | 
						|
can be bound per resource without view name. To define a JS function
 | 
						|
for the `UiAction`, "/" must be used as the name:
 | 
						|
 | 
						|
[source,javascript]
 | 
						|
----
 | 
						|
Gerrit.install(function(self) {
 | 
						|
  function onDeleteProject(c) {
 | 
						|
    [...]
 | 
						|
  }
 | 
						|
  self.onAction('project', '/', onDeleteProject);
 | 
						|
});
 | 
						|
----
 | 
						|
 | 
						|
 | 
						|
[[action-visitor]]
 | 
						|
=== Action Visitors
 | 
						|
 | 
						|
In addition to providing new actions, plugins can have fine-grained control
 | 
						|
over the link:rest-api-changes.html#action-info[ActionInfo] map, modifying or
 | 
						|
removing existing actions, including those contributed by core.
 | 
						|
 | 
						|
Visitors are provided the link:rest-api-changes.html#action-info[ActionInfo],
 | 
						|
which is mutable, along with copies of the
 | 
						|
link:rest-api-changes.html#change-info[ChangeInfo] and
 | 
						|
link:rest-api-changes.html#revision-info[RevisionInfo]. They can modify the
 | 
						|
action, or return `false` to exclude it from the resulting map.
 | 
						|
 | 
						|
These operations only affect the action buttons that are displayed in the UI;
 | 
						|
the underlying REST API endpoints are not affected. Multiple plugins may
 | 
						|
implement the visitor interface, but the order in which they are run is
 | 
						|
undefined.
 | 
						|
 | 
						|
For example, to exclude "Cherry-Pick" only from certain projects, and rename
 | 
						|
"Abandon":
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
public class MyActionVisitor implements ActionVisitor {
 | 
						|
  @Override
 | 
						|
  public boolean visit(String name, ActionInfo actionInfo,
 | 
						|
      ChangeInfo changeInfo) {
 | 
						|
    if (name.equals("abandon")) {
 | 
						|
      actionInfo.label = "Drop";
 | 
						|
    }
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  @Override
 | 
						|
  public boolean visit(String name, ActionInfo actionInfo,
 | 
						|
      ChangeInfo changeInfo, RevisionInfo revisionInfo) {
 | 
						|
    if (project.startsWith("some-team/") && name.equals("cherrypick")) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
 | 
						|
[[top-menu-extensions]]
 | 
						|
== Top Menu Extensions
 | 
						|
 | 
						|
Plugins can contribute items to Gerrit's top menu.
 | 
						|
 | 
						|
A single top menu extension can have multiple elements and will be put as
 | 
						|
the last element in Gerrit's top menu.
 | 
						|
 | 
						|
Plugins define the top menu entries by implementing `TopMenu` interface:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
public class MyTopMenuExtension implements TopMenu {
 | 
						|
 | 
						|
  @Override
 | 
						|
  public List<MenuEntry> getEntries() {
 | 
						|
    return Lists.newArrayList(
 | 
						|
               new MenuEntry("Top Menu Entry", Lists.newArrayList(
 | 
						|
                      new MenuItem("Gerrit", "http://gerrit.googlecode.com/"))));
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
Plugins can also add additional menu items to Gerrit's top menu entries
 | 
						|
by defining a `MenuEntry` that has the same name as a Gerrit top menu
 | 
						|
entry:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
public class MyTopMenuExtension implements TopMenu {
 | 
						|
 | 
						|
  @Override
 | 
						|
  public List<MenuEntry> getEntries() {
 | 
						|
    return Lists.newArrayList(
 | 
						|
               new MenuEntry(GerritTopMenu.PROJECTS, Lists.newArrayList(
 | 
						|
                      new MenuItem("Browse Repositories", "https://gerrit.googlesource.com/"))));
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
`MenuItems` that are bound for the `MenuEntry` with the name
 | 
						|
`GerritTopMenu.PROJECTS` can contain a `${projectName}` placeholder
 | 
						|
which is automatically replaced by the actual project name.
 | 
						|
 | 
						|
E.g. plugins may register an link:#http[HTTP Servlet] to handle project
 | 
						|
specific requests and add an menu item for this:
 | 
						|
 | 
						|
[source,java]
 | 
						|
---
 | 
						|
  new MenuItem("My Screen", "/plugins/myplugin/project/${projectName}");
 | 
						|
---
 | 
						|
 | 
						|
This also enables plugins to provide menu items for project aware
 | 
						|
screens:
 | 
						|
 | 
						|
[source,java]
 | 
						|
---
 | 
						|
  new MenuItem("My Screen", "/x/my-screen/for/${projectName}");
 | 
						|
---
 | 
						|
 | 
						|
If no Guice modules are declared in the manifest, the top menu extension may use
 | 
						|
auto-registration by providing an `@Listen` annotation:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
@Listen
 | 
						|
public class MyTopMenuExtension implements TopMenu {
 | 
						|
  [...]
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
Otherwise the top menu extension must be bound in the plugin module used
 | 
						|
for the Gerrit system injector (Gerrit-Module entry in MANIFEST.MF):
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
package com.googlesource.gerrit.plugins.helloworld;
 | 
						|
 | 
						|
public class HelloWorldModule extends AbstractModule {
 | 
						|
  @Override
 | 
						|
  protected void configure() {
 | 
						|
    DynamicSet.bind(binder(), TopMenu.class).to(MyTopMenuExtension.class);
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
[source,manifest]
 | 
						|
----
 | 
						|
Gerrit-ApiType: plugin
 | 
						|
Gerrit-Module: com.googlesource.gerrit.plugins.helloworld.HelloWorldModule
 | 
						|
----
 | 
						|
 | 
						|
It is also possible to show some menu entries only if the user has a
 | 
						|
certain capability:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
public class MyTopMenuExtension implements TopMenu {
 | 
						|
  private final String pluginName;
 | 
						|
  private final Provider<CurrentUser> userProvider;
 | 
						|
  private final List<MenuEntry> menuEntries;
 | 
						|
 | 
						|
  @Inject
 | 
						|
  public MyTopMenuExtension(@PluginName String pluginName,
 | 
						|
      Provider<CurrentUser> userProvider) {
 | 
						|
    this.pluginName = pluginName;
 | 
						|
    this.userProvider = userProvider;
 | 
						|
    menuEntries = new ArrayList<TopMenu.MenuEntry>();
 | 
						|
 | 
						|
    // add menu entry that is only visible to users with a certain capability
 | 
						|
    if (canSeeMenuEntry()) {
 | 
						|
      menuEntries.add(new MenuEntry("Top Menu Entry", Collections
 | 
						|
          .singletonList(new MenuItem("Gerrit", "http://gerrit.googlecode.com/"))));
 | 
						|
    }
 | 
						|
 | 
						|
    // add menu entry that is visible to all users (even anonymous users)
 | 
						|
    menuEntries.add(new MenuEntry("Top Menu Entry", Collections
 | 
						|
          .singletonList(new MenuItem("Documentation", "/plugins/myplugin/"))));
 | 
						|
  }
 | 
						|
 | 
						|
  private boolean canSeeMenuEntry() {
 | 
						|
    if (userProvider.get().isIdentifiedUser()) {
 | 
						|
      CapabilityControl ctl = userProvider.get().getCapabilities();
 | 
						|
      return ctl.canPerform(pluginName + "-" + MyCapability.ID)
 | 
						|
          || ctl.canAdministrateServer();
 | 
						|
    } else {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  @Override
 | 
						|
  public List<MenuEntry> getEntries() {
 | 
						|
    return menuEntries;
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
 | 
						|
[[settings-screen]]
 | 
						|
== Plugin Settings Screen
 | 
						|
 | 
						|
If a plugin implements a screen for administrating its settings that is
 | 
						|
available under "#/x/<plugin-name>/settings" it is automatically linked
 | 
						|
from the plugin list screen.
 | 
						|
 | 
						|
[[http]]
 | 
						|
== HTTP Servlets
 | 
						|
 | 
						|
Plugins or extensions may register additional HTTP servlets, and
 | 
						|
wrap them with HTTP filters.
 | 
						|
 | 
						|
Servlets may use auto-registration to declare the URL they handle:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
import com.google.gerrit.extensions.annotations.Export;
 | 
						|
import com.google.inject.Singleton;
 | 
						|
import javax.servlet.http.HttpServlet;
 | 
						|
import javax.servlet.http.HttpServletRequest;
 | 
						|
import javax.servlet.http.HttpServletResponse;
 | 
						|
 | 
						|
@Export("/print")
 | 
						|
@Singleton
 | 
						|
class HelloServlet extends HttpServlet {
 | 
						|
  protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
 | 
						|
    res.setContentType("text/plain");
 | 
						|
    res.setCharacterEncoding("UTF-8");
 | 
						|
    res.getWriter().write("Hello");
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
The auto registration only works for standard servlet mappings like
 | 
						|
`/foo` or `+/foo/*+`. Regex style bindings must use a Guice ServletModule
 | 
						|
to register the HTTP servlets and declare it explicitly in the manifest
 | 
						|
with the `Gerrit-HttpModule` attribute:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
import com.google.inject.servlet.ServletModule;
 | 
						|
 | 
						|
class MyWebUrls extends ServletModule {
 | 
						|
  protected void configureServlets() {
 | 
						|
    serve("/print").with(HelloServlet.class);
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
For a plugin installed as name `helloworld`, the servlet implemented
 | 
						|
by HelloServlet class will be available to users as:
 | 
						|
 | 
						|
----
 | 
						|
$ curl http://review.example.com/plugins/helloworld/print
 | 
						|
----
 | 
						|
 | 
						|
[[data-directory]]
 | 
						|
== Data Directory
 | 
						|
 | 
						|
Plugins can request a data directory with a `@PluginData` Path (or File,
 | 
						|
deprecated) dependency. A data directory will be created automatically
 | 
						|
by the server in `$site_path/data/$plugin_name` and passed to the
 | 
						|
plugin.
 | 
						|
 | 
						|
Plugins can use this to store any data they want.
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
@Inject
 | 
						|
MyType(@PluginData java.nio.file.Path myDir) {
 | 
						|
  this.in = Files.newInputStream(myDir.resolve("my.config"));
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
[[secure-store]]
 | 
						|
== SecureStore
 | 
						|
 | 
						|
SecureStore allows to change the way Gerrit stores sensitive data like
 | 
						|
passwords.
 | 
						|
 | 
						|
In order to replace the default SecureStore (no-op) implementation,
 | 
						|
a class that extends `com.google.gerrit.server.securestore.SecureStore`
 | 
						|
needs to be provided (with dependencies) in a separate jar file. Then
 | 
						|
link:pgm-SwitchSecureStore.html[SwitchSecureStore] must be run to
 | 
						|
switch implementations.
 | 
						|
 | 
						|
The SecureStore implementation is instantiated using a Guice injector
 | 
						|
which binds the `File` annotated with the `@SitePath` annotation.
 | 
						|
This means that a SecureStore implementation class can get access to
 | 
						|
the `site_path` like in the following example:
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
@Inject
 | 
						|
MySecureStore(@SitePath java.io.File sitePath) {
 | 
						|
  // your code
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
No Guice bindings or modules are required. Gerrit will automatically
 | 
						|
discover and bind the implementation.
 | 
						|
 | 
						|
[[gerrit-replica]]
 | 
						|
== Gerrit Replica
 | 
						|
 | 
						|
Gerrit can be run as a read-only replica. Some plugins may need to know
 | 
						|
whether Gerrit is run as a primary- or a replica instance. For that purpose
 | 
						|
Gerrit exposes the `@GerritIsReplica` annotation. A boolean annotated with
 | 
						|
this annotation will indicate whether Gerrit is run as a replica.
 | 
						|
 | 
						|
[[accountcreation]]
 | 
						|
== Account Creation
 | 
						|
 | 
						|
Plugins can hook into the
 | 
						|
link:rest-api-accounts.html#create-account[account creation] REST API and
 | 
						|
inject additional external identifiers for an account that represents a user
 | 
						|
in some external user store. For that, an implementation of the extension
 | 
						|
point `com.google.gerrit.server.account.AccountExternalIdCreator`
 | 
						|
must be registered.
 | 
						|
 | 
						|
[source,java]
 | 
						|
----
 | 
						|
class MyExternalIdCreator implements AccountExternalIdCreator {
 | 
						|
  @Override
 | 
						|
  public List<AccountExternalId> create(Account.Id id, String username,
 | 
						|
      String email) {
 | 
						|
    // your code
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
bind(AccountExternalIdCreator.class)
 | 
						|
  .annotatedWith(UniqueAnnotations.create())
 | 
						|
  .to(MyExternalIdCreator.class);
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
[[download-commands]]
 | 
						|
== Download Commands
 | 
						|
 | 
						|
Gerrit offers commands for downloading changes and cloning projects
 | 
						|
using different download schemes (e.g. for downloading via different
 | 
						|
network protocols). Plugins can contribute download schemes, download
 | 
						|
commands and clone commands by implementing
 | 
						|
`com.google.gerrit.extensions.config.DownloadScheme`,
 | 
						|
`com.google.gerrit.extensions.config.DownloadCommand` and
 | 
						|
`com.google.gerrit.extensions.config.CloneCommand`.
 | 
						|
 | 
						|
The download schemes, download commands and clone commands which are
 | 
						|
used most often are provided by the Gerrit core plugin
 | 
						|
`download-commands`.
 | 
						|
 | 
						|
[[included-in]]
 | 
						|
== Included In
 | 
						|
 | 
						|
For merged changes the link:user-review-ui.html#included-in[Included In]
 | 
						|
drop-down panel shows the branches and tags in which the change is
 | 
						|
included.
 | 
						|
 | 
						|
Plugins can add additional systems in which the change can be included
 | 
						|
by implementing `com.google.gerrit.extensions.config.ExternalIncludedIn`,
 | 
						|
e.g. a plugin can provide a list of servers on which the change was
 | 
						|
deployed.
 | 
						|
 | 
						|
[[change-report-formatting]]
 | 
						|
== Change Report Formatting
 | 
						|
 | 
						|
When a change is pushed for review from the command line, Gerrit reports
 | 
						|
the change(s) received with their URL and subject.
 | 
						|
 | 
						|
By implementing the
 | 
						|
`com.google.gerrit.server.git.ChangeReportFormatter` interface, a plugin
 | 
						|
may change the formatting of the report.
 | 
						|
 | 
						|
[[url-formatting]]
 | 
						|
== URL Formatting
 | 
						|
 | 
						|
URLs to various parts of Gerrit are usually formed by adding suffixes to
 | 
						|
the canonical web URL.
 | 
						|
 | 
						|
By implementing the
 | 
						|
`com.google.gerrit.server.config.UrlFormatter` interface, a plugin may
 | 
						|
change the format of the URL.
 | 
						|
 | 
						|
[[links-to-external-tools]]
 | 
						|
== Links To External Tools
 | 
						|
 | 
						|
Gerrit has extension points that enables development of a
 | 
						|
light-weight plugin that links commits to external
 | 
						|
tools (GitBlit, CGit, company specific resources etc).
 | 
						|
 | 
						|
PatchSetWebLinks will appear to the right of the commit-SHA1 in the UI.
 | 
						|
 | 
						|
[source, java]
 | 
						|
----
 | 
						|
import com.google.gerrit.extensions.annotations.Listen;
 | 
						|
import com.google.gerrit.extensions.webui.PatchSetWebLink;;
 | 
						|
import com.google.gerrit.extensions.webui.WebLinkTarget;
 | 
						|
 | 
						|
@Listen
 | 
						|
public class MyWeblinkPlugin implements PatchSetWebLink {
 | 
						|
 | 
						|
  private String name = "MyLink";
 | 
						|
  private String placeHolderUrlProjectCommit = "http://my.tool.com/project=%s/commit=%s";
 | 
						|
  private String imageUrl = "http://placehold.it/16x16.gif";
 | 
						|
 | 
						|
  @Override
 | 
						|
  public WebLinkInfo getPatchSetWebLink(String projectName, String commit,
 | 
						|
   String commitMessage, String branchName) {
 | 
						|
    return new WebLinkInfo(name,
 | 
						|
        imageUrl,
 | 
						|
        String.format(placeHolderUrlProjectCommit, project, commit),
 | 
						|
        WebLinkTarget.BLANK);
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
ParentWebLinks will appear to the right of the SHA1 of the parent
 | 
						|
revisions in the UI. The implementation should in most use cases direct
 | 
						|
to the same external service as PatchSetWebLink; it is provided as a
 | 
						|
separate interface because not all users want to have links for the
 | 
						|
parent revisions.
 | 
						|
 | 
						|
FileWebLinks will appear in the side-by-side diff screen on the right
 | 
						|
side of the patch selection on each side.
 | 
						|
 | 
						|
DiffWebLinks will appear in the side-by-side and unified diff screen in
 | 
						|
the header next to the navigation icons.
 | 
						|
 | 
						|
ProjectWebLinks will appear in the project list in the
 | 
						|
`Repository Browser` column.
 | 
						|
 | 
						|
BranchWebLinks will appear in the branch list in the last column.
 | 
						|
 | 
						|
FileHistoryWebLinks will appear on the access rights screen.
 | 
						|
 | 
						|
TagWebLinks will appear in the tag list in the last column.
 | 
						|
 | 
						|
If a `get*WebLink` implementation returns `null`, the link will be omitted. This
 | 
						|
allows the plugin to selectively "enable" itself on a per-project/branch/file
 | 
						|
basis.
 | 
						|
 | 
						|
[[lfs-extension]]
 | 
						|
== LFS Storage Plugins
 | 
						|
 | 
						|
Gerrit provides an extension point that enables development of
 | 
						|
link:https://github.com/github/git-lfs/blob/master/docs/api/v1/http-v1-batch.md[
 | 
						|
LFS (Large File Storage),role=external,window=_blank] storage plugins. Gerrit core exposes the default LFS
 | 
						|
protocol endpoint `<project-name>/info/lfs/objects/batch` and forwards the requests
 | 
						|
to the configured link:config-gerrit.html#lfs[lfs.plugin] plugin which implements
 | 
						|
the LFS protocol. By exposing the default LFS endpoint, the git-lfs client can be
 | 
						|
used without any configuration.
 | 
						|
 | 
						|
[source, java]
 | 
						|
----
 | 
						|
/** Provide an LFS protocol implementation */
 | 
						|
import org.eclipse.jgit.lfs.server.LargeFileRepository;
 | 
						|
import org.eclipse.jgit.lfs.server.LfsProtocolServlet;
 | 
						|
 | 
						|
@Singleton
 | 
						|
public class LfsApiServlet extends LfsProtocolServlet {
 | 
						|
  private static final long serialVersionUID = 1L;
 | 
						|
 | 
						|
  private final S3LargeFileRepository repository;
 | 
						|
 | 
						|
  @Inject
 | 
						|
  LfsApiServlet(S3LargeFileRepository repository) {
 | 
						|
    this.repository = repository;
 | 
						|
  }
 | 
						|
 | 
						|
  @Override
 | 
						|
  protected LargeFileRepository getLargeFileRepository() {
 | 
						|
    return repository;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/** Register the LfsApiServlet to listen on the default LFS protocol endpoint */
 | 
						|
import static com.google.gerrit.httpd.plugins.LfsPluginServlet.URL_REGEX;
 | 
						|
 | 
						|
import com.google.inject.servlet.ServletModule;
 | 
						|
 | 
						|
public class HttpModule extends ServletModule {
 | 
						|
 | 
						|
  @Override
 | 
						|
  protected void configureServlets() {
 | 
						|
    serveRegex(URL_REGEX).with(LfsApiServlet.class);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/** Provide an implementation of the LargeFileRepository */
 | 
						|
import org.eclipse.jgit.lfs.server.s3.S3Repository;
 | 
						|
 | 
						|
public class S3LargeFileRepository extends S3Repository {
 | 
						|
...
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
[[metrics]]
 | 
						|
== Metrics
 | 
						|
 | 
						|
=== Metrics Reporting
 | 
						|
 | 
						|
To send Gerrit's metrics data to an external reporting backend, a plugin can
 | 
						|
get a `MetricRegistry` injected and register an instance of a class that
 | 
						|
implements the `Reporter` interface from link:http://metrics.dropwizard.io/[
 | 
						|
DropWizard Metrics,role=external,window=_blank].
 | 
						|
 | 
						|
Metric reporting plugin implementations are provided for
 | 
						|
link:https://gerrit.googlesource.com/plugins/metrics-reporter-jmx/[JMX,role=external,window=_blank],
 | 
						|
link:https://gerrit.googlesource.com/plugins/metrics-reporter-elasticsearch/[Elastic Search,role=external,window=_blank],
 | 
						|
and link:https://gerrit.googlesource.com/plugins/metrics-reporter-graphite/[Graphite,role=external,window=_blank].
 | 
						|
 | 
						|
There is also a working example of reporting metrics to the console in the
 | 
						|
link:https://gerrit.googlesource.com/plugins/cookbook-plugin/+/master/src/main/java/com/googlesource/gerrit/plugins/cookbook/ConsoleMetricReporter.java[
 | 
						|
cookbook plugin,role=external,window=_blank].
 | 
						|
 | 
						|
=== Providing own metrics
 | 
						|
 | 
						|
Plugins may provide metrics to be dispatched to external reporting services by
 | 
						|
getting a `MetricMaker` injected and creating instances of specific types of
 | 
						|
metric:
 | 
						|
 | 
						|
* Counter
 | 
						|
+
 | 
						|
Metric whose value increments during the life of the process.
 | 
						|
 | 
						|
* Timer
 | 
						|
+
 | 
						|
Metric recording time spent on an operation.
 | 
						|
 | 
						|
* Histogram
 | 
						|
+
 | 
						|
Metric recording statistical distribution (rate) of values.
 | 
						|
 | 
						|
Note that metrics cannot be recorded from plugin init steps that
 | 
						|
are run during site initialization.
 | 
						|
 | 
						|
By default, plugin metrics are recorded under
 | 
						|
`plugins/${plugin-name}/${metric-name}`. This can be changed by
 | 
						|
setting `plugins.${plugin-name}.metricsPrefix` in the `gerrit.config`
 | 
						|
file. For example:
 | 
						|
 | 
						|
----
 | 
						|
  [plugin "my-plugin"]
 | 
						|
    metricsPrefix = my-metrics
 | 
						|
----
 | 
						|
 | 
						|
will cause the metrics to be recorded under `my-metrics/${metric-name}`.
 | 
						|
 | 
						|
See the replication metrics in the
 | 
						|
link:https://gerrit.googlesource.com/plugins/replication/+/master/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationMetrics.java[
 | 
						|
replication plugin,role=external,window=_blank] for an example of usage.
 | 
						|
 | 
						|
[[account-patch-review-store]]
 | 
						|
== AccountPatchReviewStore
 | 
						|
 | 
						|
The AccountPatchReviewStore is used to store reviewed flags on changes.
 | 
						|
A reviewed flag is a tuple of (patch set ID, file, account ID) and
 | 
						|
records whether the user has reviewed a file in a patch set. Each user
 | 
						|
can easily have thousands of reviewed flags and the number of reviewed
 | 
						|
flags is growing without bound. The store must be able handle this data
 | 
						|
volume efficiently.
 | 
						|
 | 
						|
Gerrit implements this extension point, but plugins may bind another
 | 
						|
implementation, e.g. one that supports cluster setup with multiple
 | 
						|
primary Gerrit nodes handling write operations.
 | 
						|
 | 
						|
----
 | 
						|
DynamicItem.bind(binder(), AccountPatchReviewStore.class)
 | 
						|
    .to(MultiMasterAccountPatchReviewStore.class);
 | 
						|
 | 
						|
...
 | 
						|
 | 
						|
public class MultiMasterAccountPatchReviewStore
 | 
						|
    implements AccountPatchReviewStore {
 | 
						|
  ...
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
 | 
						|
[[documentation]]
 | 
						|
== Documentation
 | 
						|
 | 
						|
If a plugin does not register a filter or servlet to handle URLs
 | 
						|
`+/Documentation/*+` or `+/static/*+`, the core Gerrit server will
 | 
						|
automatically export these resources over HTTP from the plugin JAR.
 | 
						|
 | 
						|
Static resources under the `static/` directory in the JAR will be
 | 
						|
available as `/plugins/helloworld/static/resource`. This prefix is
 | 
						|
configurable by setting the `Gerrit-HttpStaticPrefix` attribute.
 | 
						|
 | 
						|
Documentation files under the `Documentation/` directory in the JAR
 | 
						|
will be available as `/plugins/helloworld/Documentation/resource`. This
 | 
						|
prefix is configurable by setting the `Gerrit-HttpDocumentationPrefix`
 | 
						|
attribute.
 | 
						|
 | 
						|
Documentation may be written in the Markdown flavor
 | 
						|
link:https://github.com/vsch/flexmark-java[flexmark-java,role=external,window=_blank]
 | 
						|
if the file name ends with `.md`. Gerrit will automatically convert
 | 
						|
Markdown to HTML if accessed with extension `.html`.
 | 
						|
 | 
						|
[[macros]]
 | 
						|
Within the Markdown documentation files macros can be used that allow
 | 
						|
to write documentation with reasonably accurate examples that adjust
 | 
						|
automatically based on the installation.
 | 
						|
 | 
						|
The following macros are supported:
 | 
						|
 | 
						|
[width="40%",options="header"]
 | 
						|
|===================================================
 | 
						|
|Macro       | Replacement
 | 
						|
|@PLUGIN@    | name of the plugin
 | 
						|
|@URL@       | Gerrit Web URL
 | 
						|
|@SSH_HOST@  | SSH Host
 | 
						|
|@SSH_PORT@  | SSH Port
 | 
						|
|===================================================
 | 
						|
 | 
						|
The macros will be replaced when the documentation files are rendered
 | 
						|
from Markdown to HTML.
 | 
						|
 | 
						|
Macros that start with `\` such as `\@KEEP@` will render as `@KEEP@`
 | 
						|
even if there is an expansion for `KEEP` in the future.
 | 
						|
 | 
						|
Documentation should typically contain the following content:
 | 
						|
 | 
						|
[width="100%",options="header"]
 | 
						|
|===================================================
 | 
						|
|File                                           | Content
 | 
						|
|`README.md`                                    | Home page of the plugin when browsing its source code on Git
 | 
						|
|`LICENSE`                                      | Open-source license
 | 
						|
|`resources/Documentation/about.md`             | Overview of the plugin and its purpose
 | 
						|
|`resources/Documentation/config.md`            | Plugin configuration settings and sample configs
 | 
						|
|`resources/Documentation/build.md`             | How to build the plugin
 | 
						|
|`resources/Documentation/cmd-<command>.md`     | SSH commands
 | 
						|
|`resources/Documentation/rest-api-<api>.md`    | REST API
 | 
						|
|`resources/Documentation/servlet-<servlet>.md` | HTTP Servlets
 | 
						|
|===================================================
 | 
						|
 | 
						|
The documentation under resources/Documentation may contain macro that
 | 
						|
will be included and expanded by Gerrit once the plugin is loaded.
 | 
						|
 | 
						|
The files in the root directory are not included in the plugin package
 | 
						|
and must not have any macro for expansion. It may also collect
 | 
						|
additional information that would make the plugin more discoverable, such as
 | 
						|
a more user-friendly description of its use-cases.
 | 
						|
 | 
						|
The documentation can also include images that can help understanding more
 | 
						|
visually how the plugin can interact with the other Gerrit components.
 | 
						|
 | 
						|
[[auto-index]]
 | 
						|
=== Automatic Index
 | 
						|
 | 
						|
If a plugin does not handle its `/` URL itself, Gerrit will
 | 
						|
redirect clients to the plugin's `/Documentation/index.html`.
 | 
						|
Requests for `/Documentation/` (bare directory) will also redirect
 | 
						|
to `/Documentation/index.html`.
 | 
						|
 | 
						|
If neither resource `Documentation/index.html` or
 | 
						|
`Documentation/index.md` exists in the plugin JAR, Gerrit will
 | 
						|
automatically generate an index page for the plugin's documentation
 | 
						|
tree by scanning every `*.md` and `*.html` file in the Documentation/
 | 
						|
directory.
 | 
						|
 | 
						|
For any discovered Markdown (`*.md`) file, Gerrit will parse the
 | 
						|
header of the file and extract the first level one title. This
 | 
						|
title text will be used as display text for a link to the HTML
 | 
						|
version of the page.
 | 
						|
 | 
						|
For any discovered HTML (`*.html`) file, Gerrit will use the name
 | 
						|
of the file, minus the `*.html` extension, as the link text. Any
 | 
						|
hyphens in the file name will be replaced with spaces.
 | 
						|
 | 
						|
If a discovered file is named `about.md` or `about.html`, its
 | 
						|
content will be inserted in an 'About' section at the top of the
 | 
						|
auto-generated index page.  If both `about.md` and `about.html`
 | 
						|
exist, only the first discovered file will be used.
 | 
						|
 | 
						|
If a discovered file name beings with `cmd-` it will be clustered
 | 
						|
into a 'Commands' section of the generated index page.
 | 
						|
 | 
						|
If a discovered file name beings with `servlet-` it will be clustered
 | 
						|
into a 'Servlets' section of the generated index page.
 | 
						|
 | 
						|
If a discovered file name beings with `rest-api-` it will be clustered
 | 
						|
into a 'REST APIs' section of the generated index page.
 | 
						|
 | 
						|
All other files are clustered under a 'Documentation' section.
 | 
						|
 | 
						|
Some optional information from the manifest is extracted and
 | 
						|
displayed as part of the index page, if present in the manifest:
 | 
						|
 | 
						|
[width="40%",options="header"]
 | 
						|
|===================================================
 | 
						|
|Field       | Source Attribute
 | 
						|
|Name        | Implementation-Title
 | 
						|
|Vendor      | Implementation-Vendor
 | 
						|
|Version     | Implementation-Version
 | 
						|
|URL         | Implementation-URL
 | 
						|
|API Version | Gerrit-ApiVersion
 | 
						|
|===================================================
 | 
						|
 | 
						|
[[deployment]]
 | 
						|
== Deployment
 | 
						|
 | 
						|
Compiled plugins and extensions can be deployed to a running Gerrit
 | 
						|
server using the link:cmd-plugin-install.html[plugin install] command.
 | 
						|
 | 
						|
Web UI plugins distributed as a single `.js` file (or `.html` file for
 | 
						|
Polygerrit) can be deployed without the overhead of JAR packaging. For
 | 
						|
more information refer to link:cmd-plugin-install.html[plugin install]
 | 
						|
command.
 | 
						|
 | 
						|
Plugins can also be copied directly into the server's directory at
 | 
						|
`$site_path/plugins/$name.(jar|js|html)`. For Web UI plugins, the name
 | 
						|
of the file, minus the `.js` or `.html` extension, will be used as the
 | 
						|
plugin name. For JAR plugins, the value of the `Gerrit-PluginName`
 | 
						|
manifest attribute will be used, if provided, otherwise the name of
 | 
						|
the file, minus the `.jar` extension, will be used.
 | 
						|
 | 
						|
For Web UI plugins, the plugin version is derived from the filename.
 | 
						|
If the filename contains one or more hyphens, the version is taken
 | 
						|
from the portion following the last hyphen. For example if the plugin
 | 
						|
filename is `my-plugin-1.0.js` the version will be `1.0`. For JAR
 | 
						|
plugins, the version is taken from the `Version` attribute in the
 | 
						|
manifest.
 | 
						|
 | 
						|
Unless disabled, servers periodically scan the `$site_path/plugins`
 | 
						|
directory for updated plugins. The time can be adjusted by
 | 
						|
link:config-gerrit.html#plugins.checkFrequency[plugins.checkFrequency].
 | 
						|
 | 
						|
For disabling plugins the link:cmd-plugin-remove.html[plugin remove]
 | 
						|
command can be used.
 | 
						|
 | 
						|
Disabled plugins can be re-enabled using the
 | 
						|
link:cmd-plugin-enable.html[plugin enable] command.
 | 
						|
 | 
						|
[[reviewer-suggestion]]
 | 
						|
== Reviewer Suggestion Plugins
 | 
						|
 | 
						|
Gerrit provides an extension point that enables Plugins to rank
 | 
						|
the list of reviewer suggestion a user receives upon clicking "Add Reviewer" on
 | 
						|
the change screen.
 | 
						|
 | 
						|
Gerrit supports both a default suggestion that appears when the user has not yet
 | 
						|
typed anything and a filtered suggestion that is shown as the user starts
 | 
						|
typing.
 | 
						|
 | 
						|
Plugins receive a candidate list and can return a `Set` of suggested reviewers
 | 
						|
containing the `Account.Id` and a score for each reviewer. The candidate list is
 | 
						|
non-binding and plugins can choose to return reviewers not initially contained in
 | 
						|
the candidate list.
 | 
						|
 | 
						|
Server administrators can configure the overall weight of each plugin by setting
 | 
						|
the `addreviewer.pluginName-exportName.weight` value in `gerrit.config`.
 | 
						|
 | 
						|
[source, java]
 | 
						|
----
 | 
						|
import com.google.gerrit.common.Nullable;
 | 
						|
import com.google.gerrit.entities.Account;
 | 
						|
import com.google.gerrit.entities.Change;
 | 
						|
import com.google.gerrit.entities.Project;
 | 
						|
import com.google.gerrit.extensions.annotations.ExtensionPoint;
 | 
						|
 | 
						|
import java.util.Set;
 | 
						|
 | 
						|
public class MyPlugin implements ReviewerSuggestion {
 | 
						|
  public Set<SuggestedReviewer> suggestReviewers(Project.NameKey project,
 | 
						|
      @Nullable Change.Id changeId, @Nullable String query,
 | 
						|
      Set<Account.Id> candidates) {
 | 
						|
    Set<SuggestedReviewer> suggestions = new HashSet<>();
 | 
						|
    // Implement your ranking logic here
 | 
						|
    return suggestions;
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
 | 
						|
[[mail-filter]]
 | 
						|
== Mail Filter Plugins
 | 
						|
 | 
						|
Gerrit provides an extension point that enables Plugins to discard incoming
 | 
						|
messages and prevent further processing by Gerrit.
 | 
						|
 | 
						|
This can be used to implement spam checks, signature validations or organization
 | 
						|
specific checks like IP filters.
 | 
						|
 | 
						|
[source, java]
 | 
						|
----
 | 
						|
import com.google.gerrit.extensions.annotations.ExtensionPoint;
 | 
						|
import com.google.gerrit.mail.MailMessage;
 | 
						|
 | 
						|
public class MyPlugin implements MailFilter {
 | 
						|
  public boolean shouldProcessMessage(MailMessage message) {
 | 
						|
    // Implement your filter logic here
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
[[ssh-command-creation-interception]]
 | 
						|
== SSH Command Creation Interception
 | 
						|
 | 
						|
Gerrit provides an extension point that allows a plugin to intercept
 | 
						|
creation of SSH commands and override the functionality with its own
 | 
						|
implementation.
 | 
						|
 | 
						|
[source, java]
 | 
						|
----
 | 
						|
import com.google.gerrit.sshd.SshCreateCommandInterceptor;
 | 
						|
 | 
						|
class MyCommandInterceptor implements SshCreateCommandInterceptor {
 | 
						|
  @Override
 | 
						|
  public String intercept(String in) {
 | 
						|
    return pluginName + " mycommand";
 | 
						|
----
 | 
						|
 | 
						|
[[ssh-command-execution-interception]]
 | 
						|
== SSH Command Execution Interception
 | 
						|
Gerrit provides an extension point that enables plugins to check and
 | 
						|
prevent an SSH command from being run.
 | 
						|
 | 
						|
[source, java]
 | 
						|
----
 | 
						|
import com.google.gerrit.sshd.SshExecuteCommandInterceptor;
 | 
						|
 | 
						|
@Singleton
 | 
						|
public class SshExecuteCommandInterceptorImpl implements SshExecuteCommandInterceptor {
 | 
						|
  private final Provider<SshSession> sessionProvider;
 | 
						|
 | 
						|
  @Inject
 | 
						|
  SshExecuteCommandInterceptorImpl(Provider<SshSession> sessionProvider) {
 | 
						|
    this.sessionProvider = sessionProvider;
 | 
						|
  }
 | 
						|
 | 
						|
  @Override
 | 
						|
  public boolean accept(String command, List<String> arguments) {
 | 
						|
    if (command.startsWith("gerrit") && !"10.1.2.3".equals(sessionProvider.get().getRemoteAddressAsString())) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
And then declare it in your SSH module:
 | 
						|
[source, java]
 | 
						|
----
 | 
						|
  DynamicSet.bind(binder(), SshExecuteCommandInterceptor.class).to(SshExecuteCommandInterceptorImpl.class);
 | 
						|
----
 | 
						|
 | 
						|
[[pre-submit-evaluator]]
 | 
						|
== Pre-submit Validation Plugins
 | 
						|
 | 
						|
Gerrit provides an extension point that enables plugins to prevent a change
 | 
						|
from being submitted.
 | 
						|
 | 
						|
[IMPORTANT]
 | 
						|
This extension point **must NOT** be used for long or slow operations, like
 | 
						|
calling external programs or content, running unit tests...
 | 
						|
Slow operations will hurt the whole Gerrit instance.
 | 
						|
 | 
						|
This can be used to implement custom rules that changes have to match to become
 | 
						|
submittable. A more concrete example: the Prolog rules engine can be
 | 
						|
implemented using this.
 | 
						|
 | 
						|
Gerrit calls the plugins once per change and caches the results. Although it is
 | 
						|
possible to predict when this interface will be triggered, this should not be
 | 
						|
considered as a feature. Plugins should only rely on the internal state of the
 | 
						|
ChangeData, not on external values like date and time, remote content or
 | 
						|
randomness.
 | 
						|
 | 
						|
Plugins are expected to support rules inheritance themselves, providing ways
 | 
						|
to configure it and handling the logic behind it.
 | 
						|
Please note that no inheritance is sometimes better than badly handled
 | 
						|
inheritance: mis-communication and strange behaviors caused by inheritance
 | 
						|
may and will confuse the users. Each plugins is responsible for handling the
 | 
						|
project hierarchy and taking wise actions. Gerrit does not enforce it.
 | 
						|
 | 
						|
Once Gerrit has gathered every plugins' SubmitRecords, it stores them.
 | 
						|
 | 
						|
Plugins accept or reject a given change using `SubmitRecord.Status`.
 | 
						|
If a change is ready to be submitted, `OK`. If it is not ready and requires
 | 
						|
modifications, `NOT_READY`. Other statuses are available for particular cases.
 | 
						|
A change can be submitted if all the plugins accept the change.
 | 
						|
 | 
						|
Plugins may also decide not to vote on a given change by returning an
 | 
						|
`Optional.empty()` (ie: the plugin is not enabled for this repository).
 | 
						|
 | 
						|
If a plugin decides not to vote, it's name will not be displayed in the UI and
 | 
						|
it will not be recoded in the database.
 | 
						|
 | 
						|
.Gerrit's Pre-submit handling with three plugins
 | 
						|
[width="50%",cols="^m,^m,^m,^m",frame="topbot",options="header"]
 | 
						|
|=======================================================
 | 
						|
|  Plugin A  |  Plugin B  |  Plugin C  | Final decision
 | 
						|
|     OK     |     OK     |     OK     | OK
 | 
						|
|     OK     |     OK     |     /      | OK
 | 
						|
|     OK     |     OK     | RULE_ERROR | NOT_READY
 | 
						|
|     OK     | NOT_READY  |     OK     | NOT_READY
 | 
						|
|  NOT_READY |     OK     |     OK     | NOT_READY
 | 
						|
|=======================================================
 | 
						|
 | 
						|
 | 
						|
This makes composing plugins really easy.
 | 
						|
 | 
						|
- If a plugin places a veto on a change, it can't be submitted.
 | 
						|
- If a plugin isn't enabled for a project (or isn't needed for this change),
 | 
						|
  it returns an empty collection.
 | 
						|
- If all the plugins answer `OK`, the change can be submitted.
 | 
						|
 | 
						|
 | 
						|
A more rare case, but worth documenting: if there are no installed plugins,
 | 
						|
the labels will be compared to the rules defined in the project's config,
 | 
						|
and the permission system will be used to allow or deny a submit request.
 | 
						|
 | 
						|
Some rules are defined internally to provide a common base ground (and sanity):
 | 
						|
changes that are marked as WIP or that are closed (abandoned, merged) can't be merged.
 | 
						|
 | 
						|
 | 
						|
[source, java]
 | 
						|
----
 | 
						|
import java.util.Optional;
 | 
						|
import com.google.gerrit.entities.SubmitRecord;
 | 
						|
import com.google.gerrit.entities.SubmitRecord.Status;
 | 
						|
import com.google.gerrit.server.query.change.ChangeData;
 | 
						|
import com.google.gerrit.server.rules.SubmitRule;
 | 
						|
 | 
						|
public class MyPluginRules implements SubmitRule {
 | 
						|
  public Optional<SubmitRecord> evaluate(ChangeData changeData) {
 | 
						|
    // Implement your submitability logic here
 | 
						|
 | 
						|
    // Assuming we want to prevent this change from being submitted:
 | 
						|
    SubmitRecord record = new SubmitRecord();
 | 
						|
    record.status = Status.NOT_READY;
 | 
						|
    return Optional.of(record);
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
Don't forget to register your class!
 | 
						|
 | 
						|
[source, java]
 | 
						|
----
 | 
						|
import com.google.gerrit.extensions.annotations.Exports;
 | 
						|
import com.google.inject.AbstractModule;
 | 
						|
 | 
						|
public class MyPluginModule extends AbstractModule {
 | 
						|
  @Override
 | 
						|
  protected void configure() {
 | 
						|
    bind(SubmitRule.class).annotatedWith(Exports.named("myPlugin")).to(MyPluginRules.class);
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
Plugin authors should also consider binding their SubmitRule using a `Gerrit-BatchModule`.
 | 
						|
See link:dev-plugins.html[Batch runtime] for more informations.
 | 
						|
 | 
						|
 | 
						|
The SubmitRule extension point allows you to write complex rules, but writing
 | 
						|
small self-contained rules should be preferred: doing so allows end users to
 | 
						|
compose several rules to form more complex submit checks.
 | 
						|
 | 
						|
The `SubmitRequirement` class allows rules to communicate what the user needs
 | 
						|
to change in order to be compliant. These requirements should be kept once they
 | 
						|
are met, but marked as `OK`. If the requirements were not displayed, reviewers
 | 
						|
would need to use their precious time to manually check that they were met.
 | 
						|
 | 
						|
Implementors of the `SubmitRule` interface should check whether they need to
 | 
						|
contribute to the link:#change-etag-computation[change ETag computation] to
 | 
						|
prevent callers using ETags from potentially seeing outdated submittability
 | 
						|
information.
 | 
						|
 | 
						|
[[change-etag-computation]]
 | 
						|
== Change ETag Computation
 | 
						|
 | 
						|
By implementing the `com.google.gerrit.server.change.ChangeETagComputation`
 | 
						|
interface plugins can contribute a value to the change ETag computation.
 | 
						|
 | 
						|
Plugins can affect the result of the get change / get change details REST
 | 
						|
endpoints by:
 | 
						|
 | 
						|
* providing link:#query_attributes[plugin defined attributes] in
 | 
						|
  link:rest-api-changes.html#change-info[ChangeInfo]
 | 
						|
* implementing a link:#pre-submit-evaluator[pre-submit evaluator] which affects
 | 
						|
  the computation of `submittable` field in
 | 
						|
  link:rest-api-changes.html#change-info[ChangeInfo]
 | 
						|
 | 
						|
If the plugin defined part of link:rest-api-changes.html#change-info[
 | 
						|
ChangeInfo] depends on plugin specific data, callers that use change ETags to
 | 
						|
avoid unneeded recomputations of ChangeInfos may see outdated plugin attributes
 | 
						|
and/or outdated submittable information, because a ChangeInfo is only reloaded
 | 
						|
if the change ETag changes.
 | 
						|
 | 
						|
By implementating the `com.google.gerrit.server.change.ChangeETagComputation`
 | 
						|
interface plugins can contribute to the ETag computation and thus ensure that
 | 
						|
the change ETag changes when the plugin data was changed. This way it can be
 | 
						|
ensured that callers do not see outdated ChangeInfos.
 | 
						|
 | 
						|
IMPORTANT: Change ETags are computed very frequently and the computation must
 | 
						|
be cheap. Take good care to not perform any expensive computations when
 | 
						|
implementing this.
 | 
						|
 | 
						|
[source, java]
 | 
						|
----
 | 
						|
import static java.nio.charset.StandardCharsets.UTF_8;
 | 
						|
 | 
						|
import com.google.common.hash.Hasher;
 | 
						|
import com.google.gerrit.entities.Change;
 | 
						|
import com.google.gerrit.entities.Project;
 | 
						|
import com.google.gerrit.server.change.ChangeETagComputation;
 | 
						|
 | 
						|
public class MyPluginChangeETagComputation implements ChangeETagComputation {
 | 
						|
  public String getETag(Project.NameKey projectName, Change.Id changeId) {
 | 
						|
    Hasher hasher = Hashing.murmur3_128().newHasher();
 | 
						|
 | 
						|
    // Add hashes for all plugin-specific data that affects change infos.
 | 
						|
    hasher.putString(sha1OfPluginSpecificChangeRef, UTF_8);
 | 
						|
 | 
						|
    return hasher.hash().toString();
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
[[exception-hook]]
 | 
						|
== ExceptionHook
 | 
						|
 | 
						|
An `ExceptionHook` allows implementors to control how certain
 | 
						|
exceptions should be handled.
 | 
						|
 | 
						|
This interface is intended to be implemented for multi-master setups to
 | 
						|
control the behavior for handling exceptions that are thrown by a lower
 | 
						|
layer that handles the consensus and synchronization between different
 | 
						|
server nodes. E.g. if an operation fails because consensus for a Git
 | 
						|
update could not be achieved (e.g. due to slow responding server nodes)
 | 
						|
this interface can be used to retry the request instead of failing it
 | 
						|
immediately.
 | 
						|
 | 
						|
It also allows implementors to group exceptions that have the same
 | 
						|
cause into one metric bucket.
 | 
						|
 | 
						|
[[mail-soy-template-provider]]
 | 
						|
== MailSoyTemplateProvider
 | 
						|
 | 
						|
This extension point allows to provide soy templates for registration
 | 
						|
so that they can be used for sending emails from a plugin.
 | 
						|
 | 
						|
[[quota-enforcer]]
 | 
						|
== Quota Enforcer
 | 
						|
 | 
						|
Gerrit provides an extension point that allows a plugin to enforce quota.
 | 
						|
link:quota.html[This documentation page] has a list of all quota requests that
 | 
						|
Gerrit core issues. Plugins can choose to respond to all or just a subset of
 | 
						|
requests. Some implementations might want to keep track of user quota in buckets,
 | 
						|
others might just check against instance or project state to enforce limits on how
 | 
						|
many projects can be created or how large a repository can become.
 | 
						|
 | 
						|
Checking against instance state can be racy for concurrent requests as the server does not
 | 
						|
refill tokens if the action fails in a later stage (e.g. database failure). If
 | 
						|
plugins want to guarantee an absolute maximum on a resource, they have to do their own
 | 
						|
book-keeping.
 | 
						|
 | 
						|
[source, java]
 | 
						|
----
 | 
						|
import com.google.server.quota.QuotaEnforcer;
 | 
						|
 | 
						|
class ProjectLimiter implements QuotaEnforcer {
 | 
						|
  private final long maxNumberOfProjects = 100;
 | 
						|
  @Override
 | 
						|
  QuotaResponse requestTokens(String quotaGroup, QuotaRequestContext ctx, long numTokens) {
 | 
						|
    if (!"/projects/create".equals(quotaGroup)) {
 | 
						|
      return QuotaResponse.noOp();
 | 
						|
    }
 | 
						|
    // No deduction because we always check against the instance state (racy but fine for
 | 
						|
    // this plugin)
 | 
						|
    if (currentNumberOfProjects() + numTokens > maxNumberOfProjects) {
 | 
						|
      return QuotaResponse.error("too many projects");
 | 
						|
    }
 | 
						|
    return QuotaResponse.ok();
 | 
						|
  }
 | 
						|
 | 
						|
  @Override
 | 
						|
  QuotaResponse dryRun(String quotaGroup, QuotaRequestContext ctx, long numTokens) {
 | 
						|
    // Since we are not keeping any state in this enforcer, we can simply call requestTokens().
 | 
						|
    return requestTokens(quotaGroup, ctx, numTokens);
 | 
						|
  }
 | 
						|
 | 
						|
  void refill(String quotaGroup, QuotaRequestContext ctx, long numTokens) {
 | 
						|
    // No-op
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
[source, java]
 | 
						|
----
 | 
						|
import com.google.server.quota.QuotaEnforcer;
 | 
						|
 | 
						|
class ApiQpsEnforcer implements QuotaEnforcer {
 | 
						|
  // AutoRefillingPerUserBuckets is a imaginary bucket implementation that could be based on
 | 
						|
  // a loading cache or a commonly used bucketing algorithm.
 | 
						|
  private final AutoRefillingPerUserBuckets<CurrentUser, Long> buckets;
 | 
						|
  @Override
 | 
						|
  QuotaResponse requestTokens(String quotaGroup, QuotaRequestContext ctx, long numTokens) {
 | 
						|
    if (!quotaGroup.startsWith("/restapi/")) {
 | 
						|
      return QuotaResponse.noOp();
 | 
						|
    }
 | 
						|
    boolean success = buckets.deduct(ctx.user(), numTokens);
 | 
						|
    if (!success) {
 | 
						|
      return QuotaResponse.error("user sent too many qps, please wait for 5 minutes");
 | 
						|
    }
 | 
						|
    return QuotaResponse.ok();
 | 
						|
  }
 | 
						|
 | 
						|
  @Override
 | 
						|
  QuotaResponse dryRun(String quotaGroup, QuotaRequestContext ctx, long numTokens) {
 | 
						|
    if (!quotaGroup.startsWith("/restapi/")) {
 | 
						|
      return QuotaResponse.noOp();
 | 
						|
    }
 | 
						|
    boolean success = buckets.checkOnly(ctx.user(), numTokens);
 | 
						|
    if (!success) {
 | 
						|
      return QuotaResponse.error("user sent too many qps, please wait for 5 minutes");
 | 
						|
    }
 | 
						|
    return QuotaResponse.ok();
 | 
						|
  }
 | 
						|
 | 
						|
  @Override
 | 
						|
  void refill(String quotaGroup, QuotaRequestContext ctx, long numTokens) {
 | 
						|
    if (!quotaGroup.startsWith("/restapi/")) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    buckets.add(ctx.user(), numTokens);
 | 
						|
  }
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
[[performance-logger]]
 | 
						|
== Performance Logger
 | 
						|
 | 
						|
`com.google.gerrit.server.logging.PerformanceLogger` is an extension point that
 | 
						|
is invoked for all operations for which the execution time is measured. The
 | 
						|
invocation of the extension point does not happen immediately, but only at the
 | 
						|
end of a request (REST call, SSH call, git push). Implementors can write the
 | 
						|
execution times into a performance log for further analysis.
 | 
						|
 | 
						|
[[request-listener]]
 | 
						|
== Request Listener
 | 
						|
 | 
						|
`com.google.gerrit.server.RequestListener` is an extension point that is
 | 
						|
invoked each time the server executes a request from a user.
 | 
						|
 | 
						|
== SEE ALSO
 | 
						|
 | 
						|
* link:js-api.html[JavaScript API]
 | 
						|
* link:dev-rest-api.html[REST API Developers' Notes]
 | 
						|
 | 
						|
GERRIT
 | 
						|
------
 | 
						|
Part of link:index.html[Gerrit Code Review]
 | 
						|
 | 
						|
SEARCHBOX
 | 
						|
---------
 |