blob: fa2b78ca1eb42c0e8140f452a07495773db44ba5 [file] [log] [blame]
Marian Harbachebeb1542019-12-13 10:42:46 +01001:linkattrs:
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002= Gerrit Code Review - Plugin Development
Deniz Türkoglueb78b602012-05-07 14:02:36 -07003
Edwin Kempinaf275322012-07-16 11:04:01 +02004The Gerrit server functionality can be extended by installing plugins.
Luca Milanesio71e434c2019-06-03 13:13:13 +01005This page describes how plugins for Gerrit can be developed and hosted
6on gerrit-review.googlesource.com.
Edwin Kempinaf275322012-07-16 11:04:01 +02007
Ben Rohlfsda0a62b2021-04-26 17:02:19 +02008For JavaScript plugin development, consult with
9link:pg-plugin-dev.html[JavaScript Plugin Development] guide.
Viktar Donich5055e8d2017-11-09 13:02:42 -080010
Edwin Kempinaf275322012-07-16 11:04:01 +020011Depending on how tightly the extension code is coupled with the Gerrit
12server code, there is a distinction between `plugins` and `extensions`.
13
Edwin Kempinf5a77332012-07-18 11:17:53 +020014[[plugin]]
Edwin Kempin948de0f2012-07-16 10:34:35 +020015A `plugin` in Gerrit is tightly coupled code that runs in the same
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070016JVM as Gerrit. It has full access to all server internals. Plugins
17are tightly coupled to a specific major.minor server version and
18may require source code changes to compile against a different
19server version.
20
Luca Milanesio86b9b6c2017-08-09 09:54:36 +010021Plugins may require a specific major.minor.patch server version
22and may need rebuild and revalidation across different
23patch levels. A different patch level may only add new
24API interfaces and never change or extend existing ones.
25
Edwin Kempinf5a77332012-07-18 11:17:53 +020026[[extension]]
Edwin Kempin948de0f2012-07-16 10:34:35 +020027An `extension` in Gerrit runs inside of the same JVM as Gerrit
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070028in the same way as a plugin, but has limited visibility to the
Edwin Kempinfd19bfb2012-07-16 10:44:17 +020029server's internals. The limited visibility reduces the extension's
30dependencies, enabling it to be compatible across a wider range
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070031of server versions.
32
33Most of this documentation refers to either type as a plugin.
Deniz Türkoglueb78b602012-05-07 14:02:36 -070034
Edwin Kempinf878c4b2012-07-18 09:34:25 +020035[[getting-started]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -080036== Getting started
Deniz Türkoglueb78b602012-05-07 14:02:36 -070037
David Pursehouse092ac5c2019-04-25 19:55:49 +090038To get started with the development of a plugin, take a look at
39the samples in the
Marian Harbach34253372019-12-10 18:01:31 +010040link:https://gerrit.googlesource.com/plugins/examples[examples plugin project,role=external,window=_blank].
David Pursehousecf2e9002017-03-01 19:10:43 +090041
42This is a project that demonstrates the various features of the
43plugin API. It can be taken as an example to develop an own plugin.
44
Edwin Kempinf878c4b2012-07-18 09:34:25 +020045When starting from this example one should take care to adapt the
David Ostrovskya052e522016-12-10 17:53:16 +010046`Gerrit-ApiVersion` in the `BUILD` to the version of Gerrit for which
47the plugin is developed.
Dave Borowitz5cc8f662012-05-21 09:51:36 -070048
Edwin Kempinf878c4b2012-07-18 09:34:25 +020049[[API]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -080050== API
Edwin Kempinf878c4b2012-07-18 09:34:25 +020051
52There are two different API formats offered against which plugins can
53be developed:
Deniz Türkoglueb78b602012-05-07 14:02:36 -070054
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070055gerrit-extension-api.jar::
56 A stable but thin interface. Suitable for extensions that need
57 to be notified of events, but do not require tight coupling to
58 the internals of Gerrit. Extensions built against this API can
59 expect to be binary compatible across a wide range of server
60 versions.
Deniz Türkoglueb78b602012-05-07 14:02:36 -070061
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070062gerrit-plugin-api.jar::
63 The complete internals of the Gerrit server, permitting a
64 plugin to tightly couple itself and provide additional
65 functionality that is not possible as an extension. Plugins
66 built against this API are expected to break at the source
67 code level between every major.minor Gerrit release. A plugin
68 that compiles against 2.5 will probably need source code level
69 changes to work with 2.6, 2.7, and so on.
Deniz Türkoglueb78b602012-05-07 14:02:36 -070070
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -080071== Manifest
Deniz Türkoglueb78b602012-05-07 14:02:36 -070072
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070073Plugins may provide optional description information with standard
74manifest fields:
Nasser Grainawie033b262012-05-09 17:54:21 -070075
Michael Ochmannb99feab2016-07-06 14:10:22 +020076----
Nasser Grainawieea60392021-08-23 09:05:10 -060077Implementation-Title: Example plugin showing examples
78Implementation-Version: 1.0
79Implementation-Vendor: Example, Inc.
Michael Ochmannb99feab2016-07-06 14:10:22 +020080----
Nasser Grainawie033b262012-05-09 17:54:21 -070081
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -080082=== ApiType
Nasser Grainawie033b262012-05-09 17:54:21 -070083
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070084Plugins using the tightly coupled `gerrit-plugin-api.jar` must
85declare this API dependency in the manifest to gain access to server
Edwin Kempin948de0f2012-07-16 10:34:35 +020086internals. If no `Gerrit-ApiType` is specified the stable `extension`
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070087API will be assumed. This may cause ClassNotFoundExceptions when
88loading a plugin that needs the plugin API.
Nasser Grainawie033b262012-05-09 17:54:21 -070089
Michael Ochmannb99feab2016-07-06 14:10:22 +020090----
Nasser Grainawieea60392021-08-23 09:05:10 -060091Gerrit-ApiType: plugin
Michael Ochmannb99feab2016-07-06 14:10:22 +020092----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070093
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -080094=== Explicit Registration
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070095
96Plugins that use explicit Guice registration must name the Guice
97modules in the manifest. Up to three modules can be named in the
Edwin Kempin948de0f2012-07-16 10:34:35 +020098manifest. `Gerrit-Module` supplies bindings to the core server;
99`Gerrit-SshModule` supplies SSH commands to the SSH server (if
100enabled); `Gerrit-HttpModule` supplies servlets and filters to the HTTP
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700101server (if enabled). If no modules are named automatic registration
102will be performed by scanning all classes in the plugin JAR for
103`@Listen` and `@Export("")` annotations.
104
Michael Ochmannb99feab2016-07-06 14:10:22 +0200105----
Nasser Grainawieea60392021-08-23 09:05:10 -0600106Gerrit-Module: tld.example.project.CoreModuleClassName
107Gerrit-SshModule: tld.example.project.SshModuleClassName
108Gerrit-HttpModule: tld.example.project.HttpModuleClassName
Michael Ochmannb99feab2016-07-06 14:10:22 +0200109----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700110
Maxime Guerreiro5ccf0b92018-05-15 16:28:22 +0000111=== Batch runtime
112
113Gerrit can be run as a server, serving HTTP or SSH requests, or as an
114offline program. Plugins can contribute Guice modules to this batch
115runtime by binding `Gerrit-BatchModule` to one of their classes.
116The Guice injector is bound to less classes, and some Gerrit features
117will be absent - on purpose.
118
119This feature was originally introduced to support plugins during an
120offline reindexing task.
121
122----
Nasser Grainawieea60392021-08-23 09:05:10 -0600123Gerrit-BatchModule: tld.example.project.CoreModuleClassName
Maxime Guerreiro5ccf0b92018-05-15 16:28:22 +0000124----
125
126In this runtime, only the module designated by `Gerrit-BatchModule` is
127enabled, not `Gerrit-SysModule`.
128
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200129[[plugin_name]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800130=== Plugin Name
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200131
David Pursehoused128c892013-10-22 21:52:21 +0900132A plugin can optionally provide its own plugin name.
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200133
Michael Ochmannb99feab2016-07-06 14:10:22 +0200134----
Nasser Grainawieea60392021-08-23 09:05:10 -0600135Gerrit-PluginName: replication
Michael Ochmannb99feab2016-07-06 14:10:22 +0200136----
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200137
138This is useful for plugins that contribute plugin-owned capabilities that
139are stored in the `project.config` file. Another use case is to be able to put
140project specific plugin configuration section in `project.config`. In this
141case it is advantageous to reserve the plugin name to access the configuration
142section in the `project.config` file.
143
144If `Gerrit-PluginName` is omitted, then the plugin's name is determined from
145the plugin file name.
146
147If a plugin provides its own name, then that plugin cannot be deployed
148multiple times under different file names on one Gerrit site.
149
150For Maven driven plugins, the following line must be included in the pom.xml
151file:
152
153[source,xml]
154----
155<manifestEntries>
156 <Gerrit-PluginName>name</Gerrit-PluginName>
157</manifestEntries>
158----
159
David Ostrovskyfdbfcad2016-11-15 06:35:29 -0800160For Bazel driven plugins, the following line must be included in the BUILD
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200161configuration file:
162
163[source,python]
164----
David Pursehouse529ec252013-09-27 13:45:14 +0900165manifest_entries = [
166 'Gerrit-PluginName: name',
167]
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200168----
169
Edwin Kempinc0b1b0e2013-10-01 14:13:54 +0200170A plugin can get its own name injected at runtime:
171
172[source,java]
173----
174public class MyClass {
175
176 private final String pluginName;
177
178 @Inject
179 public MyClass(@PluginName String pluginName) {
180 this.pluginName = pluginName;
181 }
182
David Pursehoused128c892013-10-22 21:52:21 +0900183 [...]
Edwin Kempinc0b1b0e2013-10-01 14:13:54 +0200184}
185----
186
David Pursehouse8ed0d922013-10-18 18:57:56 +0900187A plugin can get its canonical web URL injected at runtime:
188
189[source,java]
190----
191public class MyClass {
192
193 private final String url;
194
195 @Inject
196 public MyClass(@PluginCanonicalWebUrl String url) {
197 this.url = url;
198 }
199
200 [...]
201}
202----
203
204The URL is composed of the server's canonical web URL and the plugin's
205name, i.e. `http://review.example.com:8080/plugin-name`.
206
207The canonical web URL may be injected into any .jar plugin regardless of
208whether or not the plugin provides an HTTP servlet.
209
Edwin Kempinf7295742012-07-16 15:03:46 +0200210[[reload_method]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800211=== Reload Method
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700212
213If a plugin holds an exclusive resource that must be released before
214loading the plugin again (for example listening on a network port or
Edwin Kempin948de0f2012-07-16 10:34:35 +0200215acquiring a file lock) the manifest must declare `Gerrit-ReloadMode`
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700216to be `restart`. Otherwise the preferred method of `reload` will
217be used, as it enables the server to hot-patch an updated plugin
218with no down time.
219
Michael Ochmannb99feab2016-07-06 14:10:22 +0200220----
Nasser Grainawieea60392021-08-23 09:05:10 -0600221Gerrit-ReloadMode: restart
Michael Ochmannb99feab2016-07-06 14:10:22 +0200222----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700223
224In either mode ('restart' or 'reload') any plugin or extension can
225be updated without restarting the Gerrit server. The difference is
226how Gerrit handles the upgrade:
227
228restart::
229 The old plugin is completely stopped. All registrations of SSH
230 commands and HTTP servlets are removed. All registrations of any
231 extension points are removed. All registered LifecycleListeners
232 have their `stop()` method invoked in reverse order. The new
233 plugin is started, and registrations are made from the new
234 plugin. There is a brief window where neither the old nor the
235 new plugin is connected to the server. This means SSH commands
236 and HTTP servlets will return not found errors, and the plugin
237 will not be notified of events that occurred during the restart.
238
239reload::
240 The new plugin is started. Its LifecycleListeners are permitted
241 to perform their `start()` methods. All SSH and HTTP registrations
242 are atomically swapped out from the old plugin to the new plugin,
243 ensuring the server never returns a not found error. All extension
244 point listeners are atomically swapped out from the old plugin to
245 the new plugin, ensuring no events are missed (however some events
246 may still route to the old plugin if the swap wasn't complete yet).
247 The old plugin is stopped.
248
Edwin Kempinf7295742012-07-16 15:03:46 +0200249To reload/restart a plugin the link:cmd-plugin-reload.html[plugin reload]
250command can be used.
251
Luca Milanesio737285d2012-09-25 14:26:43 +0100252[[init_step]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800253=== Init step
Luca Milanesio737285d2012-09-25 14:26:43 +0100254
255Plugins can contribute their own "init step" during the Gerrit init
256wizard. This is useful for guiding the Gerrit administrator through
David Pursehouse659860f2013-12-16 14:50:04 +0900257the settings needed by the plugin to work properly.
Luca Milanesio737285d2012-09-25 14:26:43 +0100258
259For instance plugins to integrate Jira issues to Gerrit changes may
260contribute their own "init step" to allow configuring the Jira URL,
261credentials and possibly verify connectivity to validate them.
262
Michael Ochmannb99feab2016-07-06 14:10:22 +0200263----
Nasser Grainawieea60392021-08-23 09:05:10 -0600264Gerrit-InitStep: tld.example.project.MyInitStep
Michael Ochmannb99feab2016-07-06 14:10:22 +0200265----
Luca Milanesio737285d2012-09-25 14:26:43 +0100266
267MyInitStep needs to follow the standard Gerrit InitStep syntax
David Pursehouse92463562013-06-24 10:16:28 +0900268and behavior: writing to the console using the injected ConsoleUI
Luca Milanesio737285d2012-09-25 14:26:43 +0100269and accessing / changing configuration settings using Section.Factory.
270
271In addition to the standard Gerrit init injections, plugins receive
272the @PluginName String injection containing their own plugin name.
273
Edwin Kempind4cfac12013-11-27 11:22:34 +0100274During their initialization plugins may get access to the
275`project.config` file of the `All-Projects` project and they are able
276to store configuration parameters in it. For this a plugin `InitStep`
Jiří Engelthaler3033a0a2015-02-16 09:44:32 +0100277can get `com.google.gerrit.pgm.init.api.AllProjectsConfig` injected:
Edwin Kempind4cfac12013-11-27 11:22:34 +0100278
279[source,java]
280----
Nasser Grainawieea60392021-08-23 09:05:10 -0600281public class MyInitStep implements InitStep {
282 private final String pluginName;
283 private final ConsoleUI ui;
284 private final AllProjectsConfig allProjectsConfig;
Edwin Kempind4cfac12013-11-27 11:22:34 +0100285
Nasser Grainawieea60392021-08-23 09:05:10 -0600286 @Inject
287 public MyInitStep(@PluginName String pluginName, ConsoleUI ui,
288 AllProjectsConfig allProjectsConfig) {
289 this.pluginName = pluginName;
290 this.ui = ui;
291 this.allProjectsConfig = allProjectsConfig;
Edwin Kempind4cfac12013-11-27 11:22:34 +0100292 }
Nasser Grainawieea60392021-08-23 09:05:10 -0600293
294 @Override
295 public void run() throws Exception {
296 }
297
298 @Override
299 public void postRun() throws Exception {
300 ui.message("\n");
301 ui.header(pluginName + " Integration");
302 boolean enabled = ui.yesno(true, "By default enabled for all projects");
303 Config cfg = allProjectsConfig.load().getConfig();
304 if (enabled) {
305 cfg.setBoolean("plugin", pluginName, "enabled", enabled);
306 } else {
307 cfg.unset("plugin", pluginName, "enabled");
308 }
309 allProjectsConfig.save(pluginName, "Initialize " + pluginName + " Integration");
310 }
311}
Edwin Kempind4cfac12013-11-27 11:22:34 +0100312----
313
Luca Milanesio737285d2012-09-25 14:26:43 +0100314Bear in mind that the Plugin's InitStep class will be loaded but
315the standard Gerrit runtime environment is not available and the plugin's
316own Guice modules were not initialized.
317This means the InitStep for a plugin is not executed in the same way that
318the plugin executes within the server, and may mean a plugin author cannot
319trivially reuse runtime code during init.
320
321For instance a plugin that wants to verify connectivity may need to statically
322call the constructor of their connection class, passing in values obtained
323from the Section.Factory rather than from an injected Config object.
324
David Pursehoused128c892013-10-22 21:52:21 +0900325Plugins' InitSteps are executed during the "Gerrit Plugin init" phase, after
326the extraction of the plugins embedded in the distribution .war file into
Dave Borowitzada289c2018-12-18 13:24:14 -0800327`$GERRIT_SITE/plugins` and before the site initialization or upgrade.
David Pursehoused128c892013-10-22 21:52:21 +0900328
Dave Borowitzada289c2018-12-18 13:24:14 -0800329A plugin's InitStep cannot refer to any Gerrit runtime objects injected at
330startup.
Luca Milanesio737285d2012-09-25 14:26:43 +0100331
David Pursehouse68153d72013-09-04 10:09:17 +0900332[source,java]
333----
334public class MyInitStep implements InitStep {
335 private final ConsoleUI ui;
336 private final Section.Factory sections;
337 private final String pluginName;
Luca Milanesio737285d2012-09-25 14:26:43 +0100338
David Pursehouse68153d72013-09-04 10:09:17 +0900339 @Inject
340 public GitBlitInitStep(final ConsoleUI ui, Section.Factory sections, @PluginName String pluginName) {
341 this.ui = ui;
342 this.sections = sections;
343 this.pluginName = pluginName;
Luca Milanesio737285d2012-09-25 14:26:43 +0100344 }
David Pursehouse68153d72013-09-04 10:09:17 +0900345
346 @Override
347 public void run() throws Exception {
348 ui.header("\nMy plugin");
349
350 Section mySection = getSection("myplugin", null);
351 mySection.string("Link name", "linkname", "MyLink");
352 }
Edwin Kempin93e7d5d2014-01-03 09:53:20 +0100353
354 @Override
355 public void postRun() throws Exception {
356 }
David Pursehouse68153d72013-09-04 10:09:17 +0900357}
358----
Luca Milanesio737285d2012-09-25 14:26:43 +0100359
Edwin Kempinf5a77332012-07-18 11:17:53 +0200360[[classpath]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800361== Classpath
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700362
363Each plugin is loaded into its own ClassLoader, isolating plugins
364from each other. A plugin or extension inherits the Java runtime
365and the Gerrit API chosen by `Gerrit-ApiType` (extension or plugin)
366from the hosting server.
367
368Plugins are loaded from a single JAR file. If a plugin needs
369additional libraries, it must include those dependencies within
370its own JAR. Plugins built using Maven may be able to use the
Marian Harbach34253372019-12-10 18:01:31 +0100371link:http://maven.apache.org/plugins/maven-shade-plugin/[shade plugin,role=external,window=_blank]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700372to package additional dependencies. Relocating (or renaming) classes
373should not be necessary due to the ClassLoader isolation.
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700374
Edwin Kempin98202662013-09-18 16:03:03 +0200375[[events]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800376== Listening to Events
Edwin Kempin98202662013-09-18 16:03:03 +0200377
378Certain operations in Gerrit trigger events. Plugins may receive
379notifications of these events by implementing the corresponding
380listeners.
381
Jakub Sokół87c5bfc42020-09-17 16:06:23 +0200382* `com.google.gerrit.server.events.EventListener`:
Edwin Kempin64059f52013-10-31 13:49:25 +0100383+
Hugo Arèsbc1093d2016-02-23 15:04:50 -0500384Allows to listen to events without user visibility restrictions. These
385are the same link:cmd-stream-events.html#events[events] that are also streamed by
Edwin Kempin64059f52013-10-31 13:49:25 +0100386the link:cmd-stream-events.html[gerrit stream-events] command.
387
Jakub Sokół87c5bfc42020-09-17 16:06:23 +0200388* `com.google.gerrit.server.events.UserScopedEventListener`:
Hugo Arèsbc1093d2016-02-23 15:04:50 -0500389+
390Allows to listen to events visible to the specified user. These are the
391same link:cmd-stream-events.html#events[events] that are also streamed
392by the link:cmd-stream-events.html[gerrit stream-events] command.
393
Christian Aistleitnerdb930bd2020-05-01 11:05:38 +0200394* `com.google.gerrit.extensions.events.AccountActivationListener`:
395+
396User account got activated or deactivated
397
Edwin Kempin98202662013-09-18 16:03:03 +0200398* `com.google.gerrit.extensions.events.LifecycleListener`:
399+
Edwin Kempin3e7928a2013-12-03 07:39:00 +0100400Plugin start and stop
Edwin Kempin98202662013-09-18 16:03:03 +0200401
402* `com.google.gerrit.extensions.events.NewProjectCreatedListener`:
403+
404Project creation
405
406* `com.google.gerrit.extensions.events.ProjectDeletedListener`:
407+
408Project deletion
409
Edwin Kempinb27c9392013-11-19 13:12:43 +0100410* `com.google.gerrit.extensions.events.HeadUpdatedListener`:
411+
412Update of HEAD on a project
413
Stefan Lay310d77d2014-05-28 13:45:25 +0200414* `com.google.gerrit.extensions.events.UsageDataPublishedListener`:
415+
416Publication of usage data
417
Adrian Görlerf4a4c9a2014-08-22 17:09:18 +0200418* `com.google.gerrit.extensions.events.GarbageCollectorListener`:
419+
420Garbage collection ran on a project
421
Hector Oswaldo Caballero5fbbdad2015-11-11 14:30:46 -0500422* `com.google.gerrit.server.extensions.events.ChangeIndexedListener`:
423+
Hugo Arès682171f2017-04-24 13:44:43 +0200424Update of the change secondary index
425
426* `com.google.gerrit.server.extensions.events.AccountIndexedListener`:
427+
428Update of the account secondary index
Hector Oswaldo Caballero5fbbdad2015-11-11 14:30:46 -0500429
Hugo Arèsee788ddb2017-05-08 10:23:45 -0400430* `com.google.gerrit.server.extensions.events.GroupIndexedListener`:
431+
432Update of the group secondary index
433
Xin Sun97169862017-07-13 17:31:16 -0700434* `com.google.gerrit.server.extensions.events.ProjectIndexedListener`:
435+
436Update of the project secondary index
437
Luca Milanesio45da6182016-05-12 11:33:30 +0100438* `com.google.gerrit.httpd.WebLoginListener`:
439+
440User login or logout interactively on the Web user interface.
441
442The event listener is under the Gerrit http package to automatically
443inherit the javax.servlet.http dependencies and allowing to influence
444the login or logout flow with additional redirections.
445
Yang Zhenhui2659d422013-07-30 16:59:58 +0800446[[stream-events]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800447== Sending Events to the Events Stream
Yang Zhenhui2659d422013-07-30 16:59:58 +0800448
449Plugins may send events to the events stream where consumers of
450Gerrit's `stream-events` ssh command will receive them.
451
452To send an event, the plugin must invoke one of the `postEvent`
David Pursehousea9bf4762016-07-08 09:34:35 +0900453methods in the `EventDispatcher` interface, passing an instance of
Martin Fick4c72aea2014-12-10 14:58:12 -0700454its own custom event class derived from
455`com.google.gerrit.server.events.Event`.
Yang Zhenhui2659d422013-07-30 16:59:58 +0800456
David Pursehousea9bf4762016-07-08 09:34:35 +0900457[source,java]
458----
459import com.google.gerrit.common.EventDispatcher;
Nasser Grainawiae454832019-11-15 14:25:51 -0800460import com.google.gerrit.exceptions.StorageException;
David Pursehousea9bf4762016-07-08 09:34:35 +0900461import com.google.gerrit.extensions.registration.DynamicItem;
David Pursehousea9bf4762016-07-08 09:34:35 +0900462import com.google.inject.Inject;
463
464class MyPlugin {
465 private final DynamicItem<EventDispatcher> eventDispatcher;
466
467 @Inject
468 myPlugin(DynamicItem<EventDispatcher> eventDispatcher) {
469 this.eventDispatcher = eventDispatcher;
470 }
471
472 private void postEvent(MyPluginEvent event) {
473 try {
474 eventDispatcher.get().postEvent(event);
Dave Borowitz62f32fc2019-01-15 18:45:22 -0800475 } catch (StorageException e) {
David Pursehousea9bf4762016-07-08 09:34:35 +0900476 // error handling
477 }
478 }
479}
480----
481
Martin Fick0aef6f12014-12-11 16:54:21 -0700482Plugins which define new Events should register them via the
David Ostrovsky5edf70e2020-02-04 22:57:14 +0100483`com.google.gerrit.server.events.EventTypes.register()` method.
484This will make the EventType known to the system. Deserializing
485events with the
Martin Fickf70c20a2014-12-11 17:03:15 -0700486`com.google.gerrit.server.events.EventDeserializer` class requires
487that the event be registered in EventTypes.
Martin Fick0aef6f12014-12-11 16:54:21 -0700488
Martin Fickecafc932014-12-15 14:09:41 -0700489== Modifying the Stream Event Flow
490
491It is possible to modify the stream event flow from plugins by registering
492an `com.google.gerrit.server.events.EventDispatcher`. A plugin may register
493a Dispatcher class to replace the internal Dispatcher. EventDispatcher is
494a DynamicItem, so Gerrit may only have one copy.
495
Edwin Kempin32737602014-01-23 09:04:58 +0100496[[validation]]
David Pursehouse91c5f5e2014-01-23 18:57:33 +0900497== Validation Listeners
Edwin Kempin32737602014-01-23 09:04:58 +0100498
499Certain operations in Gerrit can be validated by plugins by
500implementing the corresponding link:config-validation.html[listeners].
501
Andrii Shyshkalov6fdc8eb2016-11-29 17:45:01 +0100502[[change-message-modifier]]
503== Change Message Modifier
504
505`com.google.gerrit.server.git.ChangeMessageModifier`:
506plugins implementing this can modify commit message of the change being
507submitted by Rebase Always and Cherry Pick submit strategies as well as
508change being queried with COMMIT_FOOTERS option.
509
Edwin Kempin19e89c82017-10-11 12:00:51 +0200510[[merge-super-set-computation]]
511== Merge Super Set Computation
512
513The algorithm to compute the merge super set to detect changes that
514should be submitted together can be customized by implementing
515`com.google.gerrit.server.git.MergeSuperSetComputation`.
516MergeSuperSetComputation is a DynamicItem, so Gerrit may only have one
517implementation.
518
Saša Živkovec85a072014-01-28 10:08:25 +0100519[[receive-pack]]
520== Receive Pack Initializers
521
Dave Borowitzb8a2bae2017-10-03 10:34:28 +0100522Plugins may provide ReceivePackInitializer instances, which will be
523invoked by Gerrit just before a ReceivePack instance will be used.
524Usually, plugins will make use of the setXXX methods on the ReceivePack
525to set additional properties on it.
526
527The interactions with the core Gerrit ReceivePack initialization and
528between ReceivePackInitializers can be complex. Please read the
529ReceivePack Javadoc and Gerrit AsyncReceiveCommits implementation
530carefully.
Saša Živkovec85a072014-01-28 10:08:25 +0100531
Saša Živkov626c7312014-02-24 17:15:08 +0100532[[post-receive-hook]]
533== Post Receive-Pack Hooks
534
535Plugins may register PostReceiveHook instances in order to get
536notified when JGit successfully receives a pack. This may be useful
537for those plugins which would like to monitor changes in Git
538repositories.
539
Dave Borowitz223580f2017-10-03 09:55:51 +0100540[[upload-pack]]
541== Upload Pack Initializers
542
543Plugins may provide UploadPackInitializer instances, which will be
544invoked by Gerrit just before a UploadPack instance will be used.
545Usually, plugins will make use of the setXXX methods on the UploadPack
546to set additional properties on it.
547
548The interactions with the core Gerrit UploadPack initialization and
549between UploadPackInitializers can be complex. Please read the
550UploadPack Javadoc and Gerrit Upload/UploadFactory implementations
551carefully.
552
Hugo Arès572d5422014-06-17 14:22:03 -0400553[[pre-upload-hook]]
554== Pre Upload-Pack Hooks
555
556Plugins may register PreUploadHook instances in order to get
557notified when JGit is about to upload a pack. This may be useful
558for those plugins which would like to monitor usage in Git
559repositories.
560
Hugo Arès41b4c0d2016-08-02 15:26:57 -0400561[[post-upload-hook]]
562== Post Upload-Pack Hooks
563
564Plugins may register PostUploadHook instances in order to get notified after
565JGit is done uploading a pack.
566
Edwin Kempinf5a77332012-07-18 11:17:53 +0200567[[ssh]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800568== SSH Commands
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700569
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700570Plugins may provide commands that can be accessed through the SSH
571interface (extensions do not have this option).
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700572
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700573Command implementations must extend the base class SshCommand:
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700574
David Pursehouse68153d72013-09-04 10:09:17 +0900575[source,java]
576----
577import com.google.gerrit.sshd.SshCommand;
David Ostrovskyb7d97752013-11-09 05:23:26 +0100578import com.google.gerrit.sshd.CommandMetaData;
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700579
Ian Bulle1a12202014-02-16 17:15:42 -0800580@CommandMetaData(name="print", description="Print hello command")
David Pursehouse68153d72013-09-04 10:09:17 +0900581class PrintHello extends SshCommand {
Ian Bulle1a12202014-02-16 17:15:42 -0800582 @Override
583 protected void run() {
David Pursehouse68153d72013-09-04 10:09:17 +0900584 stdout.print("Hello\n");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700585 }
David Pursehouse68153d72013-09-04 10:09:17 +0900586}
587----
Nasser Grainawie033b262012-05-09 17:54:21 -0700588
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700589If no Guice modules are declared in the manifest, SSH commands may
Edwin Kempin948de0f2012-07-16 10:34:35 +0200590use auto-registration by providing an `@Export` annotation:
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700591
David Pursehouse68153d72013-09-04 10:09:17 +0900592[source,java]
593----
594import com.google.gerrit.extensions.annotations.Export;
595import com.google.gerrit.sshd.SshCommand;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700596
David Pursehouse68153d72013-09-04 10:09:17 +0900597@Export("print")
598class PrintHello extends SshCommand {
Ian Bulle1a12202014-02-16 17:15:42 -0800599 @Override
600 protected void run() {
David Pursehouse68153d72013-09-04 10:09:17 +0900601 stdout.print("Hello\n");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700602 }
David Pursehouse68153d72013-09-04 10:09:17 +0900603}
604----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700605
606If explicit registration is being used, a Guice module must be
607supplied to register the SSH command and declared in the manifest
608with the `Gerrit-SshModule` attribute:
609
David Pursehouse68153d72013-09-04 10:09:17 +0900610[source,java]
611----
612import com.google.gerrit.sshd.PluginCommandModule;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700613
David Pursehouse68153d72013-09-04 10:09:17 +0900614class MyCommands extends PluginCommandModule {
Ian Bulle1a12202014-02-16 17:15:42 -0800615 @Override
David Pursehouse68153d72013-09-04 10:09:17 +0900616 protected void configureCommands() {
David Ostrovskyb7d97752013-11-09 05:23:26 +0100617 command(PrintHello.class);
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700618 }
David Pursehouse68153d72013-09-04 10:09:17 +0900619}
620----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700621
622For a plugin installed as name `helloworld`, the command implemented
623by PrintHello class will be available to users as:
624
625----
Keunhong Parka09a6f12012-07-10 14:45:02 -0600626$ ssh -p 29418 review.example.com helloworld print
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700627----
628
David Ostrovsky79c4d892014-03-15 13:52:46 +0100629[[multiple-commands]]
630=== Multiple Commands bound to one implementation
631
David Ostrovskye3172b32013-10-13 14:19:13 +0200632Multiple SSH commands can be bound to the same implementation class. For
633example a Gerrit Shell plugin can bind different shell commands to the same
634implementation class:
635
636[source,java]
637----
638public class SshShellModule extends PluginCommandModule {
639 @Override
640 protected void configureCommands() {
641 command("ls").to(ShellCommand.class);
642 command("ps").to(ShellCommand.class);
643 [...]
644 }
645}
646----
647
648With the possible implementation:
649
650[source,java]
651----
652public class ShellCommand extends SshCommand {
653 @Override
654 protected void run() throws UnloggedFailure {
655 String cmd = getName().substring(getPluginName().length() + 1);
656 ProcessBuilder proc = new ProcessBuilder(cmd);
657 Process cmd = proc.start();
658 [...]
659 }
660}
661----
662
663And the call:
664
665----
666$ ssh -p 29418 review.example.com shell ls
667$ ssh -p 29418 review.example.com shell ps
668----
669
David Ostrovsky79c4d892014-03-15 13:52:46 +0100670[[root-level-commands]]
671=== Root Level Commands
672
David Ostrovskyb7d97752013-11-09 05:23:26 +0100673Single command plugins are also supported. In this scenario plugin binds
674SSH command to its own name. `SshModule` must inherit from
675`SingleCommandPluginModule` class:
676
677[source,java]
678----
679public class SshModule extends SingleCommandPluginModule {
680 @Override
681 protected void configure(LinkedBindingBuilder<Command> b) {
682 b.to(ShellCommand.class);
683 }
684}
685----
686
687If the plugin above is deployed under sh.jar file in `$site/plugins`
David Pursehouse659860f2013-12-16 14:50:04 +0900688directory, generic commands can be called without specifying the
David Ostrovskyb7d97752013-11-09 05:23:26 +0100689actual SSH command. Note in the example below, that the called commands
690`ls` and `ps` was not explicitly bound:
691
692----
693$ ssh -p 29418 review.example.com sh ls
694$ ssh -p 29418 review.example.com sh ps
695----
696
Martin Fick5f6222912015-11-12 14:52:50 -0700697[[search_operators]]
Edwin Kempin4b479772016-11-14 14:34:33 -0800698== Search Operators
Martin Fick5f6222912015-11-12 14:52:50 -0700699
700Plugins can define new search operators to extend change searching by
701implementing the `ChangeQueryBuilder.ChangeOperatorFactory` interface
702and registering it to an operator name in the plugin module's
703`configure()` method. The search operator name is defined during
704registration via the DynamicMap annotation mechanism. The plugin
705name will get appended to the annotated name, with an underscore
706in between, leading to the final operator name. An example
707registration looks like this:
708
Nasser Grainawieea60392021-08-23 09:05:10 -0600709[source,java]
710----
711bind(ChangeOperatorFactory.class)
712 .annotatedWith(Exports.named("sample"))
713 .to(SampleOperator.class);
714----
Martin Fick5f6222912015-11-12 14:52:50 -0700715
716If this is registered in the `myplugin` plugin, then the resulting
717operator will be named `sample_myplugin`.
718
719The search operator itself is implemented by ensuring that the
720`create()` method of the class implementing the
721`ChangeQueryBuilder.ChangeOperatorFactory` interface returns a
722`Predicate<ChangeData>`. Here is a sample operator factory
David Pursehousea61ee502016-09-06 16:27:09 +0900723definition which creates a `MyPredicate`:
Martin Fick5f6222912015-11-12 14:52:50 -0700724
725[source,java]
726----
Martin Fick5f6222912015-11-12 14:52:50 -0700727public class SampleOperator
728 implements ChangeQueryBuilder.ChangeOperatorFactory {
Dave Borowitzb53581b2019-04-08 08:25:32 -0700729 public static class MyPredicate extends PostFilterPredicate<ChangeData> {
Martin Fick5f6222912015-11-12 14:52:50 -0700730 ...
731 }
732
733 @Override
734 public Predicate<ChangeData> create(ChangeQueryBuilder builder, String value)
735 throws QueryParseException {
736 return new MyPredicate(value);
737 }
738}
739----
740
Craig Chapeldba4e892016-11-14 09:25:17 -0700741[[search_operands]]
Nasser Grainawieea60392021-08-23 09:05:10 -0600742== Search Operands
Craig Chapeldba4e892016-11-14 09:25:17 -0700743
744Plugins can define new search operands to extend change searching.
745Plugin methods implementing search operands (returning a
746`Predicate<ChangeData>`), must be defined on a class implementing
747one of the `ChangeQueryBuilder.ChangeOperandsFactory` interfaces
Martin Fick08534c92020-08-05 18:09:41 -0500748(.e.g., ChangeQueryBuilder.ChangeHasOperandFactory or
749ChangeQueryBuilder.ChangeIsOperandFactory). The specific
Craig Chapeldba4e892016-11-14 09:25:17 -0700750`ChangeOperandFactory` class must also be bound to the `DynamicSet` from
751a module's `configure()` method in the plugin.
752
753The new operand, when used in a search would appear as:
Nasser Grainawieea60392021-08-23 09:05:10 -0600754 `operatorName:operandName_pluginName`
Craig Chapeldba4e892016-11-14 09:25:17 -0700755
756A sample `ChangeHasOperandFactory` class implementing, and registering, a
757new `has:sample_pluginName` operand is shown below:
758
Nasser Grainawieea60392021-08-23 09:05:10 -0600759[source, java]
760----
761public class SampleHasOperand implements ChangeHasOperandFactory {
762 public static class Module extends AbstractModule {
Craig Chapeldba4e892016-11-14 09:25:17 -0700763 @Override
Nasser Grainawieea60392021-08-23 09:05:10 -0600764 protected void configure() {
765 bind(ChangeHasOperandFactory.class)
766 .annotatedWith(Exports.named("sample")
767 .to(SampleHasOperand.class);
Craig Chapeldba4e892016-11-14 09:25:17 -0700768 }
Nasser Grainawieea60392021-08-23 09:05:10 -0600769 }
770
771 @Override
772 public Predicate<ChangeData> create(ChangeQueryBuilder builder)
773 throws QueryParseException {
774 return new HasSamplePredicate();
775 }
776}
777----
Craig Chapeldba4e892016-11-14 09:25:17 -0700778
Zac Livingston4f083a82016-05-20 12:38:43 -0600779[[command_options]]
Nasser Grainawieea60392021-08-23 09:05:10 -0600780== Command Options
Zac Livingston4f083a82016-05-20 12:38:43 -0600781
782Plugins can provide additional options for each of the gerrit ssh and the
783REST API commands by implementing the DynamicBean interface and registering
784it to a command class name in the plugin module's `configure()` method. The
785plugin's name will be prepended to the name of each @Option annotation found
786on the DynamicBean object provided by the plugin. The example below shows a
787plugin that adds an option to log a value from the gerrit 'ban-commits'
788ssh command.
789
790[source, java]
791----
792public class SshModule extends AbstractModule {
Edwin Kempin8860e8e2018-05-08 11:19:37 +0200793 private static final FluentLogger logger = FluentLogger.forEnclosingClass();
Zac Livingston4f083a82016-05-20 12:38:43 -0600794
795 @Override
796 protected void configure() {
797 bind(DynamicOptions.DynamicBean.class)
798 .annotatedWith(Exports.named(
799 com.google.gerrit.sshd.commands.BanCommitCommand.class))
800 .to(BanOptions.class);
801 }
802
803 public static class BanOptions implements DynamicOptions.DynamicBean {
804 @Option(name = "--log", aliases = { "-l" }, usage = "Say Hello in the Log")
805 private void parse(String arg) {
Edwin Kempin8860e8e2018-05-08 11:19:37 +0200806 logger.atSevere().log("Say Hello in the Log %s", arg);
Zac Livingston4f083a82016-05-20 12:38:43 -0600807 }
808 }
Nasser Grainawieea60392021-08-23 09:05:10 -0600809}
Zac Livingston4f083a82016-05-20 12:38:43 -0600810----
Craig Chapeldba4e892016-11-14 09:25:17 -0700811
Prudhvi Akhil Alahari86f02232020-10-30 23:51:33 +0530812To provide additional Guice bindings for options to a command in another classloader, bind a
813ModulesClassNamesProvider which provides the name of your Modules needed for your DynamicBean
814in the other classLoader.
815
816Do this by binding to the name of the command you are going to bind to and providing an
817Iterable of Module names to instantiate and add to the Injector used to instantiate the
818DynamicBean in the other classLoader. This interface supports running LifecycleListeners
819which are defined by the Modules being provided. The duration of the lifecycle starts when
820a ssh or http request starts and ends when the request completes.
821
822[source, java]
823----
Nasser Grainawi00ea3ef2021-08-25 09:41:28 -0600824bind(DynamicOptions.DynamicBean.class)
825 .annotatedWith(Exports.named(
826 "com.google.gerrit.plugins.otherplugin.command"))
827 .to(MyOptionsModulesClassNamesProvider.class);
Prudhvi Akhil Alahari86f02232020-10-30 23:51:33 +0530828
Nasser Grainawi00ea3ef2021-08-25 09:41:28 -0600829static class MyOptionsModulesClassNamesProvider implements DynamicOptions.ModulesClassNamesProvider {
830 @Override
831 public String getClassName() {
832 return "com.googlesource.gerrit.plugins.myplugin.CommandOptions";
Prudhvi Akhil Alahari86f02232020-10-30 23:51:33 +0530833 }
Nasser Grainawi00ea3ef2021-08-25 09:41:28 -0600834 @Override
835 public Iterable<String> getModulesClassNames()() {
836 return "com.googlesource.gerrit.plugins.myplugin.MyOptionsModule";
837 }
838}
Prudhvi Akhil Alahari86f02232020-10-30 23:51:33 +0530839----
840
Martin Fick57b553a2017-11-02 15:31:28 -0600841=== Calling Command Options ===
842
843Within an OptionHandler, during the processing of an option, plugins can
844provide and call extra parameters on the current command during parsing
845simulating as if they had been passed from the command line originally.
846
847To call additional parameters from within an option handler, instantiate
848the com.google.gerrit.util.cli.CmdLineParser.Parameters class with the
849existing parameters, and then call callParameters() with the additional
850parameters to be parsed. OptionHandlers may optionally pass this class to
851other methods which may then both parse/consume more parameters and call
852additional parameters.
853
Zac Livingston99a1ad12017-11-21 12:13:29 -0700854When calling command options not provided by your plugin, there is always
855a risk that the options may not exist, perhaps because the options being
856called are to be provided by another plugin, and said plugin is not
857currently installed. To protect againt this situation, it is possible to
858define an option as being dependent on other options using the
859@RequiresOptions() annotation. If the required options are not all not
860currently present, then the dependent option will not be available or
861visible in the help.
862
Martin Fick57b553a2017-11-02 15:31:28 -0600863The example below shows a plugin that adds a "--special" option (perhaps
Zac Livingston99a1ad12017-11-21 12:13:29 -0700864for use with the Query command) that calls (and requires) the
865"--format json" option.
Martin Fick57b553a2017-11-02 15:31:28 -0600866
867[source, java]
868----
869public class JsonOutputOptionHandler<T> extends OptionHandler<T> {
870 protected com.google.gerrit.util.cli.CmdLineParser.MyParser myParser;
871
872 public JsonOutputOptionHandler(CmdLineParser parser, OptionDef option, Setter<? super T> setter) {
873 super(parser, option, setter);
874 myParser = (com.google.gerrit.util.cli.CmdLineParser.MyParser) owner;
875 }
876
877 @Override
878 public int parseArguments(org.kohsuke.args4j.spi.Parameters params) throws CmdLineException {
879 new Parameters(params, myParser).callParameters("--format", "json");
880 setter.addValue(true);
881 return 0; // we didn't consume any additional args
882 }
883
884 @Override
885 public String getDefaultMetaVariable() {
886 ...
887 }
888}
889
Zac Livingston99a1ad12017-11-21 12:13:29 -0700890@RequiresOptions("--format")
Martin Fick57b553a2017-11-02 15:31:28 -0600891@Option(
892 name = "--special",
893 usage = "ouptut results using json",
894 handler = JsonOutputOptionHandler.class
895)
896boolean json;
897----
898
Zac Livingstoncffb24592016-11-13 09:08:08 -0700899[[query_attributes]]
Nasser Grainawieea60392021-08-23 09:05:10 -0600900== Change Attributes
Zac Livingstoncffb24592016-11-13 09:08:08 -0700901
Adithya Chakilam0ddd3d22020-09-03 14:46:17 -0500902==== ChangePluginDefinedInfoFactory
903
Dave Borowitz9c6ffc12019-03-14 08:09:48 -0700904Plugins can provide additional attributes to be returned from the Get Change and
Adithya Chakilam0ddd3d22020-09-03 14:46:17 -0500905Query Change APIs by implementing the `ChangePluginDefinedInfoFactory` interface
906and adding it to the `DynamicSet` in the plugin module's `configure()` method.
907The new attribute(s) will be output under a `plugin` attribute in the change
908output. This can be further controlled by registering a class containing @Option
909declarations as a `DynamicBean`, annotated with the HTTP/SSH commands on
910which the options should be available.
Zac Livingstoncffb24592016-11-13 09:08:08 -0700911
Dave Borowitz4cf58282019-03-14 08:00:57 -0700912The example below shows a plugin that adds two attributes (`exampleName` and
913`changeValue`), to the change query output, when the query command is provided
914the `--myplugin-name--all` option.
Zac Livingstoncffb24592016-11-13 09:08:08 -0700915
916[source, java]
917----
918public class Module extends AbstractModule {
919 @Override
920 protected void configure() {
Dave Borowitz10745e12019-03-13 14:24:00 -0700921 // Register attribute factory.
Adithya Chakilam0ddd3d22020-09-03 14:46:17 -0500922 DynamicSet.bind(binder(), ChangePluginDefinedInfoFactory.class)
Zac Livingstoncffb24592016-11-13 09:08:08 -0700923 .to(AttributeFactory.class);
Dave Borowitz10745e12019-03-13 14:24:00 -0700924
925 // Register options for GET /changes/X/change and /changes/X/detail.
926 bind(DynamicBean.class)
927 .annotatedWith(Exports.named(GetChange.class))
928 .to(MyChangeOptions.class);
929
930 // Register options for GET /changes/?q=...
931 bind(DynamicBean.class)
932 .annotatedWith(Exports.named(QueryChanges.class))
933 .to(MyChangeOptions.class);
934
935 // Register options for ssh gerrit query.
936 bind(DynamicBean.class)
937 .annotatedWith(Exports.named(Query.class))
938 .to(MyChangeOptions.class);
Zac Livingstoncffb24592016-11-13 09:08:08 -0700939 }
940}
941
Dave Borowitz9c6ffc12019-03-14 08:09:48 -0700942public class MyChangeOptions implements DynamicBean {
Nasser Grainawiba5c7402018-10-05 10:35:18 -0600943 @Option(name = "--all", usage = "Include plugin output")
944 public boolean all = false;
945}
946
Adithya Chakilam0ddd3d22020-09-03 14:46:17 -0500947public class AttributeFactory implements ChangePluginDefinedInfoFactory {
Dave Borowitz9c6ffc12019-03-14 08:09:48 -0700948 protected MyChangeOptions options;
Zac Livingstoncffb24592016-11-13 09:08:08 -0700949
950 public class PluginAttribute extends PluginDefinedInfo {
951 public String exampleName;
952 public String changeValue;
953
954 public PluginAttribute(ChangeData c) {
955 this.exampleName = "Attribute Example";
956 this.changeValue = Integer.toString(c.getId().get());
957 }
958 }
959
960 @Override
Adithya Chakilam0ddd3d22020-09-03 14:46:17 -0500961 public Map<Change.Id, PluginDefinedInfo> createPluginDefinedInfos(
962 Collection<ChangeData> cds, BeanProvider bp, String plugin) {
Nasser Grainawiba5c7402018-10-05 10:35:18 -0600963 if (options == null) {
Dave Borowitz9c6ffc12019-03-14 08:09:48 -0700964 options = (MyChangeOptions) bp.getDynamicBean(plugin);
Nasser Grainawiba5c7402018-10-05 10:35:18 -0600965 }
Adithya Chakilam0ddd3d22020-09-03 14:46:17 -0500966 Map<Change.Id, PluginDefinedInfo> out = new HashMap<>();
Nasser Grainawiba5c7402018-10-05 10:35:18 -0600967 if (options.all) {
Adithya Chakilam0ddd3d22020-09-03 14:46:17 -0500968 cds.forEach(cd -> out.put(cd.getId(), new PluginAttribute(cd)));
969 return out;
Nasser Grainawiba5c7402018-10-05 10:35:18 -0600970 }
Adithya Chakilam0ddd3d22020-09-03 14:46:17 -0500971 return ImmutableMap.of();
Zac Livingstoncffb24592016-11-13 09:08:08 -0700972 }
973}
974----
975
Nasser Grainawieea60392021-08-23 09:05:10 -0600976Example:
Zac Livingstoncffb24592016-11-13 09:08:08 -0700977----
Nasser Grainawieea60392021-08-23 09:05:10 -0600978$ ssh -p 29418 localhost gerrit query --myplugin-name--all "change:1" --format json
Zac Livingstoncffb24592016-11-13 09:08:08 -0700979{
980 "url" : "http://localhost:8080/1",
981 "plugins" : [
982 {
983 "name" : "myplugin-name",
984 "exampleName" : "Attribute Example",
985 "changeValue" : "1"
986 }
987 ],
988 ...
989}
Dave Borowitz9c6ffc12019-03-14 08:09:48 -0700990
Nasser Grainawieea60392021-08-23 09:05:10 -0600991$ curl http://localhost:8080/changes/1?myplugin-name--all
Dave Borowitz9c6ffc12019-03-14 08:09:48 -0700992{
993 "_number": 1,
994 ...
995 "plugins": [
996 {
997 "name": "myplugin-name",
998 "example_name": "Attribute Example",
999 "change_value": "1"
1000 }
1001 ],
1002 ...
1003}
Zac Livingstoncffb24592016-11-13 09:08:08 -07001004----
1005
Adithya Chakilam0ddd3d22020-09-03 14:46:17 -05001006Runtime exceptions generated by the implementors of ChangePluginDefinedInfoFactory
1007are encapsulated in PluginDefinedInfo objects which are part of SSH/REST query output.
1008
Adithya Chakilam260a9102020-10-22 16:05:53 -05001009Implementors of the `ChangePluginDefinedInfoFactory` interface should check whether
1010they need to contribute to the link:#change-etag-computation[change ETag computation]
1011to prevent callers using ETags from potentially seeing outdated plugin attributes.
Edwin Kempin96915b72019-07-22 14:57:40 +02001012
Edwin Kempin78ca0942013-10-30 11:24:06 +01001013[[simple-configuration]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001014== Simple Configuration in `gerrit.config`
Edwin Kempinf7bfff82013-09-17 13:34:20 +02001015
1016In Gerrit, global configuration is stored in the `gerrit.config` file.
1017If a plugin needs global configuration, this configuration should be
1018stored in a `plugin` subsection in the `gerrit.config` file.
1019
Edwin Kempinc9b68602013-10-30 09:32:43 +01001020This approach of storing the plugin configuration is only suitable for
1021plugins that have a simple configuration that only consists of
1022key-value pairs. With this approach it is not possible to have
1023subsections in the plugin configuration. Plugins that require a complex
Edwin Kempin78ca0942013-10-30 11:24:06 +01001024configuration need to store their configuration in their
1025link:#configuration[own configuration file] where they can make use of
1026subsections. On the other hand storing the plugin configuration in a
1027'plugin' subsection in the `gerrit.config` file has the advantage that
1028administrators have all configuration parameters in one file, instead
1029of having one configuration file per plugin.
Edwin Kempinc9b68602013-10-30 09:32:43 +01001030
Edwin Kempinf7bfff82013-09-17 13:34:20 +02001031To avoid conflicts with other plugins, it is recommended that plugins
1032only use the `plugin` subsection with their own name. For example the
1033`helloworld` plugin should store its configuration in the
1034`plugin.helloworld` subsection:
1035
1036----
1037[plugin "helloworld"]
1038 language = Latin
1039----
1040
Sasa Zivkovacdf5332013-09-20 14:05:15 +02001041Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
Edwin Kempinf7bfff82013-09-17 13:34:20 +02001042plugin can easily access its configuration and there is no need for a
1043plugin to parse the `gerrit.config` file on its own:
1044
1045[source,java]
1046----
David Pursehouse529ec252013-09-27 13:45:14 +09001047@Inject
1048private com.google.gerrit.server.config.PluginConfigFactory cfg;
Edwin Kempinf7bfff82013-09-17 13:34:20 +02001049
David Pursehoused128c892013-10-22 21:52:21 +09001050[...]
Edwin Kempinf7bfff82013-09-17 13:34:20 +02001051
Edwin Kempin122622d2013-10-29 16:45:44 +01001052String language = cfg.getFromGerritConfig("helloworld")
David Pursehouse529ec252013-09-27 13:45:14 +09001053 .getString("language", "English");
Edwin Kempinf7bfff82013-09-17 13:34:20 +02001054----
1055
Edwin Kempin78ca0942013-10-30 11:24:06 +01001056[[configuration]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001057== Configuration in own config file
Edwin Kempin78ca0942013-10-30 11:24:06 +01001058
1059Plugins can store their configuration in an own configuration file.
1060This makes sense if the plugin configuration is rather complex and
1061requires the usage of subsections. Plugins that have a simple
1062key-value pair configuration can store their configuration in a
1063link:#simple-configuration[`plugin` subsection of the `gerrit.config`
1064file].
1065
1066The plugin configuration file must be named after the plugin and must
1067be located in the `etc` folder of the review site. For example a
1068configuration file for a `default-reviewer` plugin could look like
1069this:
1070
1071.$site_path/etc/default-reviewer.config
1072----
1073[branch "refs/heads/master"]
1074 reviewer = Project Owners
1075 reviewer = john.doe@example.com
1076[match "file:^.*\.txt"]
1077 reviewer = My Info Developers
1078----
1079
David Pursehouse5b47bc42016-07-22 11:00:25 +09001080Plugins that have sensitive configuration settings can store those settings in
1081an own secure configuration file. The plugin's secure configuration file must be
1082named after the plugin and must be located in the `etc` folder of the review
1083site. For example a secure configuration file for a `default-reviewer` plugin
1084could look like this:
1085
1086.$site_path/etc/default-reviewer.secure.config
1087----
1088[auth]
1089 password = secret
1090----
1091
Edwin Kempin78ca0942013-10-30 11:24:06 +01001092Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
1093plugin can easily access its configuration:
1094
1095[source,java]
1096----
1097@Inject
1098private com.google.gerrit.server.config.PluginConfigFactory cfg;
1099
1100[...]
1101
1102String[] reviewers = cfg.getGlobalPluginConfig("default-reviewer")
1103 .getStringList("branch", "refs/heads/master", "reviewer");
David Pursehouse5b47bc42016-07-22 11:00:25 +09001104String password = cfg.getGlobalPluginConfig("default-reviewer")
1105 .getString("auth", null, "password");
Edwin Kempin78ca0942013-10-30 11:24:06 +01001106----
1107
Edwin Kempin78ca0942013-10-30 11:24:06 +01001108
Edwin Kempin705f2842013-10-30 14:25:31 +01001109[[simple-project-specific-configuration]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001110== Simple Project Specific Configuration in `project.config`
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +02001111
1112In Gerrit, project specific configuration is stored in the project's
1113`project.config` file on the `refs/meta/config` branch. If a plugin
1114needs configuration on project level (e.g. to enable its functionality
1115only for certain projects), this configuration should be stored in a
1116`plugin` subsection in the project's `project.config` file.
1117
Edwin Kempinc9b68602013-10-30 09:32:43 +01001118This approach of storing the plugin configuration is only suitable for
1119plugins that have a simple configuration that only consists of
1120key-value pairs. With this approach it is not possible to have
1121subsections in the plugin configuration. Plugins that require a complex
Edwin Kempin705f2842013-10-30 14:25:31 +01001122configuration need to store their configuration in their
1123link:#project-specific-configuration[own configuration file] where they
1124can make use of subsections. On the other hand storing the plugin
1125configuration in a 'plugin' subsection in the `project.config` file has
1126the advantage that project owners have all configuration parameters in
1127one file, instead of having one configuration file per plugin.
Edwin Kempinc9b68602013-10-30 09:32:43 +01001128
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +02001129To avoid conflicts with other plugins, it is recommended that plugins
1130only use the `plugin` subsection with their own name. For example the
1131`helloworld` plugin should store its configuration in the
1132`plugin.helloworld` subsection:
1133
1134----
Nasser Grainawieea60392021-08-23 09:05:10 -06001135[plugin "helloworld"]
1136 enabled = true
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +02001137----
1138
1139Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
1140plugin can easily access its project specific configuration and there
1141is no need for a plugin to parse the `project.config` file on its own:
1142
1143[source,java]
1144----
David Pursehouse529ec252013-09-27 13:45:14 +09001145@Inject
1146private com.google.gerrit.server.config.PluginConfigFactory cfg;
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +02001147
David Pursehoused128c892013-10-22 21:52:21 +09001148[...]
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +02001149
Edwin Kempin122622d2013-10-29 16:45:44 +01001150boolean enabled = cfg.getFromProjectConfig(project, "helloworld")
David Pursehouse529ec252013-09-27 13:45:14 +09001151 .getBoolean("enabled", false);
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +02001152----
1153
Edwin Kempinca7ad8e2013-09-16 16:43:05 +02001154It is also possible to get missing configuration parameters inherited
1155from the parent projects:
1156
1157[source,java]
1158----
David Pursehouse529ec252013-09-27 13:45:14 +09001159@Inject
1160private com.google.gerrit.server.config.PluginConfigFactory cfg;
Edwin Kempinca7ad8e2013-09-16 16:43:05 +02001161
David Pursehoused128c892013-10-22 21:52:21 +09001162[...]
Edwin Kempinca7ad8e2013-09-16 16:43:05 +02001163
Edwin Kempin122622d2013-10-29 16:45:44 +01001164boolean enabled = cfg.getFromProjectConfigWithInheritance(project, "helloworld")
David Pursehouse529ec252013-09-27 13:45:14 +09001165 .getBoolean("enabled", false);
Edwin Kempinca7ad8e2013-09-16 16:43:05 +02001166----
1167
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +02001168Project owners can edit the project configuration by fetching the
1169`refs/meta/config` branch, editing the `project.config` file and
1170pushing the commit back.
1171
Edwin Kempin9ce4f552013-11-15 16:00:00 +01001172Plugin configuration values that are stored in the `project.config`
1173file can be exposed in the ProjectInfoScreen to allow project owners
1174to see and edit them from the UI.
1175
1176For this an instance of `ProjectConfigEntry` needs to be bound for each
1177parameter. The export name must be a valid Git variable name. The
1178variable name is case-insensitive, allows only alphanumeric characters
1179and '-', and must start with an alphabetic character.
1180
Edwin Kempina6c1c452013-11-28 16:55:22 +01001181The example below shows how the parameters `plugin.helloworld.enabled`
1182and `plugin.helloworld.language` are bound to be editable from the
David Pursehousea1d633b2014-05-02 17:21:02 +09001183Web UI. For the parameter `plugin.helloworld.enabled` "Enable Greeting"
Edwin Kempina6c1c452013-11-28 16:55:22 +01001184is provided as display name and the default value is set to `true`.
1185For the parameter `plugin.helloworld.language` "Preferred Language"
1186is provided as display name and "en" is set as default value.
Edwin Kempin9ce4f552013-11-15 16:00:00 +01001187
1188[source,java]
1189----
1190class Module extends AbstractModule {
1191 @Override
1192 protected void configure() {
1193 bind(ProjectConfigEntry.class)
Edwin Kempina6c1c452013-11-28 16:55:22 +01001194 .annotatedWith(Exports.named("enabled"))
1195 .toInstance(new ProjectConfigEntry("Enable Greeting", true));
1196 bind(ProjectConfigEntry.class)
Edwin Kempin9ce4f552013-11-15 16:00:00 +01001197 .annotatedWith(Exports.named("language"))
1198 .toInstance(new ProjectConfigEntry("Preferred Language", "en"));
1199 }
1200}
1201----
1202
Edwin Kempinb64d3972013-11-17 18:55:48 +01001203By overwriting the `onUpdate` method of `ProjectConfigEntry` plugins
1204can be notified when this configuration parameter is updated on a
1205project.
1206
Janice Agustine5a9d012015-08-24 09:05:56 -04001207[[configuring-groups]]
1208=== Referencing groups in `project.config`
1209
1210Plugins can refer to groups so that when they are renamed, the project
1211config will also be updated in this section. The proper format to use is
Hugo Arès532e0a32017-06-16 09:31:08 -04001212the same as for any other group reference in the `project.config`, as shown below.
Janice Agustine5a9d012015-08-24 09:05:56 -04001213
1214----
Hugo Arès532e0a32017-06-16 09:31:08 -04001215group group_name
Janice Agustine5a9d012015-08-24 09:05:56 -04001216----
1217
Hugo Arès532e0a32017-06-16 09:31:08 -04001218The file `groups` must also contains the mapping of the group name and its UUID,
1219refer to link:config-project-config.html#file-groups[file groups]
1220
Edwin Kempin705f2842013-10-30 14:25:31 +01001221[[project-specific-configuration]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001222== Project Specific Configuration in own config file
Edwin Kempin705f2842013-10-30 14:25:31 +01001223
1224Plugins can store their project specific configuration in an own
1225configuration file in the projects `refs/meta/config` branch.
1226This makes sense if the plugins project specific configuration is
1227rather complex and requires the usage of subsections. Plugins that
1228have a simple key-value pair configuration can store their project
1229specific configuration in a link:#simple-project-specific-configuration[
1230`plugin` subsection of the `project.config` file].
1231
1232The plugin configuration file in the `refs/meta/config` branch must be
1233named after the plugin. For example a configuration file for a
1234`default-reviewer` plugin could look like this:
1235
1236.default-reviewer.config
1237----
1238[branch "refs/heads/master"]
1239 reviewer = Project Owners
1240 reviewer = john.doe@example.com
1241[match "file:^.*\.txt"]
1242 reviewer = My Info Developers
1243----
1244
1245Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
1246plugin can easily access its project specific configuration:
1247
1248[source,java]
1249----
1250@Inject
1251private com.google.gerrit.server.config.PluginConfigFactory cfg;
1252
1253[...]
1254
1255String[] reviewers = cfg.getProjectPluginConfig(project, "default-reviewer")
1256 .getStringList("branch", "refs/heads/master", "reviewer");
1257----
1258
Edwin Kempin762da382013-10-30 14:50:01 +01001259It is also possible to get missing configuration parameters inherited
1260from the parent projects:
1261
1262[source,java]
1263----
1264@Inject
1265private com.google.gerrit.server.config.PluginConfigFactory cfg;
1266
1267[...]
1268
David Ostrovsky468e4c32014-03-22 06:05:35 -07001269String[] reviewers = cfg.getProjectPluginConfigWithInheritance(project, "default-reviewer")
Edwin Kempin762da382013-10-30 14:50:01 +01001270 .getStringList("branch", "refs/heads/master", "reviewer");
1271----
1272
Edwin Kempin705f2842013-10-30 14:25:31 +01001273Project owners can edit the project configuration by fetching the
1274`refs/meta/config` branch, editing the `<plugin-name>.config` file and
1275pushing the commit back.
1276
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001277== React on changes in project configuration
Edwin Kempina46b6c92013-12-04 21:05:24 +01001278
1279If a plugin wants to react on changes in the project configuration, it
1280can implement a `GitReferenceUpdatedListener` and filter on events for
1281the `refs/meta/config` branch:
1282
1283[source,java]
1284----
1285public class MyListener implements GitReferenceUpdatedListener {
1286
1287 private final MetaDataUpdate.Server metaDataUpdateFactory;
1288
1289 @Inject
1290 MyListener(MetaDataUpdate.Server metaDataUpdateFactory) {
1291 this.metaDataUpdateFactory = metaDataUpdateFactory;
1292 }
1293
1294 @Override
1295 public void onGitReferenceUpdated(Event event) {
Edwin Kempina951ba52014-01-03 14:07:28 +01001296 if (event.getRefName().equals(RefNames.REFS_CONFIG)) {
Edwin Kempina46b6c92013-12-04 21:05:24 +01001297 Project.NameKey p = new Project.NameKey(event.getProjectName());
1298 try {
Edwin Kempina951ba52014-01-03 14:07:28 +01001299 ProjectConfig oldCfg = parseConfig(p, event.getOldObjectId());
1300 ProjectConfig newCfg = parseConfig(p, event.getNewObjectId());
Edwin Kempina46b6c92013-12-04 21:05:24 +01001301
Edwin Kempina951ba52014-01-03 14:07:28 +01001302 if (oldCfg != null && newCfg != null
1303 && !oldCfg.getProject().getSubmitType().equals(newCfg.getProject().getSubmitType())) {
Edwin Kempina46b6c92013-12-04 21:05:24 +01001304 // submit type has changed
1305 ...
1306 }
1307 } catch (IOException | ConfigInvalidException e) {
1308 ...
1309 }
1310 }
1311 }
Edwin Kempina951ba52014-01-03 14:07:28 +01001312
1313 private ProjectConfig parseConfig(Project.NameKey p, String idStr)
1314 throws IOException, ConfigInvalidException, RepositoryNotFoundException {
1315 ObjectId id = ObjectId.fromString(idStr);
1316 if (ObjectId.zeroId().equals(id)) {
1317 return null;
1318 }
1319 return ProjectConfig.read(metaDataUpdateFactory.create(p), id);
1320 }
Edwin Kempina46b6c92013-12-04 21:05:24 +01001321}
1322----
1323
Fabio Ponciroli214b8962020-05-07 13:22:00 +02001324== Trace Event origin
1325
1326When plugins are installed in a multi-master setups it can be useful to know
1327the Gerrit `instanceId` of the server that has generated an Event.
1328
1329E.g. A plugin that sends an instance message for every comment on a change may
1330want to react only if the event is generated on the local Gerrit master, for
1331avoiding duplicating the notifications.
1332
1333If link:config-gerrit.html[instanceId] is set, each Event will contain its
1334origin in the `instanceId` field.
1335
1336Here and example of ref-updated JSON event payload with `instanceId`:
1337
1338[source,json]
Nasser Grainawieea60392021-08-23 09:05:10 -06001339----
Fabio Ponciroli214b8962020-05-07 13:22:00 +02001340{
1341 "submitter": {
1342 "name": "Administrator",
1343 "email": "admin@example.com",
1344 "username": "admin"
1345 },
1346 "refUpdate": {
1347 "oldRev": "a69fc95c7aad5ad41c618d31548b8af835d2959a",
1348 "newRev": "31da6556d638a74e5370b62f83e8007f94abb7c6",
1349 "refName": "refs/changes/01/1/meta",
1350 "project": "test"
1351 },
1352 "type": "ref-updated",
1353 "eventCreatedOn": 1588849085,
1354 "instanceId": "instance1"
1355}
Nasser Grainawieea60392021-08-23 09:05:10 -06001356----
Edwin Kempina46b6c92013-12-04 21:05:24 +01001357
David Ostrovsky7066cc02013-06-15 14:46:23 +02001358[[capabilities]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001359== Plugin Owned Capabilities
David Ostrovsky7066cc02013-06-15 14:46:23 +02001360
1361Plugins may provide their own capabilities and restrict usage of SSH
Dariusz Luksza112d93a2014-06-01 16:52:23 +02001362commands or `UiAction` to the users who are granted those capabilities.
David Ostrovsky7066cc02013-06-15 14:46:23 +02001363
1364Plugins define the capabilities by overriding the `CapabilityDefinition`
1365abstract class:
1366
David Pursehouse68153d72013-09-04 10:09:17 +09001367[source,java]
1368----
1369public class PrintHelloCapability extends CapabilityDefinition {
1370 @Override
1371 public String getDescription() {
1372 return "Print Hello";
David Ostrovsky7066cc02013-06-15 14:46:23 +02001373 }
David Pursehouse68153d72013-09-04 10:09:17 +09001374}
1375----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001376
Dariusz Luksza112d93a2014-06-01 16:52:23 +02001377If no Guice modules are declared in the manifest, capability may
David Ostrovsky7066cc02013-06-15 14:46:23 +02001378use auto-registration by providing an `@Export` annotation:
1379
David Pursehouse68153d72013-09-04 10:09:17 +09001380[source,java]
1381----
1382@Export("printHello")
1383public class PrintHelloCapability extends CapabilityDefinition {
David Pursehoused128c892013-10-22 21:52:21 +09001384 [...]
David Pursehouse68153d72013-09-04 10:09:17 +09001385}
1386----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001387
1388Otherwise the capability must be bound in a plugin module:
1389
David Pursehouse68153d72013-09-04 10:09:17 +09001390[source,java]
1391----
1392public class HelloWorldModule extends AbstractModule {
1393 @Override
1394 protected void configure() {
1395 bind(CapabilityDefinition.class)
1396 .annotatedWith(Exports.named("printHello"))
1397 .to(PrintHelloCapability.class);
David Ostrovsky7066cc02013-06-15 14:46:23 +02001398 }
David Pursehouse68153d72013-09-04 10:09:17 +09001399}
1400----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001401
1402With a plugin-owned capability defined in this way, it is possible to restrict
David Ostrovskyf86bae52013-09-01 09:10:39 +02001403usage of an SSH command or `UiAction` to members of the group that were granted
David Ostrovsky7066cc02013-06-15 14:46:23 +02001404this capability in the usual way, using the `RequiresCapability` annotation:
1405
David Pursehouse68153d72013-09-04 10:09:17 +09001406[source,java]
1407----
1408@RequiresCapability("printHello")
1409@CommandMetaData(name="print", description="Print greeting in different languages")
1410public final class PrintHelloWorldCommand extends SshCommand {
David Pursehoused128c892013-10-22 21:52:21 +09001411 [...]
David Pursehouse68153d72013-09-04 10:09:17 +09001412}
1413----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001414
David Ostrovskyf86bae52013-09-01 09:10:39 +02001415Or with `UiAction`:
David Ostrovsky7066cc02013-06-15 14:46:23 +02001416
David Pursehouse68153d72013-09-04 10:09:17 +09001417[source,java]
1418----
1419@RequiresCapability("printHello")
1420public class SayHelloAction extends UiAction<RevisionResource>
1421 implements RestModifyView<RevisionResource, SayHelloAction.Input> {
David Pursehoused128c892013-10-22 21:52:21 +09001422 [...]
David Pursehouse68153d72013-09-04 10:09:17 +09001423}
1424----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001425
1426Capability scope was introduced to differentiate between plugin-owned
David Pursehousebf053342013-09-05 14:55:29 +09001427capabilities and core capabilities. Per default the scope of the
1428`@RequiresCapability` annotation is `CapabilityScope.CONTEXT`, that means:
1429
David Ostrovsky7066cc02013-06-15 14:46:23 +02001430* when `@RequiresCapability` is used within a plugin the scope of the
1431capability is assumed to be that plugin.
David Pursehousebf053342013-09-05 14:55:29 +09001432
David Ostrovsky7066cc02013-06-15 14:46:23 +02001433* If `@RequiresCapability` is used within the core Gerrit Code Review server
1434(and thus is outside of a plugin) the scope is the core server and will use
1435the `GlobalCapability` known to Gerrit Code Review server.
1436
1437If a plugin needs to use a core capability name (e.g. "administrateServer")
1438this can be specified by setting `scope = CapabilityScope.CORE`:
1439
David Pursehouse68153d72013-09-04 10:09:17 +09001440[source,java]
1441----
1442@RequiresCapability(value = "administrateServer", scope =
1443 CapabilityScope.CORE)
David Pursehoused128c892013-10-22 21:52:21 +09001444 [...]
David Pursehouse68153d72013-09-04 10:09:17 +09001445----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001446
Edwin Kempin79ec4932020-12-01 15:51:29 +01001447[post_review_extensions]
1448== Post Review Extensions
1449
1450By implementing the `com.google.gerrit.server.restapi.change.OnPostReview`
1451interface plugins can extend the change message that is being posted when the
David Ostrovskyce351592021-05-03 21:14:05 +02001452link:rest-api-changes.html#set-review[post review] REST endpoint is invoked.
Edwin Kempin79ec4932020-12-01 15:51:29 +01001453
1454This is useful if certain approvals have a special meaning (e.g. custom logic
1455that is implemented in Prolog submit rules, signal for triggering an action
1456like running CI etc.), as it allows the plugin to tell users about this meaning
1457in the change message. This makes the effect of a given approval more
Ben Rohlfsda0a62b2021-04-26 17:02:19 +02001458transparent to the user.
Zac Livingstone7f3d1a2017-03-01 12:47:37 -07001459
David Ostrovskyce351592021-05-03 21:14:05 +02001460[[ui_extension]]
1461== UI Extension
Zac Livingstone7f3d1a2017-03-01 12:47:37 -07001462
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001463[[actions]]
1464=== Actions
1465
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001466Plugins can contribute UI actions on core Gerrit pages. This is useful
1467for workflow customization or exposing plugin functionality through the
1468UI in addition to SSH commands and the REST API.
David Ostrovskyf86bae52013-09-01 09:10:39 +02001469
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001470For instance a plugin to integrate Jira with Gerrit changes may
1471contribute a "File bug" button to allow filing a bug from the change
1472page or plugins to integrate continuous integration systems may
1473contribute a "Schedule" button to allow a CI build to be scheduled
1474manually from the patch set panel.
David Ostrovskyf86bae52013-09-01 09:10:39 +02001475
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001476Two different places on core Gerrit pages are supported:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001477
1478* Change screen
1479* Project info screen
1480
1481Plugins contribute UI actions by implementing the `UiAction` interface:
1482
David Pursehouse68153d72013-09-04 10:09:17 +09001483[source,java]
1484----
1485@RequiresCapability("printHello")
1486class HelloWorldAction implements UiAction<RevisionResource>,
1487 RestModifyView<RevisionResource, HelloWorldAction.Input> {
1488 static class Input {
1489 boolean french;
1490 String message;
David Ostrovskyf86bae52013-09-01 09:10:39 +02001491 }
David Pursehouse68153d72013-09-04 10:09:17 +09001492
1493 private Provider<CurrentUser> user;
1494
1495 @Inject
1496 HelloWorldAction(Provider<CurrentUser> user) {
1497 this.user = user;
1498 }
1499
1500 @Override
1501 public String apply(RevisionResource rev, Input input) {
1502 final String greeting = input.french
1503 ? "Bonjour"
1504 : "Hello";
1505 return String.format("%s %s from change %s, patch set %d!",
1506 greeting,
1507 Strings.isNullOrEmpty(input.message)
1508 ? Objects.firstNonNull(user.get().getUserName(), "world")
1509 : input.message,
1510 rev.getChange().getId().toString(),
1511 rev.getPatchSet().getPatchSetId());
1512 }
1513
1514 @Override
1515 public Description getDescription(
1516 RevisionResource resource) {
1517 return new Description()
1518 .setLabel("Say hello")
1519 .setTitle("Say hello in different languages");
1520 }
1521}
1522----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001523
David Ostrovsky450eefe2013-10-21 21:18:11 +02001524Sometimes plugins may want to be able to change the state of a patch set or
1525change in the `UiAction.apply()` method and reflect these changes on the core
1526UI. For example a buildbot plugin which exposes a 'Schedule' button on the
1527patch set panel may want to disable that button after the build was scheduled
1528and update the tooltip of that button. But because of Gerrit's caching
1529strategy the following must be taken into consideration.
1530
1531The browser is allowed to cache the `UiAction` information until something on
1532the change is modified. More accurately the change row needs to be modified in
1533the database to have a more recent `lastUpdatedOn` or a new `rowVersion`, or
1534the +refs/meta/config+ of the project or any parents needs to change to a new
1535SHA-1. The ETag SHA-1 computation code can be found in the
1536`ChangeResource.getETag()` method.
1537
David Pursehoused128c892013-10-22 21:52:21 +09001538The easiest way to accomplish this is to update `lastUpdatedOn` of the change:
David Ostrovsky450eefe2013-10-21 21:18:11 +02001539
1540[source,java]
1541----
1542@Override
1543public Object apply(RevisionResource rcrs, Input in) {
1544 // schedule a build
1545 [...]
1546 // update change
Edwin Kempine2d06b02016-02-17 18:34:17 +01001547 try (BatchUpdate bu = batchUpdateFactory.create(
Dave Borowitzada289c2018-12-18 13:24:14 -08001548 project.getNameKey(), user, TimeUtil.nowTs())) {
Edwin Kempine2d06b02016-02-17 18:34:17 +01001549 bu.addOp(change.getId(), new BatchUpdate.Op() {
1550 @Override
Dave Borowitzb91cf222017-03-10 13:11:59 -05001551 public boolean updateChange(ChangeContext ctx) {
Edwin Kempine2d06b02016-02-17 18:34:17 +01001552 return true;
1553 }
1554 });
1555 bu.execute();
David Ostrovsky450eefe2013-10-21 21:18:11 +02001556 }
David Pursehoused128c892013-10-22 21:52:21 +09001557 [...]
David Ostrovsky450eefe2013-10-21 21:18:11 +02001558}
1559----
1560
David Ostrovskyf86bae52013-09-01 09:10:39 +02001561`UiAction` must be bound in a plugin module:
1562
David Pursehouse68153d72013-09-04 10:09:17 +09001563[source,java]
1564----
1565public class Module extends AbstractModule {
1566 @Override
1567 protected void configure() {
1568 install(new RestApiModule() {
1569 @Override
1570 protected void configure() {
1571 post(REVISION_KIND, "say-hello")
1572 .to(HelloWorldAction.class);
1573 }
1574 });
David Ostrovskyf86bae52013-09-01 09:10:39 +02001575 }
David Pursehouse68153d72013-09-04 10:09:17 +09001576}
1577----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001578
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001579The module above must be declared in the `pom.xml` for Maven driven
1580plugins:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001581
David Pursehouse68153d72013-09-04 10:09:17 +09001582[source,xml]
1583----
1584<manifestEntries>
1585 <Gerrit-Module>com.googlesource.gerrit.plugins.cookbook.Module</Gerrit-Module>
1586</manifestEntries>
1587----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001588
David Ostrovskyfdbfcad2016-11-15 06:35:29 -08001589or in the `BUILD` configuration file for Bazel driven plugins:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001590
David Pursehouse68153d72013-09-04 10:09:17 +09001591[source,python]
1592----
1593manifest_entries = [
1594 'Gerrit-Module: com.googlesource.gerrit.plugins.cookbook.Module',
1595]
1596----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001597
1598In some use cases more user input must be gathered, for that `UiAction` can be
1599combined with the JavaScript API. This would display a small popup near the
1600activation button to gather additional input from the user. The JS file is
1601typically put in the `static` folder within the plugin's directory:
1602
David Pursehouse68153d72013-09-04 10:09:17 +09001603[source,javascript]
1604----
1605Gerrit.install(function(self) {
1606 function onSayHello(c) {
1607 var f = c.textfield();
1608 var t = c.checkbox();
1609 var b = c.button('Say hello', {onclick: function(){
1610 c.call(
1611 {message: f.value, french: t.checked},
1612 function(r) {
1613 c.hide();
1614 window.alert(r);
1615 c.refresh();
1616 });
1617 }});
1618 c.popup(c.div(
1619 c.prependLabel('Greeting message', f),
1620 c.br(),
1621 c.label(t, 'french'),
1622 c.br(),
1623 b));
1624 f.focus();
1625 }
1626 self.onAction('revision', 'say-hello', onSayHello);
1627});
1628----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001629
1630The JS module must be exposed as a `WebUiPlugin` and bound as
1631an HTTP Module:
1632
David Pursehouse68153d72013-09-04 10:09:17 +09001633[source,java]
1634----
Dave Borowitz20f4e8a2019-03-13 14:19:15 -07001635public class HttpModule extends ServletModule {
David Pursehouse68153d72013-09-04 10:09:17 +09001636 @Override
1637 protected void configureServlets() {
1638 DynamicSet.bind(binder(), WebUiPlugin.class)
1639 .toInstance(new JavaScriptPlugin("hello.js"));
David Ostrovskyf86bae52013-09-01 09:10:39 +02001640 }
David Pursehouse68153d72013-09-04 10:09:17 +09001641}
1642----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001643
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001644The HTTP module above must be declared in the `pom.xml` for Maven
1645driven plugins:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001646
David Pursehouse68153d72013-09-04 10:09:17 +09001647[source,xml]
1648----
1649<manifestEntries>
1650 <Gerrit-HttpModule>com.googlesource.gerrit.plugins.cookbook.HttpModule</Gerrit-HttpModule>
1651</manifestEntries>
1652----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001653
David Ostrovskyfdbfcad2016-11-15 06:35:29 -08001654or in the `BUILD` configuration file for Bazel driven plugins
David Ostrovskyf86bae52013-09-01 09:10:39 +02001655
David Pursehouse68153d72013-09-04 10:09:17 +09001656[source,python]
1657----
1658manifest_entries = [
1659 'Gerrit-HttpModule: com.googlesource.gerrit.plugins.cookbook.HttpModule',
1660]
1661----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001662
1663If `UiAction` is annotated with the `@RequiresCapability` annotation, then the
1664capability check is done during the `UiAction` gathering, so the plugin author
1665doesn't have to set `UiAction.Description.setVisible()` explicitly in this
1666case.
1667
David Pursehousea61ee502016-09-06 16:27:09 +09001668The following prerequisites must be met, to satisfy the capability check:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001669
1670* user is authenticated
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001671* user is a member of a group which has the `Administrate Server` capability, or
David Ostrovskyf86bae52013-09-01 09:10:39 +02001672* user is a member of a group which has the required capability
1673
1674The `apply` method is called when the button is clicked. If `UiAction` is
1675combined with JavaScript API (its own JavaScript function is provided),
1676then a popup dialog is normally opened to gather additional user input.
1677A new button is placed on the popup dialog to actually send the request.
1678
1679Every `UiAction` exposes a REST API endpoint. The endpoint from the example above
1680can be accessed from any REST client, i. e.:
1681
Michael Ochmannb99feab2016-07-06 14:10:22 +02001682----
Nasser Grainawieea60392021-08-23 09:05:10 -06001683$ curl -X POST -H "Content-Type: application/json" \
David Ostrovskyf86bae52013-09-01 09:10:39 +02001684 -d '{message: "François", french: true}' \
Han-Wen Nienhuys84d830b2017-02-15 16:36:04 +01001685 --user joe:secret \
David Ostrovskyf86bae52013-09-01 09:10:39 +02001686 http://host:port/a/changes/1/revisions/1/cookbook~say-hello
Nasser Grainawieea60392021-08-23 09:05:10 -06001687"Bonjour François from change 1, patch set 1!"
Michael Ochmannb99feab2016-07-06 14:10:22 +02001688----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001689
David Pursehouse42245822013-09-24 09:48:20 +09001690A special case is to bind an endpoint without a view name. This is
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001691particularly useful for `DELETE` requests:
David Ostrovskyc6d19ed2013-09-20 21:30:18 +02001692
1693[source,java]
1694----
1695public class Module extends AbstractModule {
1696 @Override
1697 protected void configure() {
1698 install(new RestApiModule() {
1699 @Override
1700 protected void configure() {
1701 delete(PROJECT_KIND)
1702 .to(DeleteProject.class);
1703 }
1704 });
1705 }
1706}
1707----
1708
David Pursehouse42245822013-09-24 09:48:20 +09001709For a `UiAction` bound this way, a JS API function can be provided.
1710
1711Currently only one restriction exists: per plugin only one `UiAction`
David Ostrovskyc6d19ed2013-09-20 21:30:18 +02001712can be bound per resource without view name. To define a JS function
1713for the `UiAction`, "/" must be used as the name:
1714
1715[source,javascript]
1716----
1717Gerrit.install(function(self) {
1718 function onDeleteProject(c) {
1719 [...]
1720 }
1721 self.onAction('project', '/', onDeleteProject);
1722});
1723----
1724
Dave Borowitzf1790ce2016-11-14 12:26:12 -08001725
1726[[action-visitor]]
1727=== Action Visitors
1728
1729In addition to providing new actions, plugins can have fine-grained control
1730over the link:rest-api-changes.html#action-info[ActionInfo] map, modifying or
1731removing existing actions, including those contributed by core.
1732
1733Visitors are provided the link:rest-api-changes.html#action-info[ActionInfo],
1734which is mutable, along with copies of the
1735link:rest-api-changes.html#change-info[ChangeInfo] and
1736link:rest-api-changes.html#revision-info[RevisionInfo]. They can modify the
1737action, or return `false` to exclude it from the resulting map.
1738
1739These operations only affect the action buttons that are displayed in the UI;
1740the underlying REST API endpoints are not affected. Multiple plugins may
1741implement the visitor interface, but the order in which they are run is
1742undefined.
1743
1744For example, to exclude "Cherry-Pick" only from certain projects, and rename
1745"Abandon":
1746
1747[source,java]
1748----
1749public class MyActionVisitor implements ActionVisitor {
1750 @Override
1751 public boolean visit(String name, ActionInfo actionInfo,
1752 ChangeInfo changeInfo) {
1753 if (name.equals("abandon")) {
1754 actionInfo.label = "Drop";
1755 }
1756 return true;
1757 }
1758
1759 @Override
1760 public boolean visit(String name, ActionInfo actionInfo,
1761 ChangeInfo changeInfo, RevisionInfo revisionInfo) {
1762 if (project.startsWith("some-team/") && name.equals("cherrypick")) {
1763 return false;
1764 }
1765 return true;
1766 }
1767}
1768----
1769
1770
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001771[[top-menu-extensions]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001772== Top Menu Extensions
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001773
1774Plugins can contribute items to Gerrit's top menu.
1775
1776A single top menu extension can have multiple elements and will be put as
1777the last element in Gerrit's top menu.
1778
1779Plugins define the top menu entries by implementing `TopMenu` interface:
1780
1781[source,java]
1782----
1783public class MyTopMenuExtension implements TopMenu {
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001784 @Override
1785 public List<MenuEntry> getEntries() {
1786 return Lists.newArrayList(
1787 new MenuEntry("Top Menu Entry", Lists.newArrayList(
1788 new MenuItem("Gerrit", "http://gerrit.googlecode.com/"))));
1789 }
1790}
1791----
1792
Edwin Kempin77f23242013-09-30 14:53:20 +02001793Plugins can also add additional menu items to Gerrit's top menu entries
1794by defining a `MenuEntry` that has the same name as a Gerrit top menu
1795entry:
1796
1797[source,java]
1798----
1799public class MyTopMenuExtension implements TopMenu {
Edwin Kempin77f23242013-09-30 14:53:20 +02001800 @Override
1801 public List<MenuEntry> getEntries() {
1802 return Lists.newArrayList(
Dariusz Luksza2d3afab2013-10-01 11:07:13 +02001803 new MenuEntry(GerritTopMenu.PROJECTS, Lists.newArrayList(
Edwin Kempin77f23242013-09-30 14:53:20 +02001804 new MenuItem("Browse Repositories", "https://gerrit.googlesource.com/"))));
1805 }
1806}
1807----
1808
Dariusz Lukszae8de74f2014-06-04 19:39:20 +02001809`MenuItems` that are bound for the `MenuEntry` with the name
1810`GerritTopMenu.PROJECTS` can contain a `${projectName}` placeholder
1811which is automatically replaced by the actual project name.
1812
1813E.g. plugins may register an link:#http[HTTP Servlet] to handle project
1814specific requests and add an menu item for this:
1815
1816[source,java]
Nasser Grainawieea60392021-08-23 09:05:10 -06001817----
1818new MenuItem("My Screen", "/plugins/myplugin/project/${projectName}");
1819----
Dariusz Lukszae8de74f2014-06-04 19:39:20 +02001820
1821This also enables plugins to provide menu items for project aware
1822screens:
1823
1824[source,java]
Nasser Grainawieea60392021-08-23 09:05:10 -06001825----
1826new MenuItem("My Screen", "/x/my-screen/for/${projectName}");
1827----
Dariusz Lukszae8de74f2014-06-04 19:39:20 +02001828
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001829If no Guice modules are declared in the manifest, the top menu extension may use
1830auto-registration by providing an `@Listen` annotation:
1831
1832[source,java]
1833----
1834@Listen
1835public class MyTopMenuExtension implements TopMenu {
David Pursehoused128c892013-10-22 21:52:21 +09001836 [...]
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001837}
1838----
1839
Luca Milanesiocb230402013-10-11 08:49:56 +01001840Otherwise the top menu extension must be bound in the plugin module used
1841for the Gerrit system injector (Gerrit-Module entry in MANIFEST.MF):
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001842
1843[source,java]
1844----
Luca Milanesiocb230402013-10-11 08:49:56 +01001845package com.googlesource.gerrit.plugins.helloworld;
1846
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001847public class HelloWorldModule extends AbstractModule {
1848 @Override
1849 protected void configure() {
1850 DynamicSet.bind(binder(), TopMenu.class).to(MyTopMenuExtension.class);
1851 }
1852}
1853----
1854
Luca Milanesiocb230402013-10-11 08:49:56 +01001855[source,manifest]
1856----
1857Gerrit-ApiType: plugin
1858Gerrit-Module: com.googlesource.gerrit.plugins.helloworld.HelloWorldModule
1859----
1860
Edwin Kempinb2e926a2013-11-11 16:38:30 +01001861It is also possible to show some menu entries only if the user has a
1862certain capability:
1863
1864[source,java]
1865----
1866public class MyTopMenuExtension implements TopMenu {
1867 private final String pluginName;
1868 private final Provider<CurrentUser> userProvider;
1869 private final List<MenuEntry> menuEntries;
1870
1871 @Inject
1872 public MyTopMenuExtension(@PluginName String pluginName,
1873 Provider<CurrentUser> userProvider) {
1874 this.pluginName = pluginName;
1875 this.userProvider = userProvider;
1876 menuEntries = new ArrayList<TopMenu.MenuEntry>();
1877
1878 // add menu entry that is only visible to users with a certain capability
1879 if (canSeeMenuEntry()) {
1880 menuEntries.add(new MenuEntry("Top Menu Entry", Collections
1881 .singletonList(new MenuItem("Gerrit", "http://gerrit.googlecode.com/"))));
1882 }
1883
1884 // add menu entry that is visible to all users (even anonymous users)
1885 menuEntries.add(new MenuEntry("Top Menu Entry", Collections
1886 .singletonList(new MenuItem("Documentation", "/plugins/myplugin/"))));
1887 }
1888
1889 private boolean canSeeMenuEntry() {
1890 if (userProvider.get().isIdentifiedUser()) {
1891 CapabilityControl ctl = userProvider.get().getCapabilities();
1892 return ctl.canPerform(pluginName + "-" + MyCapability.ID)
1893 || ctl.canAdministrateServer();
1894 } else {
1895 return false;
1896 }
1897 }
1898
1899 @Override
1900 public List<MenuEntry> getEntries() {
1901 return menuEntries;
1902 }
1903}
1904----
1905
Dave Borowitzf1790ce2016-11-14 12:26:12 -08001906
Edwin Kempin289f1a02014-02-04 16:08:25 +01001907[[settings-screen]]
1908== Plugin Settings Screen
1909
1910If a plugin implements a screen for administrating its settings that is
1911available under "#/x/<plugin-name>/settings" it is automatically linked
1912from the plugin list screen.
1913
Edwin Kempinf5a77332012-07-18 11:17:53 +02001914[[http]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001915== HTTP Servlets
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001916
1917Plugins or extensions may register additional HTTP servlets, and
1918wrap them with HTTP filters.
1919
1920Servlets may use auto-registration to declare the URL they handle:
1921
David Pursehouse68153d72013-09-04 10:09:17 +09001922[source,java]
1923----
1924import com.google.gerrit.extensions.annotations.Export;
1925import com.google.inject.Singleton;
1926import javax.servlet.http.HttpServlet;
1927import javax.servlet.http.HttpServletRequest;
1928import javax.servlet.http.HttpServletResponse;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001929
David Pursehouse68153d72013-09-04 10:09:17 +09001930@Export("/print")
1931@Singleton
1932class HelloServlet extends HttpServlet {
1933 protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
1934 res.setContentType("text/plain");
1935 res.setCharacterEncoding("UTF-8");
1936 res.getWriter().write("Hello");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001937 }
David Pursehouse68153d72013-09-04 10:09:17 +09001938}
1939----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001940
Edwin Kempin8aa650f2012-07-18 11:25:48 +02001941The auto registration only works for standard servlet mappings like
Jonathan Nieder5758f182015-03-30 11:28:55 -07001942`/foo` or `+/foo/*+`. Regex style bindings must use a Guice ServletModule
Edwin Kempin8aa650f2012-07-18 11:25:48 +02001943to register the HTTP servlets and declare it explicitly in the manifest
1944with the `Gerrit-HttpModule` attribute:
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001945
David Pursehouse68153d72013-09-04 10:09:17 +09001946[source,java]
1947----
1948import com.google.inject.servlet.ServletModule;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001949
David Pursehouse68153d72013-09-04 10:09:17 +09001950class MyWebUrls extends ServletModule {
1951 protected void configureServlets() {
1952 serve("/print").with(HelloServlet.class);
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001953 }
David Pursehouse68153d72013-09-04 10:09:17 +09001954}
1955----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001956
1957For a plugin installed as name `helloworld`, the servlet implemented
1958by HelloServlet class will be available to users as:
1959
1960----
1961$ curl http://review.example.com/plugins/helloworld/print
1962----
Nasser Grainawie033b262012-05-09 17:54:21 -07001963
Edwin Kempinf5a77332012-07-18 11:17:53 +02001964[[data-directory]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001965== Data Directory
Edwin Kempin41f63912012-07-17 12:33:55 +02001966
Dave Borowitz9e158752015-02-24 10:17:04 -08001967Plugins can request a data directory with a `@PluginData` Path (or File,
1968deprecated) dependency. A data directory will be created automatically
1969by the server in `$site_path/data/$plugin_name` and passed to the
1970plugin.
Edwin Kempin41f63912012-07-17 12:33:55 +02001971
1972Plugins can use this to store any data they want.
1973
David Pursehouse68153d72013-09-04 10:09:17 +09001974[source,java]
1975----
1976@Inject
Dave Borowitz9e158752015-02-24 10:17:04 -08001977MyType(@PluginData java.nio.file.Path myDir) {
1978 this.in = Files.newInputStream(myDir.resolve("my.config"));
David Pursehouse68153d72013-09-04 10:09:17 +09001979}
1980----
Edwin Kempin41f63912012-07-17 12:33:55 +02001981
Dariusz Lukszaebab92a2014-09-10 11:14:19 +02001982[[secure-store]]
1983== SecureStore
1984
1985SecureStore allows to change the way Gerrit stores sensitive data like
1986passwords.
1987
1988In order to replace the default SecureStore (no-op) implementation,
1989a class that extends `com.google.gerrit.server.securestore.SecureStore`
1990needs to be provided (with dependencies) in a separate jar file. Then
1991link:pgm-SwitchSecureStore.html[SwitchSecureStore] must be run to
1992switch implementations.
1993
1994The SecureStore implementation is instantiated using a Guice injector
1995which binds the `File` annotated with the `@SitePath` annotation.
1996This means that a SecureStore implementation class can get access to
1997the `site_path` like in the following example:
1998
1999[source,java]
2000----
2001@Inject
2002MySecureStore(@SitePath java.io.File sitePath) {
2003 // your code
2004}
2005----
2006
2007No Guice bindings or modules are required. Gerrit will automatically
2008discover and bind the implementation.
2009
Thomas Draebingc4e5c072020-07-10 11:44:47 +02002010[[gerrit-replica]]
2011== Gerrit Replica
2012
2013Gerrit can be run as a read-only replica. Some plugins may need to know
2014whether Gerrit is run as a primary- or a replica instance. For that purpose
2015Gerrit exposes the `@GerritIsReplica` annotation. A boolean annotated with
2016this annotation will indicate whether Gerrit is run as a replica.
2017
Michael Ochmann24612652016-02-12 17:26:18 +01002018[[accountcreation]]
2019== Account Creation
2020
2021Plugins can hook into the
2022link:rest-api-accounts.html#create-account[account creation] REST API and
2023inject additional external identifiers for an account that represents a user
2024in some external user store. For that, an implementation of the extension
Han-Wen Nienhuysa77b2e52017-11-11 11:30:05 +01002025point `com.google.gerrit.server.account.AccountExternalIdCreator`
Michael Ochmann24612652016-02-12 17:26:18 +01002026must be registered.
2027
2028[source,java]
2029----
2030class MyExternalIdCreator implements AccountExternalIdCreator {
2031 @Override
2032 public List<AccountExternalId> create(Account.Id id, String username,
2033 String email) {
2034 // your code
2035 }
2036}
2037
2038bind(AccountExternalIdCreator.class)
2039 .annotatedWith(UniqueAnnotations.create())
2040 .to(MyExternalIdCreator.class);
Michael Ochmann24612652016-02-12 17:26:18 +01002041----
2042
Edwin Kempinea621482013-10-16 12:58:24 +02002043[[download-commands]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002044== Download Commands
Edwin Kempinea621482013-10-16 12:58:24 +02002045
Edwin Kempineafde882015-05-11 15:40:44 +02002046Gerrit offers commands for downloading changes and cloning projects
2047using different download schemes (e.g. for downloading via different
2048network protocols). Plugins can contribute download schemes, download
2049commands and clone commands by implementing
2050`com.google.gerrit.extensions.config.DownloadScheme`,
2051`com.google.gerrit.extensions.config.DownloadCommand` and
2052`com.google.gerrit.extensions.config.CloneCommand`.
Edwin Kempinea621482013-10-16 12:58:24 +02002053
Edwin Kempineafde882015-05-11 15:40:44 +02002054The download schemes, download commands and clone commands which are
2055used most often are provided by the Gerrit core plugin
2056`download-commands`.
Edwin Kempinea621482013-10-16 12:58:24 +02002057
Edwin Kempin78279ba2015-05-22 15:22:41 +02002058[[included-in]]
2059== Included In
2060
2061For merged changes the link:user-review-ui.html#included-in[Included In]
2062drop-down panel shows the branches and tags in which the change is
2063included.
2064
2065Plugins can add additional systems in which the change can be included
2066by implementing `com.google.gerrit.extensions.config.ExternalIncludedIn`,
2067e.g. a plugin can provide a list of servers on which the change was
2068deployed.
2069
David Pursehouse38a27c3e2018-07-06 22:34:18 +09002070[[change-report-formatting]]
2071== Change Report Formatting
2072
2073When a change is pushed for review from the command line, Gerrit reports
2074the change(s) received with their URL and subject.
2075
2076By implementing the
2077`com.google.gerrit.server.git.ChangeReportFormatter` interface, a plugin
2078may change the formatting of the report.
2079
David Pursehousee2f25be2018-11-26 17:56:07 +09002080[[url-formatting]]
2081== URL Formatting
2082
2083URLs to various parts of Gerrit are usually formed by adding suffixes to
2084the canonical web URL.
2085
2086By implementing the
2087`com.google.gerrit.server.config.UrlFormatter` interface, a plugin may
2088change the format of the URL.
2089
Sven Selbergae1a10c2014-02-14 14:24:29 +01002090[[links-to-external-tools]]
2091== Links To External Tools
2092
2093Gerrit has extension points that enables development of a
2094light-weight plugin that links commits to external
2095tools (GitBlit, CGit, company specific resources etc).
2096
Han-Wen Nienhuys37a1cab2021-04-01 12:46:00 +02002097PatchSetWebLinks will appear to the right of the commit-SHA-1 in the UI.
Sven Selbergae1a10c2014-02-14 14:24:29 +01002098
2099[source, java]
2100----
2101import com.google.gerrit.extensions.annotations.Listen;
2102import com.google.gerrit.extensions.webui.PatchSetWebLink;;
Sven Selberga85e64d2014-09-24 10:52:21 +02002103import com.google.gerrit.extensions.webui.WebLinkTarget;
Sven Selbergae1a10c2014-02-14 14:24:29 +01002104
2105@Listen
2106public class MyWeblinkPlugin implements PatchSetWebLink {
Sven Selbergae1a10c2014-02-14 14:24:29 +01002107 private String name = "MyLink";
2108 private String placeHolderUrlProjectCommit = "http://my.tool.com/project=%s/commit=%s";
Sven Selberg55484202014-06-26 08:48:51 +02002109 private String imageUrl = "http://placehold.it/16x16.gif";
Sven Selbergae1a10c2014-02-14 14:24:29 +01002110
2111 @Override
Gal Paikin57c9d582020-11-27 14:35:22 +01002112 public WebLinkInfo getPatchSetWebLink(String projectName, String commit,
Gal Paikinbdcba692021-01-07 15:52:00 +01002113 String commitMessage, String branchName) {
Sven Selberga85e64d2014-09-24 10:52:21 +02002114 return new WebLinkInfo(name,
2115 imageUrl,
2116 String.format(placeHolderUrlProjectCommit, project, commit),
2117 WebLinkTarget.BLANK);
Edwin Kempinceeed6b2014-09-11 17:07:33 +02002118 }
Sven Selbergae1a10c2014-02-14 14:24:29 +01002119}
2120----
2121
Han-Wen Nienhuys37a1cab2021-04-01 12:46:00 +02002122ParentWebLinks will appear to the right of the SHA-1 of the parent
David Pursehouse58b8d762016-12-09 11:12:27 +09002123revisions in the UI. The implementation should in most use cases direct
2124to the same external service as PatchSetWebLink; it is provided as a
2125separate interface because not all users want to have links for the
2126parent revisions.
2127
Edwin Kempinb3696c82014-09-11 09:41:42 +02002128FileWebLinks will appear in the side-by-side diff screen on the right
2129side of the patch selection on each side.
2130
Edwin Kempin8cdce502014-12-06 10:55:38 +01002131DiffWebLinks will appear in the side-by-side and unified diff screen in
2132the header next to the navigation icons.
2133
Edwin Kempinea004752014-04-11 15:56:02 +02002134ProjectWebLinks will appear in the project list in the
2135`Repository Browser` column.
2136
Edwin Kempin0f697bd2014-09-10 18:23:29 +02002137BranchWebLinks will appear in the branch list in the last column.
2138
Edwin Kempinf82b8122016-06-03 09:20:16 +02002139FileHistoryWebLinks will appear on the access rights screen.
2140
Paladox none34da15c2017-07-01 14:49:10 +00002141TagWebLinks will appear in the tag list in the last column.
2142
Dave Borowitzd0c01fd2017-06-06 10:47:08 -04002143If a `get*WebLink` implementation returns `null`, the link will be omitted. This
2144allows the plugin to selectively "enable" itself on a per-project/branch/file
2145basis.
2146
Saša Živkovca7a67e2015-12-01 14:25:10 +01002147[[lfs-extension]]
2148== LFS Storage Plugins
2149
David Pursehouse2463c542016-08-02 16:04:58 +09002150Gerrit provides an extension point that enables development of
2151link:https://github.com/github/git-lfs/blob/master/docs/api/v1/http-v1-batch.md[
Marian Harbach34253372019-12-10 18:01:31 +01002152LFS (Large File Storage),role=external,window=_blank] storage plugins. Gerrit core exposes the default LFS
David Pursehouse2463c542016-08-02 16:04:58 +09002153protocol endpoint `<project-name>/info/lfs/objects/batch` and forwards the requests
2154to the configured link:config-gerrit.html#lfs[lfs.plugin] plugin which implements
2155the LFS protocol. By exposing the default LFS endpoint, the git-lfs client can be
2156used without any configuration.
Saša Živkovca7a67e2015-12-01 14:25:10 +01002157
2158[source, java]
2159----
2160/** Provide an LFS protocol implementation */
2161import org.eclipse.jgit.lfs.server.LargeFileRepository;
2162import org.eclipse.jgit.lfs.server.LfsProtocolServlet;
2163
2164@Singleton
2165public class LfsApiServlet extends LfsProtocolServlet {
2166 private static final long serialVersionUID = 1L;
2167
2168 private final S3LargeFileRepository repository;
2169
2170 @Inject
2171 LfsApiServlet(S3LargeFileRepository repository) {
2172 this.repository = repository;
2173 }
2174
2175 @Override
2176 protected LargeFileRepository getLargeFileRepository() {
2177 return repository;
2178 }
2179}
2180
2181/** Register the LfsApiServlet to listen on the default LFS protocol endpoint */
2182import static com.google.gerrit.httpd.plugins.LfsPluginServlet.URL_REGEX;
2183
Dave Borowitz20f4e8a2019-03-13 14:19:15 -07002184import com.google.inject.servlet.ServletModule;
Saša Živkovca7a67e2015-12-01 14:25:10 +01002185
Dave Borowitz20f4e8a2019-03-13 14:19:15 -07002186public class HttpModule extends ServletModule {
Saša Živkovca7a67e2015-12-01 14:25:10 +01002187 @Override
2188 protected void configureServlets() {
2189 serveRegex(URL_REGEX).with(LfsApiServlet.class);
2190 }
2191}
2192
2193/** Provide an implementation of the LargeFileRepository */
2194import org.eclipse.jgit.lfs.server.s3.S3Repository;
2195
2196public class S3LargeFileRepository extends S3Repository {
Nasser Grainawieea60392021-08-23 09:05:10 -06002197 ...
Saša Živkovca7a67e2015-12-01 14:25:10 +01002198}
2199----
2200
David Pursehouse8ad11732016-08-29 15:00:14 +09002201[[metrics]]
2202== Metrics
2203
2204=== Metrics Reporting
2205
2206To send Gerrit's metrics data to an external reporting backend, a plugin can
2207get a `MetricRegistry` injected and register an instance of a class that
2208implements the `Reporter` interface from link:http://metrics.dropwizard.io/[
Marian Harbach34253372019-12-10 18:01:31 +01002209DropWizard Metrics,role=external,window=_blank].
David Pursehouse8ad11732016-08-29 15:00:14 +09002210
2211Metric reporting plugin implementations are provided for
Marian Harbach34253372019-12-10 18:01:31 +01002212link:https://gerrit.googlesource.com/plugins/metrics-reporter-jmx/[JMX,role=external,window=_blank],
2213link:https://gerrit.googlesource.com/plugins/metrics-reporter-elasticsearch/[Elastic Search,role=external,window=_blank],
2214and link:https://gerrit.googlesource.com/plugins/metrics-reporter-graphite/[Graphite,role=external,window=_blank].
David Pursehouse8ad11732016-08-29 15:00:14 +09002215
2216There is also a working example of reporting metrics to the console in the
Eryk Szymanskida073b12017-09-11 14:27:57 +02002217link:https://gerrit.googlesource.com/plugins/cookbook-plugin/+/master/src/main/java/com/googlesource/gerrit/plugins/cookbook/ConsoleMetricReporter.java[
Marian Harbach34253372019-12-10 18:01:31 +01002218cookbook plugin,role=external,window=_blank].
David Pursehouse8ad11732016-08-29 15:00:14 +09002219
2220=== Providing own metrics
2221
2222Plugins may provide metrics to be dispatched to external reporting services by
2223getting a `MetricMaker` injected and creating instances of specific types of
2224metric:
2225
2226* Counter
2227+
2228Metric whose value increments during the life of the process.
2229
2230* Timer
2231+
2232Metric recording time spent on an operation.
2233
2234* Histogram
2235+
2236Metric recording statistical distribution (rate) of values.
2237
David Pursehouse48d05ea2017-02-03 19:05:29 +09002238Note that metrics cannot be recorded from plugin init steps that
2239are run during site initialization.
2240
David Pursehousec3bbd562017-02-06 20:25:29 +09002241By default, plugin metrics are recorded under
2242`plugins/${plugin-name}/${metric-name}`. This can be changed by
2243setting `plugins.${plugin-name}.metricsPrefix` in the `gerrit.config`
2244file. For example:
2245
2246----
Nasser Grainawieea60392021-08-23 09:05:10 -06002247[plugin "my-plugin"]
2248 metricsPrefix = my-metrics
David Pursehousec3bbd562017-02-06 20:25:29 +09002249----
2250
2251will cause the metrics to be recorded under `my-metrics/${metric-name}`.
David Pursehouse8ad11732016-08-29 15:00:14 +09002252
2253See the replication metrics in the
2254link:https://gerrit.googlesource.com/plugins/replication/+/master/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationMetrics.java[
Marian Harbach34253372019-12-10 18:01:31 +01002255replication plugin,role=external,window=_blank] for an example of usage.
David Pursehouse8ad11732016-08-29 15:00:14 +09002256
Edwin Kempinda17bc32016-06-14 11:50:58 +02002257[[account-patch-review-store]]
2258== AccountPatchReviewStore
2259
2260The AccountPatchReviewStore is used to store reviewed flags on changes.
2261A reviewed flag is a tuple of (patch set ID, file, account ID) and
2262records whether the user has reviewed a file in a patch set. Each user
2263can easily have thousands of reviewed flags and the number of reviewed
2264flags is growing without bound. The store must be able handle this data
2265volume efficiently.
2266
2267Gerrit implements this extension point, but plugins may bind another
Matthias Sohnd8182ba2019-12-09 14:50:23 +01002268implementation, e.g. one that supports cluster setup with multiple
2269primary Gerrit nodes handling write operations.
Edwin Kempinda17bc32016-06-14 11:50:58 +02002270
Nasser Grainawieea60392021-08-23 09:05:10 -06002271[source,java]
Edwin Kempinda17bc32016-06-14 11:50:58 +02002272----
2273DynamicItem.bind(binder(), AccountPatchReviewStore.class)
2274 .to(MultiMasterAccountPatchReviewStore.class);
2275
2276...
2277
2278public class MultiMasterAccountPatchReviewStore
2279 implements AccountPatchReviewStore {
2280 ...
2281}
2282----
2283
Dave Borowitzf1790ce2016-11-14 12:26:12 -08002284
Edwin Kempinf5a77332012-07-18 11:17:53 +02002285[[documentation]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002286== Documentation
Nasser Grainawie033b262012-05-09 17:54:21 -07002287
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002288If a plugin does not register a filter or servlet to handle URLs
Jonathan Nieder5758f182015-03-30 11:28:55 -07002289`+/Documentation/*+` or `+/static/*+`, the core Gerrit server will
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002290automatically export these resources over HTTP from the plugin JAR.
2291
David Pursehouse6853b5a2013-07-10 11:38:03 +09002292Static resources under the `static/` directory in the JAR will be
Dave Borowitzb893ac82013-03-27 10:03:55 -04002293available as `/plugins/helloworld/static/resource`. This prefix is
2294configurable by setting the `Gerrit-HttpStaticPrefix` attribute.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002295
David Pursehouse6853b5a2013-07-10 11:38:03 +09002296Documentation files under the `Documentation/` directory in the JAR
Dave Borowitzb893ac82013-03-27 10:03:55 -04002297will be available as `/plugins/helloworld/Documentation/resource`. This
2298prefix is configurable by setting the `Gerrit-HttpDocumentationPrefix`
2299attribute.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002300
Christian Aistleitner040cf822015-03-26 21:09:09 +01002301Documentation may be written in the Markdown flavor
Marian Harbach34253372019-12-10 18:01:31 +01002302link:https://github.com/vsch/flexmark-java[flexmark-java,role=external,window=_blank]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002303if the file name ends with `.md`. Gerrit will automatically convert
2304Markdown to HTML if accessed with extension `.html`.
Nasser Grainawie033b262012-05-09 17:54:21 -07002305
Edwin Kempinf5a77332012-07-18 11:17:53 +02002306[[macros]]
Edwin Kempinc78777d2012-07-16 15:55:11 +02002307Within the Markdown documentation files macros can be used that allow
2308to write documentation with reasonably accurate examples that adjust
2309automatically based on the installation.
2310
2311The following macros are supported:
2312
2313[width="40%",options="header"]
2314|===================================================
2315|Macro | Replacement
2316|@PLUGIN@ | name of the plugin
2317|@URL@ | Gerrit Web URL
2318|@SSH_HOST@ | SSH Host
2319|@SSH_PORT@ | SSH Port
2320|===================================================
2321
2322The macros will be replaced when the documentation files are rendered
2323from Markdown to HTML.
2324
2325Macros that start with `\` such as `\@KEEP@` will render as `@KEEP@`
2326even if there is an expansion for `KEEP` in the future.
2327
Luca Milanesioa3d41da2020-12-29 14:29:03 +00002328Documentation should typically contain the following content:
2329
Luca Milanesio718a0a52020-12-29 22:19:12 +00002330[width="100%",options="header"]
Luca Milanesioa3d41da2020-12-29 14:29:03 +00002331|===================================================
Luca Milanesio718a0a52020-12-29 22:19:12 +00002332|File | Content
2333|`README.md` | Home page of the plugin when browsing its source code on Git
2334|`LICENSE` | Open-source license
2335|`resources/Documentation/about.md` | Overview of the plugin and its purpose
2336|`resources/Documentation/config.md` | Plugin configuration settings and sample configs
2337|`resources/Documentation/build.md` | How to build the plugin
2338|`resources/Documentation/cmd-<command>.md` | SSH commands
2339|`resources/Documentation/rest-api-<api>.md` | REST API
2340|`resources/Documentation/servlet-<servlet>.md` | HTTP Servlets
Luca Milanesioa3d41da2020-12-29 14:29:03 +00002341|===================================================
2342
2343The documentation under resources/Documentation may contain macro that
2344will be included and expanded by Gerrit once the plugin is loaded.
2345
2346The files in the root directory are not included in the plugin package
2347and must not have any macro for expansion. It may also collect
2348additional information that would make the plugin more discoverable, such as
2349a more user-friendly description of its use-cases.
2350
2351The documentation can also include images that can help understanding more
2352visually how the plugin can interact with the other Gerrit components.
2353
Edwin Kempinf5a77332012-07-18 11:17:53 +02002354[[auto-index]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002355=== Automatic Index
Shawn O. Pearce795167c2012-05-12 11:20:18 -07002356
2357If a plugin does not handle its `/` URL itself, Gerrit will
2358redirect clients to the plugin's `/Documentation/index.html`.
2359Requests for `/Documentation/` (bare directory) will also redirect
2360to `/Documentation/index.html`.
2361
2362If neither resource `Documentation/index.html` or
2363`Documentation/index.md` exists in the plugin JAR, Gerrit will
Edwin Kempina9c529f2021-06-17 09:34:24 +02002364automatically generate an index page.
Shawn O. Pearce795167c2012-05-12 11:20:18 -07002365
Edwin Kempina9c529f2021-06-17 09:34:24 +02002366The generated index page contains 3 sections:
Shawn O. Pearce795167c2012-05-12 11:20:18 -07002367
Edwin Kempina9c529f2021-06-17 09:34:24 +020023681. Manifest section
2369+
Shawn O. Pearce795167c2012-05-12 11:20:18 -07002370Some optional information from the manifest is extracted and
2371displayed as part of the index page, if present in the manifest:
Edwin Kempina9c529f2021-06-17 09:34:24 +02002372+
Shawn O. Pearce795167c2012-05-12 11:20:18 -07002373[width="40%",options="header"]
2374|===================================================
2375|Field | Source Attribute
2376|Name | Implementation-Title
2377|Vendor | Implementation-Vendor
2378|Version | Implementation-Version
2379|URL | Implementation-URL
2380|API Version | Gerrit-ApiVersion
2381|===================================================
2382
Edwin Kempina9c529f2021-06-17 09:34:24 +020023832. About section
2384+
2385If an `about.md` or `about.html` file exists, its content will be inserted in an
2386'About' section.
2387+
2388If both `about.md` and `about.html` exist, only the first discovered file will
2389be used.
2390
23913. TOC section
2392+
2393If a `toc.md` or `toc.html` file exists, its content will be inserted in a
2394'Documentation' section.
2395+
2396`toc.md` or `toc.html` is a manually maintained index of the documentation pages
2397that exist in the plugin. Having a manually maintained index has the advantage
2398that you can group the documentation pages by topic and sort them by importance.
2399+
2400If both `toc.md` and `toc.html` exist, only the first discovered file will
2401be used.
2402+
2403If no `toc` file is present the TOC section is automatically generated by
2404scanning every `\*.md` and `*.html` file in the `Documentation/` directory.
2405+
2406For any discovered Markdown (`*.md`) file, Gerrit will parse the
2407header of the file and extract the first level one title. This
2408title text will be used as display text for a link to the HTML
2409version of the page.
2410+
2411For any discovered HTML (`\*.html`) file, Gerrit will use the name
2412of the file, minus the `*.html` extension, as the link text. Any
2413hyphens in the file name will be replaced with spaces.
2414+
2415If a discovered file name beings with `cmd-` it will be clustered
2416into a 'Commands' section of the generated index page.
2417+
2418If a discovered file name beings with `servlet-` it will be clustered
2419into a 'Servlets' section of the generated index page.
2420+
2421If a discovered file name beings with `rest-api-` it will be clustered
2422into a 'REST APIs' section of the generated index page.
2423+
2424All other files are clustered under a 'Documentation' section.
2425
Edwin Kempinf5a77332012-07-18 11:17:53 +02002426[[deployment]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002427== Deployment
Nasser Grainawie033b262012-05-09 17:54:21 -07002428
Edwin Kempinf7295742012-07-16 15:03:46 +02002429Compiled plugins and extensions can be deployed to a running Gerrit
2430server using the link:cmd-plugin-install.html[plugin install] command.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002431
Leopold Schabela46aac22021-03-15 00:05:28 +01002432Web UI plugins distributed as a single `.js` file can be deployed without the
2433overhead of JAR packaging. For more information refer to
2434link:cmd-plugin-install.html[plugin install] command.
Dariusz Luksza357a2422012-11-12 06:16:26 +01002435
David Pursehouse75021fe2017-07-31 23:07:04 +02002436Plugins can also be copied directly into the server's directory at
Leopold Schabela46aac22021-03-15 00:05:28 +01002437`$site_path/plugins/$name.(jar|js)`. For Web UI plugins, the name
2438of the file, minus the `.js` extension, will be used as the
David Pursehouse75021fe2017-07-31 23:07:04 +02002439plugin name. For JAR plugins, the value of the `Gerrit-PluginName`
2440manifest attribute will be used, if provided, otherwise the name of
2441the file, minus the `.jar` extension, will be used.
2442
Ben Rohlfsda0a62b2021-04-26 17:02:19 +02002443For JAR plugins, the version is taken from the `Version` attribute in the
David Pursehouse75021fe2017-07-31 23:07:04 +02002444manifest.
2445
2446Unless disabled, servers periodically scan the `$site_path/plugins`
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002447directory for updated plugins. The time can be adjusted by
2448link:config-gerrit.html#plugins.checkFrequency[plugins.checkFrequency].
Deniz Türkoglueb78b602012-05-07 14:02:36 -07002449
Edwin Kempinf7295742012-07-16 15:03:46 +02002450For disabling plugins the link:cmd-plugin-remove.html[plugin remove]
2451command can be used.
2452
Brad Larsond5e87c32012-07-11 12:18:49 -05002453Disabled plugins can be re-enabled using the
2454link:cmd-plugin-enable.html[plugin enable] command.
2455
Patrick Hiesel87880b02016-05-03 18:15:08 +02002456[[reviewer-suggestion]]
2457== Reviewer Suggestion Plugins
2458
2459Gerrit provides an extension point that enables Plugins to rank
2460the list of reviewer suggestion a user receives upon clicking "Add Reviewer" on
2461the change screen.
David Pursehousec2233a42018-02-07 10:31:05 +09002462
Patrick Hiesel87880b02016-05-03 18:15:08 +02002463Gerrit supports both a default suggestion that appears when the user has not yet
2464typed anything and a filtered suggestion that is shown as the user starts
2465typing.
David Pursehousec2233a42018-02-07 10:31:05 +09002466
2467Plugins receive a candidate list and can return a `Set` of suggested reviewers
2468containing the `Account.Id` and a score for each reviewer. The candidate list is
2469non-binding and plugins can choose to return reviewers not initially contained in
2470the candidate list.
2471
2472Server administrators can configure the overall weight of each plugin by setting
2473the `addreviewer.pluginName-exportName.weight` value in `gerrit.config`.
Patrick Hiesel87880b02016-05-03 18:15:08 +02002474
2475[source, java]
2476----
Patrick Hiesel2514e412016-10-13 09:34:44 +02002477import com.google.gerrit.common.Nullable;
David Ostrovskyb03a6e92019-05-26 14:11:47 +02002478import com.google.gerrit.entities.Account;
2479import com.google.gerrit.entities.Change;
2480import com.google.gerrit.entities.Project;
Patrick Hiesel2514e412016-10-13 09:34:44 +02002481import com.google.gerrit.extensions.annotations.ExtensionPoint;
Patrick Hiesel87880b02016-05-03 18:15:08 +02002482
2483import java.util.Set;
2484
2485public class MyPlugin implements ReviewerSuggestion {
Patrick Hiesel2514e412016-10-13 09:34:44 +02002486 public Set<SuggestedReviewer> suggestReviewers(Project.NameKey project,
2487 @Nullable Change.Id changeId, @Nullable String query,
2488 Set<Account.Id> candidates) {
2489 Set<SuggestedReviewer> suggestions = new HashSet<>();
2490 // Implement your ranking logic here
2491 return suggestions;
Patrick Hiesel87880b02016-05-03 18:15:08 +02002492 }
2493}
2494----
2495
2496
Patrick Hiesel30c76182017-01-20 11:46:43 +01002497[[mail-filter]]
2498== Mail Filter Plugins
2499
2500Gerrit provides an extension point that enables Plugins to discard incoming
2501messages and prevent further processing by Gerrit.
2502
David Pursehouse4b067752017-03-03 15:54:53 +09002503This can be used to implement spam checks, signature validations or organization
Patrick Hiesel30c76182017-01-20 11:46:43 +01002504specific checks like IP filters.
2505
2506[source, java]
2507----
2508import com.google.gerrit.extensions.annotations.ExtensionPoint;
Han-Wen Nienhuys50dd94e2017-11-14 18:04:28 +01002509import com.google.gerrit.mail.MailMessage;
Patrick Hiesel30c76182017-01-20 11:46:43 +01002510
2511public class MyPlugin implements MailFilter {
maximeg92419ab2018-02-16 10:27:25 +01002512 public boolean shouldProcessMessage(MailMessage message) {
Patrick Hiesel30c76182017-01-20 11:46:43 +01002513 // Implement your filter logic here
2514 return true;
2515 }
2516}
2517----
2518
frankborden2@gmail.com66d410b2021-08-26 10:27:33 +02002519
2520[[account-tag]]
2521== Account Tag Plugins
2522
2523Gerrit provides an extension point that enables Plugins to supply additional
2524tags on an account.
2525
2526[source, java]
2527----
2528import com.google.gerrit.entities.Account;
2529import com.google.gerrit.server.account.AccountTagProvider;
2530import java.util.List;
2531
2532public class MyPlugin implements AccountTagProvider {
2533 public List<String> getTags(Account.Id id) {
2534 // Implement your logic here
2535 }
2536}
2537----
2538
David Causse01c13402019-04-09 14:19:28 +02002539[[ssh-command-creation-interception]]
2540== SSH Command Creation Interception
David Pursehouse9cd079f2018-02-28 21:36:27 +09002541
2542Gerrit provides an extension point that allows a plugin to intercept
2543creation of SSH commands and override the functionality with its own
2544implementation.
2545
2546[source, java]
2547----
2548import com.google.gerrit.sshd.SshCreateCommandInterceptor;
2549
2550class MyCommandInterceptor implements SshCreateCommandInterceptor {
2551 @Override
2552 public String intercept(String in) {
2553 return pluginName + " mycommand";
Nasser Grainawieea60392021-08-23 09:05:10 -06002554 }
2555}
Maxime Guerreiroa63cc6e2018-02-28 14:31:55 +01002556----
2557
David Causse01c13402019-04-09 14:19:28 +02002558[[ssh-command-execution-interception]]
2559== SSH Command Execution Interception
2560Gerrit provides an extension point that enables plugins to check and
2561prevent an SSH command from being run.
2562
2563[source, java]
2564----
2565import com.google.gerrit.sshd.SshExecuteCommandInterceptor;
2566
2567@Singleton
2568public class SshExecuteCommandInterceptorImpl implements SshExecuteCommandInterceptor {
2569 private final Provider<SshSession> sessionProvider;
2570
2571 @Inject
2572 SshExecuteCommandInterceptorImpl(Provider<SshSession> sessionProvider) {
2573 this.sessionProvider = sessionProvider;
2574 }
2575
2576 @Override
2577 public boolean accept(String command, List<String> arguments) {
2578 if (command.startsWith("gerrit") && !"10.1.2.3".equals(sessionProvider.get().getRemoteAddressAsString())) {
2579 return false;
2580 }
2581 return true;
2582 }
2583}
2584----
2585
2586And then declare it in your SSH module:
2587[source, java]
2588----
Nasser Grainawieea60392021-08-23 09:05:10 -06002589DynamicSet.bind(binder(), SshExecuteCommandInterceptor.class)
2590 .to(SshExecuteCommandInterceptorImpl.class);
David Causse01c13402019-04-09 14:19:28 +02002591----
Maxime Guerreiroa63cc6e2018-02-28 14:31:55 +01002592
2593[[pre-submit-evaluator]]
2594== Pre-submit Validation Plugins
2595
2596Gerrit provides an extension point that enables plugins to prevent a change
2597from being submitted.
2598
2599[IMPORTANT]
2600This extension point **must NOT** be used for long or slow operations, like
2601calling external programs or content, running unit tests...
2602Slow operations will hurt the whole Gerrit instance.
2603
2604This can be used to implement custom rules that changes have to match to become
2605submittable. A more concrete example: the Prolog rules engine can be
2606implemented using this.
2607
2608Gerrit calls the plugins once per change and caches the results. Although it is
2609possible to predict when this interface will be triggered, this should not be
2610considered as a feature. Plugins should only rely on the internal state of the
2611ChangeData, not on external values like date and time, remote content or
2612randomness.
2613
2614Plugins are expected to support rules inheritance themselves, providing ways
2615to configure it and handling the logic behind it.
2616Please note that no inheritance is sometimes better than badly handled
2617inheritance: mis-communication and strange behaviors caused by inheritance
2618may and will confuse the users. Each plugins is responsible for handling the
2619project hierarchy and taking wise actions. Gerrit does not enforce it.
2620
2621Once Gerrit has gathered every plugins' SubmitRecords, it stores them.
2622
2623Plugins accept or reject a given change using `SubmitRecord.Status`.
2624If a change is ready to be submitted, `OK`. If it is not ready and requires
2625modifications, `NOT_READY`. Other statuses are available for particular cases.
2626A change can be submitted if all the plugins accept the change.
2627
Patrick Hiesel7ab1b182019-08-13 15:29:27 +02002628Plugins may also decide not to vote on a given change by returning an
2629`Optional.empty()` (ie: the plugin is not enabled for this repository).
Maxime Guerreiroa63cc6e2018-02-28 14:31:55 +01002630
2631If a plugin decides not to vote, it's name will not be displayed in the UI and
2632it will not be recoded in the database.
2633
2634.Gerrit's Pre-submit handling with three plugins
2635[width="50%",cols="^m,^m,^m,^m",frame="topbot",options="header"]
2636|=======================================================
2637| Plugin A | Plugin B | Plugin C | Final decision
2638| OK | OK | OK | OK
2639| OK | OK | / | OK
2640| OK | OK | RULE_ERROR | NOT_READY
2641| OK | NOT_READY | OK | NOT_READY
2642| NOT_READY | OK | OK | NOT_READY
2643|=======================================================
2644
2645
2646This makes composing plugins really easy.
2647
2648- If a plugin places a veto on a change, it can't be submitted.
2649- If a plugin isn't enabled for a project (or isn't needed for this change),
2650 it returns an empty collection.
2651- If all the plugins answer `OK`, the change can be submitted.
2652
2653
2654A more rare case, but worth documenting: if there are no installed plugins,
2655the labels will be compared to the rules defined in the project's config,
2656and the permission system will be used to allow or deny a submit request.
2657
2658Some rules are defined internally to provide a common base ground (and sanity):
2659changes that are marked as WIP or that are closed (abandoned, merged) can't be merged.
2660
2661
2662[source, java]
2663----
Patrick Hiesel7ab1b182019-08-13 15:29:27 +02002664import java.util.Optional;
Andrew Allen6887c732020-09-12 00:42:11 +00002665import com.google.gerrit.entities.SubmitRecord;
2666import com.google.gerrit.entities.SubmitRecord.Status;
Maxime Guerreiroa63cc6e2018-02-28 14:31:55 +01002667import com.google.gerrit.server.query.change.ChangeData;
2668import com.google.gerrit.server.rules.SubmitRule;
2669
2670public class MyPluginRules implements SubmitRule {
Patrick Hiesel7ab1b182019-08-13 15:29:27 +02002671 public Optional<SubmitRecord> evaluate(ChangeData changeData) {
Maxime Guerreiroa63cc6e2018-02-28 14:31:55 +01002672 // Implement your submitability logic here
2673
2674 // Assuming we want to prevent this change from being submitted:
Edwin Kempinf5d3e942019-07-22 15:23:47 +02002675 SubmitRecord record = new SubmitRecord();
Maxime Guerreiroa63cc6e2018-02-28 14:31:55 +01002676 record.status = Status.NOT_READY;
Patrick Hiesel7ab1b182019-08-13 15:29:27 +02002677 return Optional.of(record);
David Pursehouse9cd079f2018-02-28 21:36:27 +09002678 }
2679}
2680----
2681
Maxime Guerreiroa63cc6e2018-02-28 14:31:55 +01002682Don't forget to register your class!
2683
2684[source, java]
2685----
2686import com.google.gerrit.extensions.annotations.Exports;
2687import com.google.inject.AbstractModule;
2688
2689public class MyPluginModule extends AbstractModule {
2690 @Override
2691 protected void configure() {
2692 bind(SubmitRule.class).annotatedWith(Exports.named("myPlugin")).to(MyPluginRules.class);
2693 }
2694}
2695----
David Pursehouse9cd079f2018-02-28 21:36:27 +09002696
Maxime Guerreiro5ccf0b92018-05-15 16:28:22 +00002697Plugin authors should also consider binding their SubmitRule using a `Gerrit-BatchModule`.
2698See link:dev-plugins.html[Batch runtime] for more informations.
2699
Maxime Guerreiro97e891a2018-06-06 13:36:50 +00002700
2701The SubmitRule extension point allows you to write complex rules, but writing
2702small self-contained rules should be preferred: doing so allows end users to
2703compose several rules to form more complex submit checks.
2704
2705The `SubmitRequirement` class allows rules to communicate what the user needs
2706to change in order to be compliant. These requirements should be kept once they
2707are met, but marked as `OK`. If the requirements were not displayed, reviewers
2708would need to use their precious time to manually check that they were met.
2709
Edwin Kempin96915b72019-07-22 14:57:40 +02002710Implementors of the `SubmitRule` interface should check whether they need to
2711contribute to the link:#change-etag-computation[change ETag computation] to
2712prevent callers using ETags from potentially seeing outdated submittability
2713information.
2714
2715[[change-etag-computation]]
2716== Change ETag Computation
2717
2718By implementing the `com.google.gerrit.server.change.ChangeETagComputation`
2719interface plugins can contribute a value to the change ETag computation.
2720
2721Plugins can affect the result of the get change / get change details REST
2722endpoints by:
2723
2724* providing link:#query_attributes[plugin defined attributes] in
2725 link:rest-api-changes.html#change-info[ChangeInfo]
2726* implementing a link:#pre-submit-evaluator[pre-submit evaluator] which affects
2727 the computation of `submittable` field in
2728 link:rest-api-changes.html#change-info[ChangeInfo]
2729
2730If the plugin defined part of link:rest-api-changes.html#change-info[
2731ChangeInfo] depends on plugin specific data, callers that use change ETags to
2732avoid unneeded recomputations of ChangeInfos may see outdated plugin attributes
2733and/or outdated submittable information, because a ChangeInfo is only reloaded
2734if the change ETag changes.
2735
2736By implementating the `com.google.gerrit.server.change.ChangeETagComputation`
2737interface plugins can contribute to the ETag computation and thus ensure that
2738the change ETag changes when the plugin data was changed. This way it can be
2739ensured that callers do not see outdated ChangeInfos.
2740
2741IMPORTANT: Change ETags are computed very frequently and the computation must
2742be cheap. Take good care to not perform any expensive computations when
2743implementing this.
2744
2745[source, java]
2746----
2747import static java.nio.charset.StandardCharsets.UTF_8;
2748
2749import com.google.common.hash.Hasher;
David Pursehouse0fb1fd12019-10-16 10:41:00 +09002750import com.google.gerrit.entities.Change;
2751import com.google.gerrit.entities.Project;
Edwin Kempin96915b72019-07-22 14:57:40 +02002752import com.google.gerrit.server.change.ChangeETagComputation;
2753
2754public class MyPluginChangeETagComputation implements ChangeETagComputation {
2755 public String getETag(Project.NameKey projectName, Change.Id changeId) {
2756 Hasher hasher = Hashing.murmur3_128().newHasher();
2757
2758 // Add hashes for all plugin-specific data that affects change infos.
2759 hasher.putString(sha1OfPluginSpecificChangeRef, UTF_8);
2760
2761 return hasher.hash().toString();
2762 }
2763}
2764----
2765
Edwin Kempince8af4c2019-09-24 13:28:03 +02002766[[exception-hook]]
2767== ExceptionHook
2768
2769An `ExceptionHook` allows implementors to control how certain
2770exceptions should be handled.
2771
2772This interface is intended to be implemented for multi-master setups to
2773control the behavior for handling exceptions that are thrown by a lower
2774layer that handles the consensus and synchronization between different
2775server nodes. E.g. if an operation fails because consensus for a Git
2776update could not be achieved (e.g. due to slow responding server nodes)
2777this interface can be used to retry the request instead of failing it
2778immediately.
2779
Edwin Kempin8f29a392019-10-31 11:48:31 +01002780It also allows implementors to group exceptions that have the same
2781cause into one metric bucket.
2782
Edwin Kempin7c9aa3e2019-09-26 11:50:03 +02002783[[mail-soy-template-provider]]
2784== MailSoyTemplateProvider
2785
2786This extension point allows to provide soy templates for registration
2787so that they can be used for sending emails from a plugin.
2788
Patrick Hieselfc9817f2018-11-12 15:08:35 +01002789[[quota-enforcer]]
2790== Quota Enforcer
2791
2792Gerrit provides an extension point that allows a plugin to enforce quota.
2793link:quota.html[This documentation page] has a list of all quota requests that
2794Gerrit core issues. Plugins can choose to respond to all or just a subset of
2795requests. Some implementations might want to keep track of user quota in buckets,
2796others might just check against instance or project state to enforce limits on how
2797many projects can be created or how large a repository can become.
2798
2799Checking against instance state can be racy for concurrent requests as the server does not
2800refill tokens if the action fails in a later stage (e.g. database failure). If
2801plugins want to guarantee an absolute maximum on a resource, they have to do their own
2802book-keeping.
2803
2804[source, java]
2805----
2806import com.google.server.quota.QuotaEnforcer;
2807
2808class ProjectLimiter implements QuotaEnforcer {
2809 private final long maxNumberOfProjects = 100;
2810 @Override
2811 QuotaResponse requestTokens(String quotaGroup, QuotaRequestContext ctx, long numTokens) {
2812 if (!"/projects/create".equals(quotaGroup)) {
2813 return QuotaResponse.noOp();
2814 }
2815 // No deduction because we always check against the instance state (racy but fine for
2816 // this plugin)
2817 if (currentNumberOfProjects() + numTokens > maxNumberOfProjects) {
2818 return QuotaResponse.error("too many projects");
2819 }
2820 return QuotaResponse.ok();
2821 }
2822
2823 @Override
2824 QuotaResponse dryRun(String quotaGroup, QuotaRequestContext ctx, long numTokens) {
2825 // Since we are not keeping any state in this enforcer, we can simply call requestTokens().
2826 return requestTokens(quotaGroup, ctx, numTokens);
2827 }
2828
2829 void refill(String quotaGroup, QuotaRequestContext ctx, long numTokens) {
2830 // No-op
2831 }
2832}
2833----
2834
2835[source, java]
2836----
2837import com.google.server.quota.QuotaEnforcer;
2838
2839class ApiQpsEnforcer implements QuotaEnforcer {
2840 // AutoRefillingPerUserBuckets is a imaginary bucket implementation that could be based on
2841 // a loading cache or a commonly used bucketing algorithm.
2842 private final AutoRefillingPerUserBuckets<CurrentUser, Long> buckets;
2843 @Override
2844 QuotaResponse requestTokens(String quotaGroup, QuotaRequestContext ctx, long numTokens) {
2845 if (!quotaGroup.startsWith("/restapi/")) {
2846 return QuotaResponse.noOp();
2847 }
2848 boolean success = buckets.deduct(ctx.user(), numTokens);
2849 if (!success) {
2850 return QuotaResponse.error("user sent too many qps, please wait for 5 minutes");
2851 }
2852 return QuotaResponse.ok();
2853 }
2854
2855 @Override
2856 QuotaResponse dryRun(String quotaGroup, QuotaRequestContext ctx, long numTokens) {
2857 if (!quotaGroup.startsWith("/restapi/")) {
2858 return QuotaResponse.noOp();
2859 }
2860 boolean success = buckets.checkOnly(ctx.user(), numTokens);
2861 if (!success) {
2862 return QuotaResponse.error("user sent too many qps, please wait for 5 minutes");
2863 }
2864 return QuotaResponse.ok();
2865 }
2866
2867 @Override
2868 void refill(String quotaGroup, QuotaRequestContext ctx, long numTokens) {
2869 if (!quotaGroup.startsWith("/restapi/")) {
2870 return;
2871 }
2872 buckets.add(ctx.user(), numTokens);
2873 }
2874}
2875----
2876
Edwin Kempinf3e6cc772019-05-27 17:33:16 +02002877[[performance-logger]]
2878== Performance Logger
2879
2880`com.google.gerrit.server.logging.PerformanceLogger` is an extension point that
2881is invoked for all operations for which the execution time is measured. The
2882invocation of the extension point does not happen immediately, but only at the
2883end of a request (REST call, SSH call, git push). Implementors can write the
2884execution times into a performance log for further analysis.
2885
Edwin Kempin4a3ac162019-07-04 08:35:58 +02002886[[request-listener]]
2887== Request Listener
2888
2889`com.google.gerrit.server.RequestListener` is an extension point that is
2890invoked each time the server executes a request from a user.
2891
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002892== SEE ALSO
David Ostrovskyf86bae52013-09-01 09:10:39 +02002893
Ben Rohlfsda0a62b2021-04-26 17:02:19 +02002894* link:pg-plugin-dev.html[JavaScript Plugin API]
David Ostrovskyf86bae52013-09-01 09:10:39 +02002895* link:dev-rest-api.html[REST API Developers' Notes]
2896
Deniz Türkoglueb78b602012-05-07 14:02:36 -07002897GERRIT
2898------
2899Part of link:index.html[Gerrit Code Review]
Yuxuan 'fishy' Wang99cb68d2013-10-31 17:26:00 -07002900
2901SEARCHBOX
2902---------