Initial libraty creation
The problem is with DynamicItem - it doesn't get updated with the Gerrit
cache reference. It seems that it doesn't work across db and sys injector.
Change-Id: I05cf4766e4b74b380c9fbf7c6e25553dfa76d5d3
diff --git a/BUILD b/BUILD
new file mode 100644
index 0000000..f8f49ad
--- /dev/null
+++ b/BUILD
@@ -0,0 +1,44 @@
+load("//tools/bzl:junit.bzl", "junit_tests")
+load("//javatests/com/google/gerrit/acceptance:tests.bzl", "acceptance_tests")
+load(
+ "//tools/bzl:plugin.bzl",
+ "PLUGIN_DEPS",
+ "PLUGIN_DEPS_NEVERLINK",
+ "PLUGIN_TEST_DEPS",
+ "gerrit_plugin",
+)
+
+gerrit_plugin(
+ name = "gerrit-cached-refdb",
+ srcs = glob(["src/main/java/**/*.java"]),
+ manifest_entries = [
+ "Gerrit-PluginName: gerrit-cached-refdb",
+ "Implementation-Title: gerrit-cached-refdb plugin",
+ "Implementation-URL: TODO",
+ ],
+ resources = glob(["src/main/resources/**/*"]),
+ deps = [],
+)
+
+junit_tests(
+ name = "gerrit-cached-refdb_tests",
+ srcs = glob(["src/test/java/**/*Test.java"]),
+ resources = glob(["src/test/resources/**/*"]),
+ tags = [
+ "local",
+ "gerrit-cached-refdb",
+ ],
+ deps = PLUGIN_DEPS + PLUGIN_TEST_DEPS + [
+ ":gerrit-cached-refdb__plugin",
+ ],
+)
+
+acceptance_tests(
+ srcs = glob(["src/test/java/**/*IT.java"]),
+ group = "jgit_cache",
+ labels = ["server"],
+ vm_args = ["-Xmx2G"],
+ deps = [
+ ":gerrit-cached-refdb__plugin",
+ ],
+)
\ No newline at end of file
diff --git a/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/BatchRefUpdateWithCacheUpdate.java b/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/BatchRefUpdateWithCacheUpdate.java
new file mode 100644
index 0000000..360dfa1
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/BatchRefUpdateWithCacheUpdate.java
@@ -0,0 +1,184 @@
+// Copyright (C) 2021 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.googlesource.gerrit.plugins.gerritcachedrefdb;
+
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.PushCertificate;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.eclipse.jgit.util.time.ProposedTimestamp;
+
+class BatchRefUpdateWithCacheUpdate extends BatchRefUpdate {
+ interface Factory {
+ BatchRefUpdateWithCacheUpdate create(Repository repo, BatchRefUpdate delegate);
+ }
+
+ private final Repository repo;
+ private final RefByNameCacheWrapper refsCache;
+ private final BatchRefUpdate delegate;
+
+ @Inject
+ BatchRefUpdateWithCacheUpdate(
+ RefByNameCacheWrapper refsCache,
+ @Assisted Repository repo,
+ @Assisted BatchRefUpdate delegate) {
+ super(repo.getRefDatabase());
+ this.refsCache = refsCache;
+ this.repo = repo;
+ this.delegate = delegate;
+ }
+
+ @Override
+ public boolean isAllowNonFastForwards() {
+ return delegate.isAllowNonFastForwards();
+ }
+
+ @Override
+ public BatchRefUpdate setAllowNonFastForwards(boolean allow) {
+ delegate.setAllowNonFastForwards(allow);
+ return this;
+ }
+
+ @Override
+ public PersonIdent getRefLogIdent() {
+ return delegate.getRefLogIdent();
+ }
+
+ @Override
+ public BatchRefUpdate setRefLogIdent(PersonIdent pi) {
+ delegate.setRefLogIdent(pi);
+ return this;
+ }
+
+ @Override
+ public String getRefLogMessage() {
+ return delegate.getRefLogMessage();
+ }
+
+ @Override
+ public boolean isRefLogIncludingResult() {
+ return delegate.isRefLogIncludingResult();
+ }
+
+ @Override
+ public BatchRefUpdate setRefLogMessage(String msg, boolean appendStatus) {
+ delegate.setRefLogMessage(msg, appendStatus);
+ return this;
+ }
+
+ @Override
+ public BatchRefUpdate disableRefLog() {
+ delegate.disableRefLog();
+ return this;
+ }
+
+ @Override
+ public BatchRefUpdate setForceRefLog(boolean force) {
+ delegate.setForceRefLog(force);
+ return this;
+ }
+
+ @Override
+ public boolean isRefLogDisabled() {
+ return delegate.isRefLogDisabled();
+ }
+
+ @Override
+ public BatchRefUpdate setAtomic(boolean atomic) {
+ delegate.setAtomic(atomic);
+ return this;
+ }
+
+ @Override
+ public boolean isAtomic() {
+ return delegate.isAtomic();
+ }
+
+ @Override
+ public void setPushCertificate(PushCertificate cert) {
+ delegate.setPushCertificate(cert);
+ }
+
+ @Override
+ public List<ReceiveCommand> getCommands() {
+ return delegate.getCommands();
+ }
+
+ @Override
+ public BatchRefUpdate addCommand(ReceiveCommand cmd) {
+ delegate.addCommand(cmd);
+ return this;
+ }
+
+ @Override
+ public BatchRefUpdate addCommand(ReceiveCommand... cmd) {
+ delegate.addCommand(cmd);
+ return this;
+ }
+
+ @Override
+ public BatchRefUpdate addCommand(Collection<ReceiveCommand> cmd) {
+ delegate.addCommand(cmd);
+ return this;
+ }
+
+ @Override
+ public List<String> getPushOptions() {
+ return delegate.getPushOptions();
+ }
+
+ @Override
+ public List<ProposedTimestamp> getProposedTimestamps() {
+ return delegate.getProposedTimestamps();
+ }
+
+ @Override
+ public BatchRefUpdate addProposedTimestamp(ProposedTimestamp ts) {
+ delegate.addProposedTimestamp(ts);
+ return this;
+ }
+
+ @Override
+ public void execute(RevWalk walk, ProgressMonitor monitor, List<String> options)
+ throws IOException {
+ delegate.execute(walk, monitor, options);
+ evictCache();
+ }
+
+ @Override
+ public void execute(RevWalk walk, ProgressMonitor monitor) throws IOException {
+ delegate.execute(walk, monitor);
+ evictCache();
+ }
+
+ private void evictCache() {
+ delegate
+ .getCommands()
+ .forEach(
+ cmd -> {
+ if (cmd.getResult() == ReceiveCommand.Result.OK) {
+ refsCache.evict(repo.getIdentifier(), cmd.getRefName());
+ }
+ });
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/CachedRefDatabase.java b/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/CachedRefDatabase.java
new file mode 100644
index 0000000..79a619c
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/CachedRefDatabase.java
@@ -0,0 +1,174 @@
+// Copyright (C) 2021 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.googlesource.gerrit.plugins.gerritcachedrefdb;
+
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.RefRename;
+import org.eclipse.jgit.lib.RefUpdate;
+
+class CachedRefDatabase extends RefDatabase {
+ interface Factory {
+ CachedRefDatabase create(CachedRefRepository repo);
+ }
+
+ private final RefByNameCacheWrapper refsCache;
+ private final BatchRefUpdateWithCacheUpdate.Factory batchUpdateFactory;
+ private final RefUpdateWithCacheUpdate.Factory updateFactory;
+ private final RefRenameWithCacheUpdate.Factory renameFactory;
+ private final RefDatabase delegate;
+ private final CachedRefRepository repo;
+
+ @Inject
+ CachedRefDatabase(
+ RefByNameCacheWrapper refsCache,
+ BatchRefUpdateWithCacheUpdate.Factory batchUpdateFactory,
+ RefUpdateWithCacheUpdate.Factory updateFactory,
+ RefRenameWithCacheUpdate.Factory renameFactory,
+ @Assisted CachedRefRepository repo) {
+ this.refsCache = refsCache;
+ this.batchUpdateFactory = batchUpdateFactory;
+ this.updateFactory = updateFactory;
+ this.renameFactory = renameFactory;
+ this.delegate = repo.getDelegate().getRefDatabase();
+ this.repo = repo;
+ }
+
+ @Override
+ public void create() throws IOException {
+ delegate.create();
+ }
+
+ @Override
+ public void close() {
+ delegate.close();
+ }
+
+ @Override
+ public boolean isNameConflicting(String name) throws IOException {
+ return delegate.isNameConflicting(name);
+ }
+
+ @Override
+ public RefUpdate newUpdate(String name, boolean detach) throws IOException {
+ return updateFactory.create(this, repo, delegate.newUpdate(name, detach));
+ }
+
+ @Override
+ public RefRename newRename(String fromName, String toName) throws IOException {
+ return renameFactory.create(
+ repo,
+ delegate.newRename(fromName, toName),
+ newUpdate(fromName, false),
+ newUpdate(toName, false));
+ }
+
+ @Override
+ public Ref exactRef(String name) throws IOException {
+ return refsCache.computeIfAbsent(
+ repo.getIdentifier(), name, () -> Optional.ofNullable(delegate.exactRef(name)));
+ }
+
+ @Deprecated
+ @Override
+ public Map<String, Ref> getRefs(String prefix) throws IOException {
+ return delegate.getRefs(prefix);
+ }
+
+ @Override
+ public List<Ref> getAdditionalRefs() throws IOException {
+ return delegate.getAdditionalRefs();
+ }
+
+ @Override
+ public Ref peel(Ref ref) throws IOException {
+ return delegate.peel(ref);
+ }
+
+ @Override
+ public boolean hasVersioning() {
+ return delegate.hasVersioning();
+ }
+
+ @Override
+ public Collection<String> getConflictingNames(String name) throws IOException {
+ return delegate.getConflictingNames(name);
+ }
+
+ @Override
+ public BatchRefUpdate newBatchUpdate() {
+ return batchUpdateFactory.create(repo, delegate.newBatchUpdate());
+ }
+
+ @Override
+ public boolean performsAtomicTransactions() {
+ return delegate.performsAtomicTransactions();
+ }
+
+ @Override
+ public Map<String, Ref> exactRef(String... refs) throws IOException {
+ return delegate.exactRef(refs);
+ }
+
+ @Override
+ public Ref firstExactRef(String... refs) throws IOException {
+ return delegate.firstExactRef(refs);
+ }
+
+ @Override
+ public List<Ref> getRefs() throws IOException {
+ return delegate.getRefs();
+ }
+
+ @Override
+ public List<Ref> getRefsByPrefix(String prefix) throws IOException {
+ return delegate.getRefsByPrefix(prefix);
+ }
+
+ @Override
+ public List<Ref> getRefsByPrefix(String... prefixes) throws IOException {
+ return delegate.getRefsByPrefix(prefixes);
+ }
+
+ @Override
+ public Set<Ref> getTipsWithSha1(ObjectId id) throws IOException {
+ return delegate.getTipsWithSha1(id);
+ }
+
+ @Override
+ public boolean hasFastTipsWithSha1() throws IOException {
+ return delegate.hasFastTipsWithSha1();
+ }
+
+ @Override
+ public boolean hasRefs() throws IOException {
+ return delegate.hasRefs();
+ }
+
+ @Override
+ public void refresh() {
+ delegate.refresh();
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/CachedRefRepository.java b/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/CachedRefRepository.java
new file mode 100644
index 0000000..e7960e3
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/CachedRefRepository.java
@@ -0,0 +1,350 @@
+// Copyright (C) 2021 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.googlesource.gerrit.plugins.gerritcachedrefdb;
+
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.errors.AmbiguousObjectException;
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.NoWorkTreeException;
+import org.eclipse.jgit.errors.RevisionSyntaxException;
+import org.eclipse.jgit.events.ListenerList;
+import org.eclipse.jgit.events.RepositoryEvent;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.RebaseTodoLine;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.RefRename;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.RepositoryState;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.util.FS;
+
+class CachedRefRepository extends DelegateRepository {
+ interface Factory {
+ CachedRefRepository create(Repository repo);
+ }
+
+ private final CachedRefDatabase refDb;
+ private final RefUpdateWithCacheUpdate.Factory updateFactory;
+ private final RefRenameWithCacheUpdate.Factory renameFactory;
+
+ @Inject
+ CachedRefRepository(
+ CachedRefDatabase.Factory refDbFactory,
+ RefUpdateWithCacheUpdate.Factory updateFactory,
+ RefRenameWithCacheUpdate.Factory renameFactory,
+ @Assisted Repository repo) {
+ super(repo);
+ this.updateFactory = updateFactory;
+ this.renameFactory = renameFactory;
+ this.refDb = refDbFactory.create(this);
+ }
+
+ @Override
+ public RefDatabase getRefDatabase() {
+ return refDb;
+ }
+
+ @Override
+ public ListenerList getListenerList() {
+ return getDelegate().getListenerList();
+ }
+
+ @Override
+ public void fireEvent(RepositoryEvent<?> event) {
+ getDelegate().fireEvent(event);
+ }
+
+ @Override
+ public File getDirectory() {
+ return getDelegate().getDirectory();
+ }
+
+ @Override
+ public ObjectInserter newObjectInserter() {
+ return getDelegate().newObjectInserter();
+ }
+
+ @Override
+ public ObjectReader newObjectReader() {
+ return getDelegate().newObjectReader();
+ }
+
+ @Override
+ public FS getFS() {
+ return getDelegate().getFS();
+ }
+
+ @Deprecated
+ @Override
+ public boolean hasObject(AnyObjectId objectId) {
+ return getDelegate().hasObject(objectId);
+ }
+
+ @Override
+ public ObjectLoader open(AnyObjectId objectId) throws MissingObjectException, IOException {
+ return getDelegate().open(objectId);
+ }
+
+ @Override
+ public ObjectLoader open(AnyObjectId objectId, int typeHint)
+ throws MissingObjectException, IncorrectObjectTypeException, IOException {
+ return getDelegate().open(objectId, typeHint);
+ }
+
+ @Override
+ public RefUpdate updateRef(String ref) throws IOException {
+ return updateFactory.create(refDb, this, getDelegate().updateRef(ref));
+ }
+
+ @Override
+ public RefUpdate updateRef(String ref, boolean detach) throws IOException {
+ return updateFactory.create(refDb, this, getDelegate().updateRef(ref, detach));
+ }
+
+ @Override
+ public RefRename renameRef(String fromRef, String toRef) throws IOException {
+ return renameFactory.create(
+ this, getDelegate().renameRef(fromRef, toRef), updateRef(fromRef), updateRef(toRef));
+ }
+
+ @Override
+ public ObjectId resolve(String revstr)
+ throws AmbiguousObjectException, IncorrectObjectTypeException, RevisionSyntaxException,
+ IOException {
+ return getDelegate().resolve(revstr);
+ }
+
+ @Override
+ public String simplify(String revstr) throws AmbiguousObjectException, IOException {
+ return getDelegate().simplify(revstr);
+ }
+
+ @Override
+ public void incrementOpen() {
+ getDelegate().incrementOpen();
+ }
+
+ @Override
+ public void close() {
+ getDelegate().close();
+ }
+
+ @Override
+ public String getFullBranch() throws IOException {
+ return getDelegate().getFullBranch();
+ }
+
+ @Override
+ public String getBranch() throws IOException {
+ return getDelegate().getBranch();
+ }
+
+ @Override
+ public Set<ObjectId> getAdditionalHaves() {
+ return getDelegate().getAdditionalHaves();
+ }
+
+ @Deprecated
+ @Override
+ public Map<String, Ref> getAllRefs() {
+ return getDelegate().getAllRefs();
+ }
+
+ @Deprecated
+ @Override
+ public Map<String, Ref> getTags() {
+ return getDelegate().getTags();
+ }
+
+ @Deprecated
+ @Override
+ public Ref peel(Ref ref) {
+ return getDelegate().peel(ref);
+ }
+
+ @Override
+ public Map<AnyObjectId, Set<Ref>> getAllRefsByPeeledObjectId() {
+ return getDelegate().getAllRefsByPeeledObjectId();
+ }
+
+ @Override
+ public File getIndexFile() throws NoWorkTreeException {
+ return getDelegate().getIndexFile();
+ }
+
+ @Override
+ public RevCommit parseCommit(AnyObjectId id)
+ throws IncorrectObjectTypeException, IOException, MissingObjectException {
+ return getDelegate().parseCommit(id);
+ }
+
+ @Override
+ public DirCache readDirCache() throws NoWorkTreeException, CorruptObjectException, IOException {
+ return getDelegate().readDirCache();
+ }
+
+ @Override
+ public DirCache lockDirCache() throws NoWorkTreeException, CorruptObjectException, IOException {
+ return getDelegate().lockDirCache();
+ }
+
+ @Override
+ public RepositoryState getRepositoryState() {
+ return getDelegate().getRepositoryState();
+ }
+
+ @Override
+ public boolean isBare() {
+ return getDelegate().isBare();
+ }
+
+ @Override
+ public File getWorkTree() throws NoWorkTreeException {
+ return getDelegate().getWorkTree();
+ }
+
+ @Override
+ public String shortenRemoteBranchName(String refName) {
+ return getDelegate().shortenRemoteBranchName(refName);
+ }
+
+ @Override
+ public String getRemoteName(String refName) {
+ return getDelegate().getRemoteName(refName);
+ }
+
+ @Override
+ public String getGitwebDescription() throws IOException {
+ return getDelegate().getGitwebDescription();
+ }
+
+ @Override
+ public void setGitwebDescription(String description) throws IOException {
+ getDelegate().setGitwebDescription(description);
+ }
+
+ @Override
+ public String readMergeCommitMsg() throws IOException, NoWorkTreeException {
+ return getDelegate().readMergeCommitMsg();
+ }
+
+ @Override
+ public void writeMergeCommitMsg(String msg) throws IOException {
+ getDelegate().writeMergeCommitMsg(msg);
+ }
+
+ @Override
+ public String readCommitEditMsg() throws IOException, NoWorkTreeException {
+ return getDelegate().readCommitEditMsg();
+ }
+
+ @Override
+ public void writeCommitEditMsg(String msg) throws IOException {
+ getDelegate().writeCommitEditMsg(msg);
+ }
+
+ @Override
+ public List<ObjectId> readMergeHeads() throws IOException, NoWorkTreeException {
+ return getDelegate().readMergeHeads();
+ }
+
+ @Override
+ public void writeMergeHeads(List<? extends ObjectId> heads) throws IOException {
+ getDelegate().writeMergeHeads(heads);
+ }
+
+ @Override
+ public ObjectId readCherryPickHead() throws IOException, NoWorkTreeException {
+ return getDelegate().readCherryPickHead();
+ }
+
+ @Override
+ public ObjectId readRevertHead() throws IOException, NoWorkTreeException {
+ return getDelegate().readRevertHead();
+ }
+
+ @Override
+ public void writeCherryPickHead(ObjectId head) throws IOException {
+ getDelegate().writeCherryPickHead(head);
+ }
+
+ @Override
+ public void writeRevertHead(ObjectId head) throws IOException {
+ getDelegate().writeRevertHead(head);
+ }
+
+ @Override
+ public void writeOrigHead(ObjectId head) throws IOException {
+ getDelegate().writeOrigHead(head);
+ }
+
+ @Override
+ public ObjectId readOrigHead() throws IOException, NoWorkTreeException {
+ return getDelegate().readOrigHead();
+ }
+
+ @Override
+ public String readSquashCommitMsg() throws IOException {
+ return getDelegate().readSquashCommitMsg();
+ }
+
+ @Override
+ public void writeSquashCommitMsg(String msg) throws IOException {
+ getDelegate().writeSquashCommitMsg(msg);
+ }
+
+ @Override
+ public List<RebaseTodoLine> readRebaseTodo(String path, boolean includeComments)
+ throws IOException {
+ return getDelegate().readRebaseTodo(path, includeComments);
+ }
+
+ @Override
+ public void writeRebaseTodoFile(String path, List<RebaseTodoLine> steps, boolean append)
+ throws IOException {
+ getDelegate().writeRebaseTodoFile(path, steps, append);
+ }
+
+ @Override
+ public Set<String> getRemoteNames() {
+ return getDelegate().getRemoteNames();
+ }
+
+ @Override
+ public void autoGC(ProgressMonitor monitor) {
+ getDelegate().autoGC(monitor);
+ }
+
+ @Override
+ public void create() throws IOException {
+ getDelegate().create();
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/CachedRefsModule.java b/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/CachedRefsModule.java
new file mode 100644
index 0000000..9d6eb2d
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/CachedRefsModule.java
@@ -0,0 +1,23 @@
+// Copyright (C) 2021 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.googlesource.gerrit.plugins.gerritcachedrefdb;
+
+import com.google.gerrit.extensions.config.FactoryModule;
+
+public class CachedRefsModule extends FactoryModule {
+ @Override
+ protected void configure() {
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/DelegateRepository.java b/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/DelegateRepository.java
new file mode 100644
index 0000000..1323398
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/DelegateRepository.java
@@ -0,0 +1,97 @@
+// Copyright (C) 2021 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.googlesource.gerrit.plugins.gerritcachedrefdb;
+
+import java.io.IOException;
+import org.eclipse.jgit.attributes.AttributesNodeProvider;
+import org.eclipse.jgit.lib.BaseRepositoryBuilder;
+import org.eclipse.jgit.lib.ObjectDatabase;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.ReflogReader;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
+
+/**
+ * For the time being copied from com.google.gerrit.server.git.DelegateRepository as it is package
+ * protected in 'stable-3.2'. It can be dropped when change gets merged up to.
+ */
+class DelegateRepository extends Repository {
+
+ private final Repository delegate;
+
+ DelegateRepository(Repository delegate) {
+ super(toBuilder(delegate));
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void create(boolean bare) throws IOException {
+ delegate.create(bare);
+ }
+
+ @Override
+ public String getIdentifier() {
+ return delegate.getIdentifier();
+ }
+
+ @Override
+ public ObjectDatabase getObjectDatabase() {
+ return delegate.getObjectDatabase();
+ }
+
+ @Override
+ public RefDatabase getRefDatabase() {
+ return delegate.getRefDatabase();
+ }
+
+ @Override
+ public StoredConfig getConfig() {
+ return delegate.getConfig();
+ }
+
+ @Override
+ public AttributesNodeProvider createAttributesNodeProvider() {
+ return delegate.createAttributesNodeProvider();
+ }
+
+ @Override
+ public void scanForRepoChanges() throws IOException {
+ delegate.scanForRepoChanges();
+ }
+
+ @Override
+ public void notifyIndexChanged(boolean internal) {
+ delegate.notifyIndexChanged(internal);
+ }
+
+ @Override
+ public ReflogReader getReflogReader(String refName) throws IOException {
+ return delegate.getReflogReader(refName);
+ }
+
+ public Repository getDelegate() {
+ return delegate;
+ }
+
+ @SuppressWarnings("rawtypes")
+ private static BaseRepositoryBuilder toBuilder(Repository repo) {
+ if (!repo.isBare()) {
+ throw new IllegalArgumentException(
+ "non-bare repository is not supported: " + repo.getIdentifier());
+ }
+
+ return new BaseRepositoryBuilder<>().setFS(repo.getFS()).setGitDir(repo.getDirectory());
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/GerritCachedGitRepositoryManager.java b/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/GerritCachedGitRepositoryManager.java
new file mode 100644
index 0000000..4eb0cf1
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/GerritCachedGitRepositoryManager.java
@@ -0,0 +1,52 @@
+// Copyright (C) 2021 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.googlesource.gerrit.plugins.gerritcachedrefdb;
+
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.LocalDiskRepositoryManager;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import java.util.SortedSet;
+import org.eclipse.jgit.lib.Repository;
+
+@Singleton
+class GerritCachedGitRepositoryManager implements GitRepositoryManager {
+ private final LocalDiskRepositoryManager repoManager;
+ private final CachedRefRepository.Factory repoWrapperFactory;
+
+ @Inject
+ GerritCachedGitRepositoryManager(
+ LocalDiskRepositoryManager repoManager, CachedRefRepository.Factory repoWrapperFactory) {
+ this.repoManager = repoManager;
+ this.repoWrapperFactory = repoWrapperFactory;
+ }
+
+ @Override
+ public Repository openRepository(Project.NameKey name) throws IOException {
+ return repoWrapperFactory.create(repoManager.openRepository(name));
+ }
+
+ @Override
+ public Repository createRepository(Project.NameKey name) throws IOException {
+ return repoWrapperFactory.create(repoManager.createRepository(name));
+ }
+
+ @Override
+ public SortedSet<Project.NameKey> list() {
+ return repoManager.list();
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/LibDbModule.java b/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/LibDbModule.java
new file mode 100644
index 0000000..5787abe
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/LibDbModule.java
@@ -0,0 +1,38 @@
+// Copyright (C) 2021 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.googlesource.gerrit.plugins.gerritcachedrefdb;
+
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.extensions.config.FactoryModule;
+import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.server.git.GitRepositoryManager;
+
+public class LibDbModule extends FactoryModule {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ @Override
+ protected void configure() {
+ DynamicItem.itemOf(binder(), RefByNameCache.class);
+
+ factory(RefUpdateWithCacheUpdate.Factory.class);
+ factory(RefRenameWithCacheUpdate.Factory.class);
+ factory(BatchRefUpdateWithCacheUpdate.Factory.class);
+ factory(CachedRefDatabase.Factory.class);
+ factory(CachedRefRepository.Factory.class);
+
+ bind(GitRepositoryManager.class).to(GerritCachedGitRepositoryManager.class);
+ logger.atInfo().log("DB library loaded");
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/LibSysModule.java b/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/LibSysModule.java
new file mode 100644
index 0000000..e6dab8f
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/LibSysModule.java
@@ -0,0 +1,32 @@
+// Copyright (C) 2021 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.googlesource.gerrit.plugins.gerritcachedrefdb;
+
+import static com.google.inject.Scopes.SINGLETON;
+
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.inject.AbstractModule;
+
+public class LibSysModule extends AbstractModule {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ @Override
+ protected void configure() {
+ install(RefByNameGerritCache.module());
+ DynamicItem.bind(binder(), RefByNameCache.class).to(RefByNameGerritCache.class).in(SINGLETON);
+ logger.atInfo().log("Sys library loaded");
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/NoOpRefByNameCache.java b/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/NoOpRefByNameCache.java
new file mode 100644
index 0000000..44b6abb
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/NoOpRefByNameCache.java
@@ -0,0 +1,41 @@
+// Copyright (C) 2021 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.googlesource.gerrit.plugins.gerritcachedrefdb;
+
+import com.google.common.flogger.FluentLogger;
+import java.util.Optional;
+import java.util.concurrent.Callable;
+import org.eclipse.jgit.lib.Ref;
+
+class NoOpRefByNameCache implements RefByNameCache {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ @Override
+ public Ref computeIfAbsent(
+ String identifier, String ref, Callable<? extends Optional<Ref>> loader) {
+ try {
+ return loader.call().orElse(null);
+ } catch (Exception e) {
+ logger.atSevere().withCause(e).log(
+ "Repository '%s', getting ref '%s' failed", identifier, ref);
+ }
+ return null;
+ }
+
+ @Override
+ public void evict(String identifier, String ref) {
+ // do nothing as there is no cache to be evicted
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/RefByNameCache.java b/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/RefByNameCache.java
new file mode 100644
index 0000000..69fabfc
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/RefByNameCache.java
@@ -0,0 +1,25 @@
+// Copyright (C) 2021 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.googlesource.gerrit.plugins.gerritcachedrefdb;
+
+import java.util.Optional;
+import java.util.concurrent.Callable;
+import org.eclipse.jgit.lib.Ref;
+
+interface RefByNameCache {
+ Ref computeIfAbsent(String identifier, String ref, Callable<? extends Optional<Ref>> loader);
+
+ void evict(String identifier, String ref);
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/RefByNameCacheWrapper.java b/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/RefByNameCacheWrapper.java
new file mode 100644
index 0000000..c9744ad
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/RefByNameCacheWrapper.java
@@ -0,0 +1,45 @@
+// Copyright (C) 2021 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.googlesource.gerrit.plugins.gerritcachedrefdb;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.inject.Inject;
+import java.util.Optional;
+import java.util.concurrent.Callable;
+import org.eclipse.jgit.lib.Ref;
+
+class RefByNameCacheWrapper implements RefByNameCache {
+ private static final RefByNameCache NOOP_CACHE = new NoOpRefByNameCache();
+
+ @Inject(optional = true)
+ private DynamicItem<RefByNameCache> refByNameCache;
+
+ @Override
+ public Ref computeIfAbsent(
+ String identifier, String ref, Callable<? extends Optional<Ref>> loader) {
+ return cache().computeIfAbsent(identifier, ref, loader);
+ }
+
+ @Override
+ public void evict(String identifier, String ref) {
+ cache().evict(identifier, ref);
+ }
+
+ @VisibleForTesting
+ RefByNameCache cache() {
+ return Optional.ofNullable(refByNameCache).map(DynamicItem::get).orElse(NOOP_CACHE);
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/RefByNameGerritCache.java b/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/RefByNameGerritCache.java
new file mode 100644
index 0000000..e9fc2b7
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/RefByNameGerritCache.java
@@ -0,0 +1,70 @@
+// Copyright (C) 2021 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.googlesource.gerrit.plugins.gerritcachedrefdb;
+
+import com.google.common.cache.Cache;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.server.cache.CacheModule;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.google.inject.TypeLiteral;
+import com.google.inject.name.Named;
+import java.util.Optional;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import org.eclipse.jgit.lib.Ref;
+
+@Singleton
+class RefByNameGerritCache implements RefByNameCache {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+ private static final String REF_BY_NAME = "ref_by_name";
+
+ static com.google.inject.Module module() {
+ return new CacheModule() {
+ @Override
+ protected void configure() {
+ cache(REF_BY_NAME, String.class, new TypeLiteral<Optional<Ref>>() {});
+ }
+ };
+ }
+
+ private final Cache<String, Optional<Ref>> refByName;
+
+ @Inject
+ RefByNameGerritCache(@Named(REF_BY_NAME) Cache<String, Optional<Ref>> refByName) {
+ this.refByName = refByName;
+ }
+
+ @Override
+ public Ref computeIfAbsent(
+ String identifier, String ref, Callable<? extends Optional<Ref>> loader) {
+ String uniqueRefName = getUniqueName(identifier, ref);
+ try {
+ return refByName.get(uniqueRefName, loader).orElse(null);
+ } catch (ExecutionException e) {
+ logger.atWarning().withCause(e).log("Getting ref for [%s] failed.", uniqueRefName);
+ return null;
+ }
+ }
+
+ @Override
+ public void evict(String identifier, String ref) {
+ refByName.invalidate(getUniqueName(identifier, ref));
+ }
+
+ private static String getUniqueName(String identifier, String ref) {
+ return String.format("%s$%s", identifier, ref);
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/RefRenameWithCacheUpdate.java b/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/RefRenameWithCacheUpdate.java
new file mode 100644
index 0000000..68de4ca
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/RefRenameWithCacheUpdate.java
@@ -0,0 +1,107 @@
+// Copyright (C) 2021 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.googlesource.gerrit.plugins.gerritcachedrefdb;
+
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+import java.io.IOException;
+import java.util.EnumSet;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.RefRename;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.RefUpdate.Result;
+import org.eclipse.jgit.lib.Repository;
+
+class RefRenameWithCacheUpdate extends RefRename {
+ interface Factory {
+ RefRenameWithCacheUpdate create(
+ Repository repo,
+ RefRename delegate,
+ @Assisted("src") RefUpdate src,
+ @Assisted("dst") RefUpdate dst);
+ }
+
+ private static final String NOT_SUPPORTED_MSG = "Should never be called";
+ private static final EnumSet<Result> SUCCESSFUL_RENAMES =
+ EnumSet.of(Result.NEW, Result.FORCED, Result.FAST_FORWARD, Result.RENAMED);
+
+ private final RefByNameCacheWrapper refsCache;
+ private final Repository repo;
+ private final RefRename delegate;
+ private final RefUpdate src;
+
+ @Inject
+ RefRenameWithCacheUpdate(
+ RefByNameCacheWrapper refsCache,
+ @Assisted Repository repo,
+ @Assisted RefRename delegate,
+ @Assisted("src") RefUpdate src,
+ @Assisted("dst") RefUpdate dst) {
+ super(src, dst);
+ this.refsCache = refsCache;
+ this.repo = repo;
+ this.delegate = delegate;
+ this.src = src;
+ }
+
+ @Override
+ public PersonIdent getRefLogIdent() {
+ return delegate.getRefLogIdent();
+ }
+
+ @Override
+ public void setRefLogIdent(PersonIdent pi) {
+ delegate.setRefLogIdent(pi);
+ }
+
+ @Override
+ public String getRefLogMessage() {
+ return delegate.getRefLogMessage();
+ }
+
+ @Override
+ public void setRefLogMessage(String msg) {
+ // Method is called from the super constructor and delegate is not available yet therefore
+ // 'null' has to be checked. It doesn't matter as wrapped instance is properly constructed and
+ // its ref log message is used.
+ if (delegate != null) {
+ delegate.setRefLogMessage(msg);
+ }
+ }
+
+ @Override
+ public void disableRefLog() {
+ delegate.disableRefLog();
+ }
+
+ @Override
+ public Result getResult() {
+ return delegate.getResult();
+ }
+
+ @Override
+ public Result rename() throws IOException {
+ Result r = delegate.rename();
+ if (SUCCESSFUL_RENAMES.contains(r)) {
+ refsCache.evict(repo.getIdentifier(), src.getName());
+ }
+ return r;
+ }
+
+ @Override
+ protected Result doRename() throws IOException {
+ throw new UnsupportedOperationException(NOT_SUPPORTED_MSG);
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/RefUpdateWithCacheUpdate.java b/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/RefUpdateWithCacheUpdate.java
new file mode 100644
index 0000000..a4bee39
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/RefUpdateWithCacheUpdate.java
@@ -0,0 +1,215 @@
+package com.googlesource.gerrit.plugins.gerritcachedrefdb;
+
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+import java.io.IOException;
+import java.util.EnumSet;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.PushCertificate;
+
+class RefUpdateWithCacheUpdate extends RefUpdate {
+ interface Factory {
+ RefUpdateWithCacheUpdate create(RefDatabase refDb, Repository repo, RefUpdate delegate);
+ }
+
+ private static final String NOT_SUPPORTED_MSG = "Should never be called";
+ private static final EnumSet<Result> SUCCESSFUL_UPDATES =
+ EnumSet.of(Result.NEW, Result.FORCED, Result.FAST_FORWARD, Result.RENAMED);
+
+ private final RefByNameCacheWrapper refsCache;
+ private final RefDatabase refDb;
+ private final Repository repo;
+ private final RefUpdate delegate;
+
+ @Inject
+ RefUpdateWithCacheUpdate(
+ RefByNameCacheWrapper refsCache,
+ @Assisted RefDatabase refDb,
+ @Assisted Repository repo,
+ @Assisted RefUpdate delegate) {
+ super(delegate.getRef());
+ this.refsCache = refsCache;
+ this.refDb = refDb;
+ this.repo = repo;
+ this.delegate = delegate;
+ }
+
+ @Override
+ public String getName() {
+ return delegate.getName();
+ }
+
+ @Override
+ public Ref getRef() {
+ return delegate.getRef();
+ }
+
+ @Override
+ public ObjectId getNewObjectId() {
+ return delegate.getNewObjectId();
+ }
+
+ @Override
+ public void setDetachingSymbolicRef() {
+ delegate.setDetachingSymbolicRef();
+ }
+
+ @Override
+ public boolean isDetachingSymbolicRef() {
+ return delegate.isDetachingSymbolicRef();
+ }
+
+ @Override
+ public void setNewObjectId(AnyObjectId id) {
+ delegate.setNewObjectId(id);
+ }
+
+ @Override
+ public ObjectId getExpectedOldObjectId() {
+ return delegate.getExpectedOldObjectId();
+ }
+
+ @Override
+ public void setExpectedOldObjectId(AnyObjectId id) {
+ delegate.setExpectedOldObjectId(id);
+ }
+
+ @Override
+ public boolean isForceUpdate() {
+ return delegate.isForceUpdate();
+ }
+
+ @Override
+ public void setForceUpdate(boolean b) {
+ delegate.setForceUpdate(b);
+ }
+
+ @Override
+ public PersonIdent getRefLogIdent() {
+ return delegate.getRefLogIdent();
+ }
+
+ @Override
+ public void setRefLogIdent(PersonIdent pi) {
+ delegate.setRefLogIdent(pi);
+ }
+
+ @Override
+ public String getRefLogMessage() {
+ return delegate.getRefLogMessage();
+ }
+
+ @Override
+ public void setRefLogMessage(String msg, boolean appendStatus) {
+ delegate.setRefLogMessage(msg, appendStatus);
+ }
+
+ @Override
+ public void disableRefLog() {
+ delegate.disableRefLog();
+ }
+
+ @Override
+ public void setForceRefLog(boolean force) {
+ delegate.setForceRefLog(force);
+ }
+
+ @Override
+ public ObjectId getOldObjectId() {
+ return delegate.getOldObjectId();
+ }
+
+ @Override
+ public void setPushCertificate(PushCertificate cert) {
+ delegate.setPushCertificate(cert);
+ }
+
+ @Override
+ public Result getResult() {
+ return delegate.getResult();
+ }
+
+ @Override
+ public Result forceUpdate() throws IOException {
+ return evictCache(delegate.forceUpdate());
+ }
+
+ @Override
+ public Result update() throws IOException {
+ return evictCache(delegate.update());
+ }
+
+ @Override
+ public Result update(RevWalk walk) throws IOException {
+ return evictCache(delegate.update(walk));
+ }
+
+ @Override
+ public Result delete() throws IOException {
+ return evictCache(delegate.delete());
+ }
+
+ @Override
+ public Result delete(RevWalk walk) throws IOException {
+ return evictCache(delegate.delete(walk));
+ }
+
+ @Override
+ public Result link(String target) throws IOException {
+ return evictCache(delegate.link(target));
+ }
+
+ @Override
+ public void setCheckConflicting(boolean check) {
+ delegate.setCheckConflicting(check);
+ }
+
+ @Override
+ protected RefDatabase getRefDatabase() {
+ return refDb;
+ }
+
+ @Override
+ protected Repository getRepository() {
+ return repo;
+ }
+
+ @Override
+ protected boolean tryLock(boolean deref) throws IOException {
+ throw new UnsupportedOperationException(NOT_SUPPORTED_MSG);
+ }
+
+ @Override
+ protected void unlock() {
+ throw new UnsupportedOperationException(NOT_SUPPORTED_MSG);
+ }
+
+ @Override
+ protected Result doUpdate(Result desiredResult) throws IOException {
+ throw new UnsupportedOperationException(NOT_SUPPORTED_MSG);
+ }
+
+ @Override
+ protected Result doDelete(Result desiredResult) throws IOException {
+ throw new UnsupportedOperationException(NOT_SUPPORTED_MSG);
+ }
+
+ @Override
+ protected Result doLink(String target) throws IOException {
+ throw new UnsupportedOperationException(NOT_SUPPORTED_MSG);
+ }
+
+ private Result evictCache(Result r) {
+ if (SUCCESSFUL_UPDATES.contains(r)) {
+ refsCache.evict(repo.getIdentifier(), getName());
+ }
+ return r;
+ }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/GerritCachedRefDbIT.java b/src/test/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/GerritCachedRefDbIT.java
new file mode 100644
index 0000000..bf2ca31
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/gerritcachedrefdb/GerritCachedRefDbIT.java
@@ -0,0 +1,45 @@
+// Copyright (C) 2021 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.googlesource.gerrit.plugins.gerritcachedrefdb;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.UseLocalDisk;
+import com.google.gerrit.acceptance.config.GerritConfig;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.inject.Inject;
+import org.junit.Test;
+
+@UseLocalDisk
+@NoHttpd
+public class GerritCachedRefDbIT extends AbstractDaemonTest {
+ @Inject private GitRepositoryManager gitRepoManager;
+
+ @Inject private RefByNameCacheWrapper refByNameCacheWrapper;
+
+ @Test
+ @GerritConfig(
+ name = "gerrit.installDbModule",
+ value = "com.googlesource.gerrit.plugins.gerritcachedrefdb.LibDbModule")
+ @GerritConfig(
+ name = "gerrit.installModule",
+ value = "com.googlesource.gerrit.plugins.gerritcachedrefdb.LibSysModule")
+ public void shouldBeAbleToInstallGerritCachedGitRepoManager() {
+ assertThat(gitRepoManager).isInstanceOf(GerritCachedGitRepositoryManager.class);
+ assertThat(refByNameCacheWrapper.cache()).isInstanceOf(RefByNameGerritCache.class);
+ }
+}