From 6cc1190d8fecbd617e10640e7bae780de76f0855 Mon Sep 17 00:00:00 2001 From: Jesse Greenwald Date: Wed, 6 Oct 2010 19:46:25 -0500 Subject: [PATCH] Added support for ref-update events Whenever a ref is updated via either a direct push to a branch or a Gerrit change submission, Gerrit will now send a new "ref-updated" event to the event stream. This can be used by external agents for things like automatic submodule ref updating, replication, build bot hooks, etc. Change-Id: Ice9d65db8fd662d53df53ff6b61d811815c9f2f6 --- Documentation/cmd-stream-events.txt | 9 +++ Documentation/config-hooks.txt | 9 +++ Documentation/json.txt | 13 ++++ .../gerrit/httpd/rpc/project/AddBranch.java | 5 ++ .../httpd/rpc/project/DeleteBranches.java | 12 ++- .../gerrit/common/ChangeHookRunner.java | 74 ++++++++++++++++++- .../gerrit/server/events/EventFactory.java | 20 +++++ .../server/events/RefUpdateAttribute.java | 22 ++++++ .../gerrit/server/events/RefUpdatedEvent.java | 21 ++++++ .../com/google/gerrit/server/git/MergeOp.java | 7 ++ .../gerrit/server/git/ReceiveCommits.java | 2 + 11 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 gerrit-server/src/main/java/com/google/gerrit/server/events/RefUpdateAttribute.java create mode 100644 gerrit-server/src/main/java/com/google/gerrit/server/events/RefUpdatedEvent.java diff --git a/Documentation/cmd-stream-events.txt b/Documentation/cmd-stream-events.txt index aae908a2f7..3b348dae27 100644 --- a/Documentation/cmd-stream-events.txt +++ b/Documentation/cmd-stream-events.txt @@ -102,6 +102,15 @@ author:: link:json.html#account[account attribute] comment:: Comment text author had written +Ref Updated +^^^^^^^^^^^ +type:: "ref-updated" + +submitter:: link:json.html#account[account attribute] + +refUpdate:: link:json.html#refupdate[refupdate attribute] + + SEE ALSO -------- diff --git a/Documentation/config-hooks.txt b/Documentation/config-hooks.txt index e271ba8155..fd2ae82b93 100644 --- a/Documentation/config-hooks.txt +++ b/Documentation/config-hooks.txt @@ -66,6 +66,15 @@ Called whenever a change has been restored. change-restored --change --change-url --project --branch --restorer --reason ==== +ref-updated +~~~~~~~~~~~ + +Called whenever a ref has been updated. + +==== + ref-updated --oldrev --newrev --refname --project --submitter +==== + Configuration Settings ---------------------- diff --git a/Documentation/json.txt b/Documentation/json.txt index 1c9a808dbe..99b158daae 100644 --- a/Documentation/json.txt +++ b/Documentation/json.txt @@ -107,6 +107,19 @@ was added or last updated. by:: Reviewer of the patch set in <>. +[[refupdate]] +refupdate +-------- +Information about a ref that was updated. + +oldRev:: The old value of the ref, prior to the update. + +newRev:: The new value the ref was updated to. + +project:: Project path in Gerrit + +refName:: Ref name within project. + SEE ALSO -------- diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddBranch.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddBranch.java index 0472dd4c4f..202c9497bd 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddBranch.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddBranch.java @@ -14,6 +14,7 @@ package com.google.gerrit.httpd.rpc.project; +import com.google.gerrit.common.ChangeHookRunner; import com.google.gerrit.common.data.ListBranchesResult; import com.google.gerrit.common.errors.InvalidNameException; import com.google.gerrit.common.errors.InvalidRevisionException; @@ -58,6 +59,7 @@ class AddBranch extends Handler { private final IdentifiedUser identifiedUser; private final GitRepositoryManager repoManager; private final ReplicationQueue replication; + private final ChangeHookRunner hooks; private final Project.NameKey projectName; private final String branchName; @@ -69,6 +71,7 @@ class AddBranch extends Handler { final IdentifiedUser identifiedUser, final GitRepositoryManager repoManager, final ReplicationQueue replication, + final ChangeHookRunner hooks, @Assisted Project.NameKey projectName, @Assisted("branchName") String branchName, @@ -78,6 +81,7 @@ class AddBranch extends Handler { this.identifiedUser = identifiedUser; this.repoManager = repoManager; this.replication = replication; + this.hooks = hooks; this.projectName = projectName; this.branchName = branchName; @@ -136,6 +140,7 @@ class AddBranch extends Handler { case NEW: case NO_CHANGE: replication.scheduleUpdate(name.getParentKey(), refname); + hooks.doRefUpdatedHook(name, u, identifiedUser.getAccount()); break; default: { final String msg = diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteBranches.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteBranches.java index e8c8904e16..fffc1261ae 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteBranches.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteBranches.java @@ -14,9 +14,11 @@ package com.google.gerrit.httpd.rpc.project; +import com.google.gerrit.common.ChangeHookRunner; import com.google.gerrit.httpd.rpc.Handler; import com.google.gerrit.reviewdb.Branch; import com.google.gerrit.reviewdb.Project; +import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.ReplicationQueue; import com.google.gerrit.server.project.NoSuchProjectException; @@ -46,6 +48,8 @@ class DeleteBranches extends Handler> { private final ProjectControl.Factory projectControlFactory; private final GitRepositoryManager repoManager; private final ReplicationQueue replication; + private final IdentifiedUser identifiedUser; + private final ChangeHookRunner hooks; private final Project.NameKey projectName; private final Set toRemove; @@ -54,11 +58,15 @@ class DeleteBranches extends Handler> { DeleteBranches(final ProjectControl.Factory projectControlFactory, final GitRepositoryManager repoManager, final ReplicationQueue replication, + final IdentifiedUser identifiedUser, + final ChangeHookRunner hooks, @Assisted Project.NameKey name, @Assisted Set toRemove) { this.projectControlFactory = projectControlFactory; this.repoManager = repoManager; this.replication = replication; + this.identifiedUser = identifiedUser; + this.hooks = hooks; this.projectName = name; this.toRemove = toRemove; @@ -85,8 +93,9 @@ class DeleteBranches extends Handler> { for (final Branch.NameKey branchKey : toRemove) { final String refname = branchKey.get(); final RefUpdate.Result result; + final RefUpdate u; try { - final RefUpdate u = r.updateRef(refname); + u = r.updateRef(refname); u.setForceUpdate(true); result = u.delete(); } catch (IOException e) { @@ -101,6 +110,7 @@ class DeleteBranches extends Handler> { case FORCED: deleted.add(branchKey); replication.scheduleUpdate(projectName, refname); + hooks.doRefUpdatedHook(branchKey, u, identifiedUser.getAccount()); break; case REJECTED_CURRENT_BRANCH: 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 d29bc23964..c7c51b6cf1 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 @@ -19,6 +19,7 @@ import com.google.gerrit.common.data.ApprovalTypes; import com.google.gerrit.reviewdb.Account; import com.google.gerrit.reviewdb.ApprovalCategory; import com.google.gerrit.reviewdb.ApprovalCategoryValue; +import com.google.gerrit.reviewdb.Branch; import com.google.gerrit.reviewdb.Change; import com.google.gerrit.reviewdb.PatchSet; import com.google.gerrit.reviewdb.Project; @@ -35,6 +36,7 @@ import com.google.gerrit.server.events.ChangeRestoreEvent; import com.google.gerrit.server.events.CommentAddedEvent; import com.google.gerrit.server.events.EventFactory; import com.google.gerrit.server.events.PatchSetCreatedEvent; +import com.google.gerrit.server.events.RefUpdatedEvent; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.WorkQueue; import com.google.gerrit.server.project.ProjectCache; @@ -45,6 +47,8 @@ import com.google.inject.Singleton; import org.eclipse.jgit.errors.RepositoryNotFoundException; import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -96,6 +100,9 @@ public class ChangeHookRunner { /** Filename of the change abandoned hook. */ private final File changeRestoredHook; + /** Filename of the ref updated hook. */ + private final File refUpdatedHook; + /** Repository Manager. */ private final GitRepositoryManager repoManager; @@ -141,6 +148,7 @@ public class ChangeHookRunner { changeMergedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "changeMergedHook", "change-merged")).getPath()); changeAbandonedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "changeAbandonedHook", "change-abandoned")).getPath()); 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()); } public void addChangeListener(ChangeListener listener, IdentifiedUser user) { @@ -172,7 +180,16 @@ public class ChangeHookRunner { * @return Repository or null. */ private Repository openRepository(final Change change) { - Project.NameKey name = change.getProject(); + return openRepository(change.getProject()); + } + + /** + * 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(final Project.NameKey name) { try { return repoManager.openRepository(name.get()); } catch (RepositoryNotFoundException err) { @@ -335,6 +352,44 @@ public class ChangeHookRunner { runHook(openRepository(change), changeRestoredHook, args); } + /** + * Fire the Ref Updated Hook + * @param project The project the ref update occured on + * @param refUpdate An actual RefUpdate object + * @param account The gerrit user who moved the ref + */ + public void doRefUpdatedHook(final Branch.NameKey refName, final RefUpdate refUpdate, final Account account) { + doRefUpdatedHook(refName, refUpdate.getOldObjectId(), refUpdate.getNewObjectId(), account); + } + + /** + * 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 + */ + public void doRefUpdatedHook(final Branch.NameKey refName, final ObjectId oldId, final ObjectId newId, final Account account) { + final RefUpdatedEvent event = new RefUpdatedEvent(); + + if (account != null) { + event.submitter = eventFactory.asAccountAttribute(account); + } + event.refUpdate = eventFactory.asRefUpdateAttribute(oldId, newId, refName); + fireEvent(refName, event); + + final List args = new ArrayList(); + addArg(args, "--oldrev", event.refUpdate.oldRev); + addArg(args, "--newrev", event.refUpdate.newRev); + addArg(args, "--refname", event.refUpdate.refName); + addArg(args, "--project", event.refUpdate.project); + if (account != null) { + addArg(args, "--submitter", getDisplayName(account)); + } + + runHook(openRepository(refName.getParentKey()), refUpdatedHook, args); + } + private void fireEvent(final Change change, final ChangeEvent event) { for (ChangeListenerHolder holder : listeners.values()) { if (isVisibleTo(change, holder.user)) { @@ -343,6 +398,14 @@ public class ChangeHookRunner { } } + private void fireEvent(Branch.NameKey branchName, final ChangeEvent event) { + for (ChangeListenerHolder holder : listeners.values()) { + if (isVisibleTo(branchName, holder.user)) { + holder.listener.onChangeEvent(event); + } + } + } + private boolean isVisibleTo(Change change, IdentifiedUser user) { final ProjectState pe = projectCache.get(change.getProject()); if (pe == null) { @@ -352,6 +415,15 @@ public class ChangeHookRunner { return pc.controlFor(change).isVisible(); } + private boolean isVisibleTo(Branch.NameKey branchName, IdentifiedUser user) { + final ProjectState pe = projectCache.get(branchName.getParentKey()); + if (pe == null) { + return false; + } + final ProjectControl pc = pe.controlFor(user); + return pc.controlForRef(branchName).isVisible(); + } + /** * Create an ApprovalAttribute for the given approval suitable for serialization to JSON. * @param approval diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java index 447b0984ab..573e6bb250 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java @@ -17,6 +17,7 @@ package com.google.gerrit.server.events; import com.google.gerrit.common.data.ApprovalType; import com.google.gerrit.common.data.ApprovalTypes; import com.google.gerrit.reviewdb.Account; +import com.google.gerrit.reviewdb.Branch; import com.google.gerrit.reviewdb.Change; import com.google.gerrit.reviewdb.PatchSet; import com.google.gerrit.reviewdb.PatchSetApproval; @@ -28,6 +29,8 @@ import com.google.inject.Provider; import com.google.inject.Singleton; import com.google.inject.internal.Nullable; +import org.eclipse.jgit.lib.ObjectId; + import java.util.ArrayList; import java.util.Collection; import java.util.Map; @@ -67,6 +70,23 @@ public class EventFactory { return a; } + /** + * Create a RefUpdateAttribute for the given old ObjectId, new ObjectId, and + * branch that is suitable for serialization to JSON. + * + * @param refUpdate + * @param refName + * @return object suitable for serialization to JSON + */ + public RefUpdateAttribute asRefUpdateAttribute(final ObjectId oldId, final ObjectId newId, final Branch.NameKey refName) { + RefUpdateAttribute ru = new RefUpdateAttribute(); + ru.newRev = newId != null ? newId.getName() : ObjectId.zeroId().getName(); + ru.oldRev = oldId != null ? oldId.getName() : ObjectId.zeroId().getName(); + ru.project = refName.getParentKey().get(); + ru.refName = refName.getShortName(); + return ru; + } + /** * Extend the existing ChangeAttribute with additional fields. * diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/RefUpdateAttribute.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/RefUpdateAttribute.java new file mode 100644 index 0000000000..e4d715a3f0 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/RefUpdateAttribute.java @@ -0,0 +1,22 @@ +// 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.server.events; + +public class RefUpdateAttribute { + public String oldRev; + public String newRev; + public String refName; + public String project; +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/RefUpdatedEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/RefUpdatedEvent.java new file mode 100644 index 0000000000..f90bc81018 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/RefUpdatedEvent.java @@ -0,0 +1,21 @@ +// 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.server.events; + +public class RefUpdatedEvent extends ChangeEvent { + public final String type = "ref-updated"; + public AccountAttribute submitter; + public RefUpdateAttribute refUpdate; +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java index f699d0ddee..2efb1f4db1 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java @@ -881,6 +881,13 @@ public class MergeOp { case FAST_FORWARD: replication.scheduleUpdate(destBranch.getParentKey(), branchUpdate .getName()); + + Account account = null; + final PatchSetApproval submitter = getSubmitter(mergeTip.patchsetId); + if (submitter != null) { + account = accountCache.get(submitter.getAccountId()).getAccount(); + } + hooks.doRefUpdatedHook(destBranch, branchUpdate, account); break; default: diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java index bddb89f945..8b6f39782c 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java @@ -302,6 +302,8 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook { // Change refs are scheduled when they are created. // replication.scheduleUpdate(project.getNameKey(), c.getRefName()); + Branch.NameKey destBranch = new Branch.NameKey(project.getNameKey(), c.getRefName()); + hooks.doRefUpdatedHook(destBranch, c.getOldId(), c.getNewId(), currentUser.getAccount()); } } }