Merge "Send event and execute hooks when topic is changed"
diff --git a/Documentation/cmd-stream-events.txt b/Documentation/cmd-stream-events.txt
index d5c7f9c..512c801 100644
--- a/Documentation/cmd-stream-events.txt
+++ b/Documentation/cmd-stream-events.txt
@@ -152,6 +152,15 @@
 
 reviewer:: link:json.html#account[account attribute]
 
+Topic Changed
+^^^^^^^^^^^^^
+type:: "topic-changed"
+
+change:: link:json.html#change[change attribute]
+
+changer:: link:json.html#account[account attribute]
+
+oldTopic:: Topic name before it was changed.
 
 SEE ALSO
 --------
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 8864054..5c9bf6a 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -1441,6 +1441,11 @@
 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.
+
 [[hooks.claSignedHook]]hooks.claSignedHook::
 +
 Optional filename for the CLA signed hook, if not specified then
diff --git a/Documentation/config-hooks.txt b/Documentation/config-hooks.txt
index 4b867e6..e232c0c 100644
--- a/Documentation/config-hooks.txt
+++ b/Documentation/config-hooks.txt
@@ -120,6 +120,15 @@
   reviewer-added --change <change id> --change-url <change url> --project <project name> --branch <branch> --reviewer <reviewer>
 ====
 
+topic-changed
+~~~~~~~~~~~~~
+
+Called whenever a change's topic is changed from the Web UI or via the REST API.
+
+====
+  topic-changed --change <change id> --changer <changer> --old-topic <old topic> --new-topic <new topic>
+====
+
 cla-signed
 ~~~~~~~~~~
 
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
index 92c178d..66a6ae8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
@@ -44,6 +44,7 @@
 import com.google.gerrit.server.events.PatchSetCreatedEvent;
 import com.google.gerrit.server.events.RefUpdatedEvent;
 import com.google.gerrit.server.events.ReviewerAddedEvent;
+import com.google.gerrit.server.events.TopicChangedEvent;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.gerrit.server.project.ProjectCache;
@@ -186,6 +187,9 @@
     /** Filename of the reviewer added hook. */
     private final File reviewerAddedHook;
 
+    /** Filename of the topic changed hook. */
+    private final File topicChangedHook;
+
     /** Filename of the cla signed hook. */
     private final File claSignedHook;
 
@@ -254,6 +258,7 @@
         changeRestoredHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "changeRestoredHook", "change-restored")).getPath());
         refUpdatedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "refUpdatedHook", "ref-updated")).getPath());
         reviewerAddedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "reviewerAddedHook", "reviewer-added")).getPath());
+        topicChangedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "topicChangedHook", "topic-changed")).getPath());
         claSignedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "claSignedHook", "cla-signed")).getPath());
         refUpdateHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "refUpdateHook", "ref-update")).getPath());
         syncHookTimeout = config.getInt("hooks", "syncHookTimeout", 30);
@@ -560,6 +565,25 @@
       runHook(change.getProject(), reviewerAddedHook, args);
     }
 
+    public void doTopicChangedHook(final Change change, final Account account,
+        final String oldTopic, final ReviewDb db)
+            throws OrmException {
+      final TopicChangedEvent event = new TopicChangedEvent();
+
+      event.change = eventFactory.asChangeAttribute(change);
+      event.changer = eventFactory.asAccountAttribute(account);
+      event.oldTopic = oldTopic;
+      fireEvent(change, event, db);
+
+      final List<String> args = new ArrayList<String>();
+      addArg(args, "--change", event.change.id);
+      addArg(args, "--changer", getDisplayName(account));
+      addArg(args, "--old-topic", oldTopic);
+      addArg(args, "--new-topic", event.change.topic);
+
+      runHook(change.getProject(), topicChangedHook, args);
+    }
+
     public void doClaSignupHook(Account account, ContributorAgreement cla) {
       if (account != null) {
         final List<String> args = new ArrayList<String>();
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
index ccff39a..28c64a9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java
@@ -147,6 +147,16 @@
   public void doReviewerAddedHook(Change change, Account account,
       PatchSet patchSet, 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.
+   */
+  public void doTopicChangedHook(Change change, Account account,
+      String oldTopic, ReviewDb db) throws OrmException;
+
   public void doClaSignupHook(Account account, ContributorAgreement cla);
 
   /**
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
index db87595..021d1d2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java
@@ -91,6 +91,11 @@
   }
 
   @Override
+  public void doTopicChangedHook(Change change, Account account, String oldTopic,
+      ReviewDb db) {
+  }
+
+  @Override
   public void removeChangeListener(ChangeListener listener) {
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
index f46aa9e..346d53c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.change;
 
 import com.google.common.base.Strings;
+import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.DefaultInput;
@@ -38,6 +39,7 @@
 class PutTopic implements RestModifyView<ChangeResource, Input> {
   private final Provider<ReviewDb> dbProvider;
   private final ChangeIndexer indexer;
+  private final ChangeHooks hooks;
 
   static class Input {
     @DefaultInput
@@ -46,9 +48,11 @@
   }
 
   @Inject
-  PutTopic(Provider<ReviewDb> dbProvider, ChangeIndexer indexer) {
+  PutTopic(Provider<ReviewDb> dbProvider, ChangeIndexer indexer,
+      ChangeHooks hooks) {
     this.dbProvider = dbProvider;
     this.indexer = indexer;
+    this.hooks = hooks;
   }
 
   @Override
@@ -80,9 +84,10 @@
             oldTopicName, newTopicName);
       }
 
+      IdentifiedUser currentUser = ((IdentifiedUser) control.getCurrentUser());
       ChangeMessage cmsg = new ChangeMessage(
           new ChangeMessage.Key(change.getId(), ChangeUtil.messageUUID(db)),
-          ((IdentifiedUser) control.getCurrentUser()).getAccountId(),
+          currentUser.getAccountId(),
           change.currentPatchSetId());
       StringBuilder msgBuf = new StringBuilder(summary);
       if (!Strings.isNullOrEmpty(input.message)) {
@@ -101,6 +106,8 @@
         });
       db.changeMessages().insert(Collections.singleton(cmsg));
       indexer.index(change);
+      hooks.doTopicChangedHook(change, currentUser.getAccount(),
+          oldTopicName, db);
     }
     return Strings.isNullOrEmpty(newTopicName)
         ? Response.none()
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/TopicChangedEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/TopicChangedEvent.java
new file mode 100644
index 0000000..e725eac
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/TopicChangedEvent.java
@@ -0,0 +1,25 @@
+// Copyright (C) 2013 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.server.data.AccountAttribute;
+import com.google.gerrit.server.data.ChangeAttribute;
+
+public class TopicChangedEvent extends ChangeEvent {
+  public final String type = "topic-changed";
+  public ChangeAttribute change;
+  public AccountAttribute changer;
+  public String oldTopic;
+}
\ No newline at end of file