blob: df14c1da60067b1211cdb514ae22fc54083e84ba [file] [log] [blame]
Deniz Türkoglueb78b602012-05-07 14:02:36 -07001Gerrit Code Review - Plugin Development
2=======================================
3
Edwin Kempinaf275322012-07-16 11:04:01 +02004The Gerrit server functionality can be extended by installing plugins.
5This page describes how plugins for Gerrit can be developed.
6
7Depending on how tightly the extension code is coupled with the Gerrit
8server code, there is a distinction between `plugins` and `extensions`.
9
Edwin Kempinf5a77332012-07-18 11:17:53 +020010[[plugin]]
Edwin Kempin948de0f2012-07-16 10:34:35 +020011A `plugin` in Gerrit is tightly coupled code that runs in the same
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070012JVM as Gerrit. It has full access to all server internals. Plugins
13are tightly coupled to a specific major.minor server version and
14may require source code changes to compile against a different
15server version.
16
Edwin Kempinf5a77332012-07-18 11:17:53 +020017[[extension]]
Edwin Kempin948de0f2012-07-16 10:34:35 +020018An `extension` in Gerrit runs inside of the same JVM as Gerrit
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070019in the same way as a plugin, but has limited visibility to the
Edwin Kempinfd19bfb2012-07-16 10:44:17 +020020server's internals. The limited visibility reduces the extension's
21dependencies, enabling it to be compatible across a wider range
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070022of server versions.
23
24Most of this documentation refers to either type as a plugin.
Deniz Türkoglueb78b602012-05-07 14:02:36 -070025
Edwin Kempinf878c4b2012-07-18 09:34:25 +020026[[getting-started]]
27Getting started
28---------------
Deniz Türkoglueb78b602012-05-07 14:02:36 -070029
Edwin Kempinf878c4b2012-07-18 09:34:25 +020030To get started with the development of a plugin there are two
31recommended ways:
Dave Borowitz5cc8f662012-05-21 09:51:36 -070032
Edwin Kempinf878c4b2012-07-18 09:34:25 +020033. use the Gerrit Plugin Maven archetype to create a new plugin project:
34+
35With the Gerrit Plugin Maven archetype you can create a skeleton for a
36plugin project.
37+
38----
39mvn archetype:generate -DarchetypeGroupId=com.google.gerrit \
40 -DarchetypeArtifactId=gerrit-plugin-archetype \
David Pursehouse62864b72013-10-17 23:05:08 +090041 -DarchetypeVersion=2.9-SNAPSHOT \
Edwin Kempin91155c22013-12-02 20:25:18 +010042 -DgroupId=com.googlesource.gerrit.plugins.testplugin \
43 -DartifactId=testplugin
Edwin Kempinf878c4b2012-07-18 09:34:25 +020044----
45+
46Maven will ask for additional properties and then create the plugin in
47the current directory. To change the default property values answer 'n'
48when Maven asks to confirm the properties configuration. It will then
49ask again for all properties including those with predefined default
50values.
51
David Pursehouse2cf0cb52013-08-27 16:09:53 +090052. clone the sample plugin:
Edwin Kempinf878c4b2012-07-18 09:34:25 +020053+
David Pursehouse2cf0cb52013-08-27 16:09:53 +090054This is a project that demonstrates the various features of the
55plugin API. It can be taken as an example to develop an own plugin.
Edwin Kempinf878c4b2012-07-18 09:34:25 +020056+
Dave Borowitz5cc8f662012-05-21 09:51:36 -070057----
David Pursehouse2cf0cb52013-08-27 16:09:53 +090058$ git clone https://gerrit.googlesource.com/plugins/cookbook-plugin
Dave Borowitz5cc8f662012-05-21 09:51:36 -070059----
Edwin Kempinf878c4b2012-07-18 09:34:25 +020060+
61When starting from this example one should take care to adapt the
62`Gerrit-ApiVersion` in the `pom.xml` to the version of Gerrit for which
63the plugin is developed. If the plugin is developed for a released
64Gerrit version (no `SNAPSHOT` version) then the URL for the
65`gerrit-api-repository` in the `pom.xml` needs to be changed to
Shawn Pearced5005002013-06-21 11:01:45 -070066`https://gerrit-api.storage.googleapis.com/release/`.
Dave Borowitz5cc8f662012-05-21 09:51:36 -070067
Edwin Kempinf878c4b2012-07-18 09:34:25 +020068[[API]]
69API
70---
71
72There are two different API formats offered against which plugins can
73be developed:
Deniz Türkoglueb78b602012-05-07 14:02:36 -070074
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070075gerrit-extension-api.jar::
76 A stable but thin interface. Suitable for extensions that need
77 to be notified of events, but do not require tight coupling to
78 the internals of Gerrit. Extensions built against this API can
79 expect to be binary compatible across a wide range of server
80 versions.
Deniz Türkoglueb78b602012-05-07 14:02:36 -070081
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070082gerrit-plugin-api.jar::
83 The complete internals of the Gerrit server, permitting a
84 plugin to tightly couple itself and provide additional
85 functionality that is not possible as an extension. Plugins
86 built against this API are expected to break at the source
87 code level between every major.minor Gerrit release. A plugin
88 that compiles against 2.5 will probably need source code level
89 changes to work with 2.6, 2.7, and so on.
Deniz Türkoglueb78b602012-05-07 14:02:36 -070090
91Manifest
92--------
93
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070094Plugins may provide optional description information with standard
95manifest fields:
Nasser Grainawie033b262012-05-09 17:54:21 -070096
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070097====
98 Implementation-Title: Example plugin showing examples
99 Implementation-Version: 1.0
100 Implementation-Vendor: Example, Inc.
101 Implementation-URL: http://example.com/opensource/plugin-foo/
102====
Nasser Grainawie033b262012-05-09 17:54:21 -0700103
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700104ApiType
105~~~~~~~
Nasser Grainawie033b262012-05-09 17:54:21 -0700106
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700107Plugins using the tightly coupled `gerrit-plugin-api.jar` must
108declare this API dependency in the manifest to gain access to server
Edwin Kempin948de0f2012-07-16 10:34:35 +0200109internals. If no `Gerrit-ApiType` is specified the stable `extension`
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700110API will be assumed. This may cause ClassNotFoundExceptions when
111loading a plugin that needs the plugin API.
Nasser Grainawie033b262012-05-09 17:54:21 -0700112
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700113====
114 Gerrit-ApiType: plugin
115====
116
117Explicit Registration
118~~~~~~~~~~~~~~~~~~~~~
119
120Plugins that use explicit Guice registration must name the Guice
121modules in the manifest. Up to three modules can be named in the
Edwin Kempin948de0f2012-07-16 10:34:35 +0200122manifest. `Gerrit-Module` supplies bindings to the core server;
123`Gerrit-SshModule` supplies SSH commands to the SSH server (if
124enabled); `Gerrit-HttpModule` supplies servlets and filters to the HTTP
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700125server (if enabled). If no modules are named automatic registration
126will be performed by scanning all classes in the plugin JAR for
127`@Listen` and `@Export("")` annotations.
128
129====
130 Gerrit-Module: tld.example.project.CoreModuleClassName
131 Gerrit-SshModule: tld.example.project.SshModuleClassName
132 Gerrit-HttpModule: tld.example.project.HttpModuleClassName
133====
134
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200135[[plugin_name]]
136Plugin Name
137~~~~~~~~~~~
138
David Pursehoused128c892013-10-22 21:52:21 +0900139A plugin can optionally provide its own plugin name.
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200140
141====
142 Gerrit-PluginName: replication
143====
144
145This is useful for plugins that contribute plugin-owned capabilities that
146are stored in the `project.config` file. Another use case is to be able to put
147project specific plugin configuration section in `project.config`. In this
148case it is advantageous to reserve the plugin name to access the configuration
149section in the `project.config` file.
150
151If `Gerrit-PluginName` is omitted, then the plugin's name is determined from
152the plugin file name.
153
154If a plugin provides its own name, then that plugin cannot be deployed
155multiple times under different file names on one Gerrit site.
156
157For Maven driven plugins, the following line must be included in the pom.xml
158file:
159
160[source,xml]
161----
162<manifestEntries>
163 <Gerrit-PluginName>name</Gerrit-PluginName>
164</manifestEntries>
165----
166
167For Buck driven plugins, the following line must be included in the BUCK
168configuration file:
169
170[source,python]
171----
David Pursehouse529ec252013-09-27 13:45:14 +0900172manifest_entries = [
173 'Gerrit-PluginName: name',
174]
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200175----
176
Edwin Kempinc0b1b0e2013-10-01 14:13:54 +0200177A plugin can get its own name injected at runtime:
178
179[source,java]
180----
181public class MyClass {
182
183 private final String pluginName;
184
185 @Inject
186 public MyClass(@PluginName String pluginName) {
187 this.pluginName = pluginName;
188 }
189
David Pursehoused128c892013-10-22 21:52:21 +0900190 [...]
Edwin Kempinc0b1b0e2013-10-01 14:13:54 +0200191}
192----
193
David Pursehouse8ed0d922013-10-18 18:57:56 +0900194A plugin can get its canonical web URL injected at runtime:
195
196[source,java]
197----
198public class MyClass {
199
200 private final String url;
201
202 @Inject
203 public MyClass(@PluginCanonicalWebUrl String url) {
204 this.url = url;
205 }
206
207 [...]
208}
209----
210
211The URL is composed of the server's canonical web URL and the plugin's
212name, i.e. `http://review.example.com:8080/plugin-name`.
213
214The canonical web URL may be injected into any .jar plugin regardless of
215whether or not the plugin provides an HTTP servlet.
216
Edwin Kempinf7295742012-07-16 15:03:46 +0200217[[reload_method]]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700218Reload Method
219~~~~~~~~~~~~~
220
221If a plugin holds an exclusive resource that must be released before
222loading the plugin again (for example listening on a network port or
Edwin Kempin948de0f2012-07-16 10:34:35 +0200223acquiring a file lock) the manifest must declare `Gerrit-ReloadMode`
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700224to be `restart`. Otherwise the preferred method of `reload` will
225be used, as it enables the server to hot-patch an updated plugin
226with no down time.
227
228====
229 Gerrit-ReloadMode: restart
230====
231
232In either mode ('restart' or 'reload') any plugin or extension can
233be updated without restarting the Gerrit server. The difference is
234how Gerrit handles the upgrade:
235
236restart::
237 The old plugin is completely stopped. All registrations of SSH
238 commands and HTTP servlets are removed. All registrations of any
239 extension points are removed. All registered LifecycleListeners
240 have their `stop()` method invoked in reverse order. The new
241 plugin is started, and registrations are made from the new
242 plugin. There is a brief window where neither the old nor the
243 new plugin is connected to the server. This means SSH commands
244 and HTTP servlets will return not found errors, and the plugin
245 will not be notified of events that occurred during the restart.
246
247reload::
248 The new plugin is started. Its LifecycleListeners are permitted
249 to perform their `start()` methods. All SSH and HTTP registrations
250 are atomically swapped out from the old plugin to the new plugin,
251 ensuring the server never returns a not found error. All extension
252 point listeners are atomically swapped out from the old plugin to
253 the new plugin, ensuring no events are missed (however some events
254 may still route to the old plugin if the swap wasn't complete yet).
255 The old plugin is stopped.
256
Edwin Kempinf7295742012-07-16 15:03:46 +0200257To reload/restart a plugin the link:cmd-plugin-reload.html[plugin reload]
258command can be used.
259
Luca Milanesio737285d2012-09-25 14:26:43 +0100260[[init_step]]
261Init step
262~~~~~~~~~
263
264Plugins can contribute their own "init step" during the Gerrit init
265wizard. This is useful for guiding the Gerrit administrator through
266the settings needed by the plugin to work propertly.
267
268For instance plugins to integrate Jira issues to Gerrit changes may
269contribute their own "init step" to allow configuring the Jira URL,
270credentials and possibly verify connectivity to validate them.
271
272====
273 Gerrit-InitStep: tld.example.project.MyInitStep
274====
275
276MyInitStep needs to follow the standard Gerrit InitStep syntax
David Pursehouse92463562013-06-24 10:16:28 +0900277and behavior: writing to the console using the injected ConsoleUI
Luca Milanesio737285d2012-09-25 14:26:43 +0100278and accessing / changing configuration settings using Section.Factory.
279
280In addition to the standard Gerrit init injections, plugins receive
281the @PluginName String injection containing their own plugin name.
282
Edwin Kempind4cfac12013-11-27 11:22:34 +0100283During their initialization plugins may get access to the
284`project.config` file of the `All-Projects` project and they are able
285to store configuration parameters in it. For this a plugin `InitStep`
286can get `com.google.gerrit.pgm.init.AllProjectsConfig` injected:
287
288[source,java]
289----
290 public class MyInitStep implements InitStep {
291 private final String pluginName;
292 private final ConsoleUI ui;
293 private final AllProjectsConfig allProjectsConfig;
294
295 public MyInitStep(@PluginName String pluginName, ConsoleUI ui,
296 AllProjectsConfig allProjectsConfig) {
297 this.pluginName = pluginName;
298 this.ui = ui;
299 this.allProjectsConfig = allProjectsConfig;
300 }
301
302 @Override
303 public void run() throws Exception {
304 ui.message("\n");
305 ui.header(pluginName + " Integration");
306 boolean enabled = ui.yesno(true, "By default enabled for all projects");
307 Config cfg = allProjectsConfig.load();
308 if (enabled) {
309 cfg.setBoolean("plugin", pluginName, "enabled", enabled);
310 } else {
311 cfg.unset("plugin", pluginName, "enabled");
312 }
313 allProjectsConfig.save(pluginName, "Initialize " + pluginName + " Integration");
314 }
315 }
316----
317
Luca Milanesio737285d2012-09-25 14:26:43 +0100318Bear in mind that the Plugin's InitStep class will be loaded but
319the standard Gerrit runtime environment is not available and the plugin's
320own Guice modules were not initialized.
321This means the InitStep for a plugin is not executed in the same way that
322the plugin executes within the server, and may mean a plugin author cannot
323trivially reuse runtime code during init.
324
325For instance a plugin that wants to verify connectivity may need to statically
326call the constructor of their connection class, passing in values obtained
327from the Section.Factory rather than from an injected Config object.
328
David Pursehoused128c892013-10-22 21:52:21 +0900329Plugins' InitSteps are executed during the "Gerrit Plugin init" phase, after
330the extraction of the plugins embedded in the distribution .war file into
331`$GERRIT_SITE/plugins` and before the DB Schema initialization or upgrade.
332
333A plugin's InitStep cannot refer to Gerrit's DB Schema or any other Gerrit
334runtime objects injected at startup.
Luca Milanesio737285d2012-09-25 14:26:43 +0100335
David Pursehouse68153d72013-09-04 10:09:17 +0900336[source,java]
337----
338public class MyInitStep implements InitStep {
339 private final ConsoleUI ui;
340 private final Section.Factory sections;
341 private final String pluginName;
Luca Milanesio737285d2012-09-25 14:26:43 +0100342
David Pursehouse68153d72013-09-04 10:09:17 +0900343 @Inject
344 public GitBlitInitStep(final ConsoleUI ui, Section.Factory sections, @PluginName String pluginName) {
345 this.ui = ui;
346 this.sections = sections;
347 this.pluginName = pluginName;
Luca Milanesio737285d2012-09-25 14:26:43 +0100348 }
David Pursehouse68153d72013-09-04 10:09:17 +0900349
350 @Override
351 public void run() throws Exception {
352 ui.header("\nMy plugin");
353
354 Section mySection = getSection("myplugin", null);
355 mySection.string("Link name", "linkname", "MyLink");
356 }
357}
358----
Luca Milanesio737285d2012-09-25 14:26:43 +0100359
Edwin Kempinf5a77332012-07-18 11:17:53 +0200360[[classpath]]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700361Classpath
362---------
363
364Each plugin is loaded into its own ClassLoader, isolating plugins
365from each other. A plugin or extension inherits the Java runtime
366and the Gerrit API chosen by `Gerrit-ApiType` (extension or plugin)
367from the hosting server.
368
369Plugins are loaded from a single JAR file. If a plugin needs
370additional libraries, it must include those dependencies within
371its own JAR. Plugins built using Maven may be able to use the
372link:http://maven.apache.org/plugins/maven-shade-plugin/[shade plugin]
373to package additional dependencies. Relocating (or renaming) classes
374should not be necessary due to the ClassLoader isolation.
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700375
Edwin Kempin98202662013-09-18 16:03:03 +0200376[[events]]
377Listening to Events
378-------------------
379
380Certain operations in Gerrit trigger events. Plugins may receive
381notifications of these events by implementing the corresponding
382listeners.
383
Edwin Kempin64059f52013-10-31 13:49:25 +0100384* `com.google.gerrit.common.ChangeListener`:
385+
386Allows to listen to change events. These are the same
387link:cmd-stream-events.html#events[events] that are also streamed by
388the link:cmd-stream-events.html[gerrit stream-events] command.
389
Edwin Kempin98202662013-09-18 16:03:03 +0200390* `com.google.gerrit.extensions.events.LifecycleListener`:
391+
Edwin Kempin3e7928a2013-12-03 07:39:00 +0100392Plugin start and stop
Edwin Kempin98202662013-09-18 16:03:03 +0200393
394* `com.google.gerrit.extensions.events.NewProjectCreatedListener`:
395+
396Project creation
397
398* `com.google.gerrit.extensions.events.ProjectDeletedListener`:
399+
400Project deletion
401
Edwin Kempinb27c9392013-11-19 13:12:43 +0100402* `com.google.gerrit.extensions.events.HeadUpdatedListener`:
403+
404Update of HEAD on a project
405
Yang Zhenhui2659d422013-07-30 16:59:58 +0800406[[stream-events]]
407Sending Events to the Events Stream
408-----------------------------------
409
410Plugins may send events to the events stream where consumers of
411Gerrit's `stream-events` ssh command will receive them.
412
413To send an event, the plugin must invoke one of the `postEvent`
414methods in the `ChangeHookRunner` class, passing an instance of
415its own custom event class derived from `ChangeEvent`.
416
Edwin Kempinf5a77332012-07-18 11:17:53 +0200417[[ssh]]
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700418SSH Commands
419------------
420
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700421Plugins may provide commands that can be accessed through the SSH
422interface (extensions do not have this option).
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700423
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700424Command implementations must extend the base class SshCommand:
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700425
David Pursehouse68153d72013-09-04 10:09:17 +0900426[source,java]
427----
428import com.google.gerrit.sshd.SshCommand;
David Ostrovskyb7d97752013-11-09 05:23:26 +0100429import com.google.gerrit.sshd.CommandMetaData;
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700430
David Ostrovskyb7d97752013-11-09 05:23:26 +0100431@CommandMetaData(name="print", descr="Print hello command")
David Pursehouse68153d72013-09-04 10:09:17 +0900432class PrintHello extends SshCommand {
433 protected abstract void run() {
434 stdout.print("Hello\n");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700435 }
David Pursehouse68153d72013-09-04 10:09:17 +0900436}
437----
Nasser Grainawie033b262012-05-09 17:54:21 -0700438
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700439If no Guice modules are declared in the manifest, SSH commands may
Edwin Kempin948de0f2012-07-16 10:34:35 +0200440use auto-registration by providing an `@Export` annotation:
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700441
David Pursehouse68153d72013-09-04 10:09:17 +0900442[source,java]
443----
444import com.google.gerrit.extensions.annotations.Export;
445import com.google.gerrit.sshd.SshCommand;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700446
David Pursehouse68153d72013-09-04 10:09:17 +0900447@Export("print")
448class PrintHello extends SshCommand {
449 protected abstract void run() {
450 stdout.print("Hello\n");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700451 }
David Pursehouse68153d72013-09-04 10:09:17 +0900452}
453----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700454
455If explicit registration is being used, a Guice module must be
456supplied to register the SSH command and declared in the manifest
457with the `Gerrit-SshModule` attribute:
458
David Pursehouse68153d72013-09-04 10:09:17 +0900459[source,java]
460----
461import com.google.gerrit.sshd.PluginCommandModule;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700462
David Pursehouse68153d72013-09-04 10:09:17 +0900463class MyCommands extends PluginCommandModule {
464 protected void configureCommands() {
David Ostrovskyb7d97752013-11-09 05:23:26 +0100465 command(PrintHello.class);
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700466 }
David Pursehouse68153d72013-09-04 10:09:17 +0900467}
468----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700469
470For a plugin installed as name `helloworld`, the command implemented
471by PrintHello class will be available to users as:
472
473----
Keunhong Parka09a6f12012-07-10 14:45:02 -0600474$ ssh -p 29418 review.example.com helloworld print
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700475----
476
David Ostrovskye3172b32013-10-13 14:19:13 +0200477Multiple SSH commands can be bound to the same implementation class. For
478example a Gerrit Shell plugin can bind different shell commands to the same
479implementation class:
480
481[source,java]
482----
483public class SshShellModule extends PluginCommandModule {
484 @Override
485 protected void configureCommands() {
486 command("ls").to(ShellCommand.class);
487 command("ps").to(ShellCommand.class);
488 [...]
489 }
490}
491----
492
493With the possible implementation:
494
495[source,java]
496----
497public class ShellCommand extends SshCommand {
498 @Override
499 protected void run() throws UnloggedFailure {
500 String cmd = getName().substring(getPluginName().length() + 1);
501 ProcessBuilder proc = new ProcessBuilder(cmd);
502 Process cmd = proc.start();
503 [...]
504 }
505}
506----
507
508And the call:
509
510----
511$ ssh -p 29418 review.example.com shell ls
512$ ssh -p 29418 review.example.com shell ps
513----
514
David Ostrovskyb7d97752013-11-09 05:23:26 +0100515Single command plugins are also supported. In this scenario plugin binds
516SSH command to its own name. `SshModule` must inherit from
517`SingleCommandPluginModule` class:
518
519[source,java]
520----
521public class SshModule extends SingleCommandPluginModule {
522 @Override
523 protected void configure(LinkedBindingBuilder<Command> b) {
524 b.to(ShellCommand.class);
525 }
526}
527----
528
529If the plugin above is deployed under sh.jar file in `$site/plugins`
530directory, generic commands can be called without specifing the
531actual SSH command. Note in the example below, that the called commands
532`ls` and `ps` was not explicitly bound:
533
534----
535$ ssh -p 29418 review.example.com sh ls
536$ ssh -p 29418 review.example.com sh ps
537----
538
Edwin Kempin78ca0942013-10-30 11:24:06 +0100539[[simple-configuration]]
540Simple Configuration in `gerrit.config`
541---------------------------------------
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200542
543In Gerrit, global configuration is stored in the `gerrit.config` file.
544If a plugin needs global configuration, this configuration should be
545stored in a `plugin` subsection in the `gerrit.config` file.
546
Edwin Kempinc9b68602013-10-30 09:32:43 +0100547This approach of storing the plugin configuration is only suitable for
548plugins that have a simple configuration that only consists of
549key-value pairs. With this approach it is not possible to have
550subsections in the plugin configuration. Plugins that require a complex
Edwin Kempin78ca0942013-10-30 11:24:06 +0100551configuration need to store their configuration in their
552link:#configuration[own configuration file] where they can make use of
553subsections. On the other hand storing the plugin configuration in a
554'plugin' subsection in the `gerrit.config` file has the advantage that
555administrators have all configuration parameters in one file, instead
556of having one configuration file per plugin.
Edwin Kempinc9b68602013-10-30 09:32:43 +0100557
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200558To avoid conflicts with other plugins, it is recommended that plugins
559only use the `plugin` subsection with their own name. For example the
560`helloworld` plugin should store its configuration in the
561`plugin.helloworld` subsection:
562
563----
564[plugin "helloworld"]
565 language = Latin
566----
567
Sasa Zivkovacdf5332013-09-20 14:05:15 +0200568Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200569plugin can easily access its configuration and there is no need for a
570plugin to parse the `gerrit.config` file on its own:
571
572[source,java]
573----
David Pursehouse529ec252013-09-27 13:45:14 +0900574@Inject
575private com.google.gerrit.server.config.PluginConfigFactory cfg;
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200576
David Pursehoused128c892013-10-22 21:52:21 +0900577[...]
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200578
Edwin Kempin122622d2013-10-29 16:45:44 +0100579String language = cfg.getFromGerritConfig("helloworld")
David Pursehouse529ec252013-09-27 13:45:14 +0900580 .getString("language", "English");
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200581----
582
Edwin Kempin78ca0942013-10-30 11:24:06 +0100583[[configuration]]
584Configuration in own config file
585--------------------------------
586
587Plugins can store their configuration in an own configuration file.
588This makes sense if the plugin configuration is rather complex and
589requires the usage of subsections. Plugins that have a simple
590key-value pair configuration can store their configuration in a
591link:#simple-configuration[`plugin` subsection of the `gerrit.config`
592file].
593
594The plugin configuration file must be named after the plugin and must
595be located in the `etc` folder of the review site. For example a
596configuration file for a `default-reviewer` plugin could look like
597this:
598
599.$site_path/etc/default-reviewer.config
600----
601[branch "refs/heads/master"]
602 reviewer = Project Owners
603 reviewer = john.doe@example.com
604[match "file:^.*\.txt"]
605 reviewer = My Info Developers
606----
607
608Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
609plugin can easily access its configuration:
610
611[source,java]
612----
613@Inject
614private com.google.gerrit.server.config.PluginConfigFactory cfg;
615
616[...]
617
618String[] reviewers = cfg.getGlobalPluginConfig("default-reviewer")
619 .getStringList("branch", "refs/heads/master", "reviewer");
620----
621
622The plugin configuration is loaded only once and is then cached.
623Similar to changes in 'gerrit.config', changes to the plugin
624configuration file will only become effective after a Gerrit restart.
625
Edwin Kempin705f2842013-10-30 14:25:31 +0100626[[simple-project-specific-configuration]]
627Simple Project Specific Configuration in `project.config`
628---------------------------------------------------------
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200629
630In Gerrit, project specific configuration is stored in the project's
631`project.config` file on the `refs/meta/config` branch. If a plugin
632needs configuration on project level (e.g. to enable its functionality
633only for certain projects), this configuration should be stored in a
634`plugin` subsection in the project's `project.config` file.
635
Edwin Kempinc9b68602013-10-30 09:32:43 +0100636This approach of storing the plugin configuration is only suitable for
637plugins that have a simple configuration that only consists of
638key-value pairs. With this approach it is not possible to have
639subsections in the plugin configuration. Plugins that require a complex
Edwin Kempin705f2842013-10-30 14:25:31 +0100640configuration need to store their configuration in their
641link:#project-specific-configuration[own configuration file] where they
642can make use of subsections. On the other hand storing the plugin
643configuration in a 'plugin' subsection in the `project.config` file has
644the advantage that project owners have all configuration parameters in
645one file, instead of having one configuration file per plugin.
Edwin Kempinc9b68602013-10-30 09:32:43 +0100646
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200647To avoid conflicts with other plugins, it is recommended that plugins
648only use the `plugin` subsection with their own name. For example the
649`helloworld` plugin should store its configuration in the
650`plugin.helloworld` subsection:
651
652----
653 [plugin "helloworld"]
654 enabled = true
655----
656
657Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
658plugin can easily access its project specific configuration and there
659is no need for a plugin to parse the `project.config` file on its own:
660
661[source,java]
662----
David Pursehouse529ec252013-09-27 13:45:14 +0900663@Inject
664private com.google.gerrit.server.config.PluginConfigFactory cfg;
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200665
David Pursehoused128c892013-10-22 21:52:21 +0900666[...]
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200667
Edwin Kempin122622d2013-10-29 16:45:44 +0100668boolean enabled = cfg.getFromProjectConfig(project, "helloworld")
David Pursehouse529ec252013-09-27 13:45:14 +0900669 .getBoolean("enabled", false);
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200670----
671
Edwin Kempinca7ad8e2013-09-16 16:43:05 +0200672It is also possible to get missing configuration parameters inherited
673from the parent projects:
674
675[source,java]
676----
David Pursehouse529ec252013-09-27 13:45:14 +0900677@Inject
678private com.google.gerrit.server.config.PluginConfigFactory cfg;
Edwin Kempinca7ad8e2013-09-16 16:43:05 +0200679
David Pursehoused128c892013-10-22 21:52:21 +0900680[...]
Edwin Kempinca7ad8e2013-09-16 16:43:05 +0200681
Edwin Kempin122622d2013-10-29 16:45:44 +0100682boolean enabled = cfg.getFromProjectConfigWithInheritance(project, "helloworld")
David Pursehouse529ec252013-09-27 13:45:14 +0900683 .getBoolean("enabled", false);
Edwin Kempinca7ad8e2013-09-16 16:43:05 +0200684----
685
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200686Project owners can edit the project configuration by fetching the
687`refs/meta/config` branch, editing the `project.config` file and
688pushing the commit back.
689
Edwin Kempin705f2842013-10-30 14:25:31 +0100690[[project-specific-configuration]]
691Project Specific Configuration in own config file
692-------------------------------------------------
693
694Plugins can store their project specific configuration in an own
695configuration file in the projects `refs/meta/config` branch.
696This makes sense if the plugins project specific configuration is
697rather complex and requires the usage of subsections. Plugins that
698have a simple key-value pair configuration can store their project
699specific configuration in a link:#simple-project-specific-configuration[
700`plugin` subsection of the `project.config` file].
701
702The plugin configuration file in the `refs/meta/config` branch must be
703named after the plugin. For example a configuration file for a
704`default-reviewer` plugin could look like this:
705
706.default-reviewer.config
707----
708[branch "refs/heads/master"]
709 reviewer = Project Owners
710 reviewer = john.doe@example.com
711[match "file:^.*\.txt"]
712 reviewer = My Info Developers
713----
714
715Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
716plugin can easily access its project specific configuration:
717
718[source,java]
719----
720@Inject
721private com.google.gerrit.server.config.PluginConfigFactory cfg;
722
723[...]
724
725String[] reviewers = cfg.getProjectPluginConfig(project, "default-reviewer")
726 .getStringList("branch", "refs/heads/master", "reviewer");
727----
728
Edwin Kempin762da382013-10-30 14:50:01 +0100729It is also possible to get missing configuration parameters inherited
730from the parent projects:
731
732[source,java]
733----
734@Inject
735private com.google.gerrit.server.config.PluginConfigFactory cfg;
736
737[...]
738
739String[] reviewers = cfg.getFromPluginConfigWithInheritance(project, "default-reviewer")
740 .getStringList("branch", "refs/heads/master", "reviewer");
741----
742
Edwin Kempin705f2842013-10-30 14:25:31 +0100743Project owners can edit the project configuration by fetching the
744`refs/meta/config` branch, editing the `<plugin-name>.config` file and
745pushing the commit back.
746
Edwin Kempina46b6c92013-12-04 21:05:24 +0100747React on changes in project configuration
748-----------------------------------------
749
750If a plugin wants to react on changes in the project configuration, it
751can implement a `GitReferenceUpdatedListener` and filter on events for
752the `refs/meta/config` branch:
753
754[source,java]
755----
756public class MyListener implements GitReferenceUpdatedListener {
757
758 private final MetaDataUpdate.Server metaDataUpdateFactory;
759
760 @Inject
761 MyListener(MetaDataUpdate.Server metaDataUpdateFactory) {
762 this.metaDataUpdateFactory = metaDataUpdateFactory;
763 }
764
765 @Override
766 public void onGitReferenceUpdated(Event event) {
767 if (event.getRefName().equals(GitRepositoryManager.REF_CONFIG)) {
768 Project.NameKey p = new Project.NameKey(event.getProjectName());
769 try {
770 ProjectConfig oldCfg =
771 ProjectConfig.read(metaDataUpdateFactory.create(p),
772 ObjectId.fromString(event.getOldObjectId()));
773 ProjectConfig newCfg =
774 ProjectConfig.read(metaDataUpdateFactory.create(p),
775 ObjectId.fromString(event.getNewObjectId()));
776
777 if (!oldCfg.getProject().getSubmitType().equals(
778 newCfg.getProject().getSubmitType())) {
779 // submit type has changed
780 ...
781 }
782 } catch (IOException | ConfigInvalidException e) {
783 ...
784 }
785 }
786 }
787}
788----
789
790
David Ostrovsky7066cc02013-06-15 14:46:23 +0200791[[capabilities]]
792Plugin Owned Capabilities
793-------------------------
794
795Plugins may provide their own capabilities and restrict usage of SSH
796commands to the users who are granted those capabilities.
797
798Plugins define the capabilities by overriding the `CapabilityDefinition`
799abstract class:
800
David Pursehouse68153d72013-09-04 10:09:17 +0900801[source,java]
802----
803public class PrintHelloCapability extends CapabilityDefinition {
804 @Override
805 public String getDescription() {
806 return "Print Hello";
David Ostrovsky7066cc02013-06-15 14:46:23 +0200807 }
David Pursehouse68153d72013-09-04 10:09:17 +0900808}
809----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200810
David Ostrovskyf86bae52013-09-01 09:10:39 +0200811If no Guice modules are declared in the manifest, UI actions may
David Ostrovsky7066cc02013-06-15 14:46:23 +0200812use auto-registration by providing an `@Export` annotation:
813
David Pursehouse68153d72013-09-04 10:09:17 +0900814[source,java]
815----
816@Export("printHello")
817public class PrintHelloCapability extends CapabilityDefinition {
David Pursehoused128c892013-10-22 21:52:21 +0900818 [...]
David Pursehouse68153d72013-09-04 10:09:17 +0900819}
820----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200821
822Otherwise the capability must be bound in a plugin module:
823
David Pursehouse68153d72013-09-04 10:09:17 +0900824[source,java]
825----
826public class HelloWorldModule extends AbstractModule {
827 @Override
828 protected void configure() {
829 bind(CapabilityDefinition.class)
830 .annotatedWith(Exports.named("printHello"))
831 .to(PrintHelloCapability.class);
David Ostrovsky7066cc02013-06-15 14:46:23 +0200832 }
David Pursehouse68153d72013-09-04 10:09:17 +0900833}
834----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200835
836With a plugin-owned capability defined in this way, it is possible to restrict
David Ostrovskyf86bae52013-09-01 09:10:39 +0200837usage of an SSH command or `UiAction` to members of the group that were granted
David Ostrovsky7066cc02013-06-15 14:46:23 +0200838this capability in the usual way, using the `RequiresCapability` annotation:
839
David Pursehouse68153d72013-09-04 10:09:17 +0900840[source,java]
841----
842@RequiresCapability("printHello")
843@CommandMetaData(name="print", description="Print greeting in different languages")
844public final class PrintHelloWorldCommand extends SshCommand {
David Pursehoused128c892013-10-22 21:52:21 +0900845 [...]
David Pursehouse68153d72013-09-04 10:09:17 +0900846}
847----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200848
David Ostrovskyf86bae52013-09-01 09:10:39 +0200849Or with `UiAction`:
David Ostrovsky7066cc02013-06-15 14:46:23 +0200850
David Pursehouse68153d72013-09-04 10:09:17 +0900851[source,java]
852----
853@RequiresCapability("printHello")
854public class SayHelloAction extends UiAction<RevisionResource>
855 implements RestModifyView<RevisionResource, SayHelloAction.Input> {
David Pursehoused128c892013-10-22 21:52:21 +0900856 [...]
David Pursehouse68153d72013-09-04 10:09:17 +0900857}
858----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200859
860Capability scope was introduced to differentiate between plugin-owned
David Pursehousebf053342013-09-05 14:55:29 +0900861capabilities and core capabilities. Per default the scope of the
862`@RequiresCapability` annotation is `CapabilityScope.CONTEXT`, that means:
863
David Ostrovsky7066cc02013-06-15 14:46:23 +0200864* when `@RequiresCapability` is used within a plugin the scope of the
865capability is assumed to be that plugin.
David Pursehousebf053342013-09-05 14:55:29 +0900866
David Ostrovsky7066cc02013-06-15 14:46:23 +0200867* If `@RequiresCapability` is used within the core Gerrit Code Review server
868(and thus is outside of a plugin) the scope is the core server and will use
869the `GlobalCapability` known to Gerrit Code Review server.
870
871If a plugin needs to use a core capability name (e.g. "administrateServer")
872this can be specified by setting `scope = CapabilityScope.CORE`:
873
David Pursehouse68153d72013-09-04 10:09:17 +0900874[source,java]
875----
876@RequiresCapability(value = "administrateServer", scope =
877 CapabilityScope.CORE)
David Pursehoused128c892013-10-22 21:52:21 +0900878 [...]
David Pursehouse68153d72013-09-04 10:09:17 +0900879----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200880
David Ostrovskyf86bae52013-09-01 09:10:39 +0200881[[ui_extension]]
882UI Extension
883------------
884
Edwin Kempin7afa73c2013-11-08 07:48:47 +0100885Plugins can contribute UI actions on core Gerrit pages. This is useful
886for workflow customization or exposing plugin functionality through the
887UI in addition to SSH commands and the REST API.
David Ostrovskyf86bae52013-09-01 09:10:39 +0200888
Edwin Kempin7afa73c2013-11-08 07:48:47 +0100889For instance a plugin to integrate Jira with Gerrit changes may
890contribute a "File bug" button to allow filing a bug from the change
891page or plugins to integrate continuous integration systems may
892contribute a "Schedule" button to allow a CI build to be scheduled
893manually from the patch set panel.
David Ostrovskyf86bae52013-09-01 09:10:39 +0200894
Edwin Kempin7afa73c2013-11-08 07:48:47 +0100895Two different places on core Gerrit pages are supported:
David Ostrovskyf86bae52013-09-01 09:10:39 +0200896
897* Change screen
898* Project info screen
899
900Plugins contribute UI actions by implementing the `UiAction` interface:
901
David Pursehouse68153d72013-09-04 10:09:17 +0900902[source,java]
903----
904@RequiresCapability("printHello")
905class HelloWorldAction implements UiAction<RevisionResource>,
906 RestModifyView<RevisionResource, HelloWorldAction.Input> {
907 static class Input {
908 boolean french;
909 String message;
David Ostrovskyf86bae52013-09-01 09:10:39 +0200910 }
David Pursehouse68153d72013-09-04 10:09:17 +0900911
912 private Provider<CurrentUser> user;
913
914 @Inject
915 HelloWorldAction(Provider<CurrentUser> user) {
916 this.user = user;
917 }
918
919 @Override
920 public String apply(RevisionResource rev, Input input) {
921 final String greeting = input.french
922 ? "Bonjour"
923 : "Hello";
924 return String.format("%s %s from change %s, patch set %d!",
925 greeting,
926 Strings.isNullOrEmpty(input.message)
927 ? Objects.firstNonNull(user.get().getUserName(), "world")
928 : input.message,
929 rev.getChange().getId().toString(),
930 rev.getPatchSet().getPatchSetId());
931 }
932
933 @Override
934 public Description getDescription(
935 RevisionResource resource) {
936 return new Description()
937 .setLabel("Say hello")
938 .setTitle("Say hello in different languages");
939 }
940}
941----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200942
David Ostrovsky450eefe2013-10-21 21:18:11 +0200943Sometimes plugins may want to be able to change the state of a patch set or
944change in the `UiAction.apply()` method and reflect these changes on the core
945UI. For example a buildbot plugin which exposes a 'Schedule' button on the
946patch set panel may want to disable that button after the build was scheduled
947and update the tooltip of that button. But because of Gerrit's caching
948strategy the following must be taken into consideration.
949
950The browser is allowed to cache the `UiAction` information until something on
951the change is modified. More accurately the change row needs to be modified in
952the database to have a more recent `lastUpdatedOn` or a new `rowVersion`, or
953the +refs/meta/config+ of the project or any parents needs to change to a new
954SHA-1. The ETag SHA-1 computation code can be found in the
955`ChangeResource.getETag()` method.
956
David Pursehoused128c892013-10-22 21:52:21 +0900957The easiest way to accomplish this is to update `lastUpdatedOn` of the change:
David Ostrovsky450eefe2013-10-21 21:18:11 +0200958
959[source,java]
960----
961@Override
962public Object apply(RevisionResource rcrs, Input in) {
963 // schedule a build
964 [...]
965 // update change
966 ReviewDb db = dbProvider.get();
967 db.changes().beginTransaction(change.getId());
968 try {
969 change = db.changes().atomicUpdate(
970 change.getId(),
971 new AtomicUpdate<Change>() {
972 @Override
973 public Change update(Change change) {
974 ChangeUtil.updated(change);
975 return change;
976 }
977 });
978 db.commit();
979 } finally {
980 db.rollback();
981 }
David Pursehoused128c892013-10-22 21:52:21 +0900982 [...]
David Ostrovsky450eefe2013-10-21 21:18:11 +0200983}
984----
985
David Ostrovskyf86bae52013-09-01 09:10:39 +0200986`UiAction` must be bound in a plugin module:
987
David Pursehouse68153d72013-09-04 10:09:17 +0900988[source,java]
989----
990public class Module extends AbstractModule {
991 @Override
992 protected void configure() {
993 install(new RestApiModule() {
994 @Override
995 protected void configure() {
996 post(REVISION_KIND, "say-hello")
997 .to(HelloWorldAction.class);
998 }
999 });
David Ostrovskyf86bae52013-09-01 09:10:39 +02001000 }
David Pursehouse68153d72013-09-04 10:09:17 +09001001}
1002----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001003
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001004The module above must be declared in the `pom.xml` for Maven driven
1005plugins:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001006
David Pursehouse68153d72013-09-04 10:09:17 +09001007[source,xml]
1008----
1009<manifestEntries>
1010 <Gerrit-Module>com.googlesource.gerrit.plugins.cookbook.Module</Gerrit-Module>
1011</manifestEntries>
1012----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001013
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001014or in the `BUCK` configuration file for Buck driven plugins:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001015
David Pursehouse68153d72013-09-04 10:09:17 +09001016[source,python]
1017----
1018manifest_entries = [
1019 'Gerrit-Module: com.googlesource.gerrit.plugins.cookbook.Module',
1020]
1021----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001022
1023In some use cases more user input must be gathered, for that `UiAction` can be
1024combined with the JavaScript API. This would display a small popup near the
1025activation button to gather additional input from the user. The JS file is
1026typically put in the `static` folder within the plugin's directory:
1027
David Pursehouse68153d72013-09-04 10:09:17 +09001028[source,javascript]
1029----
1030Gerrit.install(function(self) {
1031 function onSayHello(c) {
1032 var f = c.textfield();
1033 var t = c.checkbox();
1034 var b = c.button('Say hello', {onclick: function(){
1035 c.call(
1036 {message: f.value, french: t.checked},
1037 function(r) {
1038 c.hide();
1039 window.alert(r);
1040 c.refresh();
1041 });
1042 }});
1043 c.popup(c.div(
1044 c.prependLabel('Greeting message', f),
1045 c.br(),
1046 c.label(t, 'french'),
1047 c.br(),
1048 b));
1049 f.focus();
1050 }
1051 self.onAction('revision', 'say-hello', onSayHello);
1052});
1053----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001054
1055The JS module must be exposed as a `WebUiPlugin` and bound as
1056an HTTP Module:
1057
David Pursehouse68153d72013-09-04 10:09:17 +09001058[source,java]
1059----
1060public class HttpModule extends HttpPluginModule {
1061 @Override
1062 protected void configureServlets() {
1063 DynamicSet.bind(binder(), WebUiPlugin.class)
1064 .toInstance(new JavaScriptPlugin("hello.js"));
David Ostrovskyf86bae52013-09-01 09:10:39 +02001065 }
David Pursehouse68153d72013-09-04 10:09:17 +09001066}
1067----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001068
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001069The HTTP module above must be declared in the `pom.xml` for Maven
1070driven plugins:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001071
David Pursehouse68153d72013-09-04 10:09:17 +09001072[source,xml]
1073----
1074<manifestEntries>
1075 <Gerrit-HttpModule>com.googlesource.gerrit.plugins.cookbook.HttpModule</Gerrit-HttpModule>
1076</manifestEntries>
1077----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001078
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001079or in the `BUCK` configuration file for Buck driven plugins
David Ostrovskyf86bae52013-09-01 09:10:39 +02001080
David Pursehouse68153d72013-09-04 10:09:17 +09001081[source,python]
1082----
1083manifest_entries = [
1084 'Gerrit-HttpModule: com.googlesource.gerrit.plugins.cookbook.HttpModule',
1085]
1086----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001087
1088If `UiAction` is annotated with the `@RequiresCapability` annotation, then the
1089capability check is done during the `UiAction` gathering, so the plugin author
1090doesn't have to set `UiAction.Description.setVisible()` explicitly in this
1091case.
1092
1093The following prerequisities must be met, to satisfy the capability check:
1094
1095* user is authenticated
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001096* user is a member of a group which has the `Administrate Server` capability, or
David Ostrovskyf86bae52013-09-01 09:10:39 +02001097* user is a member of a group which has the required capability
1098
1099The `apply` method is called when the button is clicked. If `UiAction` is
1100combined with JavaScript API (its own JavaScript function is provided),
1101then a popup dialog is normally opened to gather additional user input.
1102A new button is placed on the popup dialog to actually send the request.
1103
1104Every `UiAction` exposes a REST API endpoint. The endpoint from the example above
1105can be accessed from any REST client, i. e.:
1106
1107====
1108 curl -X POST -H "Content-Type: application/json" \
1109 -d '{message: "François", french: true}' \
1110 --digest --user joe:secret \
1111 http://host:port/a/changes/1/revisions/1/cookbook~say-hello
1112 "Bonjour François from change 1, patch set 1!"
1113====
1114
David Pursehouse42245822013-09-24 09:48:20 +09001115A special case is to bind an endpoint without a view name. This is
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001116particularly useful for `DELETE` requests:
David Ostrovskyc6d19ed2013-09-20 21:30:18 +02001117
1118[source,java]
1119----
1120public class Module extends AbstractModule {
1121 @Override
1122 protected void configure() {
1123 install(new RestApiModule() {
1124 @Override
1125 protected void configure() {
1126 delete(PROJECT_KIND)
1127 .to(DeleteProject.class);
1128 }
1129 });
1130 }
1131}
1132----
1133
David Pursehouse42245822013-09-24 09:48:20 +09001134For a `UiAction` bound this way, a JS API function can be provided.
1135
1136Currently only one restriction exists: per plugin only one `UiAction`
David Ostrovskyc6d19ed2013-09-20 21:30:18 +02001137can be bound per resource without view name. To define a JS function
1138for the `UiAction`, "/" must be used as the name:
1139
1140[source,javascript]
1141----
1142Gerrit.install(function(self) {
1143 function onDeleteProject(c) {
1144 [...]
1145 }
1146 self.onAction('project', '/', onDeleteProject);
1147});
1148----
1149
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001150[[top-menu-extensions]]
1151Top Menu Extensions
1152-------------------
1153
1154Plugins can contribute items to Gerrit's top menu.
1155
1156A single top menu extension can have multiple elements and will be put as
1157the last element in Gerrit's top menu.
1158
1159Plugins define the top menu entries by implementing `TopMenu` interface:
1160
1161[source,java]
1162----
1163public class MyTopMenuExtension implements TopMenu {
1164
1165 @Override
1166 public List<MenuEntry> getEntries() {
1167 return Lists.newArrayList(
1168 new MenuEntry("Top Menu Entry", Lists.newArrayList(
1169 new MenuItem("Gerrit", "http://gerrit.googlecode.com/"))));
1170 }
1171}
1172----
1173
Edwin Kempin77f23242013-09-30 14:53:20 +02001174Plugins can also add additional menu items to Gerrit's top menu entries
1175by defining a `MenuEntry` that has the same name as a Gerrit top menu
1176entry:
1177
1178[source,java]
1179----
1180public class MyTopMenuExtension implements TopMenu {
1181
1182 @Override
1183 public List<MenuEntry> getEntries() {
1184 return Lists.newArrayList(
Dariusz Luksza2d3afab2013-10-01 11:07:13 +02001185 new MenuEntry(GerritTopMenu.PROJECTS, Lists.newArrayList(
Edwin Kempin77f23242013-09-30 14:53:20 +02001186 new MenuItem("Browse Repositories", "https://gerrit.googlesource.com/"))));
1187 }
1188}
1189----
1190
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001191If no Guice modules are declared in the manifest, the top menu extension may use
1192auto-registration by providing an `@Listen` annotation:
1193
1194[source,java]
1195----
1196@Listen
1197public class MyTopMenuExtension implements TopMenu {
David Pursehoused128c892013-10-22 21:52:21 +09001198 [...]
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001199}
1200----
1201
Luca Milanesiocb230402013-10-11 08:49:56 +01001202Otherwise the top menu extension must be bound in the plugin module used
1203for the Gerrit system injector (Gerrit-Module entry in MANIFEST.MF):
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001204
1205[source,java]
1206----
Luca Milanesiocb230402013-10-11 08:49:56 +01001207package com.googlesource.gerrit.plugins.helloworld;
1208
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001209public class HelloWorldModule extends AbstractModule {
1210 @Override
1211 protected void configure() {
1212 DynamicSet.bind(binder(), TopMenu.class).to(MyTopMenuExtension.class);
1213 }
1214}
1215----
1216
Luca Milanesiocb230402013-10-11 08:49:56 +01001217[source,manifest]
1218----
1219Gerrit-ApiType: plugin
1220Gerrit-Module: com.googlesource.gerrit.plugins.helloworld.HelloWorldModule
1221----
1222
Edwin Kempinb2e926a2013-11-11 16:38:30 +01001223It is also possible to show some menu entries only if the user has a
1224certain capability:
1225
1226[source,java]
1227----
1228public class MyTopMenuExtension implements TopMenu {
1229 private final String pluginName;
1230 private final Provider<CurrentUser> userProvider;
1231 private final List<MenuEntry> menuEntries;
1232
1233 @Inject
1234 public MyTopMenuExtension(@PluginName String pluginName,
1235 Provider<CurrentUser> userProvider) {
1236 this.pluginName = pluginName;
1237 this.userProvider = userProvider;
1238 menuEntries = new ArrayList<TopMenu.MenuEntry>();
1239
1240 // add menu entry that is only visible to users with a certain capability
1241 if (canSeeMenuEntry()) {
1242 menuEntries.add(new MenuEntry("Top Menu Entry", Collections
1243 .singletonList(new MenuItem("Gerrit", "http://gerrit.googlecode.com/"))));
1244 }
1245
1246 // add menu entry that is visible to all users (even anonymous users)
1247 menuEntries.add(new MenuEntry("Top Menu Entry", Collections
1248 .singletonList(new MenuItem("Documentation", "/plugins/myplugin/"))));
1249 }
1250
1251 private boolean canSeeMenuEntry() {
1252 if (userProvider.get().isIdentifiedUser()) {
1253 CapabilityControl ctl = userProvider.get().getCapabilities();
1254 return ctl.canPerform(pluginName + "-" + MyCapability.ID)
1255 || ctl.canAdministrateServer();
1256 } else {
1257 return false;
1258 }
1259 }
1260
1261 @Override
1262 public List<MenuEntry> getEntries() {
1263 return menuEntries;
1264 }
1265}
1266----
1267
Edwin Kempin3c024ea2013-11-11 10:43:46 +01001268[[gwt_ui_extension]]
1269GWT UI Extension
1270----------------
1271Plugins can extend the Gerrit UI with own GWT code.
1272
1273The Maven archetype 'gerrit-plugin-gwt-archetype' can be used to
1274generate a GWT plugin skeleton. How to use the Maven plugin archetypes
1275is described in the link:#getting-started[Getting started] section.
1276
1277The generated GWT plugin has a link:#top-menu-extensions[top menu] that
1278opens a GWT dialog box when the user clicks on it.
1279
Edwin Kempinb74daa92013-11-11 11:28:16 +01001280In addition to the Gerrit-Plugin API a GWT plugin depends on
1281`gerrit-plugin-gwtui`. This dependency must be specified in the
1282`pom.xml`:
1283
1284[source,xml]
1285----
1286<dependency>
1287 <groupId>com.google.gerrit</groupId>
1288 <artifactId>gerrit-plugin-gwtui</artifactId>
1289 <version>${Gerrit-ApiVersion}</version>
1290</dependency>
1291----
1292
1293A GWT plugin must contain a GWT module file, e.g. `HelloPlugin.gwt.xml`,
1294that bundles together all the configuration settings of the GWT plugin:
1295
1296[source,xml]
1297----
1298<?xml version="1.0" encoding="UTF-8"?>
1299<module rename-to="hello_gwt_plugin">
1300 <!-- Inherit the core Web Toolkit stuff. -->
1301 <inherits name="com.google.gwt.user.User"/>
1302 <!-- Other module inherits -->
1303 <inherits name="com.google.gerrit.Plugin"/>
1304 <inherits name="com.google.gwt.http.HTTP"/>
1305 <!-- Using GWT built-in themes adds a number of static -->
1306 <!-- resources to the plugin. No theme inherits lines were -->
1307 <!-- added in order to make this plugin as simple as possible -->
1308 <!-- Specify the app entry point class. -->
1309 <entry-point class="${package}.client.HelloPlugin"/>
1310 <stylesheet src="hello.css"/>
1311</module>
1312----
1313
1314The GWT module must inherit `com.google.gerrit.Plugin` and
1315`com.google.gwt.http.HTTP`.
1316
1317To register the GWT module a `GwtPlugin` needs to be bound.
1318
1319If no Guice modules are declared in the manifest, the GWT plugin may
1320use auto-registration by using the `@Listen` annotation:
1321
1322[source,java]
1323----
1324@Listen
1325public class MyExtension extends GwtPlugin {
1326 public MyExtension() {
1327 super("hello_gwt_plugin");
1328 }
1329}
1330----
1331
1332Otherwise the binding must be done in an `HttpModule`:
1333
1334[source,java]
1335----
1336public class HttpModule extends HttpPluginModule {
1337
1338 @Override
1339 protected void configureServlets() {
1340 DynamicSet.bind(binder(), WebUiPlugin.class)
1341 .toInstance(new GwtPlugin("hello_gwt_plugin"));
1342 }
1343}
1344----
1345
1346The HTTP module above must be declared in the `pom.xml` for Maven
1347driven plugins:
1348
1349[source,xml]
1350----
1351<manifestEntries>
1352 <Gerrit-HttpModule>com.googlesource.gerrit.plugins.myplugin.HttpModule</Gerrit-HttpModule>
1353</manifestEntries>
1354----
1355
1356It is important that the module name that is provided to the
1357`GwtPlugin` matches the GWT module contained in the plugin. The name
1358of the GWT module can be explicitly set in the GWT module file by
1359specifying the `rename-to` attribute on the module.
1360
1361[source,xml]
1362----
1363<module rename-to="hello_gwt_plugin">
1364----
1365
1366The actual GWT code must be implemented in a class that extends
1367`com.google.gerrit.plugin.client.Plugin`:
1368
1369[source,java]
1370----
1371public class HelloPlugin extends Plugin {
1372
1373 @Override
1374 public void onModuleLoad() {
1375 // Create the dialog box
1376 final DialogBox dialogBox = new DialogBox();
1377
1378 // The content of the dialog comes from a User specified Preference
1379 dialogBox.setText("Hello from GWT Gerrit UI plugin");
1380 dialogBox.setAnimationEnabled(true);
1381 Button closeButton = new Button("Close");
1382 VerticalPanel dialogVPanel = new VerticalPanel();
1383 dialogVPanel.setWidth("100%");
1384 dialogVPanel.setHorizontalAlignment(VerticalPanel.ALIGN_CENTER);
1385 dialogVPanel.add(closeButton);
1386
1387 closeButton.addClickHandler(new ClickHandler() {
1388 public void onClick(ClickEvent event) {
1389 dialogBox.hide();
1390 }
1391 });
1392
1393 // Set the contents of the Widget
1394 dialogBox.setWidget(dialogVPanel);
1395
1396 RootPanel rootPanel = RootPanel.get(HelloMenu.MENU_ID);
1397 rootPanel.getElement().removeAttribute("href");
1398 rootPanel.addDomHandler(new ClickHandler() {
1399 @Override
1400 public void onClick(ClickEvent event) {
1401 dialogBox.center();
1402 dialogBox.show();
1403 }
1404 }, ClickEvent.getType());
1405 }
1406}
1407----
1408
1409This class must be set as entry point in the GWT module:
1410
1411[source,xml]
1412----
1413<entry-point class="${package}.client.HelloPlugin"/>
1414----
1415
1416In addition this class must be defined as module in the `pom.xml` for the
1417`gwt-maven-plugin` and the `webappDirectory` option of `gwt-maven-plugin`
1418must be set to `${project.build.directory}/classes/static`:
1419
1420[source,xml]
1421----
1422<plugin>
1423 <groupId>org.codehaus.mojo</groupId>
1424 <artifactId>gwt-maven-plugin</artifactId>
1425 <version>2.5.1</version>
1426 <configuration>
1427 <module>com.googlesource.gerrit.plugins.myplugin.HelloPlugin</module>
1428 <disableClassMetadata>true</disableClassMetadata>
1429 <disableCastChecking>true</disableCastChecking>
1430 <webappDirectory>${project.build.directory}/classes/static</webappDirectory>
1431 </configuration>
1432 <executions>
1433 <execution>
1434 <goals>
1435 <goal>compile</goal>
1436 </goals>
1437 </execution>
1438 </executions>
1439</plugin>
1440----
1441
1442To attach a GWT widget defined by the plugin to the Gerrit core UI
1443`com.google.gwt.user.client.ui.RootPanel` can be used to manipulate the
1444Gerrit core widgets:
1445
1446[source,java]
1447----
1448RootPanel rootPanel = RootPanel.get(HelloMenu.MENU_ID);
1449rootPanel.getElement().removeAttribute("href");
1450rootPanel.addDomHandler(new ClickHandler() {
1451 @Override
1452 public void onClick(ClickEvent event) {
1453 dialogBox.center();
1454 dialogBox.show();
1455 }
1456}, ClickEvent.getType());
1457----
1458
1459GWT plugins can come with their own css file. This css file must have a
1460unique name and must be registered in the GWT module:
1461
1462[source,xml]
1463----
1464<stylesheet src="hello.css"/>
1465----
1466
Edwin Kempin2570b102013-11-11 11:44:50 +01001467If a GWT plugin wants to invoke the Gerrit REST API it can use
1468`com.google.gerrit.plugin.client.rpc.RestApi` to contruct the URL
1469path and to trigger the REST calls.
1470
1471Example for invoking a Gerrit core REST endpoint:
1472
1473[source,java]
1474----
1475new RestApi("projects").id(projectName).view("description")
1476 .put("new description", new AsyncCallback<JavaScriptObject>() {
1477
1478 @Override
1479 public void onSuccess(JavaScriptObject result) {
1480 // TODO
1481 }
1482
1483 @Override
1484 public void onFailure(Throwable caught) {
1485 // never invoked
1486 }
1487});
1488----
1489
1490Example for invoking a REST endpoint defined by a plugin:
1491
1492[source,java]
1493----
1494new RestApi("projects").id(projectName).view("myplugin", "myview")
1495 .get(new AsyncCallback<JavaScriptObject>() {
1496
1497 @Override
1498 public void onSuccess(JavaScriptObject result) {
1499 // TODO
1500 }
1501
1502 @Override
1503 public void onFailure(Throwable caught) {
1504 // never invoked
1505 }
1506});
1507----
1508
1509The `onFailure(Throwable)` of the provided callback is never invoked.
1510If an error occurs, it is shown in an error dialog.
1511
1512In order to be able to do REST calls the GWT module must inherit
1513`com.google.gwt.json.JSON`:
1514
1515[source,xml]
1516----
1517<inherits name="com.google.gwt.json.JSON"/>
1518----
1519
Edwin Kempinf5a77332012-07-18 11:17:53 +02001520[[http]]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001521HTTP Servlets
1522-------------
1523
1524Plugins or extensions may register additional HTTP servlets, and
1525wrap them with HTTP filters.
1526
1527Servlets may use auto-registration to declare the URL they handle:
1528
David Pursehouse68153d72013-09-04 10:09:17 +09001529[source,java]
1530----
1531import com.google.gerrit.extensions.annotations.Export;
1532import com.google.inject.Singleton;
1533import javax.servlet.http.HttpServlet;
1534import javax.servlet.http.HttpServletRequest;
1535import javax.servlet.http.HttpServletResponse;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001536
David Pursehouse68153d72013-09-04 10:09:17 +09001537@Export("/print")
1538@Singleton
1539class HelloServlet extends HttpServlet {
1540 protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
1541 res.setContentType("text/plain");
1542 res.setCharacterEncoding("UTF-8");
1543 res.getWriter().write("Hello");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001544 }
David Pursehouse68153d72013-09-04 10:09:17 +09001545}
1546----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001547
Edwin Kempin8aa650f2012-07-18 11:25:48 +02001548The auto registration only works for standard servlet mappings like
1549`/foo` or `/foo/*`. Regex style bindings must use a Guice ServletModule
1550to register the HTTP servlets and declare it explicitly in the manifest
1551with the `Gerrit-HttpModule` attribute:
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001552
David Pursehouse68153d72013-09-04 10:09:17 +09001553[source,java]
1554----
1555import com.google.inject.servlet.ServletModule;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001556
David Pursehouse68153d72013-09-04 10:09:17 +09001557class MyWebUrls extends ServletModule {
1558 protected void configureServlets() {
1559 serve("/print").with(HelloServlet.class);
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001560 }
David Pursehouse68153d72013-09-04 10:09:17 +09001561}
1562----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001563
1564For a plugin installed as name `helloworld`, the servlet implemented
1565by HelloServlet class will be available to users as:
1566
1567----
1568$ curl http://review.example.com/plugins/helloworld/print
1569----
Nasser Grainawie033b262012-05-09 17:54:21 -07001570
Edwin Kempinf5a77332012-07-18 11:17:53 +02001571[[data-directory]]
Edwin Kempin41f63912012-07-17 12:33:55 +02001572Data Directory
1573--------------
1574
1575Plugins can request a data directory with a `@PluginData` File
1576dependency. A data directory will be created automatically by the
1577server in `$site_path/data/$plugin_name` and passed to the plugin.
1578
1579Plugins can use this to store any data they want.
1580
David Pursehouse68153d72013-09-04 10:09:17 +09001581[source,java]
1582----
1583@Inject
1584MyType(@PluginData java.io.File myDir) {
1585 new FileInputStream(new File(myDir, "my.config"));
1586}
1587----
Edwin Kempin41f63912012-07-17 12:33:55 +02001588
Edwin Kempinea621482013-10-16 12:58:24 +02001589[[download-commands]]
1590Download Commands
1591-----------------
1592
1593Gerrit offers commands for downloading changes using different
1594download schemes (e.g. for downloading via different network
1595protocols). Plugins can contribute download schemes and download
1596commands by implementing
1597`com.google.gerrit.extensions.config.DownloadScheme` and
1598`com.google.gerrit.extensions.config.DownloadCommand`.
1599
1600The download schemes and download commands which are used most often
1601are provided by the Gerrit core plugin `download-commands`.
1602
Edwin Kempinf5a77332012-07-18 11:17:53 +02001603[[documentation]]
Nasser Grainawie033b262012-05-09 17:54:21 -07001604Documentation
1605-------------
1606
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001607If a plugin does not register a filter or servlet to handle URLs
1608`/Documentation/*` or `/static/*`, the core Gerrit server will
1609automatically export these resources over HTTP from the plugin JAR.
1610
David Pursehouse6853b5a2013-07-10 11:38:03 +09001611Static resources under the `static/` directory in the JAR will be
Dave Borowitzb893ac82013-03-27 10:03:55 -04001612available as `/plugins/helloworld/static/resource`. This prefix is
1613configurable by setting the `Gerrit-HttpStaticPrefix` attribute.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001614
David Pursehouse6853b5a2013-07-10 11:38:03 +09001615Documentation files under the `Documentation/` directory in the JAR
Dave Borowitzb893ac82013-03-27 10:03:55 -04001616will be available as `/plugins/helloworld/Documentation/resource`. This
1617prefix is configurable by setting the `Gerrit-HttpDocumentationPrefix`
1618attribute.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001619
1620Documentation may be written in
1621link:http://daringfireball.net/projects/markdown/[Markdown] style
1622if the file name ends with `.md`. Gerrit will automatically convert
1623Markdown to HTML if accessed with extension `.html`.
Nasser Grainawie033b262012-05-09 17:54:21 -07001624
Edwin Kempinf5a77332012-07-18 11:17:53 +02001625[[macros]]
Edwin Kempinc78777d2012-07-16 15:55:11 +02001626Within the Markdown documentation files macros can be used that allow
1627to write documentation with reasonably accurate examples that adjust
1628automatically based on the installation.
1629
1630The following macros are supported:
1631
1632[width="40%",options="header"]
1633|===================================================
1634|Macro | Replacement
1635|@PLUGIN@ | name of the plugin
1636|@URL@ | Gerrit Web URL
1637|@SSH_HOST@ | SSH Host
1638|@SSH_PORT@ | SSH Port
1639|===================================================
1640
1641The macros will be replaced when the documentation files are rendered
1642from Markdown to HTML.
1643
1644Macros that start with `\` such as `\@KEEP@` will render as `@KEEP@`
1645even if there is an expansion for `KEEP` in the future.
1646
Edwin Kempinf5a77332012-07-18 11:17:53 +02001647[[auto-index]]
Shawn O. Pearce795167c2012-05-12 11:20:18 -07001648Automatic Index
1649~~~~~~~~~~~~~~~
1650
1651If a plugin does not handle its `/` URL itself, Gerrit will
1652redirect clients to the plugin's `/Documentation/index.html`.
1653Requests for `/Documentation/` (bare directory) will also redirect
1654to `/Documentation/index.html`.
1655
1656If neither resource `Documentation/index.html` or
1657`Documentation/index.md` exists in the plugin JAR, Gerrit will
1658automatically generate an index page for the plugin's documentation
1659tree by scanning every `*.md` and `*.html` file in the Documentation/
1660directory.
1661
1662For any discovered Markdown (`*.md`) file, Gerrit will parse the
1663header of the file and extract the first level one title. This
1664title text will be used as display text for a link to the HTML
1665version of the page.
1666
1667For any discovered HTML (`*.html`) file, Gerrit will use the name
1668of the file, minus the `*.html` extension, as the link text. Any
1669hyphens in the file name will be replaced with spaces.
1670
David Pursehouse6853b5a2013-07-10 11:38:03 +09001671If a discovered file is named `about.md` or `about.html`, its
1672content will be inserted in an 'About' section at the top of the
1673auto-generated index page. If both `about.md` and `about.html`
1674exist, only the first discovered file will be used.
1675
Shawn O. Pearce795167c2012-05-12 11:20:18 -07001676If a discovered file name beings with `cmd-` it will be clustered
David Pursehouse6853b5a2013-07-10 11:38:03 +09001677into a 'Commands' section of the generated index page.
1678
David Pursehousefe529152013-08-14 16:35:06 +09001679If a discovered file name beings with `servlet-` it will be clustered
1680into a 'Servlets' section of the generated index page.
1681
1682If a discovered file name beings with `rest-api-` it will be clustered
1683into a 'REST APIs' section of the generated index page.
1684
David Pursehouse6853b5a2013-07-10 11:38:03 +09001685All other files are clustered under a 'Documentation' section.
Shawn O. Pearce795167c2012-05-12 11:20:18 -07001686
1687Some optional information from the manifest is extracted and
1688displayed as part of the index page, if present in the manifest:
1689
1690[width="40%",options="header"]
1691|===================================================
1692|Field | Source Attribute
1693|Name | Implementation-Title
1694|Vendor | Implementation-Vendor
1695|Version | Implementation-Version
1696|URL | Implementation-URL
1697|API Version | Gerrit-ApiVersion
1698|===================================================
1699
Edwin Kempinf5a77332012-07-18 11:17:53 +02001700[[deployment]]
Nasser Grainawie033b262012-05-09 17:54:21 -07001701Deployment
1702----------
1703
Edwin Kempinf7295742012-07-16 15:03:46 +02001704Compiled plugins and extensions can be deployed to a running Gerrit
1705server using the link:cmd-plugin-install.html[plugin install] command.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001706
Dariusz Luksza357a2422012-11-12 06:16:26 +01001707WebUI plugins distributed as single `.js` file can be deployed
1708without the overhead of JAR packaging, for more information refer to
1709link:cmd-plugin-install.html[plugin install] command.
1710
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001711Plugins can also be copied directly into the server's
Dariusz Luksza357a2422012-11-12 06:16:26 +01001712directory at `$site_path/plugins/$name.(jar|js)`. The name of
1713the JAR file, minus the `.jar` or `.js` extension, will be used as the
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07001714plugin name. Unless disabled, servers periodically scan this
1715directory for updated plugins. The time can be adjusted by
1716link:config-gerrit.html#plugins.checkFrequency[plugins.checkFrequency].
Deniz Türkoglueb78b602012-05-07 14:02:36 -07001717
Edwin Kempinf7295742012-07-16 15:03:46 +02001718For disabling plugins the link:cmd-plugin-remove.html[plugin remove]
1719command can be used.
1720
Brad Larsond5e87c32012-07-11 12:18:49 -05001721Disabled plugins can be re-enabled using the
1722link:cmd-plugin-enable.html[plugin enable] command.
1723
David Ostrovskyf86bae52013-09-01 09:10:39 +02001724SEE ALSO
1725--------
1726
1727* link:js-api.html[JavaScript API]
1728* link:dev-rest-api.html[REST API Developers' Notes]
1729
Deniz Türkoglueb78b602012-05-07 14:02:36 -07001730GERRIT
1731------
1732Part of link:index.html[Gerrit Code Review]
Yuxuan 'fishy' Wang99cb68d2013-10-31 17:26:00 -07001733
1734SEARCHBOX
1735---------