diff --git a/.gitmodules b/.gitmodules index d75c98ce47..ef438d84ba 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,6 +10,10 @@ path = plugins/download-commands url = ../plugins/download-commands +[submodule "plugins/hooks"] + path = plugins/hooks + url = ../plugins/hooks + [submodule "plugins/replication"] path = plugins/replication url = ../plugins/replication diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt index 18a7a1f8ed..85bbbead11 100644 --- a/Documentation/config-gerrit.txt +++ b/Documentation/config-gerrit.txt @@ -2002,85 +2002,6 @@ all registered users. + By default, false. -[[hooks]] -=== Section hooks - -See also link:config-hooks.html[Hooks]. - -[[hooks.path]]hooks.path:: -+ -Optional path to hooks, if not specified then `'$site_path'/hooks` will be used. - -[[hooks.syncHookTimeout]]hooks.syncHookTimeout:: -+ -Optional timeout value in seconds for synchronous hooks, if not specified -then 30 seconds will be used. - -[[hooks.changeAbandonedHook]]hooks.changeAbandonedHook:: -+ -Optional filename for the change abandoned hook, if not specified then -`change-abandoned` will be used. - -[[hooks.changeMergedHook]]hooks.changeMergedHook:: -+ -Optional filename for the change merged hook, if not specified then -`change-merged` will be used. - -[[hooks.changeRestoredHook]]hooks.changeRestoredHook:: -+ -Optional filename for the change restored hook, if not specified then -`change-restored` will be used. - -[[hooks.claSignedHook]]hooks.claSignedHook:: -+ -Optional filename for the CLA signed hook, if not specified then -`cla-signed` will be used. - -[[hooks.commentAddedHook]]hooks.commentAddedHook:: -+ -Optional filename for the comment added hook, if not specified then -`comment-added` will be used. - -[[hooks.draftPublishedHook]]hooks.draftPublishedHook:: -+ -Optional filename for the draft published hook, if not specified then -`draft-published` will be used. - -[[hooks.hashtagsChangedHook]]hooks.hashtagsChangedHook:: -+ -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.patchsetCreatedHook]]hooks.patchsetCreatedHook:: -+ -Optional filename for the patchset created hook, if not specified then -`patchset-created` will be used. - -[[hooks.refUpdateHook]]hooks.refUpdateHook:: -+ -Optional filename for the ref update hook, if not specified then -`ref-update` will be used. - -[[hooks.refUpdatedHook]]hooks.refUpdatedHook:: -+ -Optional filename for the ref updated hook, if not specified then -`ref-updated` will be used. - -[[hooks.reviewerAddedHook]]hooks.reviewerAddedHook:: -+ -Optional filename for the reviewer added hook, if not specified then -`reviewer-added` will be used. - -[[hooks.topicChangedHook]]hooks.topicChangedHook:: -+ -Optional filename for the topic changed hook, if not specified then -`topic-changed` will be used. - [[http]] === Section http diff --git a/Documentation/config-hooks.txt b/Documentation/config-hooks.txt index cde7b399dd..a71595f1e7 100644 --- a/Documentation/config-hooks.txt +++ b/Documentation/config-hooks.txt @@ -1,192 +1,9 @@ = Gerrit Code Review - Hooks -Gerrit does not run any of the standard git hooks in the -repositories it works with, but it does have its own hook mechanism -included. Gerrit looks in `'$site_path'/hooks` for executables with -names listed below. - -The environment will have GIT_DIR set to the full path of the -affected git repository so that git commands can be easily run. - -Make sure your hook scripts are executable if running on *nix. - -With the exception of the ref-update hook, hooks are run in the background -after the relevant change has taken place so are unable to affect -the outcome of any given change. Because of the fact the hooks are -run in the background after the activity, a hook might not be notified -about an event if the server is shutdown before the hook can be invoked. - -== Supported Hooks - -=== ref-update - -This is called when a push request is received by Gerrit. It allows -a push to be rejected before it is committed to the Gerrit repository. -If the script exits with non-zero return code the push will be rejected. -Any output from the script will be returned to the user, regardless of the -return code. - -This hook is called synchronously so it is recommended that -it not block. A default timeout on the hook is set to 30 seconds to avoid -"runaway" hooks using up server threads. See link:config-gerrit.html#hooks.syncHookTimeout[hooks.syncHookTimeout] -for configuration details. - -==== - ref-update --project --refname --uploader --oldrev --newrev -==== - -=== patchset-created - -This is called whenever a patchset is created (this includes new -changes and drafts). - -==== - patchset-created --change --is-draft --kind --change-url --change-owner --project --branch --topic --uploader --commit --patchset -==== - -kind:: change kind represents the kind of change uploaded, also represented in link:json.html#patchSet[patchSet] - - REWORK;; Nontrivial content changes. - - TRIVIAL_REBASE;; Conflict-free merge between the new parent and the prior patch set. - - MERGE_FIRST_PARENT_UPDATE;; Conflict-free change of first (left) parent of a merge commit. - - NO_CODE_CHANGE;; No code changed; same tree and same parent tree. - - NO_CHANGE;; No changes; same commit message, same tree and same parent tree. - -=== draft-published - -This is called whenever a draft change is published. - -==== - draft-published --change --change-url --change-owner --project --branch --topic --uploader --commit --patchset -==== - -=== comment-added - -This is called whenever a comment is added to a change. - -==== - comment-added --change --is-draft --change-url --change-owner --project --branch --topic --author --commit --comment [-- -- ---oldValue ...] -==== - -=== change-merged - -Called whenever a change has been merged. - -==== - change-merged --change --change-url --change-owner --project --branch --topic --submitter --commit --newrev -==== - -=== change-abandoned - -Called whenever a change has been abandoned. - -==== - change-abandoned --change --change-url --change-owner --project --branch --topic --abandoner --commit --reason -==== - -=== change-restored - -Called whenever a change has been restored. - -==== - change-restored --change --change-url --change-owner --project --branch --topic --restorer --commit --reason -==== - -=== ref-updated - -Called whenever a ref has been updated. - -==== - ref-updated --oldrev --newrev --refname --project --submitter -==== - -=== project-created - -Called whenever a project has been created. - -==== - project-created --project --head -==== - -=== reviewer-added - -Called whenever a reviewer is added to a change. - -==== - reviewer-added --change --change-url --change-owner --project --branch --reviewer -==== - -=== reviewer-deleted - -Called whenever a reviewer (with a vote) is removed from a change. - -==== - reviewer-deleted --change --change-url --change-owner --project --branch --reviewer [-- -- ...] -==== - -=== topic-changed - -Called whenever a change's topic is changed from the Web UI or via the REST API. - -==== - topic-changed --change --change-owner --project --branch --changer --old-topic --new-topic -==== - -=== hashtags-changed - -Called whenever hashtags are added to or removed from a change from the Web UI -or via the REST API. - -==== - hashtags-changed --change --change-owner --project --branch --editor --added --removed --hashtag -==== - -The `--added` parameter may be passed multiple times, once for each -hashtag that was added to the change. - -The `--removed` parameter may be passed multiple times, once for each -hashtag that was removed from the change. - -The `--hashtag` parameter may be passed multiple times, once for each -hashtag remaining on the change after the add or remove operation has -been performed. - -=== cla-signed - -Called whenever a user signs a contributor license agreement. - -==== - cla-signed --submitter --user-id --cla-id -==== - - -== Configuration Settings - -It is possible to change where Gerrit looks for hooks, and what -filenames it looks for, by adding a [hooks] section in gerrit.config. - -Gerrit will use the value of hooks.path for the hooks directory. - -For the hook filenames, Gerrit will use the values of hooks.patchsetCreatedHook, -hooks.draftPublishedHook, hooks.commentAddedHook, hooks.changeMergedHook, -hooks.changeAbandonedHook, hooks.changeRestoredHook, hooks.refUpdatedHook, -hooks.refUpdateHook, hooks.reviewerAddedHook and hooks.claSignedHook. - -== Missing Change URLs - -If link:config-gerrit.html#gerrit.canonicalWebUrl[gerrit.canonicalWebUrl] -is not set in `gerrit.config` the `--change-url` flag may not be -passed to all hooks. Hooks started out of an SSH context (for example -the patchset-created hook) don't know the server's web URL, unless -this variable is configured. - -== SEE ALSO - -* link:config-gerrit.html#hooks[Section hooks] +Gerrit does not run any of the standard git hooks in the repositories +it works with, but it does have its own hook mechanism included via +the link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/hooks[ +hooks plugin]. GERRIT ------ diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java index 4715877563..612f0f991b 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java @@ -19,8 +19,6 @@ import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; -import com.google.gerrit.common.ChangeHookApiListener; -import com.google.gerrit.common.ChangeHookRunner; import com.google.gerrit.common.EventBroker; import com.google.gerrit.common.StreamEventsApiListener; import com.google.gerrit.gpg.GpgModule; @@ -343,9 +341,7 @@ public class Daemon extends SiteProgram { modules.add(createIndexModule()); modules.add(new WorkQueue.Module()); - modules.add(new ChangeHookApiListener.Module()); modules.add(new StreamEventsApiListener.Module()); - modules.add(new ChangeHookRunner.Module()); modules.add(new EventBroker.Module()); modules.add(test ? new H2AccountPatchReviewStore.InMemoryModule() diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchGitModule.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchGitModule.java index be573e62f3..0360cd606a 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchGitModule.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchGitModule.java @@ -14,8 +14,6 @@ package com.google.gerrit.pgm.util; -import com.google.gerrit.common.ChangeHooks; -import com.google.gerrit.common.DisabledChangeHooks; import com.google.gerrit.extensions.config.FactoryModule; import com.google.gerrit.extensions.events.GitReferenceUpdatedListener; import com.google.gerrit.extensions.registration.DynamicSet; @@ -27,7 +25,6 @@ import com.google.gerrit.server.git.validators.CommitValidators; public class BatchGitModule extends FactoryModule { @Override protected void configure() { - bind(ChangeHooks.class).to(DisabledChangeHooks.class); DynamicSet.setOf(binder(), GitReferenceUpdatedListener.class); DynamicSet.setOf(binder(), CommitValidationListener.class); factory(CommitValidators.Factory.class); diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookApiListener.java b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookApiListener.java deleted file mode 100644 index 69a3f6175f..0000000000 --- a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookApiListener.java +++ /dev/null @@ -1,376 +0,0 @@ -// 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.common; - -import static com.google.gerrit.reviewdb.client.RefNames.REFS_CHANGES; -import static org.eclipse.jgit.lib.Constants.R_HEADS; - -import com.google.gerrit.common.ChangeHookRunner.HookResult; -import com.google.gerrit.extensions.common.AccountInfo; -import com.google.gerrit.extensions.common.ApprovalInfo; -import com.google.gerrit.extensions.common.ChangeInfo; -import com.google.gerrit.extensions.common.RevisionInfo; -import com.google.gerrit.extensions.events.AgreementSignupListener; -import com.google.gerrit.extensions.events.ChangeAbandonedListener; -import com.google.gerrit.extensions.events.ChangeMergedListener; -import com.google.gerrit.extensions.events.ChangeRestoredListener; -import com.google.gerrit.extensions.events.CommentAddedListener; -import com.google.gerrit.extensions.events.DraftPublishedListener; -import com.google.gerrit.extensions.events.GitReferenceUpdatedListener; -import com.google.gerrit.extensions.events.HashtagsEditedListener; -import com.google.gerrit.extensions.events.NewProjectCreatedListener; -import com.google.gerrit.extensions.events.ReviewerAddedListener; -import com.google.gerrit.extensions.events.ReviewerDeletedListener; -import com.google.gerrit.extensions.events.RevisionCreatedListener; -import com.google.gerrit.extensions.events.TopicEditedListener; -import com.google.gerrit.extensions.registration.DynamicSet; -import com.google.gerrit.lifecycle.LifecycleModule; -import com.google.gerrit.reviewdb.client.Account; -import com.google.gerrit.reviewdb.client.Branch; -import com.google.gerrit.reviewdb.client.Change; -import com.google.gerrit.reviewdb.client.PatchSet; -import com.google.gerrit.reviewdb.client.Project; -import com.google.gerrit.reviewdb.server.ReviewDb; -import com.google.gerrit.server.IdentifiedUser; -import com.google.gerrit.server.PatchSetUtil; -import com.google.gerrit.server.account.AccountCache; -import com.google.gerrit.server.account.AccountState; -import com.google.gerrit.server.events.CommitReceivedEvent; -import com.google.gerrit.server.git.validators.CommitValidationException; -import com.google.gerrit.server.git.validators.CommitValidationListener; -import com.google.gerrit.server.git.validators.CommitValidationMessage; -import com.google.gerrit.server.notedb.ChangeNotes; -import com.google.gerrit.server.project.NoSuchChangeException; -import com.google.gwtorm.server.OrmException; -import com.google.inject.Inject; -import com.google.inject.Provider; -import com.google.inject.Singleton; - -import org.eclipse.jgit.lib.ObjectId; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -@Singleton -public class ChangeHookApiListener implements - AgreementSignupListener, - ChangeAbandonedListener, - ChangeMergedListener, - ChangeRestoredListener, - CommentAddedListener, - DraftPublishedListener, - GitReferenceUpdatedListener, - HashtagsEditedListener, - NewProjectCreatedListener, - ReviewerAddedListener, - ReviewerDeletedListener, - RevisionCreatedListener, - TopicEditedListener { - /** A logger for this class. */ - private static final Logger log = - LoggerFactory.getLogger(ChangeHookApiListener.class); - - public static class Module extends LifecycleModule { - @Override - protected void configure() { - DynamicSet.bind(binder(), AgreementSignupListener.class) - .to(ChangeHookApiListener.class); - DynamicSet.bind(binder(), ChangeAbandonedListener.class) - .to(ChangeHookApiListener.class); - DynamicSet.bind(binder(), ChangeMergedListener.class) - .to(ChangeHookApiListener.class); - DynamicSet.bind(binder(), ChangeRestoredListener.class) - .to(ChangeHookApiListener.class); - DynamicSet.bind(binder(), CommentAddedListener.class) - .to(ChangeHookApiListener.class); - DynamicSet.bind(binder(), DraftPublishedListener.class) - .to(ChangeHookApiListener.class); - DynamicSet.bind(binder(), GitReferenceUpdatedListener.class) - .to(ChangeHookApiListener.class); - DynamicSet.bind(binder(), HashtagsEditedListener.class) - .to(ChangeHookApiListener.class); - DynamicSet.bind(binder(), NewProjectCreatedListener.class) - .to(ChangeHookApiListener.class); - DynamicSet.bind(binder(), ReviewerAddedListener.class) - .to(ChangeHookApiListener.class); - DynamicSet.bind(binder(), ReviewerDeletedListener.class) - .to(ChangeHookApiListener.class); - DynamicSet.bind(binder(), RevisionCreatedListener.class) - .to(ChangeHookApiListener.class); - DynamicSet.bind(binder(), TopicEditedListener.class) - .to(ChangeHookApiListener.class); - DynamicSet.bind(binder(), CommitValidationListener.class) - .to(ChangeHookValidator.class); - } - } - - /** Reject commits that don't pass user-supplied ref-update hook. */ - public static class ChangeHookValidator implements - CommitValidationListener { - private final ChangeHooks hooks; - - @Inject - public ChangeHookValidator(ChangeHooks hooks) { - this.hooks = hooks; - } - - @Override - public List onCommitReceived( - CommitReceivedEvent receiveEvent) throws CommitValidationException { - IdentifiedUser user = receiveEvent.user; - String refname = receiveEvent.refName; - ObjectId old = ObjectId.zeroId(); - if (receiveEvent.commit.getParentCount() > 0) { - old = receiveEvent.commit.getParent(0); - } - - if (receiveEvent.command.getRefName().startsWith(REFS_CHANGES)) { - /* - * If the ref-update hook tries to distinguish behavior between pushes to - * refs/heads/... and refs/for/..., make sure we send it the correct - * refname. - * Also, if this is targetting refs/for/, make sure we behave the same as - * what a push to refs/for/ would behave; in particular, setting oldrev - * to 0000000000000000000000000000000000000000. - */ - refname = refname.replace(R_HEADS, "refs/for/refs/heads/"); - old = ObjectId.zeroId(); - } - HookResult result = hooks.doRefUpdateHook(receiveEvent.project, refname, - user.getAccount(), old, receiveEvent.commit); - if (result != null && result.getExitValue() != 0) { - throw new CommitValidationException(result.toString().trim()); - } - return Collections.emptyList(); - } - } - - private final Provider db; - private final AccountCache accounts; - private final ChangeHooks hooks; - private final PatchSetUtil psUtil; - private final ChangeNotes.Factory changeNotesFactory; - - @Inject - public ChangeHookApiListener( - Provider db, - AccountCache accounts, - ChangeHooks hooks, - PatchSetUtil psUtil, - ChangeNotes.Factory changeNotesFactory) { - this.db = db; - this.accounts = accounts; - this.hooks = hooks; - this.psUtil = psUtil; - this.changeNotesFactory = changeNotesFactory; - } - - @Override - public void onNewProjectCreated(NewProjectCreatedListener.Event ev) { - hooks.doProjectCreatedHook(new Project.NameKey(ev.getProjectName()), - ev.getHeadName()); - } - - @Override - public void onRevisionCreated(RevisionCreatedListener.Event ev) { - try { - ChangeNotes notes = getNotes(ev.getChange()); - hooks.doPatchsetCreatedHook(notes.getChange(), - getPatchSet(notes, ev.getRevision()), db.get()); - } catch (OrmException e) { - log.error("PatchsetCreated hook failed to run " - + ev.getChange()._number, e); - } - } - - @Override - public void onDraftPublished(DraftPublishedListener.Event ev) { - try { - ChangeNotes notes = getNotes(ev.getChange()); - hooks.doDraftPublishedHook(notes.getChange(), - getPatchSet(notes, ev.getRevision()), db.get()); - } catch (OrmException e) { - log.error("DraftPublished hook failed to run " - + ev.getChange()._number, e); - } - } - - @Override - public void onCommentAdded(CommentAddedListener.Event ev) { - Map approvals = convertApprovalsMap(ev.getApprovals()); - Map oldApprovals = convertApprovalsMap(ev.getOldApprovals()); - try { - ChangeNotes notes = getNotes(ev.getChange()); - hooks.doCommentAddedHook(notes.getChange(), - getAccount(ev.getAuthor()), - getPatchSet(notes, ev.getRevision()), - ev.getComment(), approvals, oldApprovals, db.get()); - } catch (OrmException e) { - log.error("CommentAdded hook failed to fun" + ev.getChange()._number, e); - } - } - - @Override - public void onChangeMerged(ChangeMergedListener.Event ev) { - try { - ChangeNotes notes = getNotes(ev.getChange()); - hooks.doChangeMergedHook(notes.getChange(), - getAccount(ev.getMerger()), - getPatchSet(notes, ev.getRevision()), - db.get(), ev.getNewRevisionId()); - } catch (OrmException e) { - log.error("ChangeMerged hook failed to run " + ev.getChange()._number, e); - } - } - - @Override - public void onChangeAbandoned(ChangeAbandonedListener.Event ev) { - try { - ChangeNotes notes = getNotes(ev.getChange()); - hooks.doChangeAbandonedHook(notes.getChange(), - getAccount(ev.getAbandoner()), - getPatchSet(notes, ev.getRevision()), - ev.getReason(), db.get()); - } catch (OrmException e) { - log.error("ChangeAbandoned hook failed to run " - + ev.getChange()._number, e); - } - } - - @Override - public void onChangeRestored(ChangeRestoredListener.Event ev) { - try { - ChangeNotes notes = getNotes(ev.getChange()); - hooks.doChangeRestoredHook(notes.getChange(), - getAccount(ev.getRestorer()), - getPatchSet(notes, ev.getRevision()), - ev.getReason(), db.get()); - } catch (OrmException e) { - log.error("ChangeRestored hook failed to run " - + ev.getChange()._number, e); - } - } - - @Override - public void onGitReferenceUpdated(GitReferenceUpdatedListener.Event ev) { - hooks.doRefUpdatedHook( - new Branch.NameKey(ev.getProjectName(), ev.getRefName()), - ObjectId.fromString(ev.getOldObjectId()), - ObjectId.fromString(ev.getNewObjectId()), - getAccount(ev.getUpdater())); - } - - @Override - public void onReviewerAdded(ReviewerAddedListener.Event ev) { - try { - ChangeNotes notes = getNotes(ev.getChange()); - hooks.doReviewerAddedHook(notes.getChange(), - getAccount(ev.getReviewer()), - psUtil.current(db.get(), notes), - db.get()); - } catch (OrmException e) { - log.error("ReviewerAdded hook failed to run " - + ev.getChange()._number, e); - } - } - - @Override - public void onReviewerDeleted(ReviewerDeletedListener.Event ev) { - try { - ChangeNotes notes = getNotes(ev.getChange()); - hooks.doReviewerDeletedHook(notes.getChange(), - getAccount(ev.getReviewer()), - psUtil.current(db.get(), notes), - ev.getComment(), - convertApprovalsMap(ev.getNewApprovals()), - convertApprovalsMap(ev.getOldApprovals()), - db.get()); - } catch (OrmException e) { - log.error("ReviewerDeleted hook failed to run " - + ev.getChange()._number, e); - } - } - - @Override - public void onTopicEdited(TopicEditedListener.Event ev) { - try { - hooks.doTopicChangedHook(getNotes(ev.getChange()).getChange(), - getAccount(ev.getEditor()), ev.getOldTopic(), db.get()); - } catch (OrmException e) { - log.error("TopicChanged hook failed to run " - + ev.getChange()._number, e); - } - } - - @Override - public void onHashtagsEdited(HashtagsEditedListener.Event ev) { - try { - hooks.doHashtagsChangedHook(getNotes(ev.getChange()).getChange(), - getAccount(ev.getEditor()), - new HashSet<>(ev.getAddedHashtags()), - new HashSet<>(ev.getRemovedHashtags()), - new HashSet<>(ev.getHashtags()), - db.get()); - } catch (OrmException e) { - log.error("HashtagsChanged hook failed to run " - + ev.getChange()._number, e); - } - } - - @Override - public void onAgreementSignup(AgreementSignupListener.Event ev) { - hooks.doClaSignupHook(getAccount(ev.getAccount()), ev.getAgreementName()); - } - - private ChangeNotes getNotes(ChangeInfo info) throws OrmException { - try { - return changeNotesFactory.createChecked(new Change.Id(info._number)); - } catch (NoSuchChangeException e) { - throw new OrmException(e); - } - } - - private PatchSet getPatchSet(ChangeNotes notes, RevisionInfo info) - throws OrmException { - return psUtil.get(db.get(), notes, PatchSet.Id.fromRef(info.ref)); - } - - private Account getAccount(AccountInfo info) { - if (info != null) { - AccountState account = accounts.get(new Account.Id(info._accountId)); - if (account != null) { - return account.getAccount(); - } - } - return null; - } - - private static Map convertApprovalsMap( - Map approvals) { - Map result = new HashMap<>(); - for (Entry e : approvals.entrySet()) { - Short value = - e.getValue().value == null ? null : e.getValue().value.shortValue(); - result.put(e.getKey(), value); - } - return result; - } -} diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java deleted file mode 100644 index c5ea982e0a..0000000000 --- a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java +++ /dev/null @@ -1,886 +0,0 @@ -// Copyright (C) 2010 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.common; - -import com.google.common.base.Optional; -import com.google.common.collect.Sets; -import com.google.common.util.concurrent.ThreadFactoryBuilder; -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.lifecycle.LifecycleModule; -import com.google.gerrit.reviewdb.client.Account; -import com.google.gerrit.reviewdb.client.Branch; -import com.google.gerrit.reviewdb.client.Change; -import com.google.gerrit.reviewdb.client.PatchSet; -import com.google.gerrit.reviewdb.client.Project; -import com.google.gerrit.reviewdb.server.ReviewDb; -import com.google.gerrit.server.account.AccountCache; -import com.google.gerrit.server.account.AccountState; -import com.google.gerrit.server.config.AnonymousCowardName; -import com.google.gerrit.server.config.GerritServerConfig; -import com.google.gerrit.server.config.SitePaths; -import com.google.gerrit.server.data.ChangeAttribute; -import com.google.gerrit.server.data.PatchSetAttribute; -import com.google.gerrit.server.data.RefUpdateAttribute; -import com.google.gerrit.server.events.EventFactory; -import com.google.gerrit.server.git.GitRepositoryManager; -import com.google.gerrit.server.git.WorkQueue; -import com.google.gerrit.server.project.ProjectCache; -import com.google.gwtorm.server.OrmException; -import com.google.inject.Inject; -import com.google.inject.Singleton; - -import org.eclipse.jgit.lib.Config; -import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.revwalk.RevWalk; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.StringReader; -import java.io.StringWriter; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.FutureTask; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -/** Spawns local executables when a hook action occurs. */ -@Singleton -public class ChangeHookRunner implements ChangeHooks, LifecycleListener { - /** A logger for this class. */ - private static final Logger log = LoggerFactory.getLogger(ChangeHookRunner.class); - - public static class Module extends LifecycleModule { - @Override - protected void configure() { - bind(ChangeHookRunner.class); - bind(ChangeHooks.class).to(ChangeHookRunner.class); - listener().to(ChangeHookRunner.class); - } - } - - /** Container class used to hold the return code and output of script hook execution */ - public static class HookResult { - private int exitValue = -1; - private String output; - private String executionError; - - private HookResult(int exitValue, String output) { - this.exitValue = exitValue; - this.output = output; - } - - private HookResult(String output, String executionError) { - this.output = output; - this.executionError = executionError; - } - - public int getExitValue() { - return exitValue; - } - - public void setExitValue(int exitValue) { - this.exitValue = exitValue; - } - - public String getOutput() { - return output; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - - if (output != null && output.length() != 0) { - sb.append(output); - - if (executionError != null) { - sb.append(" - "); - } - } - - if (executionError != null ) { - sb.append(executionError); - } - - return sb.toString(); - } - } - - /** Path of the new patchset hook. */ - private final Optional patchsetCreatedHook; - - /** Path of the draft published hook. */ - private final Optional draftPublishedHook; - - /** Path of the new comments hook. */ - private final Optional commentAddedHook; - - /** Path of the change merged hook. */ - private final Optional changeMergedHook; - - /** Path of the change abandoned hook. */ - private final Optional changeAbandonedHook; - - /** Path of the change restored hook. */ - private final Optional changeRestoredHook; - - /** Path of the ref updated hook. */ - private final Optional refUpdatedHook; - - /** Path of the reviewer added hook. */ - private final Optional reviewerAddedHook; - - /** Path of the reviewer deleted hook. */ - private final Optional reviewerDeletedHook; - - /** Path of the topic changed hook. */ - private final Optional topicChangedHook; - - /** Path of the cla signed hook. */ - private final Optional claSignedHook; - - /** Path of the update hook. */ - private final Optional refUpdateHook; - - /** Path of the hashtags changed hook */ - private final Optional hashtagsChangedHook; - - /** Path of the project created hook. */ - private final Optional projectCreatedHook; - - private final String anonymousCowardName; - - /** Repository Manager. */ - private final GitRepositoryManager repoManager; - - /** Queue of hooks that need to run. */ - private final WorkQueue.Executor hookQueue; - - private final ProjectCache projectCache; - - private final AccountCache accountCache; - - private final EventFactory eventFactory; - - private final SitePaths sitePaths; - - /** Thread pool used to monitor sync hooks */ - private final ExecutorService syncHookThreadPool; - - /** Timeout value for synchronous hooks */ - private final int syncHookTimeout; - - /** - * Create a new ChangeHookRunner. - * - * @param queue Queue to use when processing hooks. - * @param repoManager The repository manager. - * @param config Config file to use. - * @param sitePath The sitepath of this gerrit install. - * @param projectCache the project cache instance for the server. - */ - @Inject - public ChangeHookRunner(WorkQueue queue, - GitRepositoryManager repoManager, - @GerritServerConfig Config config, - @AnonymousCowardName String anonymousCowardName, - SitePaths sitePath, - ProjectCache projectCache, - AccountCache accountCache, - EventFactory eventFactory) { - this.anonymousCowardName = anonymousCowardName; - this.repoManager = repoManager; - this.hookQueue = queue.createQueue(1, "hook"); - this.projectCache = projectCache; - this.accountCache = accountCache; - this.eventFactory = eventFactory; - this.sitePaths = sitePath; - - Path hooksPath; - String hooksPathConfig = config.getString("hooks", null, "path"); - if (hooksPathConfig != null) { - hooksPath = Paths.get(hooksPathConfig); - } else { - hooksPath = sitePath.hooks_dir; - } - - // When adding a new hook, make sure to check that the setting name - // canonicalizes correctly in hook() below. - patchsetCreatedHook = hook(config, hooksPath, "patchset-created"); - draftPublishedHook = hook(config, hooksPath, "draft-published"); - commentAddedHook = hook(config, hooksPath, "comment-added"); - changeMergedHook = hook(config, hooksPath, "change-merged"); - changeAbandonedHook = hook(config, hooksPath, "change-abandoned"); - changeRestoredHook = hook(config, hooksPath, "change-restored"); - refUpdatedHook = hook(config, hooksPath, "ref-updated"); - reviewerAddedHook = hook(config, hooksPath, "reviewer-added"); - reviewerDeletedHook = hook(config, hooksPath, "reviewer-deleted"); - topicChangedHook = hook(config, hooksPath, "topic-changed"); - 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( - new ThreadFactoryBuilder() - .setNameFormat("SyncHook-%d") - .build()); - } - - private static Optional hook(Config config, Path path, String name) { - String setting = name.replace("-", "") + "hook"; - String value = config.getString("hooks", null, setting); - Path p = path.resolve(value != null ? value : name); - return Files.exists(p) ? Optional.of(p) : Optional.absent(); - } - - /** - * Get the Repository for the given project name, or null on error. - * - * @param name Project to get repo for, - * @return Repository or null. - */ - private Repository openRepository(Project.NameKey name) { - try { - return repoManager.openRepository(name); - } catch (IOException err) { - log.warn("Cannot open repository " + name.get(), err); - return null; - } - } - - private void addArg(List args, String name, String value) { - if (value != null) { - args.add(name); - args.add(value); - } - } - - @Override - public HookResult doRefUpdateHook(Project project, String refname, - Account uploader, ObjectId oldId, ObjectId newId) { - if (!refUpdateHook.isPresent()) { - return null; - } - - List args = new ArrayList<>(); - addArg(args, "--project", project.getName()); - addArg(args, "--refname", refname); - addArg(args, "--uploader", getDisplayName(uploader)); - addArg(args, "--oldrev", oldId.getName()); - addArg(args, "--newrev", newId.getName()); - - return runSyncHook(project.getNameKey(), refUpdateHook, args); - } - - @Override - public void doProjectCreatedHook(Project.NameKey project, String headName) { - if (!projectCreatedHook.isPresent()) { - return; - } - - List args = new ArrayList<>(); - addArg(args, "--project", project.get()); - addArg(args, "--head", headName); - - runHook(project, projectCreatedHook, args); - } - - @Override - public void doPatchsetCreatedHook(Change change, - PatchSet patchSet, ReviewDb db) throws OrmException { - if (!patchsetCreatedHook.isPresent()) { - return; - } - - AccountState owner = accountCache.get(change.getOwner()); - AccountState uploader = accountCache.get(patchSet.getUploader()); - List args = new ArrayList<>(); - ChangeAttribute c = eventFactory.asChangeAttribute(change); - PatchSetAttribute ps = patchSetAttribute(change, patchSet); - - addArg(args, "--change", c.id); - addArg(args, "--is-draft", String.valueOf(patchSet.isDraft())); - addArg(args, "--kind", String.valueOf(ps.kind)); - addArg(args, "--change-url", c.url); - addArg(args, "--change-owner", getDisplayName(owner.getAccount())); - addArg(args, "--project", c.project); - addArg(args, "--branch", c.branch); - addArg(args, "--topic", c.topic); - addArg(args, "--uploader", getDisplayName(uploader.getAccount())); - addArg(args, "--commit", ps.revision); - addArg(args, "--patchset", ps.number); - - runHook(change.getProject(), patchsetCreatedHook, args); - } - - @Override - public void doDraftPublishedHook(Change change, PatchSet patchSet, - ReviewDb db) throws OrmException { - if (!draftPublishedHook.isPresent()) { - return; - } - - List args = new ArrayList<>(); - ChangeAttribute c = eventFactory.asChangeAttribute(change); - PatchSetAttribute ps = patchSetAttribute(change, patchSet); - AccountState owner = accountCache.get(change.getOwner()); - AccountState uploader = accountCache.get(patchSet.getUploader()); - - addArg(args, "--change", c.id); - addArg(args, "--change-url", c.url); - addArg(args, "--change-owner", getDisplayName(owner.getAccount())); - addArg(args, "--project", c.project); - addArg(args, "--branch", c.branch); - addArg(args, "--topic", c.topic); - addArg(args, "--uploader", getDisplayName(uploader.getAccount())); - addArg(args, "--commit", ps.revision); - addArg(args, "--patchset", ps.number); - - runHook(change.getProject(), draftPublishedHook, args); - } - - @Override - public void doCommentAddedHook(final Change change, Account account, - PatchSet patchSet, String comment, final Map approvals, - final Map oldApprovals, ReviewDb db) - throws OrmException { - if (!commentAddedHook.isPresent()) { - return; - } - - List args = new ArrayList<>(); - ChangeAttribute c = eventFactory.asChangeAttribute(change); - PatchSetAttribute ps = patchSetAttribute(change, patchSet); - AccountState owner = accountCache.get(change.getOwner()); - - addArg(args, "--change", c.id); - addArg(args, "--is-draft", patchSet.isDraft() ? "true" : "false"); - addArg(args, "--change-url", c.url); - addArg(args, "--change-owner", getDisplayName(owner.getAccount())); - addArg(args, "--project", c.project); - addArg(args, "--branch", c.branch); - addArg(args, "--topic", c.topic); - addArg(args, "--author", getDisplayName(account)); - addArg(args, "--commit", ps.revision); - addArg(args, "--comment", comment == null ? "" : comment); - LabelTypes labelTypes = projectCache.get( - change.getProject()).getLabelTypes(); - for (Map.Entry approval : approvals.entrySet()) { - LabelType lt = labelTypes.byLabel(approval.getKey()); - if (lt != null && approval.getValue() != null) { - addArg(args, "--" + lt.getName(), Short.toString(approval.getValue())); - if (oldApprovals != null && !oldApprovals.isEmpty()) { - Short oldValue = oldApprovals.get(approval.getKey()); - if (oldValue != null) { - addArg(args, "--" + lt.getName() + "-oldValue", - Short.toString(oldValue)); - } - } - } - } - runHook(change.getProject(), commentAddedHook, args); - } - - @Override - public void doChangeMergedHook(Change change, Account account, - PatchSet patchSet, ReviewDb db, String mergeResultRev) - throws OrmException { - if (!changeMergedHook.isPresent()) { - return; - } - - List args = new ArrayList<>(); - ChangeAttribute c = eventFactory.asChangeAttribute(change); - PatchSetAttribute ps = patchSetAttribute(change, patchSet); - AccountState owner = accountCache.get(change.getOwner()); - - addArg(args, "--change", c.id); - addArg(args, "--change-url", c.url); - addArg(args, "--change-owner", getDisplayName(owner.getAccount())); - addArg(args, "--project", c.project); - addArg(args, "--branch", c.branch); - addArg(args, "--topic", c.topic); - addArg(args, "--submitter", getDisplayName(account)); - addArg(args, "--commit", ps.revision); - addArg(args, "--newrev", mergeResultRev); - - runHook(change.getProject(), changeMergedHook, args); - } - - @Override - public void doChangeAbandonedHook(Change change, Account account, - PatchSet patchSet, String reason, ReviewDb db) - throws OrmException { - if (!changeAbandonedHook.isPresent()) { - return; - } - - List args = new ArrayList<>(); - ChangeAttribute c = eventFactory.asChangeAttribute(change); - PatchSetAttribute ps = patchSetAttribute(change, patchSet); - AccountState owner = accountCache.get(change.getOwner()); - - addArg(args, "--change", c.id); - addArg(args, "--change-url", c.url); - addArg(args, "--change-owner", getDisplayName(owner.getAccount())); - addArg(args, "--project", c.project); - addArg(args, "--branch", c.branch); - addArg(args, "--topic", c.topic); - addArg(args, "--abandoner", getDisplayName(account)); - addArg(args, "--commit", ps.revision); - addArg(args, "--reason", reason == null ? "" : reason); - - runHook(change.getProject(), changeAbandonedHook, args); - } - - @Override - public void doChangeRestoredHook(Change change, Account account, - PatchSet patchSet, String reason, ReviewDb db) - throws OrmException { - if (!changeRestoredHook.isPresent()) { - return; - } - - List args = new ArrayList<>(); - ChangeAttribute c = eventFactory.asChangeAttribute(change); - PatchSetAttribute ps = patchSetAttribute(change, patchSet); - AccountState owner = accountCache.get(change.getOwner()); - - addArg(args, "--change", c.id); - addArg(args, "--change-url", c.url); - addArg(args, "--change-owner", getDisplayName(owner.getAccount())); - addArg(args, "--project", c.project); - addArg(args, "--branch", c.branch); - addArg(args, "--topic", c.topic); - addArg(args, "--restorer", getDisplayName(account)); - addArg(args, "--commit", ps.revision); - addArg(args, "--reason", reason == null ? "" : reason); - - runHook(change.getProject(), changeRestoredHook, args); - } - - @Override - public void doRefUpdatedHook(final Branch.NameKey refName, - final ObjectId oldId, final ObjectId newId, Account account) { - if (!refUpdatedHook.isPresent()) { - return; - } - - List args = new ArrayList<>(); - RefUpdateAttribute r = - eventFactory.asRefUpdateAttribute(oldId, newId, refName); - addArg(args, "--oldrev", r.oldRev); - addArg(args, "--newrev", r.newRev); - addArg(args, "--refname", r.refName); - addArg(args, "--project", r.project); - if (account != null) { - addArg(args, "--submitter", getDisplayName(account)); - } - - runHook(refName.getParentKey(), refUpdatedHook, args); - } - - @Override - public void doReviewerAddedHook(Change change, Account account, - PatchSet patchSet, ReviewDb db) throws OrmException { - if (!reviewerAddedHook.isPresent()) { - return; - } - - List args = new ArrayList<>(); - ChangeAttribute c = eventFactory.asChangeAttribute(change); - AccountState owner = accountCache.get(change.getOwner()); - - addArg(args, "--change", c.id); - addArg(args, "--change-url", c.url); - addArg(args, "--change-owner", getDisplayName(owner.getAccount())); - addArg(args, "--project", c.project); - addArg(args, "--branch", c.branch); - addArg(args, "--reviewer", getDisplayName(account)); - - runHook(change.getProject(), reviewerAddedHook, args); - } - - @Override - public void doReviewerDeletedHook(final Change change, Account account, - PatchSet patchSet, String comment, final Map approvals, - final Map oldApprovals, ReviewDb db) throws OrmException { - if (!reviewerDeletedHook.isPresent()) { - return; - } - - List args = new ArrayList<>(); - ChangeAttribute c = eventFactory.asChangeAttribute(change); - AccountState owner = accountCache.get(change.getOwner()); - - addArg(args, "--change", c.id); - addArg(args, "--change-url", c.url); - addArg(args, "--change-owner", getDisplayName(owner.getAccount())); - addArg(args, "--project", c.project); - addArg(args, "--branch", c.branch); - addArg(args, "--reviewer", getDisplayName(account)); - LabelTypes labelTypes = projectCache.get( - change.getProject()).getLabelTypes(); - // append votes that were removed - for (Map.Entry approval : approvals.entrySet()) { - LabelType lt = labelTypes.byLabel(approval.getKey()); - if (lt != null && approval.getValue() != null) { - addArg(args, "--" + lt.getName(), Short.toString(approval.getValue())); - if (oldApprovals != null && !oldApprovals.isEmpty()) { - Short oldValue = oldApprovals.get(approval.getKey()); - if (oldValue != null) { - addArg(args, "--" + lt.getName() + "-oldValue", - Short.toString(oldValue)); - } - } - } - } - runHook(change.getProject(), reviewerDeletedHook, args); - } - - @Override - public void doTopicChangedHook(Change change, Account account, - String oldTopic, ReviewDb db) - throws OrmException { - if (!topicChangedHook.isPresent()) { - return; - } - - List args = new ArrayList<>(); - ChangeAttribute c = eventFactory.asChangeAttribute(change); - AccountState owner = accountCache.get(change.getOwner()); - - addArg(args, "--change", c.id); - addArg(args, "--change-owner", getDisplayName(owner.getAccount())); - addArg(args, "--project", c.project); - addArg(args, "--branch", c.branch); - addArg(args, "--changer", getDisplayName(account)); - addArg(args, "--old-topic", oldTopic); - addArg(args, "--new-topic", c.topic); - - runHook(change.getProject(), topicChangedHook, args); - } - - String[] hashtagArray(Set hashtags) { - if (hashtags != null && hashtags.size() > 0) { - return Sets.newHashSet(hashtags).toArray( - new String[hashtags.size()]); - } - return null; - } - - @Override - public void doHashtagsChangedHook(Change change, Account account, - Set added, Set removed, Set hashtags, ReviewDb db) - throws OrmException { - if (!hashtagsChangedHook.isPresent()) { - return; - } - - List args = new ArrayList<>(); - ChangeAttribute c = eventFactory.asChangeAttribute(change); - AccountState owner = accountCache.get(change.getOwner()); - - addArg(args, "--change", c.id); - addArg(args, "--change-owner", getDisplayName(owner.getAccount())); - addArg(args, "--project", c.project); - addArg(args, "--branch", c.branch); - addArg(args, "--editor", getDisplayName(account)); - if (hashtags != null) { - for (String hashtag : hashtags) { - addArg(args, "--hashtag", hashtag); - } - } - if (added != null) { - for (String hashtag : added) { - addArg(args, "--added", hashtag); - } - } - if (removed != null) { - for (String hashtag : removed) { - addArg(args, "--removed", hashtag); - } - } - runHook(change.getProject(), hashtagsChangedHook, args); - } - - @Override - public void doClaSignupHook(Account account, String claName) { - if (!claSignedHook.isPresent()) { - return; - } - - if (account != null) { - List args = new ArrayList<>(); - addArg(args, "--submitter", getDisplayName(account)); - addArg(args, "--user-id", account.getId().toString()); - addArg(args, "--cla-name", claName); - - runHook(claSignedHook, args); - } - } - - private PatchSetAttribute patchSetAttribute(Change change, - PatchSet patchSet) { - try (Repository repo = - repoManager.openRepository(change.getProject()); - RevWalk revWalk = new RevWalk(repo)) { - return eventFactory.asPatchSetAttribute( - revWalk, change, patchSet); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - /** - * Get the display name for the given account. - * - * @param account Account to get name for. - * @return Name for this account. - */ - private String getDisplayName(Account account) { - if (account != null) { - String result = (account.getFullName() == null) - ? anonymousCowardName - : account.getFullName(); - if (account.getPreferredEmail() != null) { - result += " (" + account.getPreferredEmail() + ")"; - } - return result; - } - - return anonymousCowardName; - } - - /** - * Run a hook. - * - * @param project used to open repository to run the hook for. - * @param hook the hook to execute. - * @param args Arguments to use to run the hook. - */ - private synchronized void runHook(Project.NameKey project, Optional hook, - List args) { - if (project != null && hook.isPresent()) { - hookQueue.execute(new AsyncHookTask(project, hook.get(), args)); - } - } - - private synchronized void runHook(Optional hook, List args) { - if (hook.isPresent()) { - hookQueue.execute(new AsyncHookTask(null, hook.get(), args)); - } - } - - private HookResult runSyncHook(Project.NameKey project, - Optional hook, List args) { - - if (!hook.isPresent()) { - return null; - } - - SyncHookTask syncHook = new SyncHookTask(project, hook.get(), args); - FutureTask task = new FutureTask<>(syncHook); - - syncHookThreadPool.execute(task); - - String message; - - try { - return task.get(syncHookTimeout, TimeUnit.SECONDS); - } catch (TimeoutException e) { - message = "Synchronous hook timed out " + hook.get().toAbsolutePath(); - log.error(message); - } catch (Exception e) { - message = "Error running hook " + hook.get().toAbsolutePath(); - log.error(message, e); - } - - task.cancel(true); - syncHook.cancel(); - return new HookResult(syncHook.getOutput(), message); - } - - @Override - public void start() { - } - - @Override - public void stop() { - syncHookThreadPool.shutdown(); - boolean isTerminated; - do { - try { - isTerminated = syncHookThreadPool.awaitTermination(10, TimeUnit.SECONDS); - } catch (InterruptedException ie) { - isTerminated = false; - } - } while (!isTerminated); - } - - private class HookTask { - private final Project.NameKey project; - private final Path hook; - private final List args; - private StringWriter output; - private Process ps; - - protected HookTask(Project.NameKey project, Path hook, List args) { - this.project = project; - this.hook = hook; - this.args = args; - } - - public String getOutput() { - return output != null ? output.toString() : null; - } - - protected HookResult runHook() { - Repository repo = null; - HookResult result = null; - try { - - List argv = new ArrayList<>(1 + args.size()); - argv.add(hook.toAbsolutePath().toString()); - argv.addAll(args); - - ProcessBuilder pb = new ProcessBuilder(argv); - pb.redirectErrorStream(true); - - if (project != null) { - repo = openRepository(project); - } - - Map env = pb.environment(); - env.put("GERRIT_SITE", sitePaths.site_path.toAbsolutePath().toString()); - - if (repo != null) { - pb.directory(repo.getDirectory()); - - env.put("GIT_DIR", repo.getDirectory().getAbsolutePath()); - } - - ps = pb.start(); - ps.getOutputStream().close(); - String output = null; - try (InputStream is = ps.getInputStream()) { - output = readOutput(is); - } finally { - ps.waitFor(); - result = new HookResult(ps.exitValue(), output); - } - } catch (InterruptedException iex) { - // InterruptedExeception - timeout or cancel - } catch (Throwable err) { - log.error("Error running hook " + hook.toAbsolutePath(), err); - } finally { - if (repo != null) { - repo.close(); - } - } - - if (result != null) { - int exitValue = result.getExitValue(); - if (exitValue == 0) { - log.debug("hook[" + getName() + "] exitValue:" + exitValue); - } else { - log.info("hook[" + getName() + "] exitValue:" + exitValue); - } - - BufferedReader br = - new BufferedReader(new StringReader(result.getOutput())); - try { - String line; - while ((line = br.readLine()) != null) { - log.info("hook[" + getName() + "] output: " + line); - } - } catch (IOException iox) { - log.error("Error writing hook output", iox); - } - } - - return result; - } - - private String readOutput(InputStream is) throws IOException { - output = new StringWriter(); - InputStreamReader input = new InputStreamReader(is); - char[] buffer = new char[4096]; - int n; - while ((n = input.read(buffer)) != -1) { - output.write(buffer, 0, n); - } - - return output.toString(); - } - - protected String getName() { - return hook.getFileName().toString(); - } - - @Override - public String toString() { - return "hook " + hook.getFileName(); - } - - public void cancel() { - ps.destroy(); - } - } - - /** Callable type used to run synchronous hooks */ - private final class SyncHookTask extends HookTask - implements Callable { - - private SyncHookTask(Project.NameKey project, Path hook, List args) { - super(project, hook, args); - } - - @Override - public HookResult call() throws Exception { - return super.runHook(); - } - } - - /** Runnable type used to run asynchronous hooks */ - private final class AsyncHookTask extends HookTask implements Runnable { - - private AsyncHookTask(Project.NameKey project, Path hook, List args) { - super(project, hook, args); - } - - @Override - public void run() { - super.runHook(); - } - } -} diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java deleted file mode 100644 index b6b497102d..0000000000 --- a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright (C) 2012 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.common; - -import com.google.gerrit.common.ChangeHookRunner.HookResult; -import com.google.gerrit.reviewdb.client.Account; -import com.google.gerrit.reviewdb.client.Branch; -import com.google.gerrit.reviewdb.client.Change; -import com.google.gerrit.reviewdb.client.PatchSet; -import com.google.gerrit.reviewdb.client.Project; -import com.google.gerrit.reviewdb.server.ReviewDb; -import com.google.gwtorm.server.OrmException; - -import org.eclipse.jgit.lib.ObjectId; - -import java.util.Map; -import java.util.Set; - -/** Invokes hooks on server actions. */ -public interface ChangeHooks { - /** - * Fire the Patchset Created Hook. - * - * @param change The change itself. - * @param patchSet The Patchset that was created. - * @param db The review database. - * @throws OrmException - */ - void doPatchsetCreatedHook(Change change, PatchSet patchSet, - ReviewDb db) throws OrmException; - - /** - * Fire the Draft Published Hook. - * - * @param change The change itself. - * @param patchSet The Patchset that was published. - * @param db The review database. - * @throws OrmException - */ - void doDraftPublishedHook(Change change, PatchSet patchSet, - ReviewDb db) throws OrmException; - - /** - * Fire the Comment Added Hook. - * - * @param change The change itself. - * @param account The gerrit user who added the comment. - * @param patchSet The patchset this comment is related to. - * @param comment The comment given. - * @param approvals Map of label IDs to scores - * @param oldApprovals Map of label IDs to old approval scores - * @param db The review database. - * @throws OrmException - */ - void doCommentAddedHook(Change change, Account account, - PatchSet patchSet, String comment, - Map approvals, Map oldApprovals, - ReviewDb db) - throws OrmException; - - /** - * Fire the Change Merged Hook. - * - * @param change The change itself. - * @param account The gerrit user who submitted the change. - * @param patchSet The patchset that was merged. - * @param db The review database. - * @param mergeResultRev The SHA-1 of the merge result revision. - * @throws OrmException - */ - void doChangeMergedHook(Change change, Account account, - PatchSet patchSet, ReviewDb db, String mergeResultRev) throws OrmException; - - /** - * Fire the Change Abandoned Hook. - * - * @param change The change itself. - * @param account The gerrit user who abandoned the change. - * @param reason Reason for abandoning the change. - * @param db The review database. - * @throws OrmException - */ - void doChangeAbandonedHook(Change change, Account account, - PatchSet patchSet, String reason, ReviewDb db) throws OrmException; - - /** - * Fire the Change Restored Hook. - * - * @param change The change itself. - * @param account The gerrit user who restored the change. - * @param patchSet The patchset that was restored. - * @param reason Reason for restoring the change. - * @param db The review database. - * @throws OrmException - */ - void doChangeRestoredHook(Change change, Account account, - PatchSet patchSet, String reason, ReviewDb db) throws OrmException; - - /** - * Fire the Ref Updated Hook. - * - * @param refName The Branch.NameKey of the ref that was updated. - * @param oldId The ref's old id. - * @param newId The ref's new id. - * @param account The gerrit user who moved the ref. - */ - void doRefUpdatedHook(Branch.NameKey refName, ObjectId oldId, - ObjectId newId, Account account); - - /** - * Fire the Reviewer Added Hook. - * - * @param change The change itself. - * @param patchSet The patchset that the reviewer was added on. - * @param account The gerrit user who was added as reviewer. - * @param db The review database. - */ - void doReviewerAddedHook(Change change, Account account, - PatchSet patchSet, ReviewDb db) throws OrmException; - - /** - * Fire the Reviewer Deleted Hook - * - * @param change The change itself. - * @param account The reviewer that was removed. - * @param patchSet The patchset that the reviewer was removed from. - * @param comment The comment given. - * @param approvals Map of label IDs to scores. - * @param oldApprovals Map of label IDs to old approval scores - * @param db The review database. - * @throws OrmException - */ - void doReviewerDeletedHook(Change change, Account account, PatchSet patchSet, - String comment, Map approvals, - Map oldApprovals, ReviewDb db) throws OrmException; - - /** - * Fire the Topic Changed Hook - * - * @param change The change itself. - * @param account The gerrit user who changed the topic. - * @param oldTopic The old topic name. - * @param db The review database. - */ - void doTopicChangedHook(Change change, Account account, - String oldTopic, ReviewDb db) throws OrmException; - - /** - * Fire the contributor license agreement signup hook. - * - * @param account The gerrit user who signed the contributor license - * agreement. - * @param claName The name of the contributor license agreement. - */ - void doClaSignupHook(Account account, String claName); - - /** - * Fire the Ref update Hook. - * - * @param project The target project. - * @param refName The Branch.NameKey of the ref provided by client. - * @param uploader The gerrit user running the command. - * @param oldId The ref's old id. - * @param newId The ref's new id. - */ - HookResult doRefUpdateHook(Project project, String refName, - Account uploader, ObjectId oldId, ObjectId newId); - - /** - * Fire the hashtags changed Hook. - * - * @param change The change itself. - * @param account The gerrit user changing the hashtags. - * @param added List of hashtags that were added to the change. - * @param removed List of hashtags that were removed from the change. - * @param hashtags List of hashtags on the change after adding or removing. - * @param db The review database. - * @throws OrmException - */ - void doHashtagsChangedHook(Change change, Account account, - Setadded, Set removed, Set 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. - */ - void doProjectCreatedHook(Project.NameKey project, String headName); -} diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java b/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java deleted file mode 100644 index 33661cec4b..0000000000 --- a/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (C) 2012 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.common; - -import com.google.gerrit.common.ChangeHookRunner.HookResult; -import com.google.gerrit.reviewdb.client.Account; -import com.google.gerrit.reviewdb.client.Branch; -import com.google.gerrit.reviewdb.client.Change; -import com.google.gerrit.reviewdb.client.PatchSet; -import com.google.gerrit.reviewdb.client.Project; -import com.google.gerrit.reviewdb.server.ReviewDb; - -import org.eclipse.jgit.lib.ObjectId; - -import java.util.Map; -import java.util.Set; - -/** Does not invoke hooks. */ -public final class DisabledChangeHooks implements ChangeHooks { - @Override - public void doChangeAbandonedHook(Change change, Account account, - PatchSet patchSet, String reason, ReviewDb db) { - } - - @Override - public void doChangeMergedHook(Change change, Account account, - PatchSet patchSet, ReviewDb db, String mergeResultRev) { - } - - @Override - public void doChangeRestoredHook(Change change, Account account, - PatchSet patchSet, String reason, ReviewDb db) { - } - - @Override - public void doClaSignupHook(Account account, String claName) { - } - - @Override - public void doCommentAddedHook(Change change, Account account, - PatchSet patchSet, String comment, - Map approvals, Map oldApprovals, - ReviewDb db) { - } - - @Override - public void doPatchsetCreatedHook(Change change, PatchSet patchSet, - ReviewDb db) { - } - - @Override - public void doDraftPublishedHook(Change change, PatchSet patchSet, - ReviewDb db) { - } - - @Override - public void doRefUpdatedHook(Branch.NameKey refName, ObjectId oldId, - ObjectId newId, Account account) { - } - - @Override - public void doReviewerAddedHook(Change change, Account account, PatchSet patchSet, - ReviewDb db) { - } - - @Override - public void doReviewerDeletedHook(Change change, Account account, - PatchSet patchSet, String comment, Map approvals, - Map oldApprovals, ReviewDb db) { - } - - @Override - public void doTopicChangedHook(Change change, Account account, String oldTopic, - ReviewDb db) { - } - - @Override - public void doHashtagsChangedHook(Change change, Account account, Set added, - Set removed, Set hashtags, ReviewDb db) { - } - - @Override - public HookResult doRefUpdateHook(Project project, String refName, - Account uploader, ObjectId oldId, ObjectId newId) { - return null; - } - - @Override - public void doProjectCreatedHook(Project.NameKey project, String headName) { - } -} diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java index 9f1f031ff7..9e5b7762c7 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java +++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java @@ -18,8 +18,6 @@ import static com.google.inject.Scopes.SINGLETON; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; -import com.google.gerrit.common.ChangeHooks; -import com.google.gerrit.common.DisabledChangeHooks; import com.google.gerrit.extensions.config.FactoryModule; import com.google.gerrit.gpg.GpgModule; import com.google.gerrit.metrics.DisabledMetricMaker; @@ -180,7 +178,6 @@ public class InMemoryModule extends FactoryModule { bind(SecureStore.class).to(DefaultSecureStore.class); - bind(ChangeHooks.class).to(DisabledChangeHooks.class); install(NoSshKeyCache.module()); install(new CanonicalWebUrlModule() { @Override diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java index 64dce26f7b..cf80df74ac 100644 --- a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java +++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java @@ -18,8 +18,6 @@ import static com.google.inject.Scopes.SINGLETON; import static com.google.inject.Stage.PRODUCTION; import com.google.common.base.Splitter; -import com.google.gerrit.common.ChangeHookApiListener; -import com.google.gerrit.common.ChangeHookRunner; import com.google.gerrit.common.EventBroker; import com.google.gerrit.common.StreamEventsApiListener; import com.google.gerrit.gpg.GpgModule; @@ -300,9 +298,7 @@ public class WebAppInitializer extends GuiceServletContextListener modules.add(new EventBroker.Module()); modules.add(new H2AccountPatchReviewStore.Module()); modules.add(cfgInjector.getInstance(GitRepositoryManagerModule.class)); - modules.add(new ChangeHookApiListener.Module()); modules.add(new StreamEventsApiListener.Module()); - modules.add(new ChangeHookRunner.Module()); modules.add(new ReceiveCommitsExecutorModule()); modules.add(new DiffExecutorModule()); modules.add(new MimeUtil2Module()); diff --git a/plugins/BUCK b/plugins/BUCK index 9948720a8b..c6bb7f1824 100644 --- a/plugins/BUCK +++ b/plugins/BUCK @@ -2,6 +2,7 @@ BASE = get_base_path() CORE = [ 'commit-message-length-validator', 'download-commands', + 'hooks', 'replication', 'reviewnotes', 'singleusergroup' diff --git a/plugins/hooks b/plugins/hooks new file mode 160000 index 0000000000..ac06f40436 --- /dev/null +++ b/plugins/hooks @@ -0,0 +1 @@ +Subproject commit ac06f404363a29d52ef06d607e29f4a553d1c455