//Copyright (C) 2016 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.server.schema; import com.google.auto.value.AutoValue; import com.google.common.collect.ListMultimap; import com.google.common.collect.MultimapBuilder; import com.google.gerrit.common.Nullable; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.GerritPersonIdent; import com.google.gerrit.server.account.WatchConfig; import com.google.gerrit.server.account.WatchConfig.NotifyType; import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey; import com.google.gerrit.server.config.AllUsersName; import com.google.gerrit.server.extensions.events.GitReferenceUpdated; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.MetaDataUpdate; import com.google.gwtorm.jdbc.JdbcSchema; import com.google.gwtorm.server.OrmDuplicateKeyException; import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; import com.google.inject.Provider; import java.io.IOException; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Collection; import java.util.EnumSet; import java.util.HashMap; import java.util.Map; import java.util.Set; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.lib.BatchRefUpdate; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevWalk; public class Schema_139 extends SchemaVersion { private static final String MSG = "Migrate project watches to git"; private final GitRepositoryManager repoManager; private final AllUsersName allUsersName; private final PersonIdent serverUser; @Inject Schema_139( Provider<Schema_138> prior, GitRepositoryManager repoManager, AllUsersName allUsersName, @GerritPersonIdent PersonIdent serverUser) { super(prior); this.repoManager = repoManager; this.allUsersName = allUsersName; this.serverUser = serverUser; } @Override protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException { ListMultimap<Account.Id, ProjectWatch> imports = MultimapBuilder.hashKeys().arrayListValues().build(); try (Statement stmt = ((JdbcSchema) db).getConnection().createStatement(); ResultSet rs = stmt.executeQuery( "SELECT " + "account_id, " + "project_name, " + "filter, " + "notify_abandoned_changes, " + "notify_all_comments, " + "notify_new_changes, " + "notify_new_patch_sets, " + "notify_submitted_changes " + "FROM account_project_watches")) { while (rs.next()) { Account.Id accountId = new Account.Id(rs.getInt(1)); ProjectWatch.Builder b = ProjectWatch.builder() .project(new Project.NameKey(rs.getString(2))) .filter(rs.getString(3)) .notifyAbandonedChanges(rs.getBoolean(4)) .notifyAllComments(rs.getBoolean(5)) .notifyNewChanges(rs.getBoolean(6)) .notifyNewPatchSets(rs.getBoolean(7)) .notifySubmittedChanges(rs.getBoolean(8)); imports.put(accountId, b.build()); } } if (imports.isEmpty()) { return; } try (Repository git = repoManager.openRepository(allUsersName); RevWalk rw = new RevWalk(git)) { BatchRefUpdate bru = git.getRefDatabase().newBatchUpdate(); bru.setRefLogIdent(serverUser); bru.setRefLogMessage(MSG, false); for (Map.Entry<Account.Id, Collection<ProjectWatch>> e : imports.asMap().entrySet()) { Map<ProjectWatchKey, Set<NotifyType>> projectWatches = new HashMap<>(); for (ProjectWatch projectWatch : e.getValue()) { ProjectWatchKey key = ProjectWatchKey.create(projectWatch.project(), projectWatch.filter()); if (projectWatches.containsKey(key)) { throw new OrmDuplicateKeyException( "Duplicate key for watched project: " + key.toString()); } Set<NotifyType> notifyValues = EnumSet.noneOf(NotifyType.class); if (projectWatch.notifyAbandonedChanges()) { notifyValues.add(NotifyType.ABANDONED_CHANGES); } if (projectWatch.notifyAllComments()) { notifyValues.add(NotifyType.ALL_COMMENTS); } if (projectWatch.notifyNewChanges()) { notifyValues.add(NotifyType.NEW_CHANGES); } if (projectWatch.notifyNewPatchSets()) { notifyValues.add(NotifyType.NEW_PATCHSETS); } if (projectWatch.notifySubmittedChanges()) { notifyValues.add(NotifyType.SUBMITTED_CHANGES); } projectWatches.put(key, notifyValues); } try (MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, allUsersName, git, bru)) { md.getCommitBuilder().setAuthor(serverUser); md.getCommitBuilder().setCommitter(serverUser); md.setMessage(MSG); WatchConfig watchConfig = new WatchConfig(e.getKey()); watchConfig.load(md); watchConfig.setProjectWatches(projectWatches); watchConfig.commit(md); } } bru.execute(rw, NullProgressMonitor.INSTANCE); } catch (IOException | ConfigInvalidException ex) { throw new OrmException(ex); } } @AutoValue abstract static class ProjectWatch { abstract Project.NameKey project(); abstract @Nullable String filter(); abstract boolean notifyAbandonedChanges(); abstract boolean notifyAllComments(); abstract boolean notifyNewChanges(); abstract boolean notifyNewPatchSets(); abstract boolean notifySubmittedChanges(); static Builder builder() { return new AutoValue_Schema_139_ProjectWatch.Builder(); } @AutoValue.Builder abstract static class Builder { abstract Builder project(Project.NameKey project); abstract Builder filter(@Nullable String filter); abstract Builder notifyAbandonedChanges(boolean notifyAbandonedChanges); abstract Builder notifyAllComments(boolean notifyAllComments); abstract Builder notifyNewChanges(boolean notifyNewChanges); abstract Builder notifyNewPatchSets(boolean notifyNewPatchSets); abstract Builder notifySubmittedChanges(boolean notifySubmittedChanges); abstract ProjectWatch build(); } } }