blob: c77073346a4ca2375efbd2892afacc7d5cbc9826 [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
Viktar Donich5055e8d2017-11-09 13:02:42 -08008For PolyGerrit-specific plugin development, consult with
9link:pg-plugin-dev.html[PolyGerrit Plugin Development] guide.
10
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----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070077 Implementation-Title: Example plugin showing examples
78 Implementation-Version: 1.0
79 Implementation-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----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070091 Gerrit-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----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700106 Gerrit-Module: tld.example.project.CoreModuleClassName
107 Gerrit-SshModule: tld.example.project.SshModuleClassName
108 Gerrit-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----
123 Gerrit-BatchModule: tld.example.project.CoreModuleClassName
124----
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----
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200135 Gerrit-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----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700221 Gerrit-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----
Luca Milanesio737285d2012-09-25 14:26:43 +0100264 Gerrit-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----
281 public class MyInitStep implements InitStep {
282 private final String pluginName;
283 private final ConsoleUI ui;
284 private final AllProjectsConfig allProjectsConfig;
285
Doug Kelly732ad202015-11-13 13:11:32 -0800286 @Inject
Edwin Kempind4cfac12013-11-27 11:22:34 +0100287 public MyInitStep(@PluginName String pluginName, ConsoleUI ui,
288 AllProjectsConfig allProjectsConfig) {
289 this.pluginName = pluginName;
290 this.ui = ui;
291 this.allProjectsConfig = allProjectsConfig;
292 }
293
294 @Override
295 public void run() throws Exception {
Edwin Kempin93e7d5d2014-01-03 09:53:20 +0100296 }
297
298 @Override
299 public void postRun() throws Exception {
Edwin Kempind4cfac12013-11-27 11:22:34 +0100300 ui.message("\n");
301 ui.header(pluginName + " Integration");
302 boolean enabled = ui.yesno(true, "By default enabled for all projects");
Adrian Görlerd1612972014-10-20 17:06:07 +0200303 Config cfg = allProjectsConfig.load().getConfig();
Edwin Kempind4cfac12013-11-27 11:22:34 +0100304 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 }
312----
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
709 bind(ChangeOperatorFactory.class)
710 .annotatedWith(Exports.named("sample"))
711 .to(SampleOperator.class);
712
713If this is registered in the `myplugin` plugin, then the resulting
714operator will be named `sample_myplugin`.
715
716The search operator itself is implemented by ensuring that the
717`create()` method of the class implementing the
718`ChangeQueryBuilder.ChangeOperatorFactory` interface returns a
719`Predicate<ChangeData>`. Here is a sample operator factory
David Pursehousea61ee502016-09-06 16:27:09 +0900720definition which creates a `MyPredicate`:
Martin Fick5f6222912015-11-12 14:52:50 -0700721
722[source,java]
723----
Martin Fick5f6222912015-11-12 14:52:50 -0700724public class SampleOperator
725 implements ChangeQueryBuilder.ChangeOperatorFactory {
Dave Borowitzb53581b2019-04-08 08:25:32 -0700726 public static class MyPredicate extends PostFilterPredicate<ChangeData> {
Martin Fick5f6222912015-11-12 14:52:50 -0700727 ...
728 }
729
730 @Override
731 public Predicate<ChangeData> create(ChangeQueryBuilder builder, String value)
732 throws QueryParseException {
733 return new MyPredicate(value);
734 }
735}
736----
737
Craig Chapeldba4e892016-11-14 09:25:17 -0700738[[search_operands]]
739=== Search Operands ===
740
741Plugins can define new search operands to extend change searching.
742Plugin methods implementing search operands (returning a
743`Predicate<ChangeData>`), must be defined on a class implementing
744one of the `ChangeQueryBuilder.ChangeOperandsFactory` interfaces
Martin Fick08534c92020-08-05 18:09:41 -0500745(.e.g., ChangeQueryBuilder.ChangeHasOperandFactory or
746ChangeQueryBuilder.ChangeIsOperandFactory). The specific
Craig Chapeldba4e892016-11-14 09:25:17 -0700747`ChangeOperandFactory` class must also be bound to the `DynamicSet` from
748a module's `configure()` method in the plugin.
749
750The new operand, when used in a search would appear as:
751 operatorName:operandName_pluginName
752
753A sample `ChangeHasOperandFactory` class implementing, and registering, a
754new `has:sample_pluginName` operand is shown below:
755
756====
Craig Chapeldba4e892016-11-14 09:25:17 -0700757 public class SampleHasOperand implements ChangeHasOperandFactory {
758 public static class Module extends AbstractModule {
759 @Override
760 protected void configure() {
761 bind(ChangeHasOperandFactory.class)
762 .annotatedWith(Exports.named("sample")
763 .to(SampleHasOperand.class);
764 }
765 }
766
767 @Override
768 public Predicate<ChangeData> create(ChangeQueryBuilder builder)
769 throws QueryParseException {
770 return new HasSamplePredicate();
771 }
772====
773
Zac Livingston4f083a82016-05-20 12:38:43 -0600774[[command_options]]
775=== Command Options ===
776
777Plugins can provide additional options for each of the gerrit ssh and the
778REST API commands by implementing the DynamicBean interface and registering
779it to a command class name in the plugin module's `configure()` method. The
780plugin's name will be prepended to the name of each @Option annotation found
781on the DynamicBean object provided by the plugin. The example below shows a
782plugin that adds an option to log a value from the gerrit 'ban-commits'
783ssh command.
784
785[source, java]
786----
787public class SshModule extends AbstractModule {
Edwin Kempin8860e8e2018-05-08 11:19:37 +0200788 private static final FluentLogger logger = FluentLogger.forEnclosingClass();
Zac Livingston4f083a82016-05-20 12:38:43 -0600789
790 @Override
791 protected void configure() {
792 bind(DynamicOptions.DynamicBean.class)
793 .annotatedWith(Exports.named(
794 com.google.gerrit.sshd.commands.BanCommitCommand.class))
795 .to(BanOptions.class);
796 }
797
798 public static class BanOptions implements DynamicOptions.DynamicBean {
799 @Option(name = "--log", aliases = { "-l" }, usage = "Say Hello in the Log")
800 private void parse(String arg) {
Edwin Kempin8860e8e2018-05-08 11:19:37 +0200801 logger.atSevere().log("Say Hello in the Log %s", arg);
Zac Livingston4f083a82016-05-20 12:38:43 -0600802 }
803 }
804----
Craig Chapeldba4e892016-11-14 09:25:17 -0700805
Prudhvi Akhil Alahari86f02232020-10-30 23:51:33 +0530806To provide additional Guice bindings for options to a command in another classloader, bind a
807ModulesClassNamesProvider which provides the name of your Modules needed for your DynamicBean
808in the other classLoader.
809
810Do this by binding to the name of the command you are going to bind to and providing an
811Iterable of Module names to instantiate and add to the Injector used to instantiate the
812DynamicBean in the other classLoader. This interface supports running LifecycleListeners
813which are defined by the Modules being provided. The duration of the lifecycle starts when
814a ssh or http request starts and ends when the request completes.
815
816[source, java]
817----
818 bind(DynamicOptions.DynamicBean.class)
819 .annotatedWith(Exports.named(
820 "com.google.gerrit.plugins.otherplugin.command"))
821 .to(MyOptionsModulesClassNamesProvider.class);
822
823 static class MyOptionsModulesClassNamesProvider implements DynamicOptions.ModulesClassNamesProvider {
824 {@literal @}Override
825 public String getClassName() {
826 return "com.googlesource.gerrit.plugins.myplugin.CommandOptions";
827 }
828 {@literal @}Override
829 public Iterable<String> getModulesClassNames()() {
830 return "com.googlesource.gerrit.plugins.myplugin.MyOptionsModule";
831 }
832 }
833----
834
Martin Fick57b553a2017-11-02 15:31:28 -0600835=== Calling Command Options ===
836
837Within an OptionHandler, during the processing of an option, plugins can
838provide and call extra parameters on the current command during parsing
839simulating as if they had been passed from the command line originally.
840
841To call additional parameters from within an option handler, instantiate
842the com.google.gerrit.util.cli.CmdLineParser.Parameters class with the
843existing parameters, and then call callParameters() with the additional
844parameters to be parsed. OptionHandlers may optionally pass this class to
845other methods which may then both parse/consume more parameters and call
846additional parameters.
847
Zac Livingston99a1ad12017-11-21 12:13:29 -0700848When calling command options not provided by your plugin, there is always
849a risk that the options may not exist, perhaps because the options being
850called are to be provided by another plugin, and said plugin is not
851currently installed. To protect againt this situation, it is possible to
852define an option as being dependent on other options using the
853@RequiresOptions() annotation. If the required options are not all not
854currently present, then the dependent option will not be available or
855visible in the help.
856
Martin Fick57b553a2017-11-02 15:31:28 -0600857The example below shows a plugin that adds a "--special" option (perhaps
Zac Livingston99a1ad12017-11-21 12:13:29 -0700858for use with the Query command) that calls (and requires) the
859"--format json" option.
Martin Fick57b553a2017-11-02 15:31:28 -0600860
861[source, java]
862----
863public class JsonOutputOptionHandler<T> extends OptionHandler<T> {
864 protected com.google.gerrit.util.cli.CmdLineParser.MyParser myParser;
865
866 public JsonOutputOptionHandler(CmdLineParser parser, OptionDef option, Setter<? super T> setter) {
867 super(parser, option, setter);
868 myParser = (com.google.gerrit.util.cli.CmdLineParser.MyParser) owner;
869 }
870
871 @Override
872 public int parseArguments(org.kohsuke.args4j.spi.Parameters params) throws CmdLineException {
873 new Parameters(params, myParser).callParameters("--format", "json");
874 setter.addValue(true);
875 return 0; // we didn't consume any additional args
876 }
877
878 @Override
879 public String getDefaultMetaVariable() {
880 ...
881 }
882}
883
Zac Livingston99a1ad12017-11-21 12:13:29 -0700884@RequiresOptions("--format")
Martin Fick57b553a2017-11-02 15:31:28 -0600885@Option(
886 name = "--special",
887 usage = "ouptut results using json",
888 handler = JsonOutputOptionHandler.class
889)
890boolean json;
891----
892
Zac Livingstoncffb24592016-11-13 09:08:08 -0700893[[query_attributes]]
Dave Borowitz9c6ffc12019-03-14 08:09:48 -0700894=== Change Attributes ===
Zac Livingstoncffb24592016-11-13 09:08:08 -0700895
Adithya Chakilam0ddd3d22020-09-03 14:46:17 -0500896==== ChangePluginDefinedInfoFactory
897
Dave Borowitz9c6ffc12019-03-14 08:09:48 -0700898Plugins can provide additional attributes to be returned from the Get Change and
Adithya Chakilam0ddd3d22020-09-03 14:46:17 -0500899Query Change APIs by implementing the `ChangePluginDefinedInfoFactory` interface
900and adding it to the `DynamicSet` in the plugin module's `configure()` method.
901The new attribute(s) will be output under a `plugin` attribute in the change
902output. This can be further controlled by registering a class containing @Option
903declarations as a `DynamicBean`, annotated with the HTTP/SSH commands on
904which the options should be available.
Zac Livingstoncffb24592016-11-13 09:08:08 -0700905
Dave Borowitz4cf58282019-03-14 08:00:57 -0700906The example below shows a plugin that adds two attributes (`exampleName` and
907`changeValue`), to the change query output, when the query command is provided
908the `--myplugin-name--all` option.
Zac Livingstoncffb24592016-11-13 09:08:08 -0700909
910[source, java]
911----
912public class Module extends AbstractModule {
913 @Override
914 protected void configure() {
Dave Borowitz10745e12019-03-13 14:24:00 -0700915 // Register attribute factory.
Adithya Chakilam0ddd3d22020-09-03 14:46:17 -0500916 DynamicSet.bind(binder(), ChangePluginDefinedInfoFactory.class)
Zac Livingstoncffb24592016-11-13 09:08:08 -0700917 .to(AttributeFactory.class);
Dave Borowitz10745e12019-03-13 14:24:00 -0700918
919 // Register options for GET /changes/X/change and /changes/X/detail.
920 bind(DynamicBean.class)
921 .annotatedWith(Exports.named(GetChange.class))
922 .to(MyChangeOptions.class);
923
924 // Register options for GET /changes/?q=...
925 bind(DynamicBean.class)
926 .annotatedWith(Exports.named(QueryChanges.class))
927 .to(MyChangeOptions.class);
928
929 // Register options for ssh gerrit query.
930 bind(DynamicBean.class)
931 .annotatedWith(Exports.named(Query.class))
932 .to(MyChangeOptions.class);
Zac Livingstoncffb24592016-11-13 09:08:08 -0700933 }
934}
935
Dave Borowitz9c6ffc12019-03-14 08:09:48 -0700936public class MyChangeOptions implements DynamicBean {
Nasser Grainawiba5c7402018-10-05 10:35:18 -0600937 @Option(name = "--all", usage = "Include plugin output")
938 public boolean all = false;
939}
940
Adithya Chakilam0ddd3d22020-09-03 14:46:17 -0500941public class AttributeFactory implements ChangePluginDefinedInfoFactory {
Dave Borowitz9c6ffc12019-03-14 08:09:48 -0700942 protected MyChangeOptions options;
Zac Livingstoncffb24592016-11-13 09:08:08 -0700943
944 public class PluginAttribute extends PluginDefinedInfo {
945 public String exampleName;
946 public String changeValue;
947
948 public PluginAttribute(ChangeData c) {
949 this.exampleName = "Attribute Example";
950 this.changeValue = Integer.toString(c.getId().get());
951 }
952 }
953
954 @Override
Adithya Chakilam0ddd3d22020-09-03 14:46:17 -0500955 public Map<Change.Id, PluginDefinedInfo> createPluginDefinedInfos(
956 Collection<ChangeData> cds, BeanProvider bp, String plugin) {
Nasser Grainawiba5c7402018-10-05 10:35:18 -0600957 if (options == null) {
Dave Borowitz9c6ffc12019-03-14 08:09:48 -0700958 options = (MyChangeOptions) bp.getDynamicBean(plugin);
Nasser Grainawiba5c7402018-10-05 10:35:18 -0600959 }
Adithya Chakilam0ddd3d22020-09-03 14:46:17 -0500960 Map<Change.Id, PluginDefinedInfo> out = new HashMap<>();
Nasser Grainawiba5c7402018-10-05 10:35:18 -0600961 if (options.all) {
Adithya Chakilam0ddd3d22020-09-03 14:46:17 -0500962 cds.forEach(cd -> out.put(cd.getId(), new PluginAttribute(cd)));
963 return out;
Nasser Grainawiba5c7402018-10-05 10:35:18 -0600964 }
Adithya Chakilam0ddd3d22020-09-03 14:46:17 -0500965 return ImmutableMap.of();
Zac Livingstoncffb24592016-11-13 09:08:08 -0700966 }
967}
968----
969
970Example
971----
972
Nasser Grainawiba5c7402018-10-05 10:35:18 -0600973ssh -p 29418 localhost gerrit query --myplugin-name--all "change:1" --format json
Zac Livingstoncffb24592016-11-13 09:08:08 -0700974
975Output:
976
977{
978 "url" : "http://localhost:8080/1",
979 "plugins" : [
980 {
981 "name" : "myplugin-name",
982 "exampleName" : "Attribute Example",
983 "changeValue" : "1"
984 }
985 ],
986 ...
987}
Dave Borowitz9c6ffc12019-03-14 08:09:48 -0700988
989curl http://localhost:8080/changes/1?myplugin-name--all
990
991Output:
992
993{
994 "_number": 1,
995 ...
996 "plugins": [
997 {
998 "name": "myplugin-name",
999 "example_name": "Attribute Example",
1000 "change_value": "1"
1001 }
1002 ],
1003 ...
1004}
Zac Livingstoncffb24592016-11-13 09:08:08 -07001005----
1006
Adithya Chakilam0ddd3d22020-09-03 14:46:17 -05001007Runtime exceptions generated by the implementors of ChangePluginDefinedInfoFactory
1008are encapsulated in PluginDefinedInfo objects which are part of SSH/REST query output.
1009
1010==== ChangeAttributeFactory
1011
1012Alternatively, there is also `ChangeAttributeFactory` which takes in one single
1013`ChangeData` at a time. `ChangePluginDefinedInfoFactory` should be preferred
1014over this as it handles many changes at once which also decreases the round-trip
1015time for queries resulting in performance increase for bulk queries.
1016
1017Implementors of the `ChangePluginDefinedInfoFactory` and `ChangeAttributeFactory`
1018interfaces should check whether they need to contribute to the
1019link:#change-etag-computation[change ETag computation] to prevent callers using
1020ETags from potentially seeing outdated plugin attributes.
Edwin Kempin96915b72019-07-22 14:57:40 +02001021
Edwin Kempin78ca0942013-10-30 11:24:06 +01001022[[simple-configuration]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001023== Simple Configuration in `gerrit.config`
Edwin Kempinf7bfff82013-09-17 13:34:20 +02001024
1025In Gerrit, global configuration is stored in the `gerrit.config` file.
1026If a plugin needs global configuration, this configuration should be
1027stored in a `plugin` subsection in the `gerrit.config` file.
1028
Edwin Kempinc9b68602013-10-30 09:32:43 +01001029This approach of storing the plugin configuration is only suitable for
1030plugins that have a simple configuration that only consists of
1031key-value pairs. With this approach it is not possible to have
1032subsections in the plugin configuration. Plugins that require a complex
Edwin Kempin78ca0942013-10-30 11:24:06 +01001033configuration need to store their configuration in their
1034link:#configuration[own configuration file] where they can make use of
1035subsections. On the other hand storing the plugin configuration in a
1036'plugin' subsection in the `gerrit.config` file has the advantage that
1037administrators have all configuration parameters in one file, instead
1038of having one configuration file per plugin.
Edwin Kempinc9b68602013-10-30 09:32:43 +01001039
Edwin Kempinf7bfff82013-09-17 13:34:20 +02001040To avoid conflicts with other plugins, it is recommended that plugins
1041only use the `plugin` subsection with their own name. For example the
1042`helloworld` plugin should store its configuration in the
1043`plugin.helloworld` subsection:
1044
1045----
1046[plugin "helloworld"]
1047 language = Latin
1048----
1049
Sasa Zivkovacdf5332013-09-20 14:05:15 +02001050Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
Edwin Kempinf7bfff82013-09-17 13:34:20 +02001051plugin can easily access its configuration and there is no need for a
1052plugin to parse the `gerrit.config` file on its own:
1053
1054[source,java]
1055----
David Pursehouse529ec252013-09-27 13:45:14 +09001056@Inject
1057private com.google.gerrit.server.config.PluginConfigFactory cfg;
Edwin Kempinf7bfff82013-09-17 13:34:20 +02001058
David Pursehoused128c892013-10-22 21:52:21 +09001059[...]
Edwin Kempinf7bfff82013-09-17 13:34:20 +02001060
Edwin Kempin122622d2013-10-29 16:45:44 +01001061String language = cfg.getFromGerritConfig("helloworld")
David Pursehouse529ec252013-09-27 13:45:14 +09001062 .getString("language", "English");
Edwin Kempinf7bfff82013-09-17 13:34:20 +02001063----
1064
Edwin Kempin78ca0942013-10-30 11:24:06 +01001065[[configuration]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001066== Configuration in own config file
Edwin Kempin78ca0942013-10-30 11:24:06 +01001067
1068Plugins can store their configuration in an own configuration file.
1069This makes sense if the plugin configuration is rather complex and
1070requires the usage of subsections. Plugins that have a simple
1071key-value pair configuration can store their configuration in a
1072link:#simple-configuration[`plugin` subsection of the `gerrit.config`
1073file].
1074
1075The plugin configuration file must be named after the plugin and must
1076be located in the `etc` folder of the review site. For example a
1077configuration file for a `default-reviewer` plugin could look like
1078this:
1079
1080.$site_path/etc/default-reviewer.config
1081----
1082[branch "refs/heads/master"]
1083 reviewer = Project Owners
1084 reviewer = john.doe@example.com
1085[match "file:^.*\.txt"]
1086 reviewer = My Info Developers
1087----
1088
David Pursehouse5b47bc42016-07-22 11:00:25 +09001089Plugins that have sensitive configuration settings can store those settings in
1090an own secure configuration file. The plugin's secure configuration file must be
1091named after the plugin and must be located in the `etc` folder of the review
1092site. For example a secure configuration file for a `default-reviewer` plugin
1093could look like this:
1094
1095.$site_path/etc/default-reviewer.secure.config
1096----
1097[auth]
1098 password = secret
1099----
1100
Edwin Kempin78ca0942013-10-30 11:24:06 +01001101Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
1102plugin can easily access its configuration:
1103
1104[source,java]
1105----
1106@Inject
1107private com.google.gerrit.server.config.PluginConfigFactory cfg;
1108
1109[...]
1110
1111String[] reviewers = cfg.getGlobalPluginConfig("default-reviewer")
1112 .getStringList("branch", "refs/heads/master", "reviewer");
David Pursehouse5b47bc42016-07-22 11:00:25 +09001113String password = cfg.getGlobalPluginConfig("default-reviewer")
1114 .getString("auth", null, "password");
Edwin Kempin78ca0942013-10-30 11:24:06 +01001115----
1116
Edwin Kempin78ca0942013-10-30 11:24:06 +01001117
Edwin Kempin705f2842013-10-30 14:25:31 +01001118[[simple-project-specific-configuration]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001119== Simple Project Specific Configuration in `project.config`
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +02001120
1121In Gerrit, project specific configuration is stored in the project's
1122`project.config` file on the `refs/meta/config` branch. If a plugin
1123needs configuration on project level (e.g. to enable its functionality
1124only for certain projects), this configuration should be stored in a
1125`plugin` subsection in the project's `project.config` file.
1126
Edwin Kempinc9b68602013-10-30 09:32:43 +01001127This approach of storing the plugin configuration is only suitable for
1128plugins that have a simple configuration that only consists of
1129key-value pairs. With this approach it is not possible to have
1130subsections in the plugin configuration. Plugins that require a complex
Edwin Kempin705f2842013-10-30 14:25:31 +01001131configuration need to store their configuration in their
1132link:#project-specific-configuration[own configuration file] where they
1133can make use of subsections. On the other hand storing the plugin
1134configuration in a 'plugin' subsection in the `project.config` file has
1135the advantage that project owners have all configuration parameters in
1136one file, instead of having one configuration file per plugin.
Edwin Kempinc9b68602013-10-30 09:32:43 +01001137
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +02001138To avoid conflicts with other plugins, it is recommended that plugins
1139only use the `plugin` subsection with their own name. For example the
1140`helloworld` plugin should store its configuration in the
1141`plugin.helloworld` subsection:
1142
1143----
1144 [plugin "helloworld"]
1145 enabled = true
1146----
1147
1148Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
1149plugin can easily access its project specific configuration and there
1150is no need for a plugin to parse the `project.config` file on its own:
1151
1152[source,java]
1153----
David Pursehouse529ec252013-09-27 13:45:14 +09001154@Inject
1155private com.google.gerrit.server.config.PluginConfigFactory cfg;
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +02001156
David Pursehoused128c892013-10-22 21:52:21 +09001157[...]
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +02001158
Edwin Kempin122622d2013-10-29 16:45:44 +01001159boolean enabled = cfg.getFromProjectConfig(project, "helloworld")
David Pursehouse529ec252013-09-27 13:45:14 +09001160 .getBoolean("enabled", false);
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +02001161----
1162
Edwin Kempinca7ad8e2013-09-16 16:43:05 +02001163It is also possible to get missing configuration parameters inherited
1164from the parent projects:
1165
1166[source,java]
1167----
David Pursehouse529ec252013-09-27 13:45:14 +09001168@Inject
1169private com.google.gerrit.server.config.PluginConfigFactory cfg;
Edwin Kempinca7ad8e2013-09-16 16:43:05 +02001170
David Pursehoused128c892013-10-22 21:52:21 +09001171[...]
Edwin Kempinca7ad8e2013-09-16 16:43:05 +02001172
Edwin Kempin122622d2013-10-29 16:45:44 +01001173boolean enabled = cfg.getFromProjectConfigWithInheritance(project, "helloworld")
David Pursehouse529ec252013-09-27 13:45:14 +09001174 .getBoolean("enabled", false);
Edwin Kempinca7ad8e2013-09-16 16:43:05 +02001175----
1176
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +02001177Project owners can edit the project configuration by fetching the
1178`refs/meta/config` branch, editing the `project.config` file and
1179pushing the commit back.
1180
Edwin Kempin9ce4f552013-11-15 16:00:00 +01001181Plugin configuration values that are stored in the `project.config`
1182file can be exposed in the ProjectInfoScreen to allow project owners
1183to see and edit them from the UI.
1184
1185For this an instance of `ProjectConfigEntry` needs to be bound for each
1186parameter. The export name must be a valid Git variable name. The
1187variable name is case-insensitive, allows only alphanumeric characters
1188and '-', and must start with an alphabetic character.
1189
Edwin Kempina6c1c452013-11-28 16:55:22 +01001190The example below shows how the parameters `plugin.helloworld.enabled`
1191and `plugin.helloworld.language` are bound to be editable from the
David Pursehousea1d633b2014-05-02 17:21:02 +09001192Web UI. For the parameter `plugin.helloworld.enabled` "Enable Greeting"
Edwin Kempina6c1c452013-11-28 16:55:22 +01001193is provided as display name and the default value is set to `true`.
1194For the parameter `plugin.helloworld.language` "Preferred Language"
1195is provided as display name and "en" is set as default value.
Edwin Kempin9ce4f552013-11-15 16:00:00 +01001196
1197[source,java]
1198----
1199class Module extends AbstractModule {
1200 @Override
1201 protected void configure() {
1202 bind(ProjectConfigEntry.class)
Edwin Kempina6c1c452013-11-28 16:55:22 +01001203 .annotatedWith(Exports.named("enabled"))
1204 .toInstance(new ProjectConfigEntry("Enable Greeting", true));
1205 bind(ProjectConfigEntry.class)
Edwin Kempin9ce4f552013-11-15 16:00:00 +01001206 .annotatedWith(Exports.named("language"))
1207 .toInstance(new ProjectConfigEntry("Preferred Language", "en"));
1208 }
1209}
1210----
1211
Edwin Kempinb64d3972013-11-17 18:55:48 +01001212By overwriting the `onUpdate` method of `ProjectConfigEntry` plugins
1213can be notified when this configuration parameter is updated on a
1214project.
1215
Janice Agustine5a9d012015-08-24 09:05:56 -04001216[[configuring-groups]]
1217=== Referencing groups in `project.config`
1218
1219Plugins can refer to groups so that when they are renamed, the project
1220config will also be updated in this section. The proper format to use is
Hugo Arès532e0a32017-06-16 09:31:08 -04001221the same as for any other group reference in the `project.config`, as shown below.
Janice Agustine5a9d012015-08-24 09:05:56 -04001222
1223----
Hugo Arès532e0a32017-06-16 09:31:08 -04001224group group_name
Janice Agustine5a9d012015-08-24 09:05:56 -04001225----
1226
Hugo Arès532e0a32017-06-16 09:31:08 -04001227The file `groups` must also contains the mapping of the group name and its UUID,
1228refer to link:config-project-config.html#file-groups[file groups]
1229
Edwin Kempin705f2842013-10-30 14:25:31 +01001230[[project-specific-configuration]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001231== Project Specific Configuration in own config file
Edwin Kempin705f2842013-10-30 14:25:31 +01001232
1233Plugins can store their project specific configuration in an own
1234configuration file in the projects `refs/meta/config` branch.
1235This makes sense if the plugins project specific configuration is
1236rather complex and requires the usage of subsections. Plugins that
1237have a simple key-value pair configuration can store their project
1238specific configuration in a link:#simple-project-specific-configuration[
1239`plugin` subsection of the `project.config` file].
1240
1241The plugin configuration file in the `refs/meta/config` branch must be
1242named after the plugin. For example a configuration file for a
1243`default-reviewer` plugin could look like this:
1244
1245.default-reviewer.config
1246----
1247[branch "refs/heads/master"]
1248 reviewer = Project Owners
1249 reviewer = john.doe@example.com
1250[match "file:^.*\.txt"]
1251 reviewer = My Info Developers
1252----
1253
1254Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
1255plugin can easily access its project specific configuration:
1256
1257[source,java]
1258----
1259@Inject
1260private com.google.gerrit.server.config.PluginConfigFactory cfg;
1261
1262[...]
1263
1264String[] reviewers = cfg.getProjectPluginConfig(project, "default-reviewer")
1265 .getStringList("branch", "refs/heads/master", "reviewer");
1266----
1267
Edwin Kempin762da382013-10-30 14:50:01 +01001268It is also possible to get missing configuration parameters inherited
1269from the parent projects:
1270
1271[source,java]
1272----
1273@Inject
1274private com.google.gerrit.server.config.PluginConfigFactory cfg;
1275
1276[...]
1277
David Ostrovsky468e4c32014-03-22 06:05:35 -07001278String[] reviewers = cfg.getProjectPluginConfigWithInheritance(project, "default-reviewer")
Edwin Kempin762da382013-10-30 14:50:01 +01001279 .getStringList("branch", "refs/heads/master", "reviewer");
1280----
1281
Edwin Kempin705f2842013-10-30 14:25:31 +01001282Project owners can edit the project configuration by fetching the
1283`refs/meta/config` branch, editing the `<plugin-name>.config` file and
1284pushing the commit back.
1285
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001286== React on changes in project configuration
Edwin Kempina46b6c92013-12-04 21:05:24 +01001287
1288If a plugin wants to react on changes in the project configuration, it
1289can implement a `GitReferenceUpdatedListener` and filter on events for
1290the `refs/meta/config` branch:
1291
1292[source,java]
1293----
1294public class MyListener implements GitReferenceUpdatedListener {
1295
1296 private final MetaDataUpdate.Server metaDataUpdateFactory;
1297
1298 @Inject
1299 MyListener(MetaDataUpdate.Server metaDataUpdateFactory) {
1300 this.metaDataUpdateFactory = metaDataUpdateFactory;
1301 }
1302
1303 @Override
1304 public void onGitReferenceUpdated(Event event) {
Edwin Kempina951ba52014-01-03 14:07:28 +01001305 if (event.getRefName().equals(RefNames.REFS_CONFIG)) {
Edwin Kempina46b6c92013-12-04 21:05:24 +01001306 Project.NameKey p = new Project.NameKey(event.getProjectName());
1307 try {
Edwin Kempina951ba52014-01-03 14:07:28 +01001308 ProjectConfig oldCfg = parseConfig(p, event.getOldObjectId());
1309 ProjectConfig newCfg = parseConfig(p, event.getNewObjectId());
Edwin Kempina46b6c92013-12-04 21:05:24 +01001310
Edwin Kempina951ba52014-01-03 14:07:28 +01001311 if (oldCfg != null && newCfg != null
1312 && !oldCfg.getProject().getSubmitType().equals(newCfg.getProject().getSubmitType())) {
Edwin Kempina46b6c92013-12-04 21:05:24 +01001313 // submit type has changed
1314 ...
1315 }
1316 } catch (IOException | ConfigInvalidException e) {
1317 ...
1318 }
1319 }
1320 }
Edwin Kempina951ba52014-01-03 14:07:28 +01001321
1322 private ProjectConfig parseConfig(Project.NameKey p, String idStr)
1323 throws IOException, ConfigInvalidException, RepositoryNotFoundException {
1324 ObjectId id = ObjectId.fromString(idStr);
1325 if (ObjectId.zeroId().equals(id)) {
1326 return null;
1327 }
1328 return ProjectConfig.read(metaDataUpdateFactory.create(p), id);
1329 }
Edwin Kempina46b6c92013-12-04 21:05:24 +01001330}
1331----
1332
Fabio Ponciroli214b8962020-05-07 13:22:00 +02001333== Trace Event origin
1334
1335When plugins are installed in a multi-master setups it can be useful to know
1336the Gerrit `instanceId` of the server that has generated an Event.
1337
1338E.g. A plugin that sends an instance message for every comment on a change may
1339want to react only if the event is generated on the local Gerrit master, for
1340avoiding duplicating the notifications.
1341
1342If link:config-gerrit.html[instanceId] is set, each Event will contain its
1343origin in the `instanceId` field.
1344
1345Here and example of ref-updated JSON event payload with `instanceId`:
1346
1347[source,json]
1348---
1349{
1350 "submitter": {
1351 "name": "Administrator",
1352 "email": "admin@example.com",
1353 "username": "admin"
1354 },
1355 "refUpdate": {
1356 "oldRev": "a69fc95c7aad5ad41c618d31548b8af835d2959a",
1357 "newRev": "31da6556d638a74e5370b62f83e8007f94abb7c6",
1358 "refName": "refs/changes/01/1/meta",
1359 "project": "test"
1360 },
1361 "type": "ref-updated",
1362 "eventCreatedOn": 1588849085,
1363 "instanceId": "instance1"
1364}
1365---
Edwin Kempina46b6c92013-12-04 21:05:24 +01001366
David Ostrovsky7066cc02013-06-15 14:46:23 +02001367[[capabilities]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001368== Plugin Owned Capabilities
David Ostrovsky7066cc02013-06-15 14:46:23 +02001369
1370Plugins may provide their own capabilities and restrict usage of SSH
Dariusz Luksza112d93a2014-06-01 16:52:23 +02001371commands or `UiAction` to the users who are granted those capabilities.
David Ostrovsky7066cc02013-06-15 14:46:23 +02001372
1373Plugins define the capabilities by overriding the `CapabilityDefinition`
1374abstract class:
1375
David Pursehouse68153d72013-09-04 10:09:17 +09001376[source,java]
1377----
1378public class PrintHelloCapability extends CapabilityDefinition {
1379 @Override
1380 public String getDescription() {
1381 return "Print Hello";
David Ostrovsky7066cc02013-06-15 14:46:23 +02001382 }
David Pursehouse68153d72013-09-04 10:09:17 +09001383}
1384----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001385
Dariusz Luksza112d93a2014-06-01 16:52:23 +02001386If no Guice modules are declared in the manifest, capability may
David Ostrovsky7066cc02013-06-15 14:46:23 +02001387use auto-registration by providing an `@Export` annotation:
1388
David Pursehouse68153d72013-09-04 10:09:17 +09001389[source,java]
1390----
1391@Export("printHello")
1392public class PrintHelloCapability extends CapabilityDefinition {
David Pursehoused128c892013-10-22 21:52:21 +09001393 [...]
David Pursehouse68153d72013-09-04 10:09:17 +09001394}
1395----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001396
1397Otherwise the capability must be bound in a plugin module:
1398
David Pursehouse68153d72013-09-04 10:09:17 +09001399[source,java]
1400----
1401public class HelloWorldModule extends AbstractModule {
1402 @Override
1403 protected void configure() {
1404 bind(CapabilityDefinition.class)
1405 .annotatedWith(Exports.named("printHello"))
1406 .to(PrintHelloCapability.class);
David Ostrovsky7066cc02013-06-15 14:46:23 +02001407 }
David Pursehouse68153d72013-09-04 10:09:17 +09001408}
1409----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001410
1411With a plugin-owned capability defined in this way, it is possible to restrict
David Ostrovskyf86bae52013-09-01 09:10:39 +02001412usage of an SSH command or `UiAction` to members of the group that were granted
David Ostrovsky7066cc02013-06-15 14:46:23 +02001413this capability in the usual way, using the `RequiresCapability` annotation:
1414
David Pursehouse68153d72013-09-04 10:09:17 +09001415[source,java]
1416----
1417@RequiresCapability("printHello")
1418@CommandMetaData(name="print", description="Print greeting in different languages")
1419public final class PrintHelloWorldCommand extends SshCommand {
David Pursehoused128c892013-10-22 21:52:21 +09001420 [...]
David Pursehouse68153d72013-09-04 10:09:17 +09001421}
1422----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001423
David Ostrovskyf86bae52013-09-01 09:10:39 +02001424Or with `UiAction`:
David Ostrovsky7066cc02013-06-15 14:46:23 +02001425
David Pursehouse68153d72013-09-04 10:09:17 +09001426[source,java]
1427----
1428@RequiresCapability("printHello")
1429public class SayHelloAction extends UiAction<RevisionResource>
1430 implements RestModifyView<RevisionResource, SayHelloAction.Input> {
David Pursehoused128c892013-10-22 21:52:21 +09001431 [...]
David Pursehouse68153d72013-09-04 10:09:17 +09001432}
1433----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001434
1435Capability scope was introduced to differentiate between plugin-owned
David Pursehousebf053342013-09-05 14:55:29 +09001436capabilities and core capabilities. Per default the scope of the
1437`@RequiresCapability` annotation is `CapabilityScope.CONTEXT`, that means:
1438
David Ostrovsky7066cc02013-06-15 14:46:23 +02001439* when `@RequiresCapability` is used within a plugin the scope of the
1440capability is assumed to be that plugin.
David Pursehousebf053342013-09-05 14:55:29 +09001441
David Ostrovsky7066cc02013-06-15 14:46:23 +02001442* If `@RequiresCapability` is used within the core Gerrit Code Review server
1443(and thus is outside of a plugin) the scope is the core server and will use
1444the `GlobalCapability` known to Gerrit Code Review server.
1445
1446If a plugin needs to use a core capability name (e.g. "administrateServer")
1447this can be specified by setting `scope = CapabilityScope.CORE`:
1448
David Pursehouse68153d72013-09-04 10:09:17 +09001449[source,java]
1450----
1451@RequiresCapability(value = "administrateServer", scope =
1452 CapabilityScope.CORE)
David Pursehoused128c892013-10-22 21:52:21 +09001453 [...]
David Pursehouse68153d72013-09-04 10:09:17 +09001454----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001455
Edwin Kempin79ec4932020-12-01 15:51:29 +01001456[post_review_extensions]
1457== Post Review Extensions
1458
1459By implementing the `com.google.gerrit.server.restapi.change.OnPostReview`
1460interface plugins can extend the change message that is being posted when the
1461[post review](rest-api-changes.html#set-review) REST endpoint is invoked.
1462
1463This is useful if certain approvals have a special meaning (e.g. custom logic
1464that is implemented in Prolog submit rules, signal for triggering an action
1465like running CI etc.), as it allows the plugin to tell users about this meaning
1466in the change message. This makes the effect of a given approval more
1467transparent to the user.
1468
David Ostrovskyf86bae52013-09-01 09:10:39 +02001469[[ui_extension]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001470== UI Extension
David Ostrovskyf86bae52013-09-01 09:10:39 +02001471
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001472[[panels]]
1473=== Panels
1474
David Ostrovsky7163dac2017-07-29 06:49:38 +02001475UI plugins can contribute panels to Gerrit screens.
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001476
1477Gerrit screens define extension points where plugins can add GWT
1478panels with custom controls:
1479
1480* Change Screen:
Edwin Kempin2a8c5152015-07-08 14:28:57 +02001481** `GerritUiExtensionPoint.CHANGE_SCREEN_HEADER`:
1482+
1483Panel will be shown in the header bar to the right of the change
1484status.
1485
Edwin Kempin745021e2015-07-09 13:09:44 +02001486** `GerritUiExtensionPoint.CHANGE_SCREEN_HEADER_RIGHT_OF_BUTTONS`:
1487+
1488Panel will be shown in the header bar on the right side of the buttons.
1489
Edwin Kempincbc95252015-07-09 11:37:53 +02001490** `GerritUiExtensionPoint.CHANGE_SCREEN_HEADER_RIGHT_OF_POP_DOWNS`:
1491+
1492Panel will be shown in the header bar on the right side of the pop down
1493buttons.
1494
Khai Do675afc02016-07-28 16:30:37 -07001495** `GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_COMMIT_INFO_BLOCK`:
1496+
1497Panel will be shown below the commit info block.
1498
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001499** `GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK`:
1500+
1501Panel will be shown below the change info block.
1502
Khai Do76c830c2016-07-28 16:35:45 -07001503** `GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_RELATED_INFO_BLOCK`:
1504+
1505Panel will be shown below the related info block.
1506
Khai Do83940ba2016-09-20 15:15:45 +02001507** `GerritUiExtensionPoint.CHANGE_SCREEN_HISTORY_RIGHT_OF_BUTTONS`:
1508+
1509Panel will be shown in the history bar on the right side of the buttons.
1510
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001511** The following parameters are provided:
Edwin Kempin5d683cc2015-07-10 15:47:14 +02001512*** `GerritUiExtensionPoint.Key.CHANGE_INFO`:
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001513+
Edwin Kempin5d683cc2015-07-10 15:47:14 +02001514The link:rest-api-changes.html#change-info[ChangeInfo] entity for the
1515current change.
David Ostrovsky916ae0c2016-03-15 17:05:41 +01001516+
1517The link:rest-api-changes.html#revision-info[RevisionInfo] entity for
1518the current patch set.
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001519
Edwin Kempin88b947a2015-07-08 09:03:56 +02001520* Project Info Screen:
1521** `GerritUiExtensionPoint.PROJECT_INFO_SCREEN_TOP`:
1522+
1523Panel will be shown at the top of the screen.
1524
1525** `GerritUiExtensionPoint.PROJECT_INFO_SCREEN_BOTTOM`:
1526+
1527Panel will be shown at the bottom of the screen.
1528
1529** The following parameters are provided:
1530*** `GerritUiExtensionPoint.Key.PROJECT_NAME`:
1531+
1532The name of the project.
1533
Edwin Kempin241d9db2015-07-08 13:53:50 +02001534* User Password Screen:
1535** `GerritUiExtensionPoint.PASSWORD_SCREEN_BOTTOM`:
1536+
1537Panel will be shown at the bottom of the screen.
1538
Edwin Kempin30c6f472015-07-09 14:27:52 +02001539** The following parameters are provided:
1540*** `GerritUiExtensionPoint.Key.ACCOUNT_INFO`:
1541+
1542The link:rest-api-accounts.html#account-info[AccountInfo] entity for
1543the current user.
1544
Edwin Kempin1cd95f92015-07-14 08:27:20 +02001545* User Preferences Screen:
1546** `GerritUiExtensionPoint.PREFERENCES_SCREEN_BOTTOM`:
1547+
1548Panel will be shown at the bottom of the screen.
1549
1550** The following parameters are provided:
1551*** `GerritUiExtensionPoint.Key.ACCOUNT_INFO`:
1552+
1553The link:rest-api-accounts.html#account-info[AccountInfo] entity for
1554the current user.
1555
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001556* User Profile Screen:
1557** `GerritUiExtensionPoint.PROFILE_SCREEN_BOTTOM`:
1558+
1559Panel will be shown at the bottom of the screen below the grid with the
1560profile data.
1561
Edwin Kempin30c6f472015-07-09 14:27:52 +02001562** The following parameters are provided:
1563*** `GerritUiExtensionPoint.Key.ACCOUNT_INFO`:
1564+
1565The link:rest-api-accounts.html#account-info[AccountInfo] entity for
1566the current user.
1567
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001568Example panel:
1569[source,java]
1570----
1571public class MyPlugin extends PluginEntryPoint {
1572 @Override
1573 public void onPluginLoad() {
1574 Plugin.get().panel(GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK,
Zac Livingstone7f3d1a2017-03-01 12:47:37 -07001575 "my_panel_name",
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001576 new Panel.EntryPoint() {
1577 @Override
1578 public void onLoad(Panel panel) {
1579 panel.setWidget(new InlineLabel("My Panel for change "
1580 + panel.getInt(GerritUiExtensionPoint.Key.CHANGE_ID, -1));
1581 }
1582 });
1583 }
1584}
1585----
1586
Zac Livingstone7f3d1a2017-03-01 12:47:37 -07001587Change Screen panel ordering may be specified in the
1588project config. Values may be either "plugin name" or
1589"plugin name"."panel name".
1590Panels not specified in the config will be added
1591to the end in load order. Panels specified in the config that
1592are not found will be ignored.
1593
1594Example config:
1595----
1596[extension-panels "CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK"]
1597 panel = helloworld.change_id
1598 panel = myotherplugin
1599 panel = myplugin.my_panel_name
1600----
1601
1602
1603
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001604[[actions]]
1605=== Actions
1606
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001607Plugins can contribute UI actions on core Gerrit pages. This is useful
1608for workflow customization or exposing plugin functionality through the
1609UI in addition to SSH commands and the REST API.
David Ostrovskyf86bae52013-09-01 09:10:39 +02001610
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001611For instance a plugin to integrate Jira with Gerrit changes may
1612contribute a "File bug" button to allow filing a bug from the change
1613page or plugins to integrate continuous integration systems may
1614contribute a "Schedule" button to allow a CI build to be scheduled
1615manually from the patch set panel.
David Ostrovskyf86bae52013-09-01 09:10:39 +02001616
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001617Two different places on core Gerrit pages are supported:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001618
1619* Change screen
1620* Project info screen
1621
1622Plugins contribute UI actions by implementing the `UiAction` interface:
1623
David Pursehouse68153d72013-09-04 10:09:17 +09001624[source,java]
1625----
1626@RequiresCapability("printHello")
1627class HelloWorldAction implements UiAction<RevisionResource>,
1628 RestModifyView<RevisionResource, HelloWorldAction.Input> {
1629 static class Input {
1630 boolean french;
1631 String message;
David Ostrovskyf86bae52013-09-01 09:10:39 +02001632 }
David Pursehouse68153d72013-09-04 10:09:17 +09001633
1634 private Provider<CurrentUser> user;
1635
1636 @Inject
1637 HelloWorldAction(Provider<CurrentUser> user) {
1638 this.user = user;
1639 }
1640
1641 @Override
1642 public String apply(RevisionResource rev, Input input) {
1643 final String greeting = input.french
1644 ? "Bonjour"
1645 : "Hello";
1646 return String.format("%s %s from change %s, patch set %d!",
1647 greeting,
1648 Strings.isNullOrEmpty(input.message)
1649 ? Objects.firstNonNull(user.get().getUserName(), "world")
1650 : input.message,
1651 rev.getChange().getId().toString(),
1652 rev.getPatchSet().getPatchSetId());
1653 }
1654
1655 @Override
1656 public Description getDescription(
1657 RevisionResource resource) {
1658 return new Description()
1659 .setLabel("Say hello")
1660 .setTitle("Say hello in different languages");
1661 }
1662}
1663----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001664
David Ostrovsky450eefe2013-10-21 21:18:11 +02001665Sometimes plugins may want to be able to change the state of a patch set or
1666change in the `UiAction.apply()` method and reflect these changes on the core
1667UI. For example a buildbot plugin which exposes a 'Schedule' button on the
1668patch set panel may want to disable that button after the build was scheduled
1669and update the tooltip of that button. But because of Gerrit's caching
1670strategy the following must be taken into consideration.
1671
1672The browser is allowed to cache the `UiAction` information until something on
1673the change is modified. More accurately the change row needs to be modified in
1674the database to have a more recent `lastUpdatedOn` or a new `rowVersion`, or
1675the +refs/meta/config+ of the project or any parents needs to change to a new
1676SHA-1. The ETag SHA-1 computation code can be found in the
1677`ChangeResource.getETag()` method.
1678
David Pursehoused128c892013-10-22 21:52:21 +09001679The easiest way to accomplish this is to update `lastUpdatedOn` of the change:
David Ostrovsky450eefe2013-10-21 21:18:11 +02001680
1681[source,java]
1682----
1683@Override
1684public Object apply(RevisionResource rcrs, Input in) {
1685 // schedule a build
1686 [...]
1687 // update change
Edwin Kempine2d06b02016-02-17 18:34:17 +01001688 try (BatchUpdate bu = batchUpdateFactory.create(
Dave Borowitzada289c2018-12-18 13:24:14 -08001689 project.getNameKey(), user, TimeUtil.nowTs())) {
Edwin Kempine2d06b02016-02-17 18:34:17 +01001690 bu.addOp(change.getId(), new BatchUpdate.Op() {
1691 @Override
Dave Borowitzb91cf222017-03-10 13:11:59 -05001692 public boolean updateChange(ChangeContext ctx) {
Edwin Kempine2d06b02016-02-17 18:34:17 +01001693 return true;
1694 }
1695 });
1696 bu.execute();
David Ostrovsky450eefe2013-10-21 21:18:11 +02001697 }
David Pursehoused128c892013-10-22 21:52:21 +09001698 [...]
David Ostrovsky450eefe2013-10-21 21:18:11 +02001699}
1700----
1701
David Ostrovskyf86bae52013-09-01 09:10:39 +02001702`UiAction` must be bound in a plugin module:
1703
David Pursehouse68153d72013-09-04 10:09:17 +09001704[source,java]
1705----
1706public class Module extends AbstractModule {
1707 @Override
1708 protected void configure() {
1709 install(new RestApiModule() {
1710 @Override
1711 protected void configure() {
1712 post(REVISION_KIND, "say-hello")
1713 .to(HelloWorldAction.class);
1714 }
1715 });
David Ostrovskyf86bae52013-09-01 09:10:39 +02001716 }
David Pursehouse68153d72013-09-04 10:09:17 +09001717}
1718----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001719
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001720The module above must be declared in the `pom.xml` for Maven driven
1721plugins:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001722
David Pursehouse68153d72013-09-04 10:09:17 +09001723[source,xml]
1724----
1725<manifestEntries>
1726 <Gerrit-Module>com.googlesource.gerrit.plugins.cookbook.Module</Gerrit-Module>
1727</manifestEntries>
1728----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001729
David Ostrovskyfdbfcad2016-11-15 06:35:29 -08001730or in the `BUILD` configuration file for Bazel driven plugins:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001731
David Pursehouse68153d72013-09-04 10:09:17 +09001732[source,python]
1733----
1734manifest_entries = [
1735 'Gerrit-Module: com.googlesource.gerrit.plugins.cookbook.Module',
1736]
1737----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001738
1739In some use cases more user input must be gathered, for that `UiAction` can be
1740combined with the JavaScript API. This would display a small popup near the
1741activation button to gather additional input from the user. The JS file is
1742typically put in the `static` folder within the plugin's directory:
1743
David Pursehouse68153d72013-09-04 10:09:17 +09001744[source,javascript]
1745----
1746Gerrit.install(function(self) {
1747 function onSayHello(c) {
1748 var f = c.textfield();
1749 var t = c.checkbox();
1750 var b = c.button('Say hello', {onclick: function(){
1751 c.call(
1752 {message: f.value, french: t.checked},
1753 function(r) {
1754 c.hide();
1755 window.alert(r);
1756 c.refresh();
1757 });
1758 }});
1759 c.popup(c.div(
1760 c.prependLabel('Greeting message', f),
1761 c.br(),
1762 c.label(t, 'french'),
1763 c.br(),
1764 b));
1765 f.focus();
1766 }
1767 self.onAction('revision', 'say-hello', onSayHello);
1768});
1769----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001770
1771The JS module must be exposed as a `WebUiPlugin` and bound as
1772an HTTP Module:
1773
David Pursehouse68153d72013-09-04 10:09:17 +09001774[source,java]
1775----
Dave Borowitz20f4e8a2019-03-13 14:19:15 -07001776public class HttpModule extends ServletModule {
David Pursehouse68153d72013-09-04 10:09:17 +09001777 @Override
1778 protected void configureServlets() {
1779 DynamicSet.bind(binder(), WebUiPlugin.class)
1780 .toInstance(new JavaScriptPlugin("hello.js"));
David Ostrovskyf86bae52013-09-01 09:10:39 +02001781 }
David Pursehouse68153d72013-09-04 10:09:17 +09001782}
1783----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001784
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001785The HTTP module above must be declared in the `pom.xml` for Maven
1786driven plugins:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001787
David Pursehouse68153d72013-09-04 10:09:17 +09001788[source,xml]
1789----
1790<manifestEntries>
1791 <Gerrit-HttpModule>com.googlesource.gerrit.plugins.cookbook.HttpModule</Gerrit-HttpModule>
1792</manifestEntries>
1793----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001794
David Ostrovskyfdbfcad2016-11-15 06:35:29 -08001795or in the `BUILD` configuration file for Bazel driven plugins
David Ostrovskyf86bae52013-09-01 09:10:39 +02001796
David Pursehouse68153d72013-09-04 10:09:17 +09001797[source,python]
1798----
1799manifest_entries = [
1800 'Gerrit-HttpModule: com.googlesource.gerrit.plugins.cookbook.HttpModule',
1801]
1802----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001803
1804If `UiAction` is annotated with the `@RequiresCapability` annotation, then the
1805capability check is done during the `UiAction` gathering, so the plugin author
1806doesn't have to set `UiAction.Description.setVisible()` explicitly in this
1807case.
1808
David Pursehousea61ee502016-09-06 16:27:09 +09001809The following prerequisites must be met, to satisfy the capability check:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001810
1811* user is authenticated
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001812* user is a member of a group which has the `Administrate Server` capability, or
David Ostrovskyf86bae52013-09-01 09:10:39 +02001813* user is a member of a group which has the required capability
1814
1815The `apply` method is called when the button is clicked. If `UiAction` is
1816combined with JavaScript API (its own JavaScript function is provided),
1817then a popup dialog is normally opened to gather additional user input.
1818A new button is placed on the popup dialog to actually send the request.
1819
1820Every `UiAction` exposes a REST API endpoint. The endpoint from the example above
1821can be accessed from any REST client, i. e.:
1822
Michael Ochmannb99feab2016-07-06 14:10:22 +02001823----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001824 curl -X POST -H "Content-Type: application/json" \
1825 -d '{message: "François", french: true}' \
Han-Wen Nienhuys84d830b2017-02-15 16:36:04 +01001826 --user joe:secret \
David Ostrovskyf86bae52013-09-01 09:10:39 +02001827 http://host:port/a/changes/1/revisions/1/cookbook~say-hello
1828 "Bonjour François from change 1, patch set 1!"
Michael Ochmannb99feab2016-07-06 14:10:22 +02001829----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001830
David Pursehouse42245822013-09-24 09:48:20 +09001831A special case is to bind an endpoint without a view name. This is
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001832particularly useful for `DELETE` requests:
David Ostrovskyc6d19ed2013-09-20 21:30:18 +02001833
1834[source,java]
1835----
1836public class Module extends AbstractModule {
1837 @Override
1838 protected void configure() {
1839 install(new RestApiModule() {
1840 @Override
1841 protected void configure() {
1842 delete(PROJECT_KIND)
1843 .to(DeleteProject.class);
1844 }
1845 });
1846 }
1847}
1848----
1849
David Pursehouse42245822013-09-24 09:48:20 +09001850For a `UiAction` bound this way, a JS API function can be provided.
1851
1852Currently only one restriction exists: per plugin only one `UiAction`
David Ostrovskyc6d19ed2013-09-20 21:30:18 +02001853can be bound per resource without view name. To define a JS function
1854for the `UiAction`, "/" must be used as the name:
1855
1856[source,javascript]
1857----
1858Gerrit.install(function(self) {
1859 function onDeleteProject(c) {
1860 [...]
1861 }
1862 self.onAction('project', '/', onDeleteProject);
1863});
1864----
1865
Dave Borowitzf1790ce2016-11-14 12:26:12 -08001866
1867[[action-visitor]]
1868=== Action Visitors
1869
1870In addition to providing new actions, plugins can have fine-grained control
1871over the link:rest-api-changes.html#action-info[ActionInfo] map, modifying or
1872removing existing actions, including those contributed by core.
1873
1874Visitors are provided the link:rest-api-changes.html#action-info[ActionInfo],
1875which is mutable, along with copies of the
1876link:rest-api-changes.html#change-info[ChangeInfo] and
1877link:rest-api-changes.html#revision-info[RevisionInfo]. They can modify the
1878action, or return `false` to exclude it from the resulting map.
1879
1880These operations only affect the action buttons that are displayed in the UI;
1881the underlying REST API endpoints are not affected. Multiple plugins may
1882implement the visitor interface, but the order in which they are run is
1883undefined.
1884
1885For example, to exclude "Cherry-Pick" only from certain projects, and rename
1886"Abandon":
1887
1888[source,java]
1889----
1890public class MyActionVisitor implements ActionVisitor {
1891 @Override
1892 public boolean visit(String name, ActionInfo actionInfo,
1893 ChangeInfo changeInfo) {
1894 if (name.equals("abandon")) {
1895 actionInfo.label = "Drop";
1896 }
1897 return true;
1898 }
1899
1900 @Override
1901 public boolean visit(String name, ActionInfo actionInfo,
1902 ChangeInfo changeInfo, RevisionInfo revisionInfo) {
1903 if (project.startsWith("some-team/") && name.equals("cherrypick")) {
1904 return false;
1905 }
1906 return true;
1907 }
1908}
1909----
1910
1911
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001912[[top-menu-extensions]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001913== Top Menu Extensions
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001914
1915Plugins can contribute items to Gerrit's top menu.
1916
1917A single top menu extension can have multiple elements and will be put as
1918the last element in Gerrit's top menu.
1919
1920Plugins define the top menu entries by implementing `TopMenu` interface:
1921
1922[source,java]
1923----
1924public class MyTopMenuExtension implements TopMenu {
1925
1926 @Override
1927 public List<MenuEntry> getEntries() {
1928 return Lists.newArrayList(
1929 new MenuEntry("Top Menu Entry", Lists.newArrayList(
1930 new MenuItem("Gerrit", "http://gerrit.googlecode.com/"))));
1931 }
1932}
1933----
1934
Edwin Kempin77f23242013-09-30 14:53:20 +02001935Plugins can also add additional menu items to Gerrit's top menu entries
1936by defining a `MenuEntry` that has the same name as a Gerrit top menu
1937entry:
1938
1939[source,java]
1940----
1941public class MyTopMenuExtension implements TopMenu {
1942
1943 @Override
1944 public List<MenuEntry> getEntries() {
1945 return Lists.newArrayList(
Dariusz Luksza2d3afab2013-10-01 11:07:13 +02001946 new MenuEntry(GerritTopMenu.PROJECTS, Lists.newArrayList(
Edwin Kempin77f23242013-09-30 14:53:20 +02001947 new MenuItem("Browse Repositories", "https://gerrit.googlesource.com/"))));
1948 }
1949}
1950----
1951
Dariusz Lukszae8de74f2014-06-04 19:39:20 +02001952`MenuItems` that are bound for the `MenuEntry` with the name
1953`GerritTopMenu.PROJECTS` can contain a `${projectName}` placeholder
1954which is automatically replaced by the actual project name.
1955
1956E.g. plugins may register an link:#http[HTTP Servlet] to handle project
1957specific requests and add an menu item for this:
1958
1959[source,java]
1960---
1961 new MenuItem("My Screen", "/plugins/myplugin/project/${projectName}");
1962---
1963
1964This also enables plugins to provide menu items for project aware
1965screens:
1966
1967[source,java]
1968---
1969 new MenuItem("My Screen", "/x/my-screen/for/${projectName}");
1970---
1971
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001972If no Guice modules are declared in the manifest, the top menu extension may use
1973auto-registration by providing an `@Listen` annotation:
1974
1975[source,java]
1976----
1977@Listen
1978public class MyTopMenuExtension implements TopMenu {
David Pursehoused128c892013-10-22 21:52:21 +09001979 [...]
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001980}
1981----
1982
Luca Milanesiocb230402013-10-11 08:49:56 +01001983Otherwise the top menu extension must be bound in the plugin module used
1984for the Gerrit system injector (Gerrit-Module entry in MANIFEST.MF):
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001985
1986[source,java]
1987----
Luca Milanesiocb230402013-10-11 08:49:56 +01001988package com.googlesource.gerrit.plugins.helloworld;
1989
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001990public class HelloWorldModule extends AbstractModule {
1991 @Override
1992 protected void configure() {
1993 DynamicSet.bind(binder(), TopMenu.class).to(MyTopMenuExtension.class);
1994 }
1995}
1996----
1997
Luca Milanesiocb230402013-10-11 08:49:56 +01001998[source,manifest]
1999----
2000Gerrit-ApiType: plugin
2001Gerrit-Module: com.googlesource.gerrit.plugins.helloworld.HelloWorldModule
2002----
2003
Edwin Kempinb2e926a2013-11-11 16:38:30 +01002004It is also possible to show some menu entries only if the user has a
2005certain capability:
2006
2007[source,java]
2008----
2009public class MyTopMenuExtension implements TopMenu {
2010 private final String pluginName;
2011 private final Provider<CurrentUser> userProvider;
2012 private final List<MenuEntry> menuEntries;
2013
2014 @Inject
2015 public MyTopMenuExtension(@PluginName String pluginName,
2016 Provider<CurrentUser> userProvider) {
2017 this.pluginName = pluginName;
2018 this.userProvider = userProvider;
2019 menuEntries = new ArrayList<TopMenu.MenuEntry>();
2020
2021 // add menu entry that is only visible to users with a certain capability
2022 if (canSeeMenuEntry()) {
2023 menuEntries.add(new MenuEntry("Top Menu Entry", Collections
2024 .singletonList(new MenuItem("Gerrit", "http://gerrit.googlecode.com/"))));
2025 }
2026
2027 // add menu entry that is visible to all users (even anonymous users)
2028 menuEntries.add(new MenuEntry("Top Menu Entry", Collections
2029 .singletonList(new MenuItem("Documentation", "/plugins/myplugin/"))));
2030 }
2031
2032 private boolean canSeeMenuEntry() {
2033 if (userProvider.get().isIdentifiedUser()) {
2034 CapabilityControl ctl = userProvider.get().getCapabilities();
2035 return ctl.canPerform(pluginName + "-" + MyCapability.ID)
2036 || ctl.canAdministrateServer();
2037 } else {
2038 return false;
2039 }
2040 }
2041
2042 @Override
2043 public List<MenuEntry> getEntries() {
2044 return menuEntries;
2045 }
2046}
2047----
2048
Dave Borowitzf1790ce2016-11-14 12:26:12 -08002049
Edwin Kempin289f1a02014-02-04 16:08:25 +01002050[[settings-screen]]
2051== Plugin Settings Screen
2052
2053If a plugin implements a screen for administrating its settings that is
2054available under "#/x/<plugin-name>/settings" it is automatically linked
2055from the plugin list screen.
2056
Edwin Kempinf5a77332012-07-18 11:17:53 +02002057[[http]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002058== HTTP Servlets
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002059
2060Plugins or extensions may register additional HTTP servlets, and
2061wrap them with HTTP filters.
2062
2063Servlets may use auto-registration to declare the URL they handle:
2064
David Pursehouse68153d72013-09-04 10:09:17 +09002065[source,java]
2066----
2067import com.google.gerrit.extensions.annotations.Export;
2068import com.google.inject.Singleton;
2069import javax.servlet.http.HttpServlet;
2070import javax.servlet.http.HttpServletRequest;
2071import javax.servlet.http.HttpServletResponse;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002072
David Pursehouse68153d72013-09-04 10:09:17 +09002073@Export("/print")
2074@Singleton
2075class HelloServlet extends HttpServlet {
2076 protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
2077 res.setContentType("text/plain");
2078 res.setCharacterEncoding("UTF-8");
2079 res.getWriter().write("Hello");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002080 }
David Pursehouse68153d72013-09-04 10:09:17 +09002081}
2082----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002083
Edwin Kempin8aa650f2012-07-18 11:25:48 +02002084The auto registration only works for standard servlet mappings like
Jonathan Nieder5758f182015-03-30 11:28:55 -07002085`/foo` or `+/foo/*+`. Regex style bindings must use a Guice ServletModule
Edwin Kempin8aa650f2012-07-18 11:25:48 +02002086to register the HTTP servlets and declare it explicitly in the manifest
2087with the `Gerrit-HttpModule` attribute:
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002088
David Pursehouse68153d72013-09-04 10:09:17 +09002089[source,java]
2090----
2091import com.google.inject.servlet.ServletModule;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002092
David Pursehouse68153d72013-09-04 10:09:17 +09002093class MyWebUrls extends ServletModule {
2094 protected void configureServlets() {
2095 serve("/print").with(HelloServlet.class);
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002096 }
David Pursehouse68153d72013-09-04 10:09:17 +09002097}
2098----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002099
2100For a plugin installed as name `helloworld`, the servlet implemented
2101by HelloServlet class will be available to users as:
2102
2103----
2104$ curl http://review.example.com/plugins/helloworld/print
2105----
Nasser Grainawie033b262012-05-09 17:54:21 -07002106
Edwin Kempinf5a77332012-07-18 11:17:53 +02002107[[data-directory]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002108== Data Directory
Edwin Kempin41f63912012-07-17 12:33:55 +02002109
Dave Borowitz9e158752015-02-24 10:17:04 -08002110Plugins can request a data directory with a `@PluginData` Path (or File,
2111deprecated) dependency. A data directory will be created automatically
2112by the server in `$site_path/data/$plugin_name` and passed to the
2113plugin.
Edwin Kempin41f63912012-07-17 12:33:55 +02002114
2115Plugins can use this to store any data they want.
2116
David Pursehouse68153d72013-09-04 10:09:17 +09002117[source,java]
2118----
2119@Inject
Dave Borowitz9e158752015-02-24 10:17:04 -08002120MyType(@PluginData java.nio.file.Path myDir) {
2121 this.in = Files.newInputStream(myDir.resolve("my.config"));
David Pursehouse68153d72013-09-04 10:09:17 +09002122}
2123----
Edwin Kempin41f63912012-07-17 12:33:55 +02002124
Dariusz Lukszaebab92a2014-09-10 11:14:19 +02002125[[secure-store]]
2126== SecureStore
2127
2128SecureStore allows to change the way Gerrit stores sensitive data like
2129passwords.
2130
2131In order to replace the default SecureStore (no-op) implementation,
2132a class that extends `com.google.gerrit.server.securestore.SecureStore`
2133needs to be provided (with dependencies) in a separate jar file. Then
2134link:pgm-SwitchSecureStore.html[SwitchSecureStore] must be run to
2135switch implementations.
2136
2137The SecureStore implementation is instantiated using a Guice injector
2138which binds the `File` annotated with the `@SitePath` annotation.
2139This means that a SecureStore implementation class can get access to
2140the `site_path` like in the following example:
2141
2142[source,java]
2143----
2144@Inject
2145MySecureStore(@SitePath java.io.File sitePath) {
2146 // your code
2147}
2148----
2149
2150No Guice bindings or modules are required. Gerrit will automatically
2151discover and bind the implementation.
2152
Thomas Draebingc4e5c072020-07-10 11:44:47 +02002153[[gerrit-replica]]
2154== Gerrit Replica
2155
2156Gerrit can be run as a read-only replica. Some plugins may need to know
2157whether Gerrit is run as a primary- or a replica instance. For that purpose
2158Gerrit exposes the `@GerritIsReplica` annotation. A boolean annotated with
2159this annotation will indicate whether Gerrit is run as a replica.
2160
Michael Ochmann24612652016-02-12 17:26:18 +01002161[[accountcreation]]
2162== Account Creation
2163
2164Plugins can hook into the
2165link:rest-api-accounts.html#create-account[account creation] REST API and
2166inject additional external identifiers for an account that represents a user
2167in some external user store. For that, an implementation of the extension
Han-Wen Nienhuysa77b2e52017-11-11 11:30:05 +01002168point `com.google.gerrit.server.account.AccountExternalIdCreator`
Michael Ochmann24612652016-02-12 17:26:18 +01002169must be registered.
2170
2171[source,java]
2172----
2173class MyExternalIdCreator implements AccountExternalIdCreator {
2174 @Override
2175 public List<AccountExternalId> create(Account.Id id, String username,
2176 String email) {
2177 // your code
2178 }
2179}
2180
2181bind(AccountExternalIdCreator.class)
2182 .annotatedWith(UniqueAnnotations.create())
2183 .to(MyExternalIdCreator.class);
2184}
2185----
2186
Edwin Kempinea621482013-10-16 12:58:24 +02002187[[download-commands]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002188== Download Commands
Edwin Kempinea621482013-10-16 12:58:24 +02002189
Edwin Kempineafde882015-05-11 15:40:44 +02002190Gerrit offers commands for downloading changes and cloning projects
2191using different download schemes (e.g. for downloading via different
2192network protocols). Plugins can contribute download schemes, download
2193commands and clone commands by implementing
2194`com.google.gerrit.extensions.config.DownloadScheme`,
2195`com.google.gerrit.extensions.config.DownloadCommand` and
2196`com.google.gerrit.extensions.config.CloneCommand`.
Edwin Kempinea621482013-10-16 12:58:24 +02002197
Edwin Kempineafde882015-05-11 15:40:44 +02002198The download schemes, download commands and clone commands which are
2199used most often are provided by the Gerrit core plugin
2200`download-commands`.
Edwin Kempinea621482013-10-16 12:58:24 +02002201
Edwin Kempin78279ba2015-05-22 15:22:41 +02002202[[included-in]]
2203== Included In
2204
2205For merged changes the link:user-review-ui.html#included-in[Included In]
2206drop-down panel shows the branches and tags in which the change is
2207included.
2208
2209Plugins can add additional systems in which the change can be included
2210by implementing `com.google.gerrit.extensions.config.ExternalIncludedIn`,
2211e.g. a plugin can provide a list of servers on which the change was
2212deployed.
2213
David Pursehouse38a27c3e2018-07-06 22:34:18 +09002214[[change-report-formatting]]
2215== Change Report Formatting
2216
2217When a change is pushed for review from the command line, Gerrit reports
2218the change(s) received with their URL and subject.
2219
2220By implementing the
2221`com.google.gerrit.server.git.ChangeReportFormatter` interface, a plugin
2222may change the formatting of the report.
2223
David Pursehousee2f25be2018-11-26 17:56:07 +09002224[[url-formatting]]
2225== URL Formatting
2226
2227URLs to various parts of Gerrit are usually formed by adding suffixes to
2228the canonical web URL.
2229
2230By implementing the
2231`com.google.gerrit.server.config.UrlFormatter` interface, a plugin may
2232change the format of the URL.
2233
Sven Selbergae1a10c2014-02-14 14:24:29 +01002234[[links-to-external-tools]]
2235== Links To External Tools
2236
2237Gerrit has extension points that enables development of a
2238light-weight plugin that links commits to external
2239tools (GitBlit, CGit, company specific resources etc).
2240
2241PatchSetWebLinks will appear to the right of the commit-SHA1 in the UI.
2242
2243[source, java]
2244----
2245import com.google.gerrit.extensions.annotations.Listen;
2246import com.google.gerrit.extensions.webui.PatchSetWebLink;;
Sven Selberga85e64d2014-09-24 10:52:21 +02002247import com.google.gerrit.extensions.webui.WebLinkTarget;
Sven Selbergae1a10c2014-02-14 14:24:29 +01002248
2249@Listen
2250public class MyWeblinkPlugin implements PatchSetWebLink {
2251
2252 private String name = "MyLink";
2253 private String placeHolderUrlProjectCommit = "http://my.tool.com/project=%s/commit=%s";
Sven Selberg55484202014-06-26 08:48:51 +02002254 private String imageUrl = "http://placehold.it/16x16.gif";
Sven Selbergae1a10c2014-02-14 14:24:29 +01002255
2256 @Override
Gal Paikin57c9d582020-11-27 14:35:22 +01002257 public WebLinkInfo getPatchSetWebLink(String projectName, String commit,
2258 String subject, String branchName) {
Sven Selberga85e64d2014-09-24 10:52:21 +02002259 return new WebLinkInfo(name,
2260 imageUrl,
2261 String.format(placeHolderUrlProjectCommit, project, commit),
2262 WebLinkTarget.BLANK);
Edwin Kempinceeed6b2014-09-11 17:07:33 +02002263 }
Sven Selbergae1a10c2014-02-14 14:24:29 +01002264}
2265----
2266
David Pursehouse58b8d762016-12-09 11:12:27 +09002267ParentWebLinks will appear to the right of the SHA1 of the parent
2268revisions in the UI. The implementation should in most use cases direct
2269to the same external service as PatchSetWebLink; it is provided as a
2270separate interface because not all users want to have links for the
2271parent revisions.
2272
Edwin Kempinb3696c82014-09-11 09:41:42 +02002273FileWebLinks will appear in the side-by-side diff screen on the right
2274side of the patch selection on each side.
2275
Edwin Kempin8cdce502014-12-06 10:55:38 +01002276DiffWebLinks will appear in the side-by-side and unified diff screen in
2277the header next to the navigation icons.
2278
Edwin Kempinea004752014-04-11 15:56:02 +02002279ProjectWebLinks will appear in the project list in the
2280`Repository Browser` column.
2281
Edwin Kempin0f697bd2014-09-10 18:23:29 +02002282BranchWebLinks will appear in the branch list in the last column.
2283
Edwin Kempinf82b8122016-06-03 09:20:16 +02002284FileHistoryWebLinks will appear on the access rights screen.
2285
Paladox none34da15c2017-07-01 14:49:10 +00002286TagWebLinks will appear in the tag list in the last column.
2287
Dave Borowitzd0c01fd2017-06-06 10:47:08 -04002288If a `get*WebLink` implementation returns `null`, the link will be omitted. This
2289allows the plugin to selectively "enable" itself on a per-project/branch/file
2290basis.
2291
Saša Živkovca7a67e2015-12-01 14:25:10 +01002292[[lfs-extension]]
2293== LFS Storage Plugins
2294
David Pursehouse2463c542016-08-02 16:04:58 +09002295Gerrit provides an extension point that enables development of
2296link:https://github.com/github/git-lfs/blob/master/docs/api/v1/http-v1-batch.md[
Marian Harbach34253372019-12-10 18:01:31 +01002297LFS (Large File Storage),role=external,window=_blank] storage plugins. Gerrit core exposes the default LFS
David Pursehouse2463c542016-08-02 16:04:58 +09002298protocol endpoint `<project-name>/info/lfs/objects/batch` and forwards the requests
2299to the configured link:config-gerrit.html#lfs[lfs.plugin] plugin which implements
2300the LFS protocol. By exposing the default LFS endpoint, the git-lfs client can be
2301used without any configuration.
Saša Živkovca7a67e2015-12-01 14:25:10 +01002302
2303[source, java]
2304----
2305/** Provide an LFS protocol implementation */
2306import org.eclipse.jgit.lfs.server.LargeFileRepository;
2307import org.eclipse.jgit.lfs.server.LfsProtocolServlet;
2308
2309@Singleton
2310public class LfsApiServlet extends LfsProtocolServlet {
2311 private static final long serialVersionUID = 1L;
2312
2313 private final S3LargeFileRepository repository;
2314
2315 @Inject
2316 LfsApiServlet(S3LargeFileRepository repository) {
2317 this.repository = repository;
2318 }
2319
2320 @Override
2321 protected LargeFileRepository getLargeFileRepository() {
2322 return repository;
2323 }
2324}
2325
2326/** Register the LfsApiServlet to listen on the default LFS protocol endpoint */
2327import static com.google.gerrit.httpd.plugins.LfsPluginServlet.URL_REGEX;
2328
Dave Borowitz20f4e8a2019-03-13 14:19:15 -07002329import com.google.inject.servlet.ServletModule;
Saša Živkovca7a67e2015-12-01 14:25:10 +01002330
Dave Borowitz20f4e8a2019-03-13 14:19:15 -07002331public class HttpModule extends ServletModule {
Saša Živkovca7a67e2015-12-01 14:25:10 +01002332
2333 @Override
2334 protected void configureServlets() {
2335 serveRegex(URL_REGEX).with(LfsApiServlet.class);
2336 }
2337}
2338
2339/** Provide an implementation of the LargeFileRepository */
2340import org.eclipse.jgit.lfs.server.s3.S3Repository;
2341
2342public class S3LargeFileRepository extends S3Repository {
2343...
2344}
2345----
2346
David Pursehouse8ad11732016-08-29 15:00:14 +09002347[[metrics]]
2348== Metrics
2349
2350=== Metrics Reporting
2351
2352To send Gerrit's metrics data to an external reporting backend, a plugin can
2353get a `MetricRegistry` injected and register an instance of a class that
2354implements the `Reporter` interface from link:http://metrics.dropwizard.io/[
Marian Harbach34253372019-12-10 18:01:31 +01002355DropWizard Metrics,role=external,window=_blank].
David Pursehouse8ad11732016-08-29 15:00:14 +09002356
2357Metric reporting plugin implementations are provided for
Marian Harbach34253372019-12-10 18:01:31 +01002358link:https://gerrit.googlesource.com/plugins/metrics-reporter-jmx/[JMX,role=external,window=_blank],
2359link:https://gerrit.googlesource.com/plugins/metrics-reporter-elasticsearch/[Elastic Search,role=external,window=_blank],
2360and link:https://gerrit.googlesource.com/plugins/metrics-reporter-graphite/[Graphite,role=external,window=_blank].
David Pursehouse8ad11732016-08-29 15:00:14 +09002361
2362There is also a working example of reporting metrics to the console in the
Eryk Szymanskida073b12017-09-11 14:27:57 +02002363link: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 +01002364cookbook plugin,role=external,window=_blank].
David Pursehouse8ad11732016-08-29 15:00:14 +09002365
2366=== Providing own metrics
2367
2368Plugins may provide metrics to be dispatched to external reporting services by
2369getting a `MetricMaker` injected and creating instances of specific types of
2370metric:
2371
2372* Counter
2373+
2374Metric whose value increments during the life of the process.
2375
2376* Timer
2377+
2378Metric recording time spent on an operation.
2379
2380* Histogram
2381+
2382Metric recording statistical distribution (rate) of values.
2383
David Pursehouse48d05ea2017-02-03 19:05:29 +09002384Note that metrics cannot be recorded from plugin init steps that
2385are run during site initialization.
2386
David Pursehousec3bbd562017-02-06 20:25:29 +09002387By default, plugin metrics are recorded under
2388`plugins/${plugin-name}/${metric-name}`. This can be changed by
2389setting `plugins.${plugin-name}.metricsPrefix` in the `gerrit.config`
2390file. For example:
2391
2392----
2393 [plugin "my-plugin"]
2394 metricsPrefix = my-metrics
2395----
2396
2397will cause the metrics to be recorded under `my-metrics/${metric-name}`.
David Pursehouse8ad11732016-08-29 15:00:14 +09002398
2399See the replication metrics in the
2400link:https://gerrit.googlesource.com/plugins/replication/+/master/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationMetrics.java[
Marian Harbach34253372019-12-10 18:01:31 +01002401replication plugin,role=external,window=_blank] for an example of usage.
David Pursehouse8ad11732016-08-29 15:00:14 +09002402
Edwin Kempinda17bc32016-06-14 11:50:58 +02002403[[account-patch-review-store]]
2404== AccountPatchReviewStore
2405
2406The AccountPatchReviewStore is used to store reviewed flags on changes.
2407A reviewed flag is a tuple of (patch set ID, file, account ID) and
2408records whether the user has reviewed a file in a patch set. Each user
2409can easily have thousands of reviewed flags and the number of reviewed
2410flags is growing without bound. The store must be able handle this data
2411volume efficiently.
2412
2413Gerrit implements this extension point, but plugins may bind another
Matthias Sohnd8182ba2019-12-09 14:50:23 +01002414implementation, e.g. one that supports cluster setup with multiple
2415primary Gerrit nodes handling write operations.
Edwin Kempinda17bc32016-06-14 11:50:58 +02002416
2417----
2418DynamicItem.bind(binder(), AccountPatchReviewStore.class)
2419 .to(MultiMasterAccountPatchReviewStore.class);
2420
2421...
2422
2423public class MultiMasterAccountPatchReviewStore
2424 implements AccountPatchReviewStore {
2425 ...
2426}
2427----
2428
Dave Borowitzf1790ce2016-11-14 12:26:12 -08002429
Edwin Kempinf5a77332012-07-18 11:17:53 +02002430[[documentation]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002431== Documentation
Nasser Grainawie033b262012-05-09 17:54:21 -07002432
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002433If a plugin does not register a filter or servlet to handle URLs
Jonathan Nieder5758f182015-03-30 11:28:55 -07002434`+/Documentation/*+` or `+/static/*+`, the core Gerrit server will
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002435automatically export these resources over HTTP from the plugin JAR.
2436
David Pursehouse6853b5a2013-07-10 11:38:03 +09002437Static resources under the `static/` directory in the JAR will be
Dave Borowitzb893ac82013-03-27 10:03:55 -04002438available as `/plugins/helloworld/static/resource`. This prefix is
2439configurable by setting the `Gerrit-HttpStaticPrefix` attribute.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002440
David Pursehouse6853b5a2013-07-10 11:38:03 +09002441Documentation files under the `Documentation/` directory in the JAR
Dave Borowitzb893ac82013-03-27 10:03:55 -04002442will be available as `/plugins/helloworld/Documentation/resource`. This
2443prefix is configurable by setting the `Gerrit-HttpDocumentationPrefix`
2444attribute.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002445
Christian Aistleitner040cf822015-03-26 21:09:09 +01002446Documentation may be written in the Markdown flavor
Marian Harbach34253372019-12-10 18:01:31 +01002447link:https://github.com/vsch/flexmark-java[flexmark-java,role=external,window=_blank]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002448if the file name ends with `.md`. Gerrit will automatically convert
2449Markdown to HTML if accessed with extension `.html`.
Nasser Grainawie033b262012-05-09 17:54:21 -07002450
Edwin Kempinf5a77332012-07-18 11:17:53 +02002451[[macros]]
Edwin Kempinc78777d2012-07-16 15:55:11 +02002452Within the Markdown documentation files macros can be used that allow
2453to write documentation with reasonably accurate examples that adjust
2454automatically based on the installation.
2455
2456The following macros are supported:
2457
2458[width="40%",options="header"]
2459|===================================================
2460|Macro | Replacement
2461|@PLUGIN@ | name of the plugin
2462|@URL@ | Gerrit Web URL
2463|@SSH_HOST@ | SSH Host
2464|@SSH_PORT@ | SSH Port
2465|===================================================
2466
2467The macros will be replaced when the documentation files are rendered
2468from Markdown to HTML.
2469
2470Macros that start with `\` such as `\@KEEP@` will render as `@KEEP@`
2471even if there is an expansion for `KEEP` in the future.
2472
Edwin Kempinf5a77332012-07-18 11:17:53 +02002473[[auto-index]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002474=== Automatic Index
Shawn O. Pearce795167c2012-05-12 11:20:18 -07002475
2476If a plugin does not handle its `/` URL itself, Gerrit will
2477redirect clients to the plugin's `/Documentation/index.html`.
2478Requests for `/Documentation/` (bare directory) will also redirect
2479to `/Documentation/index.html`.
2480
2481If neither resource `Documentation/index.html` or
2482`Documentation/index.md` exists in the plugin JAR, Gerrit will
2483automatically generate an index page for the plugin's documentation
2484tree by scanning every `*.md` and `*.html` file in the Documentation/
2485directory.
2486
2487For any discovered Markdown (`*.md`) file, Gerrit will parse the
2488header of the file and extract the first level one title. This
2489title text will be used as display text for a link to the HTML
2490version of the page.
2491
2492For any discovered HTML (`*.html`) file, Gerrit will use the name
2493of the file, minus the `*.html` extension, as the link text. Any
2494hyphens in the file name will be replaced with spaces.
2495
David Pursehouse6853b5a2013-07-10 11:38:03 +09002496If a discovered file is named `about.md` or `about.html`, its
2497content will be inserted in an 'About' section at the top of the
2498auto-generated index page. If both `about.md` and `about.html`
2499exist, only the first discovered file will be used.
2500
Shawn O. Pearce795167c2012-05-12 11:20:18 -07002501If a discovered file name beings with `cmd-` it will be clustered
David Pursehouse6853b5a2013-07-10 11:38:03 +09002502into a 'Commands' section of the generated index page.
2503
David Pursehousefe529152013-08-14 16:35:06 +09002504If a discovered file name beings with `servlet-` it will be clustered
2505into a 'Servlets' section of the generated index page.
2506
2507If a discovered file name beings with `rest-api-` it will be clustered
2508into a 'REST APIs' section of the generated index page.
2509
David Pursehouse6853b5a2013-07-10 11:38:03 +09002510All other files are clustered under a 'Documentation' section.
Shawn O. Pearce795167c2012-05-12 11:20:18 -07002511
2512Some optional information from the manifest is extracted and
2513displayed as part of the index page, if present in the manifest:
2514
2515[width="40%",options="header"]
2516|===================================================
2517|Field | Source Attribute
2518|Name | Implementation-Title
2519|Vendor | Implementation-Vendor
2520|Version | Implementation-Version
2521|URL | Implementation-URL
2522|API Version | Gerrit-ApiVersion
2523|===================================================
2524
Edwin Kempinf5a77332012-07-18 11:17:53 +02002525[[deployment]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002526== Deployment
Nasser Grainawie033b262012-05-09 17:54:21 -07002527
Edwin Kempinf7295742012-07-16 15:03:46 +02002528Compiled plugins and extensions can be deployed to a running Gerrit
2529server using the link:cmd-plugin-install.html[plugin install] command.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002530
Viktar Donich15ef24c2017-12-07 16:04:38 -08002531Web UI plugins distributed as a single `.js` file (or `.html` file for
David Pursehouse00c70812017-07-31 10:57:26 +01002532Polygerrit) can be deployed without the overhead of JAR packaging. For
2533more information refer to link:cmd-plugin-install.html[plugin install]
2534command.
Dariusz Luksza357a2422012-11-12 06:16:26 +01002535
David Pursehouse75021fe2017-07-31 23:07:04 +02002536Plugins can also be copied directly into the server's directory at
2537`$site_path/plugins/$name.(jar|js|html)`. For Web UI plugins, the name
2538of the file, minus the `.js` or `.html` extension, will be used as the
2539plugin name. For JAR plugins, the value of the `Gerrit-PluginName`
2540manifest attribute will be used, if provided, otherwise the name of
2541the file, minus the `.jar` extension, will be used.
2542
2543For Web UI plugins, the plugin version is derived from the filename.
2544If the filename contains one or more hyphens, the version is taken
2545from the portion following the last hyphen. For example if the plugin
2546filename is `my-plugin-1.0.js` the version will be `1.0`. For JAR
2547plugins, the version is taken from the `Version` attribute in the
2548manifest.
2549
2550Unless disabled, servers periodically scan the `$site_path/plugins`
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002551directory for updated plugins. The time can be adjusted by
2552link:config-gerrit.html#plugins.checkFrequency[plugins.checkFrequency].
Deniz Türkoglueb78b602012-05-07 14:02:36 -07002553
Edwin Kempinf7295742012-07-16 15:03:46 +02002554For disabling plugins the link:cmd-plugin-remove.html[plugin remove]
2555command can be used.
2556
Brad Larsond5e87c32012-07-11 12:18:49 -05002557Disabled plugins can be re-enabled using the
2558link:cmd-plugin-enable.html[plugin enable] command.
2559
Patrick Hiesel87880b02016-05-03 18:15:08 +02002560[[reviewer-suggestion]]
2561== Reviewer Suggestion Plugins
2562
2563Gerrit provides an extension point that enables Plugins to rank
2564the list of reviewer suggestion a user receives upon clicking "Add Reviewer" on
2565the change screen.
David Pursehousec2233a42018-02-07 10:31:05 +09002566
Patrick Hiesel87880b02016-05-03 18:15:08 +02002567Gerrit supports both a default suggestion that appears when the user has not yet
2568typed anything and a filtered suggestion that is shown as the user starts
2569typing.
David Pursehousec2233a42018-02-07 10:31:05 +09002570
2571Plugins receive a candidate list and can return a `Set` of suggested reviewers
2572containing the `Account.Id` and a score for each reviewer. The candidate list is
2573non-binding and plugins can choose to return reviewers not initially contained in
2574the candidate list.
2575
2576Server administrators can configure the overall weight of each plugin by setting
2577the `addreviewer.pluginName-exportName.weight` value in `gerrit.config`.
Patrick Hiesel87880b02016-05-03 18:15:08 +02002578
2579[source, java]
2580----
Patrick Hiesel2514e412016-10-13 09:34:44 +02002581import com.google.gerrit.common.Nullable;
David Ostrovskyb03a6e92019-05-26 14:11:47 +02002582import com.google.gerrit.entities.Account;
2583import com.google.gerrit.entities.Change;
2584import com.google.gerrit.entities.Project;
Patrick Hiesel2514e412016-10-13 09:34:44 +02002585import com.google.gerrit.extensions.annotations.ExtensionPoint;
Patrick Hiesel87880b02016-05-03 18:15:08 +02002586
2587import java.util.Set;
2588
2589public class MyPlugin implements ReviewerSuggestion {
Patrick Hiesel2514e412016-10-13 09:34:44 +02002590 public Set<SuggestedReviewer> suggestReviewers(Project.NameKey project,
2591 @Nullable Change.Id changeId, @Nullable String query,
2592 Set<Account.Id> candidates) {
2593 Set<SuggestedReviewer> suggestions = new HashSet<>();
2594 // Implement your ranking logic here
2595 return suggestions;
Patrick Hiesel87880b02016-05-03 18:15:08 +02002596 }
2597}
2598----
2599
2600
Patrick Hiesel30c76182017-01-20 11:46:43 +01002601[[mail-filter]]
2602== Mail Filter Plugins
2603
2604Gerrit provides an extension point that enables Plugins to discard incoming
2605messages and prevent further processing by Gerrit.
2606
David Pursehouse4b067752017-03-03 15:54:53 +09002607This can be used to implement spam checks, signature validations or organization
Patrick Hiesel30c76182017-01-20 11:46:43 +01002608specific checks like IP filters.
2609
2610[source, java]
2611----
2612import com.google.gerrit.extensions.annotations.ExtensionPoint;
Han-Wen Nienhuys50dd94e2017-11-14 18:04:28 +01002613import com.google.gerrit.mail.MailMessage;
Patrick Hiesel30c76182017-01-20 11:46:43 +01002614
2615public class MyPlugin implements MailFilter {
maximeg92419ab2018-02-16 10:27:25 +01002616 public boolean shouldProcessMessage(MailMessage message) {
Patrick Hiesel30c76182017-01-20 11:46:43 +01002617 // Implement your filter logic here
2618 return true;
2619 }
2620}
2621----
2622
David Causse01c13402019-04-09 14:19:28 +02002623[[ssh-command-creation-interception]]
2624== SSH Command Creation Interception
David Pursehouse9cd079f2018-02-28 21:36:27 +09002625
2626Gerrit provides an extension point that allows a plugin to intercept
2627creation of SSH commands and override the functionality with its own
2628implementation.
2629
2630[source, java]
2631----
2632import com.google.gerrit.sshd.SshCreateCommandInterceptor;
2633
2634class MyCommandInterceptor implements SshCreateCommandInterceptor {
2635 @Override
2636 public String intercept(String in) {
2637 return pluginName + " mycommand";
Maxime Guerreiroa63cc6e2018-02-28 14:31:55 +01002638----
2639
David Causse01c13402019-04-09 14:19:28 +02002640[[ssh-command-execution-interception]]
2641== SSH Command Execution Interception
2642Gerrit provides an extension point that enables plugins to check and
2643prevent an SSH command from being run.
2644
2645[source, java]
2646----
2647import com.google.gerrit.sshd.SshExecuteCommandInterceptor;
2648
2649@Singleton
2650public class SshExecuteCommandInterceptorImpl implements SshExecuteCommandInterceptor {
2651 private final Provider<SshSession> sessionProvider;
2652
2653 @Inject
2654 SshExecuteCommandInterceptorImpl(Provider<SshSession> sessionProvider) {
2655 this.sessionProvider = sessionProvider;
2656 }
2657
2658 @Override
2659 public boolean accept(String command, List<String> arguments) {
2660 if (command.startsWith("gerrit") && !"10.1.2.3".equals(sessionProvider.get().getRemoteAddressAsString())) {
2661 return false;
2662 }
2663 return true;
2664 }
2665}
2666----
2667
2668And then declare it in your SSH module:
2669[source, java]
2670----
2671 DynamicSet.bind(binder(), SshExecuteCommandInterceptor.class).to(SshExecuteCommandInterceptorImpl.class);
2672----
Maxime Guerreiroa63cc6e2018-02-28 14:31:55 +01002673
2674[[pre-submit-evaluator]]
2675== Pre-submit Validation Plugins
2676
2677Gerrit provides an extension point that enables plugins to prevent a change
2678from being submitted.
2679
2680[IMPORTANT]
2681This extension point **must NOT** be used for long or slow operations, like
2682calling external programs or content, running unit tests...
2683Slow operations will hurt the whole Gerrit instance.
2684
2685This can be used to implement custom rules that changes have to match to become
2686submittable. A more concrete example: the Prolog rules engine can be
2687implemented using this.
2688
2689Gerrit calls the plugins once per change and caches the results. Although it is
2690possible to predict when this interface will be triggered, this should not be
2691considered as a feature. Plugins should only rely on the internal state of the
2692ChangeData, not on external values like date and time, remote content or
2693randomness.
2694
2695Plugins are expected to support rules inheritance themselves, providing ways
2696to configure it and handling the logic behind it.
2697Please note that no inheritance is sometimes better than badly handled
2698inheritance: mis-communication and strange behaviors caused by inheritance
2699may and will confuse the users. Each plugins is responsible for handling the
2700project hierarchy and taking wise actions. Gerrit does not enforce it.
2701
2702Once Gerrit has gathered every plugins' SubmitRecords, it stores them.
2703
2704Plugins accept or reject a given change using `SubmitRecord.Status`.
2705If a change is ready to be submitted, `OK`. If it is not ready and requires
2706modifications, `NOT_READY`. Other statuses are available for particular cases.
2707A change can be submitted if all the plugins accept the change.
2708
Patrick Hiesel7ab1b182019-08-13 15:29:27 +02002709Plugins may also decide not to vote on a given change by returning an
2710`Optional.empty()` (ie: the plugin is not enabled for this repository).
Maxime Guerreiroa63cc6e2018-02-28 14:31:55 +01002711
2712If a plugin decides not to vote, it's name will not be displayed in the UI and
2713it will not be recoded in the database.
2714
2715.Gerrit's Pre-submit handling with three plugins
2716[width="50%",cols="^m,^m,^m,^m",frame="topbot",options="header"]
2717|=======================================================
2718| Plugin A | Plugin B | Plugin C | Final decision
2719| OK | OK | OK | OK
2720| OK | OK | / | OK
2721| OK | OK | RULE_ERROR | NOT_READY
2722| OK | NOT_READY | OK | NOT_READY
2723| NOT_READY | OK | OK | NOT_READY
2724|=======================================================
2725
2726
2727This makes composing plugins really easy.
2728
2729- If a plugin places a veto on a change, it can't be submitted.
2730- If a plugin isn't enabled for a project (or isn't needed for this change),
2731 it returns an empty collection.
2732- If all the plugins answer `OK`, the change can be submitted.
2733
2734
2735A more rare case, but worth documenting: if there are no installed plugins,
2736the labels will be compared to the rules defined in the project's config,
2737and the permission system will be used to allow or deny a submit request.
2738
2739Some rules are defined internally to provide a common base ground (and sanity):
2740changes that are marked as WIP or that are closed (abandoned, merged) can't be merged.
2741
2742
2743[source, java]
2744----
Patrick Hiesel7ab1b182019-08-13 15:29:27 +02002745import java.util.Optional;
Andrew Allen6887c732020-09-12 00:42:11 +00002746import com.google.gerrit.entities.SubmitRecord;
2747import com.google.gerrit.entities.SubmitRecord.Status;
Maxime Guerreiroa63cc6e2018-02-28 14:31:55 +01002748import com.google.gerrit.server.query.change.ChangeData;
2749import com.google.gerrit.server.rules.SubmitRule;
2750
2751public class MyPluginRules implements SubmitRule {
Patrick Hiesel7ab1b182019-08-13 15:29:27 +02002752 public Optional<SubmitRecord> evaluate(ChangeData changeData) {
Maxime Guerreiroa63cc6e2018-02-28 14:31:55 +01002753 // Implement your submitability logic here
2754
2755 // Assuming we want to prevent this change from being submitted:
Edwin Kempinf5d3e942019-07-22 15:23:47 +02002756 SubmitRecord record = new SubmitRecord();
Maxime Guerreiroa63cc6e2018-02-28 14:31:55 +01002757 record.status = Status.NOT_READY;
Patrick Hiesel7ab1b182019-08-13 15:29:27 +02002758 return Optional.of(record);
David Pursehouse9cd079f2018-02-28 21:36:27 +09002759 }
2760}
2761----
2762
Maxime Guerreiroa63cc6e2018-02-28 14:31:55 +01002763Don't forget to register your class!
2764
2765[source, java]
2766----
2767import com.google.gerrit.extensions.annotations.Exports;
2768import com.google.inject.AbstractModule;
2769
2770public class MyPluginModule extends AbstractModule {
2771 @Override
2772 protected void configure() {
2773 bind(SubmitRule.class).annotatedWith(Exports.named("myPlugin")).to(MyPluginRules.class);
2774 }
2775}
2776----
David Pursehouse9cd079f2018-02-28 21:36:27 +09002777
Maxime Guerreiro5ccf0b92018-05-15 16:28:22 +00002778Plugin authors should also consider binding their SubmitRule using a `Gerrit-BatchModule`.
2779See link:dev-plugins.html[Batch runtime] for more informations.
2780
Maxime Guerreiro97e891a2018-06-06 13:36:50 +00002781
2782The SubmitRule extension point allows you to write complex rules, but writing
2783small self-contained rules should be preferred: doing so allows end users to
2784compose several rules to form more complex submit checks.
2785
2786The `SubmitRequirement` class allows rules to communicate what the user needs
2787to change in order to be compliant. These requirements should be kept once they
2788are met, but marked as `OK`. If the requirements were not displayed, reviewers
2789would need to use their precious time to manually check that they were met.
2790
Edwin Kempin96915b72019-07-22 14:57:40 +02002791Implementors of the `SubmitRule` interface should check whether they need to
2792contribute to the link:#change-etag-computation[change ETag computation] to
2793prevent callers using ETags from potentially seeing outdated submittability
2794information.
2795
2796[[change-etag-computation]]
2797== Change ETag Computation
2798
2799By implementing the `com.google.gerrit.server.change.ChangeETagComputation`
2800interface plugins can contribute a value to the change ETag computation.
2801
2802Plugins can affect the result of the get change / get change details REST
2803endpoints by:
2804
2805* providing link:#query_attributes[plugin defined attributes] in
2806 link:rest-api-changes.html#change-info[ChangeInfo]
2807* implementing a link:#pre-submit-evaluator[pre-submit evaluator] which affects
2808 the computation of `submittable` field in
2809 link:rest-api-changes.html#change-info[ChangeInfo]
2810
2811If the plugin defined part of link:rest-api-changes.html#change-info[
2812ChangeInfo] depends on plugin specific data, callers that use change ETags to
2813avoid unneeded recomputations of ChangeInfos may see outdated plugin attributes
2814and/or outdated submittable information, because a ChangeInfo is only reloaded
2815if the change ETag changes.
2816
2817By implementating the `com.google.gerrit.server.change.ChangeETagComputation`
2818interface plugins can contribute to the ETag computation and thus ensure that
2819the change ETag changes when the plugin data was changed. This way it can be
2820ensured that callers do not see outdated ChangeInfos.
2821
2822IMPORTANT: Change ETags are computed very frequently and the computation must
2823be cheap. Take good care to not perform any expensive computations when
2824implementing this.
2825
2826[source, java]
2827----
2828import static java.nio.charset.StandardCharsets.UTF_8;
2829
2830import com.google.common.hash.Hasher;
David Pursehouse0fb1fd12019-10-16 10:41:00 +09002831import com.google.gerrit.entities.Change;
2832import com.google.gerrit.entities.Project;
Edwin Kempin96915b72019-07-22 14:57:40 +02002833import com.google.gerrit.server.change.ChangeETagComputation;
2834
2835public class MyPluginChangeETagComputation implements ChangeETagComputation {
2836 public String getETag(Project.NameKey projectName, Change.Id changeId) {
2837 Hasher hasher = Hashing.murmur3_128().newHasher();
2838
2839 // Add hashes for all plugin-specific data that affects change infos.
2840 hasher.putString(sha1OfPluginSpecificChangeRef, UTF_8);
2841
2842 return hasher.hash().toString();
2843 }
2844}
2845----
2846
Edwin Kempince8af4c2019-09-24 13:28:03 +02002847[[exception-hook]]
2848== ExceptionHook
2849
2850An `ExceptionHook` allows implementors to control how certain
2851exceptions should be handled.
2852
2853This interface is intended to be implemented for multi-master setups to
2854control the behavior for handling exceptions that are thrown by a lower
2855layer that handles the consensus and synchronization between different
2856server nodes. E.g. if an operation fails because consensus for a Git
2857update could not be achieved (e.g. due to slow responding server nodes)
2858this interface can be used to retry the request instead of failing it
2859immediately.
2860
Edwin Kempin8f29a392019-10-31 11:48:31 +01002861It also allows implementors to group exceptions that have the same
2862cause into one metric bucket.
2863
Edwin Kempin7c9aa3e2019-09-26 11:50:03 +02002864[[mail-soy-template-provider]]
2865== MailSoyTemplateProvider
2866
2867This extension point allows to provide soy templates for registration
2868so that they can be used for sending emails from a plugin.
2869
Patrick Hieselfc9817f2018-11-12 15:08:35 +01002870[[quota-enforcer]]
2871== Quota Enforcer
2872
2873Gerrit provides an extension point that allows a plugin to enforce quota.
2874link:quota.html[This documentation page] has a list of all quota requests that
2875Gerrit core issues. Plugins can choose to respond to all or just a subset of
2876requests. Some implementations might want to keep track of user quota in buckets,
2877others might just check against instance or project state to enforce limits on how
2878many projects can be created or how large a repository can become.
2879
2880Checking against instance state can be racy for concurrent requests as the server does not
2881refill tokens if the action fails in a later stage (e.g. database failure). If
2882plugins want to guarantee an absolute maximum on a resource, they have to do their own
2883book-keeping.
2884
2885[source, java]
2886----
2887import com.google.server.quota.QuotaEnforcer;
2888
2889class ProjectLimiter implements QuotaEnforcer {
2890 private final long maxNumberOfProjects = 100;
2891 @Override
2892 QuotaResponse requestTokens(String quotaGroup, QuotaRequestContext ctx, long numTokens) {
2893 if (!"/projects/create".equals(quotaGroup)) {
2894 return QuotaResponse.noOp();
2895 }
2896 // No deduction because we always check against the instance state (racy but fine for
2897 // this plugin)
2898 if (currentNumberOfProjects() + numTokens > maxNumberOfProjects) {
2899 return QuotaResponse.error("too many projects");
2900 }
2901 return QuotaResponse.ok();
2902 }
2903
2904 @Override
2905 QuotaResponse dryRun(String quotaGroup, QuotaRequestContext ctx, long numTokens) {
2906 // Since we are not keeping any state in this enforcer, we can simply call requestTokens().
2907 return requestTokens(quotaGroup, ctx, numTokens);
2908 }
2909
2910 void refill(String quotaGroup, QuotaRequestContext ctx, long numTokens) {
2911 // No-op
2912 }
2913}
2914----
2915
2916[source, java]
2917----
2918import com.google.server.quota.QuotaEnforcer;
2919
2920class ApiQpsEnforcer implements QuotaEnforcer {
2921 // AutoRefillingPerUserBuckets is a imaginary bucket implementation that could be based on
2922 // a loading cache or a commonly used bucketing algorithm.
2923 private final AutoRefillingPerUserBuckets<CurrentUser, Long> buckets;
2924 @Override
2925 QuotaResponse requestTokens(String quotaGroup, QuotaRequestContext ctx, long numTokens) {
2926 if (!quotaGroup.startsWith("/restapi/")) {
2927 return QuotaResponse.noOp();
2928 }
2929 boolean success = buckets.deduct(ctx.user(), numTokens);
2930 if (!success) {
2931 return QuotaResponse.error("user sent too many qps, please wait for 5 minutes");
2932 }
2933 return QuotaResponse.ok();
2934 }
2935
2936 @Override
2937 QuotaResponse dryRun(String quotaGroup, QuotaRequestContext ctx, long numTokens) {
2938 if (!quotaGroup.startsWith("/restapi/")) {
2939 return QuotaResponse.noOp();
2940 }
2941 boolean success = buckets.checkOnly(ctx.user(), numTokens);
2942 if (!success) {
2943 return QuotaResponse.error("user sent too many qps, please wait for 5 minutes");
2944 }
2945 return QuotaResponse.ok();
2946 }
2947
2948 @Override
2949 void refill(String quotaGroup, QuotaRequestContext ctx, long numTokens) {
2950 if (!quotaGroup.startsWith("/restapi/")) {
2951 return;
2952 }
2953 buckets.add(ctx.user(), numTokens);
2954 }
2955}
2956----
2957
Edwin Kempinf3e6cc772019-05-27 17:33:16 +02002958[[performance-logger]]
2959== Performance Logger
2960
2961`com.google.gerrit.server.logging.PerformanceLogger` is an extension point that
2962is invoked for all operations for which the execution time is measured. The
2963invocation of the extension point does not happen immediately, but only at the
2964end of a request (REST call, SSH call, git push). Implementors can write the
2965execution times into a performance log for further analysis.
2966
Edwin Kempin4a3ac162019-07-04 08:35:58 +02002967[[request-listener]]
2968== Request Listener
2969
2970`com.google.gerrit.server.RequestListener` is an extension point that is
2971invoked each time the server executes a request from a user.
2972
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002973== SEE ALSO
David Ostrovskyf86bae52013-09-01 09:10:39 +02002974
2975* link:js-api.html[JavaScript API]
2976* link:dev-rest-api.html[REST API Developers' Notes]
2977
Deniz Türkoglueb78b602012-05-07 14:02:36 -07002978GERRIT
2979------
2980Part of link:index.html[Gerrit Code Review]
Yuxuan 'fishy' Wang99cb68d2013-10-31 17:26:00 -07002981
2982SEARCHBOX
2983---------