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
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
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
`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::
+
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>
====
=== project-created
Called whenever a project has been created.
====
project-created --project <project name> --head <head name>
====
=== reviewer-added
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.LabelTypes;
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.lifecycle.LifecycleModule;
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.CommentAddedEvent;
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.HashtagsChangedEvent;
import com.google.gerrit.server.events.MergeFailedEvent;
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.ReviewerAddedEvent;
import com.google.gerrit.server.events.TopicChangedEvent;
@ -89,7 +90,7 @@ import java.util.concurrent.TimeoutException;
/** Spawns local executables when a hook action occurs. */
@Singleton
public class ChangeHookRunner implements ChangeHooks, EventDispatcher,
EventSource, LifecycleListener {
EventSource, LifecycleListener, NewProjectCreatedListener {
/** A logger for this 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(EventDispatcher.class).to(ChangeHookRunner.class);
bind(EventSource.class).to(ChangeHookRunner.class);
DynamicSet.bind(binder(), NewProjectCreatedListener.class).to(ChangeHookRunner.class);
listener().to(ChangeHookRunner.class);
}
}
@ -209,6 +211,9 @@ public class ChangeHookRunner implements ChangeHooks, EventDispatcher,
/** Path of the hashtags changed hook */
private final Path hashtagsChangedHook;
/** Path of the project created hook. */
private final Path projectCreatedHook;
private final String anonymousCowardName;
/** Repository Manager. */
@ -282,6 +287,7 @@ public class ChangeHookRunner implements ChangeHooks, EventDispatcher,
claSignedHook = hook(config, hooksPath, "cla-signed");
refUpdateHook = hook(config, hooksPath, "ref-update");
hashtagsChangedHook = hook(config, hooksPath, "hashtags-changed");
projectCreatedHook = hook(config, hooksPath, "project-created");
syncHookTimeout = config.getInt("hooks", "syncHookTimeout", 30);
syncHookThreadPool = Executors.newCachedThreadPool(
@ -346,6 +352,20 @@ public class ChangeHookRunner implements ChangeHooks, EventDispatcher,
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.
*
@ -695,24 +715,24 @@ public class ChangeHookRunner implements ChangeHooks, EventDispatcher,
}
@Override
public void postEvent(Change change, Event event, ReviewDb db)
throws OrmException {
public void postEvent(Change change, com.google.gerrit.server.events.Event event,
ReviewDb db) throws OrmException {
fireEvent(change, event, db);
}
@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);
}
private void fireEventForUnrestrictedListeners(Event event) {
private void fireEventForUnrestrictedListeners(com.google.gerrit.server.events.Event event) {
for (EventListener listener : unrestrictedListeners) {
listener.onEvent(event);
}
}
private void fireEvent(Change change, Event event, ReviewDb db)
throws OrmException {
private void fireEvent(Change change, com.google.gerrit.server.events.Event event,
ReviewDb db) throws OrmException {
for (EventListenerHolder holder : listeners.values()) {
if (isVisibleTo(change, holder.user, db)) {
holder.listener.onEvent(event);
@ -722,7 +742,32 @@ public class ChangeHookRunner implements ChangeHooks, EventDispatcher,
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()) {
if (isVisibleTo(branchName, holder.user)) {
holder.listener.onEvent(event);
@ -995,4 +1040,10 @@ public class ChangeHookRunner implements ChangeHooks, EventDispatcher,
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,
Set<String>added, Set<String> removed, Set<String> hashtags,
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;
}
@Override
public void doProjectCreatedHook(Project.NameKey project, String headName) {
}
@Override
public void postEvent(Change change, Event event, ReviewDb db) {
}

View File

@ -35,6 +35,7 @@ public class EventTypes {
registerClass(new ReviewerAddedEvent());
registerClass(new PatchSetCreatedEvent());
registerClass(new TopicChangedEvent());
registerClass(new ProjectCreatedEvent());
}
/** 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;
}
}