Merge "Add note to use init when upgrading"
diff --git a/java/com/google/gerrit/plugins/checks/CheckerRef.java b/java/com/google/gerrit/plugins/checks/CheckerRef.java
index 2e7e263..6e2e246 100644
--- a/java/com/google/gerrit/plugins/checks/CheckerRef.java
+++ b/java/com/google/gerrit/plugins/checks/CheckerRef.java
@@ -24,7 +24,7 @@
public static final String REFS_CHECKERS = "refs/checkers/";
/** Ref that stores the repository to checkers map. */
- public static final String REFS_META_CHECKERS = "refs/meta/checkers/";
+ public static final String REFS_META_CHECKERS = "refs/meta/checkers";
/** Suffix for check refs. */
public static final String CHECKS_SUFFIX = "/checks";
diff --git a/java/com/google/gerrit/plugins/checks/Init.java b/java/com/google/gerrit/plugins/checks/Init.java
index 5e7407b..eddfad2 100644
--- a/java/com/google/gerrit/plugins/checks/Init.java
+++ b/java/com/google/gerrit/plugins/checks/Init.java
@@ -18,6 +18,7 @@
import static com.google.gerrit.pgm.init.api.InitUtil.extract;
import com.google.gerrit.pgm.init.api.InitStep;
+import com.google.gerrit.plugins.checks.db.CheckerRefMigration;
import com.google.gerrit.plugins.checks.email.ChecksEmailModule;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
@@ -25,16 +26,19 @@
public class Init implements InitStep {
private final SitePaths site;
+ private final CheckerRefMigration checkerRefMigration;
@Inject
- Init(SitePaths site) {
+ Init(SitePaths site, CheckerRefMigration checkerRefMigration) {
this.site = site;
+ this.checkerRefMigration = checkerRefMigration;
}
@Override
public void run() throws Exception {
extractMailExample("CombinedCheckStateUpdated.soy");
extractMailExample("CombinedCheckStateUpdatedHtml.soy");
+ checkerRefMigration.migrate();
}
private void extractMailExample(String orig) throws Exception {
diff --git a/java/com/google/gerrit/plugins/checks/db/CheckerRefMigration.java b/java/com/google/gerrit/plugins/checks/db/CheckerRefMigration.java
new file mode 100644
index 0000000..09e5485
--- /dev/null
+++ b/java/com/google/gerrit/plugins/checks/db/CheckerRefMigration.java
@@ -0,0 +1,86 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.gerrit.plugins.checks.db;
+
+import com.google.gerrit.plugins.checks.CheckerRef;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+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;
+
+@Singleton
+public class CheckerRefMigration {
+ private static final String TMP_REF = "refs/tmp/checker-migration";
+ private static final String LEGACY_REFS_META_CHECKERS = "refs/meta/checkers/";
+
+ private final GitRepositoryManager repoManager;
+ private final AllProjectsName allProjectsName;
+
+ @Inject
+ CheckerRefMigration(GitRepositoryManager repoManager, AllProjectsName allProjectsName) {
+ this.repoManager = repoManager;
+ this.allProjectsName = allProjectsName;
+ }
+
+ public void migrate() throws Exception {
+ try (Repository repo = repoManager.openRepository(allProjectsName)) {
+
+ // This part is specifically for cases where the rename failed half-way last time.
+ Ref ref = repo.exactRef(TMP_REF);
+ if (ref != null) {
+ renameRef(TMP_REF, CheckerRef.REFS_META_CHECKERS, repo);
+ }
+
+ ref = repo.exactRef(LEGACY_REFS_META_CHECKERS);
+ if (ref == null) {
+ return;
+ }
+ renameRef(LEGACY_REFS_META_CHECKERS, TMP_REF, repo);
+ renameRef(TMP_REF, CheckerRef.REFS_META_CHECKERS, repo);
+ } catch (Exception ex) {
+ throw new IllegalStateException(
+ ex.getMessage()
+ + " Ensure that refs/tmp/checker-migration doesn't exist. Also,"
+ + "ensure that refs/meta/checkers/ (with trailing '/') has been migrated to refs/meta/checkers (without "
+ + "trailing '/'). Consider documentation for updating refs manually: https://git-scm.com/docs/git-update-ref");
+ }
+ }
+
+ private void renameRef(String currentName, String targetName, Repository repo)
+ throws IOException {
+ RefRename refRename = repo.renameRef(currentName, targetName);
+ Result result = refRename.rename();
+ if (result != Result.RENAMED) {
+ throw new IllegalStateException(
+ String.format(
+ "Rename of %s to %s failed with the failure: %s.",
+ currentName, targetName, result.name()));
+ }
+ // After the rename, we need to delete. For same reason, due to a bug in JGit, the rename
+ // doesn't delete the previous
+ // name.
+ RefUpdate update = repo.updateRef(currentName);
+ update.setExpectedOldObjectId(repo.exactRef(currentName).getObjectId());
+ update.setNewObjectId(ObjectId.zeroId());
+ update.setForceUpdate(true);
+ update.delete();
+ }
+}
diff --git a/java/com/google/gerrit/plugins/checks/db/CheckersByRepositoryNotes.java b/java/com/google/gerrit/plugins/checks/db/CheckersByRepositoryNotes.java
index 3d99cbb..5019e9e 100644
--- a/java/com/google/gerrit/plugins/checks/db/CheckersByRepositoryNotes.java
+++ b/java/com/google/gerrit/plugins/checks/db/CheckersByRepositoryNotes.java
@@ -31,6 +31,7 @@
import com.google.common.hash.Hashing;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Project;
+import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.plugins.checks.CheckerRef;
import com.google.gerrit.plugins.checks.CheckerUuid;
import com.google.gerrit.server.config.AllProjectsName;
@@ -111,7 +112,18 @@
@Override
protected String getRefName() {
- return CheckerRef.REFS_META_CHECKERS;
+ // To allow for an online migration of the old checker ref (refs/meta/checkers/) to the new ref
+ // (refs/meta/checkers) we need to check which state we are in here. If we omit the legacy ref
+ // exists, we operate on that instead. The migration will move to the new ref eventually and
+ // delete the old ref. At that point, we'll start using the new ref here.
+ // TODO(paiking): Remove when migration on googlesource.com is done.
+ try {
+ return repo.exactRef("refs/meta/checkers/") != null
+ ? "refs/meta/checkers/"
+ : CheckerRef.REFS_META_CHECKERS;
+ } catch (IOException e) {
+ throw new StorageException(e);
+ }
}
/**
diff --git a/javatests/com/google/gerrit/plugins/checks/db/CheckerRefMigrationTest.java b/javatests/com/google/gerrit/plugins/checks/db/CheckerRefMigrationTest.java
new file mode 100644
index 0000000..ba80ba9
--- /dev/null
+++ b/javatests/com/google/gerrit/plugins/checks/db/CheckerRefMigrationTest.java
@@ -0,0 +1,98 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.plugins.checks.db;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.plugins.checks.CheckerUuid;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.testing.InMemoryRepositoryManager;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Repository;
+import org.junit.Before;
+import org.junit.Test;
+
+public class CheckerRefMigrationTest {
+ private InMemoryRepositoryManager inMemoryRepositoryManager;
+ private AllProjectsName allProjectsName;
+ private Repository allProjectsRepo;
+
+ @Before
+ public void setUp() throws Exception {
+ inMemoryRepositoryManager = new InMemoryRepositoryManager();
+ allProjectsName = new AllProjectsName("Test Repository");
+ allProjectsRepo = inMemoryRepositoryManager.createRepository(allProjectsName);
+ }
+
+ @Test
+ public void migrateLegacyMetaRef() throws Exception {
+ CheckerUuid checkerWithBadRef = CheckerUuid.parse("test:my-checker1");
+ try (TestRepository<Repository> testRepo = new TestRepository<>(allProjectsRepo)) {
+ testRepo
+ .branch("refs/meta/checkers/")
+ .commit()
+ .add(
+ CheckersByRepositoryNotes.computeRepositorySha1(allProjectsName).getName(),
+ checkerWithBadRef.toString())
+ .create();
+ }
+
+ assertThat(allProjectsRepo.exactRef("refs/meta/checkers/")).isNotNull();
+ assertThat(allProjectsRepo.exactRef("refs/meta/checkers")).isNull();
+ CheckerRefMigration checkerRefMigration =
+ new CheckerRefMigration(inMemoryRepositoryManager, allProjectsName);
+ checkerRefMigration.migrate();
+ assertThat(allProjectsRepo.exactRef("refs/meta/checkers")).isNotNull();
+ assertThat(allProjectsRepo.exactRef("refs/meta/checkers/")).isNull();
+ }
+
+ @Test
+ public void readFromLegacyRefUntilMigrated() throws Exception {
+ CheckerUuid checkerUuid = CheckerUuid.parse("test:my-checker1");
+ Project.NameKey projectName = Project.nameKey("foo");
+ // Create a checker map on the legacy ref
+ try (TestRepository<Repository> repo = new TestRepository<>(allProjectsRepo)) {
+ repo.branch("refs/meta/checkers/")
+ .commit()
+ .add(
+ CheckersByRepositoryNotes.computeRepositorySha1(projectName).getName(),
+ checkerUuid.toString())
+ .create();
+ }
+ // Assert that the map gets loaded even though it's on the legacy ref.
+ // This is important to allow for an online migration.
+ assertThat(CheckersByRepositoryNotes.load(allProjectsName, allProjectsRepo).get(projectName))
+ .containsExactly(checkerUuid);
+ }
+
+ @Test
+ public void readFromNewRefWhenMigrated() throws Exception {
+ CheckerUuid checkerUuid = CheckerUuid.parse("test:my-checker1");
+ Project.NameKey projectName = Project.nameKey("foo");
+ // Create a checker map on the new ref
+ try (TestRepository<Repository> repo = new TestRepository<>(allProjectsRepo)) {
+ repo.branch("refs/meta/checkers")
+ .commit()
+ .add(
+ CheckersByRepositoryNotes.computeRepositorySha1(projectName).getName(),
+ checkerUuid.toString())
+ .create();
+ }
+ // Assert that the map gets loaded
+ assertThat(CheckersByRepositoryNotes.load(allProjectsName, allProjectsRepo).get(projectName))
+ .containsExactly(checkerUuid);
+ }
+}