| // Copyright (C) 2015 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; |
| |
| import com.google.common.base.Function; |
| import com.google.common.base.Predicate; |
| import com.google.common.collect.FluentIterable; |
| import com.google.common.collect.Iterators; |
| import com.google.common.collect.Lists; |
| import com.google.gerrit.reviewdb.client.Account; |
| import com.google.gerrit.reviewdb.client.Change; |
| import com.google.gerrit.reviewdb.client.RefNames; |
| import com.google.gerrit.reviewdb.client.StarredChange; |
| import com.google.gerrit.reviewdb.server.ReviewDb; |
| import com.google.gerrit.server.config.AllUsersName; |
| import com.google.gerrit.server.git.GitRepositoryManager; |
| import com.google.gerrit.server.notedb.NotesMigration; |
| import com.google.gwtorm.server.ListResultSet; |
| import com.google.gwtorm.server.OrmException; |
| import com.google.gwtorm.server.ResultSet; |
| import com.google.inject.Inject; |
| import com.google.inject.Provider; |
| import com.google.inject.Singleton; |
| |
| import org.eclipse.jgit.lib.BatchRefUpdate; |
| import org.eclipse.jgit.lib.Constants; |
| import org.eclipse.jgit.lib.NullProgressMonitor; |
| import org.eclipse.jgit.lib.ObjectId; |
| import org.eclipse.jgit.lib.ObjectInserter; |
| import org.eclipse.jgit.lib.PersonIdent; |
| import org.eclipse.jgit.lib.Ref; |
| import org.eclipse.jgit.lib.RefDatabase; |
| import org.eclipse.jgit.lib.RefUpdate; |
| import org.eclipse.jgit.lib.Repository; |
| import org.eclipse.jgit.revwalk.RevWalk; |
| import org.eclipse.jgit.transport.ReceiveCommand; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.io.IOException; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| |
| @Singleton |
| public class StarredChangesUtil { |
| private static final Logger log = |
| LoggerFactory.getLogger(StarredChangesUtil.class); |
| |
| private final GitRepositoryManager repoManager; |
| private final AllUsersName allUsers; |
| private final NotesMigration migration; |
| private final Provider<ReviewDb> dbProvider; |
| private final PersonIdent serverIdent; |
| |
| @Inject |
| StarredChangesUtil(GitRepositoryManager repoManager, |
| AllUsersName allUsers, |
| NotesMigration migration, |
| Provider<ReviewDb> dbProvider, |
| @GerritPersonIdent PersonIdent serverIdent) { |
| this.repoManager = repoManager; |
| this.allUsers = allUsers; |
| this.migration = migration; |
| this.dbProvider = dbProvider; |
| this.serverIdent = serverIdent; |
| } |
| |
| public void star(Account.Id accountId, Change.Id changeId) |
| throws OrmException { |
| dbProvider.get().starredChanges() |
| .insert(Collections.singleton(new StarredChange( |
| new StarredChange.Key(accountId, changeId)))); |
| if (!migration.writeChanges()) { |
| return; |
| } |
| try (Repository repo = repoManager.openMetadataRepository(allUsers); |
| RevWalk rw = new RevWalk(repo)) { |
| RefUpdate u = repo.updateRef( |
| RefNames.refsStarredChanges(accountId, changeId)); |
| u.setExpectedOldObjectId(ObjectId.zeroId()); |
| u.setNewObjectId(emptyTree(repo)); |
| u.setRefLogIdent(serverIdent); |
| u.setRefLogMessage("Star change " + changeId.get(), false); |
| RefUpdate.Result result = u.update(rw); |
| switch (result) { |
| case NEW: |
| return; |
| case FAST_FORWARD: |
| case FORCED: |
| case IO_FAILURE: |
| case LOCK_FAILURE: |
| case NOT_ATTEMPTED: |
| case NO_CHANGE: |
| case REJECTED: |
| case REJECTED_CURRENT_BRANCH: |
| case RENAMED: |
| default: |
| throw new OrmException( |
| String.format("Star change %d for account %d failed: %s", |
| changeId.get(), accountId.get(), result.name())); |
| } |
| } catch (IOException e) { |
| throw new OrmException( |
| String.format("Star change %d for account %d failed", |
| changeId.get(), accountId.get()), e); |
| } |
| } |
| |
| private static ObjectId emptyTree(Repository repo) throws IOException { |
| try (ObjectInserter oi = repo.newObjectInserter()) { |
| ObjectId id = oi.insert(Constants.OBJ_TREE, new byte[] {}); |
| oi.flush(); |
| return id; |
| } |
| } |
| |
| public void unstar(Account.Id accountId, Change.Id changeId) |
| throws OrmException { |
| dbProvider.get().starredChanges() |
| .delete(Collections.singleton(new StarredChange( |
| new StarredChange.Key(accountId, changeId)))); |
| if (!migration.writeChanges()) { |
| return; |
| } |
| try (Repository repo = repoManager.openMetadataRepository(allUsers); |
| RevWalk rw = new RevWalk(repo)) { |
| RefUpdate u = repo.updateRef( |
| RefNames.refsStarredChanges(accountId, changeId)); |
| u.setForceUpdate(true); |
| u.setRefLogIdent(serverIdent); |
| u.setRefLogMessage("Unstar change " + changeId.get(), true); |
| RefUpdate.Result result = u.delete(); |
| switch (result) { |
| case FORCED: |
| return; |
| case FAST_FORWARD: |
| case IO_FAILURE: |
| case LOCK_FAILURE: |
| case NEW: |
| case NOT_ATTEMPTED: |
| case NO_CHANGE: |
| case REJECTED: |
| case REJECTED_CURRENT_BRANCH: |
| case RENAMED: |
| default: |
| throw new OrmException( |
| String.format("Unstar change %d for account %d failed: %s", |
| changeId.get(), accountId.get(), result.name())); |
| } |
| } catch (IOException e) { |
| throw new OrmException( |
| String.format("Unstar change %d for account %d failed", |
| changeId.get(), accountId.get()), e); |
| } |
| } |
| |
| public void unstarAll(Change.Id changeId) throws OrmException { |
| dbProvider.get().starredChanges().delete( |
| dbProvider.get().starredChanges().byChange(changeId)); |
| if (!migration.writeChanges()) { |
| return; |
| } |
| try (Repository repo = repoManager.openMetadataRepository(allUsers); |
| RevWalk rw = new RevWalk(repo)) { |
| BatchRefUpdate batchUpdate = repo.getRefDatabase().newBatchUpdate(); |
| batchUpdate.setAllowNonFastForwards(true); |
| batchUpdate.setRefLogIdent(serverIdent); |
| batchUpdate.setRefLogMessage("Unstar change " + changeId.get(), true); |
| for (Account.Id accountId : byChange(changeId)) { |
| String refName = RefNames.refsStarredChanges(accountId, changeId); |
| Ref ref = repo.getRefDatabase().getRef(refName); |
| batchUpdate.addCommand(new ReceiveCommand(ref.getObjectId(), |
| ObjectId.zeroId(), refName)); |
| } |
| batchUpdate.execute(rw, NullProgressMonitor.INSTANCE); |
| for (ReceiveCommand command : batchUpdate.getCommands()) { |
| if (command.getResult() != ReceiveCommand.Result.OK) { |
| throw new IOException(String.format( |
| "Unstar change %d failed, ref %s could not be deleted: %s", |
| changeId.get(), command.getRefName(), command.getResult())); |
| } |
| } |
| } catch (IOException e) { |
| throw new OrmException( |
| String.format("Unstar change %d failed", changeId.get()), e); |
| } |
| } |
| |
| public Iterable<Account.Id> byChange(final Change.Id changeId) |
| throws OrmException { |
| if (!migration.readChanges()) { |
| return FluentIterable |
| .from(dbProvider.get().starredChanges().byChange(changeId)) |
| .transform(new Function<StarredChange, Account.Id>() { |
| @Override |
| public Account.Id apply(StarredChange in) { |
| return in.getAccountId(); |
| } |
| }); |
| } |
| return FluentIterable.from(getRefNames(RefNames.REFS_STARRED_CHANGES)) |
| .filter(new Predicate<String>() { |
| @Override |
| public boolean apply(String refPart) { |
| return refPart.endsWith("/" + changeId.get()); |
| } |
| }) |
| .transform(new Function<String, Account.Id>() { |
| @Override |
| public Account.Id apply(String refPart) { |
| return Account.Id.fromRefPart(refPart); |
| } |
| }); |
| } |
| |
| public ResultSet<Change.Id> query(Account.Id accountId) { |
| try { |
| if (!migration.readChanges()) { |
| return new ChangeIdResultSet( |
| dbProvider.get().starredChanges().byAccount(accountId)); |
| } |
| |
| return new ListResultSet<>(FluentIterable |
| .from(getRefNames(RefNames.refsStarredChangesPrefix(accountId))) |
| .transform(new Function<String, Change.Id>() { |
| @Override |
| public Change.Id apply(String changeId) { |
| return Change.Id.parse(changeId); |
| } |
| }).toList()); |
| } catch (OrmException | RuntimeException e) { |
| log.warn(String.format("Cannot query starred changes for account %d", |
| accountId.get()), e); |
| List<Change.Id> empty = Collections.emptyList(); |
| return new ListResultSet<>(empty); |
| } |
| } |
| |
| private Set<String> getRefNames(String prefix) throws OrmException { |
| try (Repository repo = repoManager.openMetadataRepository(allUsers)) { |
| RefDatabase refDb = repo.getRefDatabase(); |
| return refDb.getRefs(prefix).keySet(); |
| } catch (IOException e) { |
| throw new OrmException(e); |
| } |
| } |
| |
| private static class ChangeIdResultSet implements ResultSet<Change.Id> { |
| private static final Function<StarredChange, Change.Id> |
| STARRED_CHANGE_TO_CHANGE_ID = |
| new Function<StarredChange, Change.Id>() { |
| @Override |
| public Change.Id apply(StarredChange starredChange) { |
| return starredChange.getChangeId(); |
| } |
| }; |
| |
| private final ResultSet<StarredChange> starredChangesResultSet; |
| |
| ChangeIdResultSet(ResultSet<StarredChange> starredChangesResultSet) { |
| this.starredChangesResultSet = starredChangesResultSet; |
| } |
| |
| @Override |
| public Iterator<Change.Id> iterator() { |
| return Iterators.transform(starredChangesResultSet.iterator(), |
| STARRED_CHANGE_TO_CHANGE_ID); |
| } |
| |
| @Override |
| public List<Change.Id> toList() { |
| return Lists.transform(starredChangesResultSet.toList(), |
| STARRED_CHANGE_TO_CHANGE_ID); |
| } |
| |
| @Override |
| public void close() { |
| starredChangesResultSet.close(); |
| } |
| } |
| } |