Use a global version number instead of the commit timestamp The commit timestamp is not a reliable source of repository version numbering because of unreliability of the local time of the committer and forced push use-case, where the repository is brought back in time. Define a new global version numbering (current system time at the moment) and use it as monotonic versioning for the repository. Bug: Issue 12413 Change-Id: If1b642a56791bd3a4c1d6ef903ec2f7c61c19928
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 d13ed79..3b82582 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
@@ -43,8 +43,6 @@ import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.revwalk.RevWalk; @Singleton public class ProjectVersionRefUpdate implements EventListener { @@ -101,18 +99,16 @@ } try { Project.NameKey projectNameKey = refUpdatedEvent.getProjectNameKey(); + long newVersion = getCurrentGlobalVersionNumber(); - Optional<Long> lastRefUpdatedTimestamp = getLastRefUpdatedTimestamp(projectNameKey, refName); Optional<RefUpdate> newProjectVersionRefUpdate = - updateLocalProjectVersion(projectNameKey, lastRefUpdatedTimestamp); + updateLocalProjectVersion(projectNameKey, newVersion); - if (newProjectVersionRefUpdate.isPresent() && lastRefUpdatedTimestamp.isPresent()) { - verLogger.log(projectNameKey, lastRefUpdatedTimestamp.get(), 0L); + if (newProjectVersionRefUpdate.isPresent()) { + verLogger.log(projectNameKey, newVersion, 0L); if (updateSharedProjectVersion( - projectNameKey, - newProjectVersionRefUpdate.get().getNewObjectId(), - lastRefUpdatedTimestamp.get())) { + projectNameKey, newProjectVersionRefUpdate.get().getNewObjectId(), newVersion)) { gitReferenceUpdated.fire(projectNameKey, newProjectVersionRefUpdate.get(), null); } } else { @@ -142,31 +138,6 @@ return newId; } - private Optional<Long> getLastRefUpdatedTimestamp(Project.NameKey projectNameKey, String refName) - throws LocalProjectVersionUpdateException { - logger.atFine().log( - String.format( - "Getting last ref updated time for project %s, ref %s", projectNameKey.get(), refName)); - try (Repository repository = gitRepositoryManager.openRepository(projectNameKey)) { - Ref ref = repository.findRef(refName); - if (ref == null) { - logger.atWarning().log("Unable to find ref " + refName + " in project " + projectNameKey); - return Optional.empty(); - } - try (RevWalk walk = new RevWalk(repository)) { - RevCommit commit = walk.parseCommit(ref.getObjectId()); - return Optional.of(Integer.toUnsignedLong(commit.getCommitTime())); - } - } catch (IOException ioe) { - String message = - String.format( - "Error while getting last ref updated time for project %s, ref %s", - projectNameKey.get(), refName); - logger.atSevere().withCause(ioe).log(message); - throw new LocalProjectVersionUpdateException(message); - } - } - private boolean updateSharedProjectVersion( Project.NameKey projectNameKey, ObjectId newObjectId, Long newVersion) throws SharedProjectVersionUpdateException { @@ -282,21 +253,19 @@ } private Optional<RefUpdate> updateLocalProjectVersion( - Project.NameKey projectNameKey, Optional<Long> lastRefUpdatedTimestamp) + Project.NameKey projectNameKey, long newVersionNumber) throws LocalProjectVersionUpdateException { - if (!lastRefUpdatedTimestamp.isPresent()) { - return Optional.empty(); - } - - logger.atFine().log("Updating local version for project " + projectNameKey.get()); + 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, lastRefUpdatedTimestamp.get()); + 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(), lastRefUpdatedTimestamp.get()); + result.name(), projectNameKey.get(), newVersionNumber); logger.atSevere().log(message); throw new LocalProjectVersionUpdateException(message); } @@ -309,6 +278,10 @@ } } + private long getCurrentGlobalVersionNumber() { + return System.currentTimeMillis() / 1000; + } + private Boolean isSuccessful(RefUpdate.Result result) { return SUCCESSFUL_RESULTS.contains(result); }
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 1839fe1..6360227 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
@@ -116,10 +116,56 @@ assertThat(ref).isNotNull(); ObjectLoader loader = repo.getRepository().open(ref.getObjectId()); - String storedVersion = IOUtils.toString(loader.openStream(), StandardCharsets.UTF_8.name()); - assertThat(Long.parseLong(storedVersion)).isEqualTo(masterCommit.getCommitTime()); + long storedVersion = + Long.parseLong(IOUtils.toString(loader.openStream(), StandardCharsets.UTF_8.name())); + assertThat(storedVersion).isGreaterThan((long) masterCommit.getCommitTime()); - verify(verLogger).log(A_TEST_PROJECT_NAME_KEY, masterCommit.getCommitTime(), 0); + verify(verLogger).log(A_TEST_PROJECT_NAME_KEY, storedVersion, 0); + } + + @Test + public void producerShouldUpdateProjectVersionUponForcedPushRefUpdatedEvent() throws Exception { + Context.setForwardedEvent(false); + + Thread.sleep(1000L); + RevCommit masterPlusOneCommit = repo.branch("master").commit().create(); + + Thread.sleep(1000L); + repo.branch("master").update(masterCommit); + + when(sharedRefDb.get( + A_TEST_PROJECT_NAME_KEY, + ProjectVersionRefUpdate.MULTI_SITE_VERSIONING_REF, + String.class)) + .thenReturn(Optional.of("26f7ee61bf0e470e8393c884526eec8a9b943a63")); + when(sharedRefDb.get( + A_TEST_PROJECT_NAME_KEY, + ProjectVersionRefUpdate.MULTI_SITE_VERSIONING_VALUE_REF, + String.class)) + .thenReturn(Optional.of("" + (masterCommit.getCommitTime() - 1))); + when(sharedRefDb.compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class))) + .thenReturn(true); + when(sharedRefDb.compareAndPut(any(Project.NameKey.class), any(String.class), any(), any())) + .thenReturn(true); + when(refUpdatedEvent.getProjectNameKey()).thenReturn(A_TEST_PROJECT_NAME_KEY); + when(refUpdatedEvent.getRefName()).thenReturn(A_TEST_REF_NAME); + + new ProjectVersionRefUpdate(repoManager, sharedRefDb, gitReferenceUpdated, verLogger) + .onEvent(refUpdatedEvent); + + Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF); + + verify(sharedRefDb, atMost(1)) + .compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class)); + + assertThat(ref).isNotNull(); + + ObjectLoader loader = repo.getRepository().open(ref.getObjectId()); + long storedVersion = + Long.parseLong(IOUtils.toString(loader.openStream(), StandardCharsets.UTF_8.name())); + assertThat(storedVersion).isGreaterThan((long) masterPlusOneCommit.getCommitTime()); + + verify(verLogger).log(A_TEST_PROJECT_NAME_KEY, storedVersion, 0); } @Test @@ -155,10 +201,11 @@ assertThat(ref).isNotNull(); ObjectLoader loader = repo.getRepository().open(ref.getObjectId()); - String storedVersion = IOUtils.toString(loader.openStream(), StandardCharsets.UTF_8.name()); - assertThat(Long.parseLong(storedVersion)).isEqualTo(masterCommit.getCommitTime()); + long storedVersion = + Long.parseLong(IOUtils.toString(loader.openStream(), StandardCharsets.UTF_8.name())); + assertThat(storedVersion).isGreaterThan((long) masterCommit.getCommitTime()); - verify(verLogger).log(A_TEST_PROJECT_NAME_KEY, masterCommit.getCommitTime(), 0); + verify(verLogger).log(A_TEST_PROJECT_NAME_KEY, storedVersion, 0); } @Test