Merge "Fix issue with disabling ref-database" into stable-3.3
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/GitModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/GitModule.java
index 87e9d22..38cddd5 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/GitModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/GitModule.java
@@ -14,6 +14,7 @@
 
 package com.googlesource.gerrit.plugins.multisite;
 
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbConfiguration;
 import com.google.inject.AbstractModule;
 import com.google.inject.Inject;
 import com.googlesource.gerrit.plugins.multisite.validation.ValidationModule;
@@ -28,6 +29,8 @@
 
   @Override
   protected void configure() {
+    bind(SharedRefDbConfiguration.class).toInstance(config.getSharedRefDbConfiguration());
+    bind(ProjectVersionLogger.class).to(Log4jProjectVersionLogger.class);
     if (config.getSharedRefDbConfiguration().getSharedRefDb().isEnabled()) {
       install(new ValidationModule(config));
     }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/Module.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/Module.java
index f44f4f7..91a927b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/Module.java
@@ -66,7 +66,7 @@
       install(new CacheModule());
     }
     if (config.event().synchronize()) {
-      install(new EventModule());
+      install(new EventModule(config));
     }
     if (config.index().synchronize()) {
       install(new IndexModule());
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/ReplicationStatus.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/ReplicationStatus.java
index c45dfa7..7360b8f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/ReplicationStatus.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/ReplicationStatus.java
@@ -25,7 +25,6 @@
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.inject.Inject;
 import com.google.inject.Module;
-import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import com.google.inject.name.Named;
 import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger;
@@ -60,14 +59,14 @@
 
   private final Map<String, Long> localVersionPerProject = new HashMap<>();
   private final Cache<String, Long> cache;
-  private final Provider<ProjectVersionRefUpdate> projectVersionRefUpdate;
+  private final Optional<ProjectVersionRefUpdate> projectVersionRefUpdate;
   private final ProjectVersionLogger verLogger;
   private final ProjectCache projectCache;
 
   @Inject
   public ReplicationStatus(
       @Named(REPLICATION_STATUS_CACHE) Cache<String, Long> cache,
-      Provider<ProjectVersionRefUpdate> projectVersionRefUpdate,
+      Optional<ProjectVersionRefUpdate> projectVersionRefUpdate,
       ProjectVersionLogger verLogger,
       ProjectCache projectCache) {
     this.cache = cache;
@@ -98,9 +97,11 @@
 
   public void updateReplicationLag(Project.NameKey projectName) {
     Optional<Long> remoteVersion =
-        projectVersionRefUpdate.get().getProjectRemoteVersion(projectName.get());
+        projectVersionRefUpdate.flatMap(
+            refUpdate -> refUpdate.getProjectRemoteVersion(projectName.get()));
     Optional<Long> localVersion =
-        projectVersionRefUpdate.get().getProjectLocalVersion(projectName.get());
+        projectVersionRefUpdate.flatMap(
+            refUpdate -> refUpdate.getProjectLocalVersion(projectName.get()));
     if (remoteVersion.isPresent() && localVersion.isPresent()) {
       long lag = remoteVersion.get() - localVersion.get();
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/event/EventModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/event/EventModule.java
index 1c0c644..5f16210 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/event/EventModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/event/EventModule.java
@@ -17,16 +17,34 @@
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.server.events.EventListener;
+import com.google.inject.Scopes;
+import com.google.inject.multibindings.OptionalBinder;
+import com.googlesource.gerrit.plugins.multisite.Configuration;
 import com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdate;
+import com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdateImpl;
 import java.util.concurrent.Executor;
 
 public class EventModule extends LifecycleModule {
 
+  private final Configuration config;
+
+  public EventModule(Configuration config) {
+    this.config = config;
+  }
+
   @Override
   protected void configure() {
     bind(Executor.class).annotatedWith(EventExecutor.class).toProvider(EventExecutorProvider.class);
     listener().to(EventExecutorProvider.class);
     DynamicSet.bind(binder(), EventListener.class).to(EventHandler.class);
-    DynamicSet.bind(binder(), EventListener.class).to(ProjectVersionRefUpdate.class);
+    OptionalBinder<ProjectVersionRefUpdate> projectVersionRefUpdateBinder =
+        OptionalBinder.newOptionalBinder(binder(), ProjectVersionRefUpdate.class);
+    if (config.getSharedRefDbConfiguration().getSharedRefDb().isEnabled()) {
+      DynamicSet.bind(binder(), EventListener.class).to(ProjectVersionRefUpdateImpl.class);
+      projectVersionRefUpdateBinder
+          .setBinding()
+          .to(ProjectVersionRefUpdateImpl.class)
+          .in(Scopes.SINGLETON);
+    }
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdate.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdate.java
index 28eddbb..1526059 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdate.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdate.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2020 The Android Open Source Project
+// Copyright (C) 2022 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.
@@ -14,297 +14,19 @@
 
 package com.googlesource.gerrit.plugins.multisite.validation;
 
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
-
-import com.gerritforge.gerrit.globalrefdb.GlobalRefDbSystemError;
-import com.gerritforge.gerrit.globalrefdb.validation.ProjectsFilter;
-import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDatabaseWrapper;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.entities.Project;
-import com.google.gerrit.entities.RefNames;
-import com.google.gerrit.server.events.Event;
-import com.google.gerrit.server.events.EventListener;
-import com.google.gerrit.server.events.RefUpdatedEvent;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.notedb.IntBlob;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger;
-import com.googlesource.gerrit.plugins.multisite.forwarder.Context;
-import java.io.IOException;
 import java.util.Optional;
-import java.util.Set;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectIdRef;
-import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.Repository;
 
-@Singleton
-public class ProjectVersionRefUpdate implements EventListener {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-  private static final Set<RefUpdate.Result> SUCCESSFUL_RESULTS =
-      ImmutableSet.of(RefUpdate.Result.NEW, RefUpdate.Result.FORCED, RefUpdate.Result.NO_CHANGE);
+public interface ProjectVersionRefUpdate {
 
-  public static final String MULTI_SITE_VERSIONING_REF = "refs/multi-site/version";
-  public static final String MULTI_SITE_VERSIONING_VALUE_REF = "refs/multi-site/version/value";
-  public static final Ref NULL_PROJECT_VERSION_REF =
+  String MULTI_SITE_VERSIONING_REF = "refs/multi-site/version";
+  String MULTI_SITE_VERSIONING_VALUE_REF = "refs/multi-site/version/value";
+  Ref NULL_PROJECT_VERSION_REF =
       new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, MULTI_SITE_VERSIONING_REF, ObjectId.zeroId());
 
-  private final GitRepositoryManager gitRepositoryManager;
-  private final GitReferenceUpdated gitReferenceUpdated;
-  private final ProjectVersionLogger verLogger;
-  private final ProjectsFilter projectsFilter;
+  Optional<Long> getProjectLocalVersion(String projectName);
 
-  protected final SharedRefDatabaseWrapper sharedRefDb;
-
-  @Inject
-  public ProjectVersionRefUpdate(
-      GitRepositoryManager gitRepositoryManager,
-      SharedRefDatabaseWrapper sharedRefDb,
-      GitReferenceUpdated gitReferenceUpdated,
-      ProjectVersionLogger verLogger,
-      ProjectsFilter projectsFilter) {
-    this.gitRepositoryManager = gitRepositoryManager;
-    this.sharedRefDb = sharedRefDb;
-    this.gitReferenceUpdated = gitReferenceUpdated;
-    this.verLogger = verLogger;
-    this.projectsFilter = projectsFilter;
-  }
-
-  @Override
-  public void onEvent(Event event) {
-    logger.atFine().log("Processing event type: " + event.type);
-    // Producer of the Event use RefUpdatedEvent to trigger the version update
-    if (!Context.isForwardedEvent() && event instanceof RefUpdatedEvent) {
-      if (projectsFilter.matches(event)) {
-        updateProducerProjectVersionUpdate((RefUpdatedEvent) event);
-      }
-    }
-  }
-
-  private boolean isSpecialRefName(String refName) {
-    return refName.startsWith(RefNames.REFS_SEQUENCES)
-        || refName.startsWith(RefNames.REFS_STARRED_CHANGES)
-        || refName.equals(MULTI_SITE_VERSIONING_REF);
-  }
-
-  private void updateProducerProjectVersionUpdate(RefUpdatedEvent refUpdatedEvent) {
-    String refName = refUpdatedEvent.getRefName();
-
-    if (isSpecialRefName(refName)) {
-      logger.atFine().log(
-          "Found a special ref name %s, skipping update for %s",
-          refName, refUpdatedEvent.getProjectNameKey().get());
-      return;
-    }
-    try {
-      Project.NameKey projectNameKey = refUpdatedEvent.getProjectNameKey();
-      long newVersion = getCurrentGlobalVersionNumber();
-
-      Optional<RefUpdate> newProjectVersionRefUpdate =
-          updateLocalProjectVersion(projectNameKey, newVersion);
-
-      if (newProjectVersionRefUpdate.isPresent()) {
-        verLogger.log(projectNameKey, newVersion, 0L);
-
-        if (updateSharedProjectVersion(
-            projectNameKey, newProjectVersionRefUpdate.get().getNewObjectId(), newVersion)) {
-          gitReferenceUpdated.fire(projectNameKey, newProjectVersionRefUpdate.get(), null);
-        }
-      } else {
-        logger.atWarning().log(
-            "Ref %s not found on projet %s: skipping project version update",
-            refUpdatedEvent.getRefName(), projectNameKey);
-      }
-    } catch (LocalProjectVersionUpdateException | SharedProjectVersionUpdateException e) {
-      logger.atSevere().withCause(e).log(
-          "Issue encountered when updating version for project "
-              + refUpdatedEvent.getProjectNameKey());
-    }
-  }
-
-  private RefUpdate getProjectVersionRefUpdate(Repository repository, Long version)
-      throws IOException {
-    RefUpdate refUpdate = repository.getRefDatabase().newUpdate(MULTI_SITE_VERSIONING_REF, false);
-    refUpdate.setNewObjectId(getNewId(repository, version));
-    refUpdate.setForceUpdate(true);
-    return refUpdate;
-  }
-
-  private ObjectId getNewId(Repository repository, Long version) throws IOException {
-    ObjectInserter ins = repository.newObjectInserter();
-    ObjectId newId = ins.insert(OBJ_BLOB, Long.toString(version).getBytes(UTF_8));
-    ins.flush();
-    return newId;
-  }
-
-  private boolean updateSharedProjectVersion(
-      Project.NameKey projectNameKey, ObjectId newObjectId, Long newVersion)
-      throws SharedProjectVersionUpdateException {
-
-    Ref sharedRef =
-        sharedRefDb
-            .get(projectNameKey, MULTI_SITE_VERSIONING_REF, String.class)
-            .map(
-                (String objectId) ->
-                    new ObjectIdRef.Unpeeled(
-                        Ref.Storage.NEW, MULTI_SITE_VERSIONING_REF, ObjectId.fromString(objectId)))
-            .orElse(
-                new ObjectIdRef.Unpeeled(
-                    Ref.Storage.NEW, MULTI_SITE_VERSIONING_REF, ObjectId.zeroId()));
-    Optional<Long> sharedVersion =
-        sharedRefDb
-            .get(projectNameKey, MULTI_SITE_VERSIONING_VALUE_REF, String.class)
-            .map(Long::parseLong);
-
-    try {
-      if (sharedVersion.isPresent() && sharedVersion.get() >= newVersion) {
-        logger.atWarning().log(
-            String.format(
-                "NOT Updating project %s version %s (value=%d) in shared ref-db because is more recent than the local one %s (value=%d) ",
-                projectNameKey.get(),
-                newObjectId,
-                newVersion,
-                sharedRef.getObjectId().getName(),
-                sharedVersion.get()));
-        return false;
-      }
-
-      logger.atFine().log(
-          String.format(
-              "Updating shared project %s version to %s (value=%d)",
-              projectNameKey.get(), newObjectId, newVersion));
-
-      boolean success = sharedRefDb.compareAndPut(projectNameKey, sharedRef, newObjectId);
-      if (!success) {
-        String message =
-            String.format(
-                "Project version blob update failed for %s. Current value %s, new value: %s",
-                projectNameKey.get(), safeGetObjectId(sharedRef), newObjectId);
-        logger.atSevere().log(message);
-        throw new SharedProjectVersionUpdateException(message);
-      }
-
-      success =
-          sharedRefDb.compareAndPut(
-              projectNameKey,
-              MULTI_SITE_VERSIONING_VALUE_REF,
-              sharedVersion.map(Object::toString).orElse(null),
-              newVersion.toString());
-      if (!success) {
-        String message =
-            String.format(
-                "Project version update failed for %s. Current value %s, new value: %s",
-                projectNameKey.get(), safeGetObjectId(sharedRef), newObjectId);
-        logger.atSevere().log(message);
-        throw new SharedProjectVersionUpdateException(message);
-      }
-
-      return true;
-    } catch (GlobalRefDbSystemError refDbSystemError) {
-      String message =
-          String.format(
-              "Error while updating shared project version for %s. Current value %s, new value: %s. Error: %s",
-              projectNameKey.get(),
-              sharedRef.getObjectId(),
-              newObjectId,
-              refDbSystemError.getMessage());
-      logger.atSevere().withCause(refDbSystemError).log(message);
-      throw new SharedProjectVersionUpdateException(message);
-    }
-  }
-
-  public Optional<Long> getProjectLocalVersion(String projectName) {
-    try (Repository repository =
-        gitRepositoryManager.openRepository(Project.NameKey.parse(projectName))) {
-      Optional<IntBlob> blob = IntBlob.parse(repository, MULTI_SITE_VERSIONING_REF);
-      if (blob.isPresent()) {
-        Long repoVersion = Integer.toUnsignedLong(blob.get().value());
-        logger.atFine().log("Local project '%s' has version %d", projectName, repoVersion);
-        return Optional.of(repoVersion);
-      }
-    } catch (RepositoryNotFoundException re) {
-      logger.atFine().log("Project '%s' not found", projectName);
-    } catch (IOException e) {
-      logger.atSevere().withCause(e).log("Cannot read local project '%s' version", projectName);
-    }
-    return Optional.empty();
-  }
-
-  public Optional<Long> getProjectRemoteVersion(String projectName) {
-    Optional<String> globalVersion =
-        sharedRefDb.get(
-            Project.NameKey.parse(projectName), MULTI_SITE_VERSIONING_VALUE_REF, String.class);
-    return globalVersion.flatMap(longString -> getLongValueOf(longString));
-  }
-
-  private Object safeGetObjectId(Ref currentRef) {
-    return currentRef == null ? "null" : currentRef.getObjectId();
-  }
-
-  private Optional<Long> getLongValueOf(String longString) {
-    try {
-      return Optional.ofNullable(Long.parseLong(longString));
-    } catch (NumberFormatException e) {
-      logger.atSevere().withCause(e).log(
-          "Unable to parse timestamp value %s into Long", longString);
-      return Optional.empty();
-    }
-  }
-
-  private Optional<RefUpdate> updateLocalProjectVersion(
-      Project.NameKey projectNameKey, long newVersionNumber)
-      throws LocalProjectVersionUpdateException {
-    logger.atFine().log(
-        "Updating local version for project %s with version %d",
-        projectNameKey.get(), newVersionNumber);
-    try (Repository repository = gitRepositoryManager.openRepository(projectNameKey)) {
-      RefUpdate refUpdate = getProjectVersionRefUpdate(repository, newVersionNumber);
-      RefUpdate.Result result = refUpdate.update();
-      if (!isSuccessful(result)) {
-        String message =
-            String.format(
-                "RefUpdate failed with result %s for: project=%s, version=%d",
-                result.name(), projectNameKey.get(), newVersionNumber);
-        logger.atSevere().log(message);
-        throw new LocalProjectVersionUpdateException(message);
-      }
-
-      return Optional.of(refUpdate);
-    } catch (IOException e) {
-      String message = "Cannot create versioning command for " + projectNameKey.get();
-      logger.atSevere().withCause(e).log(message);
-      throw new LocalProjectVersionUpdateException(message);
-    }
-  }
-
-  private long getCurrentGlobalVersionNumber() {
-    return System.currentTimeMillis() / 1000;
-  }
-
-  private Boolean isSuccessful(RefUpdate.Result result) {
-    return SUCCESSFUL_RESULTS.contains(result);
-  }
-
-  public static class LocalProjectVersionUpdateException extends Exception {
-    private static final long serialVersionUID = 7649956232401457023L;
-
-    public LocalProjectVersionUpdateException(String projectName) {
-      super("Cannot update local project version of " + projectName);
-    }
-  }
-
-  public static class SharedProjectVersionUpdateException extends Exception {
-    private static final long serialVersionUID = -9153858177700286314L;
-
-    public SharedProjectVersionUpdateException(String projectName) {
-      super("Cannot update shared project version of " + projectName);
-    }
-  }
+  Optional<Long> getProjectRemoteVersion(String projectName);
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateImpl.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateImpl.java
new file mode 100644
index 0000000..469122b
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateImpl.java
@@ -0,0 +1,311 @@
+// Copyright (C) 2022 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.multisite.validation;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+
+import com.gerritforge.gerrit.globalrefdb.GlobalRefDbSystemError;
+import com.gerritforge.gerrit.globalrefdb.validation.ProjectsFilter;
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDatabaseWrapper;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.server.events.Event;
+import com.google.gerrit.server.events.EventListener;
+import com.google.gerrit.server.events.RefUpdatedEvent;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.notedb.IntBlob;
+import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger;
+import com.googlesource.gerrit.plugins.multisite.forwarder.Context;
+import java.io.IOException;
+import java.util.Optional;
+import java.util.Set;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+
+public class ProjectVersionRefUpdateImpl implements EventListener, ProjectVersionRefUpdate {
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+  private static final Set<RefUpdate.Result> SUCCESSFUL_RESULTS =
+      ImmutableSet.of(RefUpdate.Result.NEW, RefUpdate.Result.FORCED, RefUpdate.Result.NO_CHANGE);
+
+  private final GitRepositoryManager gitRepositoryManager;
+  private final GitReferenceUpdated gitReferenceUpdated;
+  private final ProjectVersionLogger verLogger;
+  private final ProjectsFilter projectsFilter;
+
+  protected final SharedRefDatabaseWrapper sharedRefDb;
+
+  @Inject
+  public ProjectVersionRefUpdateImpl(
+      GitRepositoryManager gitRepositoryManager,
+      SharedRefDatabaseWrapper sharedRefDb,
+      GitReferenceUpdated gitReferenceUpdated,
+      ProjectVersionLogger verLogger,
+      ProjectsFilter projectsFilter) {
+    this.gitRepositoryManager = gitRepositoryManager;
+    this.sharedRefDb = sharedRefDb;
+    this.gitReferenceUpdated = gitReferenceUpdated;
+    this.verLogger = verLogger;
+    this.projectsFilter = projectsFilter;
+  }
+
+  @Override
+  public void onEvent(Event event) {
+    logger.atFine().log("Processing event type: " + event.type);
+    // Producer of the Event use RefUpdatedEvent to trigger the version update
+    if (!Context.isForwardedEvent() && event instanceof RefUpdatedEvent) {
+      if (projectsFilter.matches(event)) {
+        updateProducerProjectVersionUpdate((RefUpdatedEvent) event);
+      }
+    }
+  }
+
+  private boolean isSpecialRefName(String refName) {
+    return refName.startsWith(RefNames.REFS_SEQUENCES)
+        || refName.startsWith(RefNames.REFS_STARRED_CHANGES)
+        || refName.equals(MULTI_SITE_VERSIONING_REF);
+  }
+
+  private void updateProducerProjectVersionUpdate(RefUpdatedEvent refUpdatedEvent) {
+    String refName = refUpdatedEvent.getRefName();
+
+    if (isSpecialRefName(refName)) {
+      logger.atFine().log(
+          "Found a special ref name %s, skipping update for %s",
+          refName, refUpdatedEvent.getProjectNameKey().get());
+      return;
+    }
+    try {
+      Project.NameKey projectNameKey = refUpdatedEvent.getProjectNameKey();
+      long newVersion = getCurrentGlobalVersionNumber();
+
+      Optional<RefUpdate> newProjectVersionRefUpdate =
+          updateLocalProjectVersion(projectNameKey, newVersion);
+
+      if (newProjectVersionRefUpdate.isPresent()) {
+        verLogger.log(projectNameKey, newVersion, 0L);
+
+        if (updateSharedProjectVersion(
+            projectNameKey, newProjectVersionRefUpdate.get().getNewObjectId(), newVersion)) {
+          gitReferenceUpdated.fire(projectNameKey, newProjectVersionRefUpdate.get(), null);
+        }
+      } else {
+        logger.atWarning().log(
+            "Ref %s not found on projet %s: skipping project version update",
+            refUpdatedEvent.getRefName(), projectNameKey);
+      }
+    } catch (LocalProjectVersionUpdateException | SharedProjectVersionUpdateException e) {
+      logger.atSevere().withCause(e).log(
+          "Issue encountered when updating version for project "
+              + refUpdatedEvent.getProjectNameKey());
+    }
+  }
+
+  private RefUpdate getProjectVersionRefUpdate(Repository repository, Long version)
+      throws IOException {
+    RefUpdate refUpdate = repository.getRefDatabase().newUpdate(MULTI_SITE_VERSIONING_REF, false);
+    refUpdate.setNewObjectId(getNewId(repository, version));
+    refUpdate.setForceUpdate(true);
+    return refUpdate;
+  }
+
+  private ObjectId getNewId(Repository repository, Long version) throws IOException {
+    ObjectInserter ins = repository.newObjectInserter();
+    ObjectId newId = ins.insert(OBJ_BLOB, Long.toString(version).getBytes(UTF_8));
+    ins.flush();
+    return newId;
+  }
+
+  private boolean updateSharedProjectVersion(
+      Project.NameKey projectNameKey, ObjectId newObjectId, Long newVersion)
+      throws SharedProjectVersionUpdateException {
+
+    Ref sharedRef =
+        sharedRefDb
+            .get(projectNameKey, MULTI_SITE_VERSIONING_REF, String.class)
+            .map(
+                (String objectId) ->
+                    new ObjectIdRef.Unpeeled(
+                        Ref.Storage.NEW, MULTI_SITE_VERSIONING_REF, ObjectId.fromString(objectId)))
+            .orElse(
+                new ObjectIdRef.Unpeeled(
+                    Ref.Storage.NEW, MULTI_SITE_VERSIONING_REF, ObjectId.zeroId()));
+    Optional<Long> sharedVersion =
+        sharedRefDb
+            .get(projectNameKey, MULTI_SITE_VERSIONING_VALUE_REF, String.class)
+            .map(Long::parseLong);
+
+    try {
+      if (sharedVersion.isPresent() && sharedVersion.get() >= newVersion) {
+        logger.atWarning().log(
+            String.format(
+                "NOT Updating project %s version %s (value=%d) in shared ref-db because is more recent than the local one %s (value=%d) ",
+                projectNameKey.get(),
+                newObjectId,
+                newVersion,
+                sharedRef.getObjectId().getName(),
+                sharedVersion.get()));
+        return false;
+      }
+
+      logger.atFine().log(
+          String.format(
+              "Updating shared project %s version to %s (value=%d)",
+              projectNameKey.get(), newObjectId, newVersion));
+
+      boolean success = sharedRefDb.compareAndPut(projectNameKey, sharedRef, newObjectId);
+      if (!success) {
+        String message =
+            String.format(
+                "Project version blob update failed for %s. Current value %s, new value: %s",
+                projectNameKey.get(), safeGetObjectId(sharedRef), newObjectId);
+        logger.atSevere().log(message);
+        throw new SharedProjectVersionUpdateException(message);
+      }
+
+      success =
+          sharedRefDb.compareAndPut(
+              projectNameKey,
+              MULTI_SITE_VERSIONING_VALUE_REF,
+              sharedVersion.map(Object::toString).orElse(null),
+              newVersion.toString());
+      if (!success) {
+        String message =
+            String.format(
+                "Project version update failed for %s. Current value %s, new value: %s",
+                projectNameKey.get(), safeGetObjectId(sharedRef), newObjectId);
+        logger.atSevere().log(message);
+        throw new SharedProjectVersionUpdateException(message);
+      }
+
+      return true;
+    } catch (GlobalRefDbSystemError refDbSystemError) {
+      String message =
+          String.format(
+              "Error while updating shared project version for %s. Current value %s, new value: %s. Error: %s",
+              projectNameKey.get(),
+              sharedRef.getObjectId(),
+              newObjectId,
+              refDbSystemError.getMessage());
+      logger.atSevere().withCause(refDbSystemError).log(message);
+      throw new SharedProjectVersionUpdateException(message);
+    }
+  }
+
+  /* (non-Javadoc)
+   * @see com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdate#getProjectLocalVersion(java.lang.String)
+   */
+  @Override
+  public Optional<Long> getProjectLocalVersion(String projectName) {
+    try (Repository repository =
+        gitRepositoryManager.openRepository(Project.NameKey.parse(projectName))) {
+      Optional<IntBlob> blob = IntBlob.parse(repository, MULTI_SITE_VERSIONING_REF);
+      if (blob.isPresent()) {
+        Long repoVersion = Integer.toUnsignedLong(blob.get().value());
+        logger.atFine().log("Local project '%s' has version %d", projectName, repoVersion);
+        return Optional.of(repoVersion);
+      }
+    } catch (RepositoryNotFoundException re) {
+      logger.atFine().log("Project '%s' not found", projectName);
+    } catch (IOException e) {
+      logger.atSevere().withCause(e).log("Cannot read local project '%s' version", projectName);
+    }
+    return Optional.empty();
+  }
+
+  /* (non-Javadoc)
+   * @see com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdate#getProjectRemoteVersion(java.lang.String)
+   */
+  @Override
+  public Optional<Long> getProjectRemoteVersion(String projectName) {
+    Optional<String> globalVersion =
+        sharedRefDb.get(
+            Project.NameKey.parse(projectName), MULTI_SITE_VERSIONING_VALUE_REF, String.class);
+    return globalVersion.flatMap(longString -> getLongValueOf(longString));
+  }
+
+  private Object safeGetObjectId(Ref currentRef) {
+    return currentRef == null ? "null" : currentRef.getObjectId();
+  }
+
+  private Optional<Long> getLongValueOf(String longString) {
+    try {
+      return Optional.ofNullable(Long.parseLong(longString));
+    } catch (NumberFormatException e) {
+      logger.atSevere().withCause(e).log(
+          "Unable to parse timestamp value %s into Long", longString);
+      return Optional.empty();
+    }
+  }
+
+  private Optional<RefUpdate> updateLocalProjectVersion(
+      Project.NameKey projectNameKey, long newVersionNumber)
+      throws LocalProjectVersionUpdateException {
+    logger.atFine().log(
+        "Updating local version for project %s with version %d",
+        projectNameKey.get(), newVersionNumber);
+    try (Repository repository = gitRepositoryManager.openRepository(projectNameKey)) {
+      RefUpdate refUpdate = getProjectVersionRefUpdate(repository, newVersionNumber);
+      RefUpdate.Result result = refUpdate.update();
+      if (!isSuccessful(result)) {
+        String message =
+            String.format(
+                "RefUpdate failed with result %s for: project=%s, version=%d",
+                result.name(), projectNameKey.get(), newVersionNumber);
+        logger.atSevere().log(message);
+        throw new LocalProjectVersionUpdateException(message);
+      }
+
+      return Optional.of(refUpdate);
+    } catch (IOException e) {
+      String message = "Cannot create versioning command for " + projectNameKey.get();
+      logger.atSevere().withCause(e).log(message);
+      throw new LocalProjectVersionUpdateException(message);
+    }
+  }
+
+  private long getCurrentGlobalVersionNumber() {
+    return System.currentTimeMillis() / 1000;
+  }
+
+  private Boolean isSuccessful(RefUpdate.Result result) {
+    return SUCCESSFUL_RESULTS.contains(result);
+  }
+
+  public static class LocalProjectVersionUpdateException extends Exception {
+    private static final long serialVersionUID = 7649956232401457023L;
+
+    public LocalProjectVersionUpdateException(String projectName) {
+      super("Cannot update local project version of " + projectName);
+    }
+  }
+
+  public static class SharedProjectVersionUpdateException extends Exception {
+    private static final long serialVersionUID = -9153858177700286314L;
+
+    public SharedProjectVersionUpdateException(String projectName) {
+      super("Cannot update shared project version of " + projectName);
+    }
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationModule.java
index fe224ae..f66bf37 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationModule.java
@@ -20,7 +20,6 @@
 import com.gerritforge.gerrit.globalrefdb.validation.RefUpdateValidator;
 import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDatabaseWrapper;
 import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbBatchRefUpdate;
-import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbConfiguration;
 import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbGitRepositoryManager;
 import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbRefDatabase;
 import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbRefUpdate;
@@ -37,8 +36,6 @@
 import com.google.inject.TypeLiteral;
 import com.google.inject.name.Names;
 import com.googlesource.gerrit.plugins.multisite.Configuration;
-import com.googlesource.gerrit.plugins.multisite.Log4jProjectVersionLogger;
-import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger;
 import com.googlesource.gerrit.plugins.replication.ReplicationExtensionPointModule;
 import com.googlesource.gerrit.plugins.replication.ReplicationPushFilter;
 
@@ -55,7 +52,6 @@
 
     bind(SharedRefDatabaseWrapper.class).in(Scopes.SINGLETON);
     bind(SharedRefLogger.class).to(Log4jSharedRefLogger.class);
-    bind(ProjectVersionLogger.class).to(Log4jProjectVersionLogger.class);
     factory(LockWrapper.Factory.class);
 
     factory(SharedRefDbRepository.Factory.class);
@@ -65,7 +61,6 @@
     factory(RefUpdateValidator.Factory.class);
     factory(BatchRefUpdateValidator.Factory.class);
 
-    bind(SharedRefDbConfiguration.class).toInstance(cfg.getSharedRefDbConfiguration());
     bind(new TypeLiteral<ImmutableSet<String>>() {})
         .annotatedWith(Names.named(SharedRefDbGitRepositoryManager.IGNORED_REFS))
         .toInstance(
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/consumer/ReplicationStatusTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/consumer/ReplicationStatusTest.java
index dc1a564..ce82954 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/consumer/ReplicationStatusTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/consumer/ReplicationStatusTest.java
@@ -15,7 +15,6 @@
 package com.googlesource.gerrit.plugins.multisite.consumer;
 
 import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.lenient;
 import static org.mockito.Mockito.when;
 
 import com.google.common.cache.Cache;
@@ -23,9 +22,9 @@
 import com.google.common.collect.ImmutableSortedSet;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.server.project.ProjectCache;
-import com.google.inject.Provider;
 import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger;
 import com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdate;
+import java.util.Optional;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -37,7 +36,6 @@
 
   @Mock private ProjectVersionLogger verLogger;
   @Mock private ProjectCache projectCache;
-  @Mock private Provider<ProjectVersionRefUpdate> projectVersionRefUpdateProvider;
   @Mock private ProjectVersionRefUpdate projectVersionRefUpdate;
   private ReplicationStatus objectUnderTest;
   private Cache<String, Long> replicationStatusCache;
@@ -50,8 +48,7 @@
     replicationStatusCache = CacheBuilder.newBuilder().build();
     objectUnderTest =
         new ReplicationStatus(
-            replicationStatusCache, projectVersionRefUpdateProvider, verLogger, projectCache);
-    lenient().when(projectVersionRefUpdateProvider.get()).thenReturn(projectVersionRefUpdate);
+            replicationStatusCache, Optional.of(projectVersionRefUpdate), verLogger, projectCache);
   }
 
   @Test
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/consumer/SubscriberMetricsTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/consumer/SubscriberMetricsTest.java
index 6aa2ce6..4c45e07 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/consumer/SubscriberMetricsTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/consumer/SubscriberMetricsTest.java
@@ -27,7 +27,6 @@
 import com.google.gerrit.server.events.RefUpdatedEvent;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.project.ProjectCache;
-import com.google.inject.Provider;
 import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger;
 import com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdate;
 import java.util.Optional;
@@ -49,7 +48,6 @@
   @Mock private MetricMaker metricMaker;
   @Mock private ProjectVersionLogger verLogger;
   @Mock private ProjectCache projectCache;
-  @Mock private Provider<ProjectVersionRefUpdate> projectVersionRefUpdateProvider;
   @Mock private ProjectVersionRefUpdate projectVersionRefUpdate;
   private SubscriberMetrics metrics;
   private EventMessage.Header msgHeader;
@@ -62,10 +60,9 @@
             metricMaker,
             new ReplicationStatus(
                 CacheBuilder.newBuilder().build(),
-                projectVersionRefUpdateProvider,
+                Optional.of(projectVersionRefUpdate),
                 verLogger,
                 projectCache));
-    when(projectVersionRefUpdateProvider.get()).thenReturn(projectVersionRefUpdate);
   }
 
   @Test
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/http/ReplicationStatusServletIT.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/http/ReplicationStatusServletIT.java
index da02d02..0739eab 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/http/ReplicationStatusServletIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/http/ReplicationStatusServletIT.java
@@ -27,6 +27,8 @@
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.httpd.restapi.RestApiServlet;
 import com.google.inject.AbstractModule;
+import com.google.inject.Scopes;
+import com.google.inject.multibindings.OptionalBinder;
 import com.googlesource.gerrit.plugins.multisite.Log4jProjectVersionLogger;
 import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger;
 import com.googlesource.gerrit.plugins.multisite.cache.CacheModule;
@@ -35,6 +37,8 @@
 import com.googlesource.gerrit.plugins.multisite.forwarder.ForwarderModule;
 import com.googlesource.gerrit.plugins.multisite.forwarder.router.RouterModule;
 import com.googlesource.gerrit.plugins.multisite.index.IndexModule;
+import com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdate;
+import com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdateImpl;
 import java.io.IOException;
 import org.eclipse.jgit.lib.Config;
 import org.junit.Before;
@@ -64,6 +68,10 @@
       bind(SharedRefDbConfiguration.class).toInstance(sharedRefDbConfig);
       bind(ProjectVersionLogger.class).to(Log4jProjectVersionLogger.class);
       bind(SharedRefLogger.class).to(Log4jSharedRefLogger.class);
+      OptionalBinder.newOptionalBinder(binder(), ProjectVersionRefUpdate.class)
+          .setBinding()
+          .to(ProjectVersionRefUpdateImpl.class)
+          .in(Scopes.SINGLETON);
     }
   }
 
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateTest.java
index 917c6bf..0d6fb67 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateTest.java
@@ -110,7 +110,7 @@
     when(refUpdatedEvent.getProjectNameKey()).thenReturn(A_TEST_PROJECT_NAME_KEY);
     when(refUpdatedEvent.getRefName()).thenReturn(A_TEST_REF_NAME);
 
-    new ProjectVersionRefUpdate(
+    new ProjectVersionRefUpdateImpl(
             repoManager, sharedRefDb, gitReferenceUpdated, verLogger, projectsFilter)
         .onEvent(refUpdatedEvent);
 
@@ -155,7 +155,7 @@
     when(refUpdatedEvent.getProjectNameKey()).thenReturn(A_TEST_PROJECT_NAME_KEY);
     when(refUpdatedEvent.getRefName()).thenReturn(A_TEST_REF_NAME);
 
-    new ProjectVersionRefUpdate(
+    new ProjectVersionRefUpdateImpl(
             repoManager, sharedRefDb, gitReferenceUpdated, verLogger, projectsFilter)
         .onEvent(refUpdatedEvent);
 
@@ -195,7 +195,7 @@
     when(refUpdatedEvent.getProjectNameKey()).thenReturn(A_TEST_PROJECT_NAME_KEY);
     when(refUpdatedEvent.getRefName()).thenReturn(A_TEST_REF_NAME);
 
-    new ProjectVersionRefUpdate(
+    new ProjectVersionRefUpdateImpl(
             repoManager, sharedRefDb, gitReferenceUpdated, verLogger, projectsFilter)
         .onEvent(refUpdatedEvent);
 
@@ -238,7 +238,7 @@
     when(refUpdatedEvent.getRefName()).thenReturn(magicRefName);
     repo.branch(magicRefName).commit().create();
 
-    new ProjectVersionRefUpdate(
+    new ProjectVersionRefUpdateImpl(
             repoManager, sharedRefDb, gitReferenceUpdated, verLogger, projectsFilter)
         .onEvent(refUpdatedEvent);
 
@@ -254,7 +254,7 @@
     when(refUpdatedEvent.getProjectNameKey()).thenReturn(Project.nameKey("aNonExistentProject"));
     when(refUpdatedEvent.getRefName()).thenReturn(A_TEST_REF_NAME);
 
-    new ProjectVersionRefUpdate(
+    new ProjectVersionRefUpdateImpl(
             repoManager, sharedRefDb, gitReferenceUpdated, verLogger, projectsFilter)
         .onEvent(refUpdatedEvent);
 
@@ -276,7 +276,7 @@
     Thread.sleep(1000L);
     repo.branch("master").update(masterCommit);
 
-    new ProjectVersionRefUpdate(
+    new ProjectVersionRefUpdateImpl(
             repoManager, sharedRefDb, gitReferenceUpdated, verLogger, projectsFilter)
         .onEvent(refUpdatedEvent);
 
@@ -292,7 +292,7 @@
         .thenReturn(Optional.of("123"));
 
     Optional<Long> version =
-        new ProjectVersionRefUpdate(
+        new ProjectVersionRefUpdateImpl(
                 repoManager, sharedRefDb, gitReferenceUpdated, verLogger, projectsFilter)
             .getProjectRemoteVersion(A_TEST_PROJECT_NAME);