From 4c8142df0d76fb4b6b79c66cf8f18ee4c46b28f4 Mon Sep 17 00:00:00 2001 From: Zhen Chen Date: Thu, 16 Jun 2016 17:21:05 -0700 Subject: [PATCH] Add RepoOnlyOp into BatchUpdate RepoOnlyOps is for operations that not tied to a Change, usually, the operations that only touch the repository, e.g., commit created by Gerrit. For example, during submodule submission, there are two changes in the same topic: one change is for the master branch of superproject A, the other is for the master branch of a submodule A'. At the same time, dev branch of A is also subscribed to master branch of A'. When we submit the whole topic together, we need update master branch of A and A' by SubmitStrategyOp, also the dev branch of A with the gitlink commit by this RepoOnlyOp. And group all the Ops for superproject A in a single BatchUpdate. Change-Id: I18b391f5c858b8608745435d465a63596b3eccac --- .../google/gerrit/server/git/BatchUpdate.java | 39 +++-- .../gerrit/server/git/BatchUpdateTest.java | 134 ++++++++++++++++++ 2 files changed, 163 insertions(+), 10 deletions(-) create mode 100644 gerrit-server/src/test/java/com/google/gerrit/server/git/BatchUpdateTest.java diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java index 76d9441f07..6141cf2cad 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java @@ -259,7 +259,7 @@ public class BatchUpdate implements AutoCloseable { } } - public static class Op { + public static class RepoOnlyOp { /** * Override this method to update the repo. * @@ -268,6 +268,18 @@ public class BatchUpdate implements AutoCloseable { public void updateRepo(RepoContext ctx) throws Exception { } + /** + * Override this method to do something after the update + * e.g. send email or run hooks + * + * @param ctx context + */ + //TODO(dborowitz): Support async operations? + public void postUpdate(Context ctx) throws Exception { + } + } + + public static class Op extends RepoOnlyOp { /** * Override this method to modify a change. * @@ -278,15 +290,6 @@ public class BatchUpdate implements AutoCloseable { public boolean updateChange(ChangeContext ctx) throws Exception { return false; } - - /** - * Override this method to perform operations after the update. - * - * @param ctx context - */ - // TODO(dborowitz): Support async operations? - public void postUpdate(Context ctx) throws Exception { - } } public abstract static class InsertChangeOp extends Op { @@ -456,6 +459,7 @@ public class BatchUpdate implements AutoCloseable { private final Map newChanges = new HashMap<>(); private final List> indexFutures = new ArrayList<>(); + private final List repoOnlyOps = new ArrayList<>(); private Repository repo; private ObjectInserter inserter; @@ -572,6 +576,12 @@ public class BatchUpdate implements AutoCloseable { return this; } + public BatchUpdate addRepoOnlyOp(RepoOnlyOp op) { + checkArgument(!(op instanceof Op), "use addOp()"); + repoOnlyOps.add(op); + return this; + } + public BatchUpdate insertChange(InsertChangeOp op) { Context ctx = new Context(); Change c = op.createChange(ctx); @@ -597,6 +607,11 @@ public class BatchUpdate implements AutoCloseable { for (Op op : ops.values()) { op.updateRepo(ctx); } + + for (RepoOnlyOp op : repoOnlyOps) { + op.updateRepo(ctx); + } + if (inserter != null) { inserter.flush(); } @@ -911,5 +926,9 @@ public class BatchUpdate implements AutoCloseable { for (Op op : ops.values()) { op.postUpdate(ctx); } + + for (RepoOnlyOp op : repoOnlyOps) { + op.postUpdate(ctx); + } } } diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/git/BatchUpdateTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/git/BatchUpdateTest.java new file mode 100644 index 0000000000..87bfa001b1 --- /dev/null +++ b/gerrit-server/src/test/java/com/google/gerrit/server/git/BatchUpdateTest.java @@ -0,0 +1,134 @@ +package com.google.gerrit.server.git; + +import static org.junit.Assert.assertEquals; + +import com.google.gerrit.common.TimeUtil; +import com.google.gerrit.lifecycle.LifecycleManager; +import com.google.gerrit.reviewdb.client.Account; +import com.google.gerrit.reviewdb.client.Project; +import com.google.gerrit.reviewdb.server.ReviewDb; +import com.google.gerrit.server.CurrentUser; +import com.google.gerrit.server.IdentifiedUser; +import com.google.gerrit.server.account.AccountManager; +import com.google.gerrit.server.account.AuthRequest; +import com.google.gerrit.server.git.BatchUpdate.RepoContext; +import com.google.gerrit.server.git.BatchUpdate.RepoOnlyOp; +import com.google.gerrit.server.schema.SchemaCreator; +import com.google.gerrit.server.util.RequestContext; +import com.google.gerrit.server.util.ThreadLocalRequestContext; +import com.google.gerrit.testutil.InMemoryDatabase; +import com.google.gerrit.testutil.InMemoryModule; +import com.google.gerrit.testutil.InMemoryRepositoryManager; +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Provider; +import com.google.inject.util.Providers; + +import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.transport.ReceiveCommand; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class BatchUpdateTest { + @Inject + private AccountManager accountManager; + + @Inject + private IdentifiedUser.GenericFactory userFactory; + + @Inject + private InMemoryDatabase schemaFactory; + + @Inject + private InMemoryRepositoryManager repoManager; + + @Inject + private SchemaCreator schemaCreator; + + @Inject + private ThreadLocalRequestContext requestContext; + + @Inject + private BatchUpdate.Factory batchUpdateFactory; + + private LifecycleManager lifecycle; + private ReviewDb db; + private TestRepository repo; + private Project.NameKey project; + private IdentifiedUser user; + + @Before + public void setUp() throws Exception { + Injector injector = Guice.createInjector(new InMemoryModule()); + injector.injectMembers(this); + lifecycle = new LifecycleManager(); + lifecycle.add(injector); + lifecycle.start(); + + db = schemaFactory.open(); + schemaCreator.create(db); + Account.Id userId = accountManager.authenticate(AuthRequest.forUser("user")) + .getAccountId(); + user = userFactory.create(userId); + + project = new Project.NameKey("test"); + + InMemoryRepository inMemoryRepo = repoManager.createRepository(project); + repo = new TestRepository<>(inMemoryRepo); + + requestContext.setContext(new RequestContext() { + @Override + public CurrentUser getUser() { + return user; + } + + @Override + public Provider getReviewDbProvider() { + return Providers.of(db); + } + }); + } + + @After + public void tearDown() { + if (repo != null) { + repo.getRepository().close(); + } + if (lifecycle != null) { + lifecycle.stop(); + } + requestContext.setContext(null); + if (db != null) { + db.close(); + } + InMemoryDatabase.drop(schemaFactory); + } + + @Test + public void addRefUpdateFromFastForwardCommit() throws Exception { + final RevCommit masterCommit = repo.branch("master").commit().create(); + final RevCommit branchCommit = + repo.branch("branch").commit().parent(masterCommit).create(); + + try (BatchUpdate bu = batchUpdateFactory + .create(db, project, user, TimeUtil.nowTs())) { + bu.addRepoOnlyOp(new RepoOnlyOp() { + @Override + public void updateRepo(RepoContext ctx) throws Exception { + ctx.addRefUpdate( + new ReceiveCommand(masterCommit.getId(), branchCommit.getId(), + "refs/heads/master")); + } + }); + bu.execute(); + } + + assertEquals( + repo.getRepository().exactRef("refs/heads/master").getObjectId(), + branchCommit.getId()); + } +}