Add the ability to run a new project created hook

Notify users when a new project has been created. This could solve
an issue in the Jenkins gerrit-trigger-plugin where we need to
continuously fetch the whole project list (using the command
gerrit ls-projects) to get project name auto completion working
in the Project Configuration pages. By letting the plug-in pickup
new projects on the fly, our auto completion would be up-to-date
much quicker and also drain less resources from Gerrit.

The hook takes the following form:

project-created --project <project name> --head <head name>

Change-Id: Ibf53946b12df4efd2f929fa7fc6d23499ed7ed88
This commit is contained in:
Liu Forest 2015-04-15 14:20:43 +08:00 committed by David Pursehouse
parent 507b58dae2
commit 53286f2503
8 changed files with 134 additions and 9 deletions

View File

@ -153,6 +153,19 @@ hashtags:: List of hashtags on the change after tags were added or removed
eventCreatedOn:: Time in seconds since the UNIX epoch when this event was eventCreatedOn:: Time in seconds since the UNIX epoch when this event was
created. created.
=== Project Created
Sent when a new project has been created.
type:: "project-created"
projectName:: The created project name
projectHead:: The created project head name
eventCreatedOn:: Time in seconds since the UNIX epoch when this event was
created.
=== Merge Failed === Merge Failed
Sent when a change has failed to be merged into the git repository. Sent when a change has failed to be merged into the git repository.

View File

@ -1832,6 +1832,11 @@ Optional filename for the draft published hook, if not specified then
Optional filename for the hashtags changed hook, if not specified then Optional filename for the hashtags changed hook, if not specified then
`hashtags-changed` will be used. `hashtags-changed` will be used.
[[hooks.projectCreatedHook]]hooks.projectCreatedHook::
+
Optional filename for the project created hook, if not specified then
`project-created` will be used.
[[hooks.mergeFailedHook]]hooks.mergeFailedHook:: [[hooks.mergeFailedHook]]hooks.mergeFailedHook::
+ +
Optional filename for the merge failed hook, if not specified then Optional filename for the merge failed hook, if not specified then

View File

@ -110,6 +110,14 @@ Called whenever a ref has been updated.
ref-updated --oldrev <old rev> --newrev <new rev> --refname <ref name> --project <project name> --submitter <submitter> ref-updated --oldrev <old rev> --newrev <new rev> --refname <ref name> --project <project name> --submitter <submitter>
==== ====
=== project-created
Called whenever a project has been created.
====
project-created --project <project name> --head <head name>
====
=== reviewer-added === reviewer-added
Called whenever a reviewer is added to a change. Called whenever a reviewer is added to a change.

View File

@ -20,6 +20,7 @@ import com.google.gerrit.common.data.ContributorAgreement;
import com.google.gerrit.common.data.LabelType; import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelTypes; import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.extensions.events.LifecycleListener; import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.extensions.events.NewProjectCreatedListener;
import com.google.gerrit.extensions.registration.DynamicSet; import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.lifecycle.LifecycleModule; import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Account;
@ -40,11 +41,11 @@ import com.google.gerrit.server.events.ChangeMergedEvent;
import com.google.gerrit.server.events.ChangeRestoredEvent; import com.google.gerrit.server.events.ChangeRestoredEvent;
import com.google.gerrit.server.events.CommentAddedEvent; import com.google.gerrit.server.events.CommentAddedEvent;
import com.google.gerrit.server.events.DraftPublishedEvent; import com.google.gerrit.server.events.DraftPublishedEvent;
import com.google.gerrit.server.events.Event;
import com.google.gerrit.server.events.EventFactory; import com.google.gerrit.server.events.EventFactory;
import com.google.gerrit.server.events.HashtagsChangedEvent; import com.google.gerrit.server.events.HashtagsChangedEvent;
import com.google.gerrit.server.events.MergeFailedEvent; import com.google.gerrit.server.events.MergeFailedEvent;
import com.google.gerrit.server.events.PatchSetCreatedEvent; import com.google.gerrit.server.events.PatchSetCreatedEvent;
import com.google.gerrit.server.events.ProjectCreatedEvent;
import com.google.gerrit.server.events.RefUpdatedEvent; import com.google.gerrit.server.events.RefUpdatedEvent;
import com.google.gerrit.server.events.ReviewerAddedEvent; import com.google.gerrit.server.events.ReviewerAddedEvent;
import com.google.gerrit.server.events.TopicChangedEvent; import com.google.gerrit.server.events.TopicChangedEvent;
@ -89,7 +90,7 @@ import java.util.concurrent.TimeoutException;
/** Spawns local executables when a hook action occurs. */ /** Spawns local executables when a hook action occurs. */
@Singleton @Singleton
public class ChangeHookRunner implements ChangeHooks, EventDispatcher, public class ChangeHookRunner implements ChangeHooks, EventDispatcher,
EventSource, LifecycleListener { EventSource, LifecycleListener, NewProjectCreatedListener {
/** A logger for this class. */ /** A logger for this class. */
private static final Logger log = LoggerFactory.getLogger(ChangeHookRunner.class); private static final Logger log = LoggerFactory.getLogger(ChangeHookRunner.class);
@ -100,6 +101,7 @@ public class ChangeHookRunner implements ChangeHooks, EventDispatcher,
bind(ChangeHooks.class).to(ChangeHookRunner.class); bind(ChangeHooks.class).to(ChangeHookRunner.class);
bind(EventDispatcher.class).to(ChangeHookRunner.class); bind(EventDispatcher.class).to(ChangeHookRunner.class);
bind(EventSource.class).to(ChangeHookRunner.class); bind(EventSource.class).to(ChangeHookRunner.class);
DynamicSet.bind(binder(), NewProjectCreatedListener.class).to(ChangeHookRunner.class);
listener().to(ChangeHookRunner.class); listener().to(ChangeHookRunner.class);
} }
} }
@ -209,6 +211,9 @@ public class ChangeHookRunner implements ChangeHooks, EventDispatcher,
/** Path of the hashtags changed hook */ /** Path of the hashtags changed hook */
private final Path hashtagsChangedHook; private final Path hashtagsChangedHook;
/** Path of the project created hook. */
private final Path projectCreatedHook;
private final String anonymousCowardName; private final String anonymousCowardName;
/** Repository Manager. */ /** Repository Manager. */
@ -282,6 +287,7 @@ public class ChangeHookRunner implements ChangeHooks, EventDispatcher,
claSignedHook = hook(config, hooksPath, "cla-signed"); claSignedHook = hook(config, hooksPath, "cla-signed");
refUpdateHook = hook(config, hooksPath, "ref-update"); refUpdateHook = hook(config, hooksPath, "ref-update");
hashtagsChangedHook = hook(config, hooksPath, "hashtags-changed"); hashtagsChangedHook = hook(config, hooksPath, "hashtags-changed");
projectCreatedHook = hook(config, hooksPath, "project-created");
syncHookTimeout = config.getInt("hooks", "syncHookTimeout", 30); syncHookTimeout = config.getInt("hooks", "syncHookTimeout", 30);
syncHookThreadPool = Executors.newCachedThreadPool( syncHookThreadPool = Executors.newCachedThreadPool(
@ -346,6 +352,20 @@ public class ChangeHookRunner implements ChangeHooks, EventDispatcher,
return runSyncHook(project.getNameKey(), refUpdateHook, args); return runSyncHook(project.getNameKey(), refUpdateHook, args);
} }
@Override
public void doProjectCreatedHook(Project.NameKey project, String headName) {
ProjectCreatedEvent event = new ProjectCreatedEvent();
event.projectName = project.get();
event.headName = headName;
fireEvent(project, event);
List<String> args = new ArrayList<>();
addArg(args, "--project", project.get());
addArg(args, "--head", headName);
runHook(project, projectCreatedHook, args);
}
/** /**
* Fire the Patchset Created Hook. * Fire the Patchset Created Hook.
* *
@ -695,24 +715,24 @@ public class ChangeHookRunner implements ChangeHooks, EventDispatcher,
} }
@Override @Override
public void postEvent(Change change, Event event, ReviewDb db) public void postEvent(Change change, com.google.gerrit.server.events.Event event,
throws OrmException { ReviewDb db) throws OrmException {
fireEvent(change, event, db); fireEvent(change, event, db);
} }
@Override @Override
public void postEvent(Branch.NameKey branchName, Event event) { public void postEvent(Branch.NameKey branchName, com.google.gerrit.server.events.Event event) {
fireEvent(branchName, event); fireEvent(branchName, event);
} }
private void fireEventForUnrestrictedListeners(Event event) { private void fireEventForUnrestrictedListeners(com.google.gerrit.server.events.Event event) {
for (EventListener listener : unrestrictedListeners) { for (EventListener listener : unrestrictedListeners) {
listener.onEvent(event); listener.onEvent(event);
} }
} }
private void fireEvent(Change change, Event event, ReviewDb db) private void fireEvent(Change change, com.google.gerrit.server.events.Event event,
throws OrmException { ReviewDb db) throws OrmException {
for (EventListenerHolder holder : listeners.values()) { for (EventListenerHolder holder : listeners.values()) {
if (isVisibleTo(change, holder.user, db)) { if (isVisibleTo(change, holder.user, db)) {
holder.listener.onEvent(event); holder.listener.onEvent(event);
@ -722,7 +742,32 @@ public class ChangeHookRunner implements ChangeHooks, EventDispatcher,
fireEventForUnrestrictedListeners( event ); fireEventForUnrestrictedListeners( event );
} }
private void fireEvent(Branch.NameKey branchName, Event event) { private void fireEvent(Project.NameKey project, ProjectCreatedEvent event) {
for (EventListenerHolder holder : listeners.values()) {
if (isVisibleTo(project, event, holder.user)) {
holder.listener.onEvent(event);
}
}
fireEventForUnrestrictedListeners(event);
}
private void fireEventForUnrestrictedListeners(ProjectCreatedEvent event) {
for (EventListener listener : unrestrictedListeners) {
listener.onEvent(event);
}
}
private boolean isVisibleTo(Project.NameKey project, ProjectCreatedEvent event, CurrentUser user) {
ProjectState pe = projectCache.get(project);
if (pe == null) {
return false;
}
ProjectControl pc = pe.controlFor(user);
return pc.controlForRef(event.getHeadName()).isVisible();
}
private void fireEvent(Branch.NameKey branchName, com.google.gerrit.server.events.Event event) {
for (EventListenerHolder holder : listeners.values()) { for (EventListenerHolder holder : listeners.values()) {
if (isVisibleTo(branchName, holder.user)) { if (isVisibleTo(branchName, holder.user)) {
holder.listener.onEvent(event); holder.listener.onEvent(event);
@ -995,4 +1040,10 @@ public class ChangeHookRunner implements ChangeHooks, EventDispatcher,
super.runHook(); super.runHook();
} }
} }
@Override
public void onNewProjectCreated(NewProjectCreatedListener.Event event) {
Project.NameKey project = new Project.NameKey(event.getProjectName());
doProjectCreatedHook(project, event.getHeadName());
}
} }

View File

@ -181,4 +181,12 @@ public interface ChangeHooks {
public void doHashtagsChangedHook(Change change, Account account, public void doHashtagsChangedHook(Change change, Account account,
Set<String>added, Set<String> removed, Set<String> hashtags, Set<String>added, Set<String> removed, Set<String> hashtags,
ReviewDb db) throws OrmException; ReviewDb db) throws OrmException;
/**
* Fire the project created hook
*
* @param project The project that was created
* @param headName The head name of the created project
*/
public void doProjectCreatedHook(Project.NameKey project, String headName);
} }

View File

@ -113,6 +113,10 @@ public final class DisabledChangeHooks implements ChangeHooks, EventDispatcher,
return null; return null;
} }
@Override
public void doProjectCreatedHook(Project.NameKey project, String headName) {
}
@Override @Override
public void postEvent(Change change, Event event, ReviewDb db) { public void postEvent(Change change, Event event, ReviewDb db) {
} }

View File

@ -35,6 +35,7 @@ public class EventTypes {
registerClass(new ReviewerAddedEvent()); registerClass(new ReviewerAddedEvent());
registerClass(new PatchSetCreatedEvent()); registerClass(new PatchSetCreatedEvent());
registerClass(new TopicChangedEvent()); registerClass(new TopicChangedEvent());
registerClass(new ProjectCreatedEvent());
} }
/** Register an event. /** Register an event.

View File

@ -0,0 +1,35 @@
// Copyright (C) 2015 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.events;
import com.google.gerrit.reviewdb.client.Project;
public class ProjectCreatedEvent extends ProjectEvent {
public String projectName;
public String headName;
public ProjectCreatedEvent() {
super("project-created");
}
@Override
public Project.NameKey getProjectNameKey() {
return new Project.NameKey(projectName);
}
public String getHeadName() {
return headName;
}
}