docs: public static synchronized void migrate(ProjectManager projectManager, CommandExecutor executor) {
          requireNonNull(projectManager, "projectManager");
          requireNonNull(executor, "executor");
  
          final RepositorySupport<ProjectMetadata> metadataRepo =
                  new RepositorySupport<>(projectManager, executor,
                                          entry -> convertWithJackson(entry, ProjectMetadata.class));
  
          final UserAndTimestamp userAndTimestamp = UserAndTimestamp.of(author);
  
          // Check whether tokens are migrated or not.
          final Entry<?> tokenEntry = projectManager.get(INTERNAL_PROJ).repos()
                                                    .get(Project.REPO_DOGMA)
                                                    .getOrNull(Revision.HEAD, TOKEN_JSON).join();
          final Collection<Token> migratedTokens =
                  tokenEntry == null ? migrateTokens(projectManager, executor) : ImmutableSet.of();
          migratedTokens.forEach(token -> logger.info("Token '{}' has been migrated", token.id()));
  
          // Create a metadata.json file if it does not exist in the meta repository of the project.
          // Use SafeProjectManager in order not to migrate internal projects.
          final SafeProjectManager safeProjectManager = new SafeProjectManager(projectManager);
  
          // Check whether "${project}/meta/metadata.json" exists or not in case of upgrading from 0.23.0.
          safeProjectManager.list().values().forEach(p -> {
              if (alreadyMigrated(p)) {
                  return;
              }
              if (!p.repos().exists(Project.REPO_META)) {
                  return;
              }
              try {
                  final ProjectMetadata metadata =
                          metadataRepo.fetch(p.name(), Project.REPO_META, METADATA_JSON).join().object();
                  // "meta" repository is changed as a user repository now.
                  metadata.repos().put(Project.REPO_META,
                                       new RepositoryMetadata(Project.REPO_META, userAndTimestamp,
                                                              PerRolePermissions.ofPublic()));
                  commitProjectMetadata(metadataRepo, executor, p.name(), metadata);
  
                  metadataRepo.push(p.name(), Project.REPO_META, author,
                                    "Remove the old metadata file", Change.ofRemoval(METADATA_JSON)).join();
              } catch (Throwable cause) {
                  cause = Exceptions.peel(cause);
                  if (!(cause instanceof EntryNotFoundException)) {
                      Exceptions.throwUnsafely(cause);
                  }
              }
          });
  
          // Create a token registration map for metadata migration.
          final UserAndTimestamp creationTime = UserAndTimestamp.of(author);
          final Map<String, TokenRegistration> registrations =
                  migratedTokens.stream()
                                .map(t -> new TokenRegistration(t.id(), ProjectRole.MEMBER, creationTime))
                                .collect(toMap(TokenRegistration::id, Function.identity()));
  
          safeProjectManager.list().values().forEach(p -> {
              if (alreadyMigrated(p)) {
                  return;
              }
              final Map<String, RepositoryMetadata> repos =
                      p.repos().list().values().stream()
                       .filter(r -> !r.name().equals(Project.REPO_DOGMA))
                       .map(r -> new RepositoryMetadata(r.name(), userAndTimestamp,
                                                        PerRolePermissions.ofPublic()))
                       .collect(toMap(RepositoryMetadata::name, Function.identity()));
  
              final ProjectMetadata metadata = new ProjectMetadata(p.name(), repos, ImmutableMap.of(),
                                                                   registrations, userAndTimestamp, null);
              commitProjectMetadata(metadataRepo, executor, p.name(), metadata);
          });
      }