All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gerrit.server.notedb.StarredChangesUtilNoteDbImpl Maven / Gradle / Ivy

There is a newer version: 3.11.0-rc3
Show newest version
// 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.notedb;

import static com.google.gerrit.server.update.context.RefUpdateContext.RefUpdateType.CHANGE_MODIFICATION;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.toSet;

import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import com.google.common.primitives.Ints;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.git.GitUpdateFailureException;
import com.google.gerrit.git.LockFailureException;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.StarredChangesUtil;
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.logging.Metadata;
import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.gerrit.server.update.context.RefUpdateContext;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
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;

@Singleton
public class StarredChangesUtilNoteDbImpl implements StarredChangesUtil {
  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
  private static final String DEFAULT_STAR_LABEL = "star";

  private final GitRepositoryManager repoManager;
  private final GitReferenceUpdated gitRefUpdated;
  private final AllUsersName allUsers;
  private final Provider serverIdent;

  @Inject
  StarredChangesUtilNoteDbImpl(
      GitRepositoryManager repoManager,
      GitReferenceUpdated gitRefUpdated,
      AllUsersName allUsers,
      @GerritPersonIdent Provider serverIdent) {
    this.repoManager = repoManager;
    this.gitRefUpdated = gitRefUpdated;
    this.allUsers = allUsers;
    this.serverIdent = serverIdent;
  }

  @Override
  public boolean isStarred(Account.Id accountId, Change.Id virtualId) {
    try (Repository repo = repoManager.openRepository(allUsers)) {
      return getStarRef(repo, RefNames.refsStarredChanges(virtualId, accountId)).isPresent();
    } catch (IOException e) {
      throw new StorageException(
          String.format(
              "Reading stars from change %d for account %d failed",
              virtualId.get(), accountId.get()),
          e);
    }
  }

  @Override
  public void star(Account.Id accountId, Change.Id virtualId) {
    updateStar(accountId, virtualId, true);
  }

  @Override
  public void unstar(Account.Id accountId, Change.Id virtualId) {
    updateStar(accountId, virtualId, false);
  }

  private void updateStar(Account.Id accountId, Change.Id virtualId, boolean shouldAdd) {
    try (Repository repo = repoManager.openRepository(allUsers)) {
      String refName = RefNames.refsStarredChanges(virtualId, accountId);
      if (shouldAdd) {
        addRef(repo, refName, null);
      } else {
        Optional ref = getStarRef(repo, refName);
        if (ref.isPresent()) {
          deleteRef(repo, refName, ref.get().getObjectId());
        }
      }
    } catch (IOException e) {
      throw new StorageException(
          String.format("Star change %d for account %d failed", virtualId.get(), accountId.get()),
          e);
    }
  }

  @Override
  public Set areStarred(
      Repository allUsersRepo, List virtualIds, Account.Id caller) {
    List starRefs =
        virtualIds.stream()
            .map(c -> RefNames.refsStarredChanges(c, caller))
            .collect(Collectors.toList());
    try {
      return allUsersRepo.getRefDatabase().exactRef(starRefs.toArray(new String[0])).keySet()
          .stream()
          .map(r -> Change.Id.fromAllUsersRef(r))
          .collect(Collectors.toSet());
    } catch (IOException e) {
      logger.atWarning().withCause(e).log(
          "Failed getting starred changes for account %d within changes: %s",
          caller.get(), Joiner.on(", ").join(virtualIds));
      return ImmutableSet.of();
    }
  }

  @Override
  public void unstarAllForChangeDeletion(Change.Id virtualId) throws IOException {
    try (Repository repo = repoManager.openRepository(allUsers);
        RevWalk rw = new RevWalk(repo)) {
      BatchRefUpdate batchUpdate = repo.getRefDatabase().newBatchUpdate();
      batchUpdate.setAllowNonFastForwards(true);
      batchUpdate.setRefLogIdent(serverIdent.get());
      batchUpdate.setRefLogMessage("Unstar change " + virtualId.get(), true);
      for (Account.Id accountId : getStars(repo, virtualId)) {
        String refName = RefNames.refsStarredChanges(virtualId, accountId);
        Ref ref = repo.getRefDatabase().exactRef(refName);
        if (ref != null) {
          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) {
          String message =
              String.format(
                  "Unstar change %d failed, ref %s could not be deleted: %s",
                  virtualId.get(), command.getRefName(), command.getResult());
          if (command.getResult() == ReceiveCommand.Result.LOCK_FAILURE) {
            throw new LockFailureException(message, batchUpdate);
          }
          throw new GitUpdateFailureException(message, batchUpdate);
        }
      }
    }
  }

  @Override
  public ImmutableMap byChange(Change.Id virtualId) {
    try (Repository repo = repoManager.openRepository(allUsers)) {
      ImmutableMap.Builder builder = ImmutableMap.builder();
      for (Account.Id accountId : getStars(repo, virtualId)) {
        Optional starRef = getStarRef(repo, RefNames.refsStarredChanges(virtualId, accountId));
        if (starRef.isPresent()) {
          builder.put(accountId, starRef.get());
        }
      }
      return builder.build();
    } catch (IOException e) {
      throw new StorageException(
          String.format("Get accounts that starred change %d failed", virtualId.get()), e);
    }
  }

  @Override
  public ImmutableSet byAccountId(Account.Id accountId) {
    return byAccountId(accountId, true);
  }

  @Override
  public ImmutableSet byAccountId(Account.Id accountId, boolean skipInvalidChanges) {
    try (Repository repo = repoManager.openRepository(allUsers)) {
      ImmutableSet.Builder builder = ImmutableSet.builder();
      for (Ref ref : repo.getRefDatabase().getRefsByPrefix(RefNames.REFS_STARRED_CHANGES)) {
        Account.Id currentAccountId = Account.Id.fromRef(ref.getName());
        // Skip all refs that don't correspond with accountId.
        if (currentAccountId == null || !currentAccountId.equals(accountId)) {
          continue;
        }

        // Skip invalid change ids.
        Change.Id changeId = Change.Id.fromAllUsersRef(ref.getName());
        if (skipInvalidChanges && changeId == null) {
          continue;
        }
        builder.add(changeId);
      }
      return builder.build();
    } catch (IOException e) {
      throw new StorageException(
          String.format("Get starred changes for account %d failed", accountId.get()), e);
    }
  }

  private static Set getStars(Repository allUsers, Change.Id virtualId)
      throws IOException {
    String prefix = RefNames.refsStarredChangesPrefix(virtualId);
    RefDatabase refDb = allUsers.getRefDatabase();
    return refDb.getRefsByPrefix(prefix).stream()
        .map(r -> r.getName().substring(prefix.length()))
        .map(refPart -> Ints.tryParse(refPart))
        .filter(Objects::nonNull)
        .map(id -> Account.id(id))
        .collect(toSet());
  }

  private static Optional getStarRef(Repository repo, @Nullable String refName)
      throws IOException {
    if (refName == null) {
      return Optional.empty();
    }
    Ref ref = repo.exactRef(refName);
    return Optional.ofNullable(ref);
  }

  private static ObjectId writeStarredRefContent(Repository repo) throws IOException {
    try (ObjectInserter oi = repo.newObjectInserter()) {
      ObjectId id = oi.insert(Constants.OBJ_BLOB, DEFAULT_STAR_LABEL.getBytes(UTF_8));
      oi.flush();
      return id;
    }
  }

  private void addRef(Repository repo, String refName, ObjectId oldObjectId) throws IOException {
    try (TraceTimer traceTimer =
            TraceContext.newTimer(
                "Add star ref",
                Metadata.builder().noteDbRefName(refName).resourceCount(1).build());
        RevWalk rw = new RevWalk(repo)) {
      RefUpdate u = repo.updateRef(refName);
      u.setExpectedOldObjectId(oldObjectId);
      u.setForceUpdate(true);
      u.setNewObjectId(writeStarredRefContent(repo));
      u.setRefLogIdent(serverIdent.get());
      u.setRefLogMessage("Add star ref", true);
      try (RefUpdateContext ctx = RefUpdateContext.open(CHANGE_MODIFICATION)) {
        RefUpdate.Result result = u.update(rw);
        switch (result) {
          case NEW:
          case FORCED:
          case NO_CHANGE:
          case FAST_FORWARD:
            gitRefUpdated.fire(allUsers, u, null);
            return;
          case LOCK_FAILURE:
            throw new LockFailureException(
                String.format("Add star ref on ref %s failed", refName), u);
          case IO_FAILURE:
          case NOT_ATTEMPTED:
          case REJECTED:
          case REJECTED_CURRENT_BRANCH:
          case RENAMED:
          case REJECTED_MISSING_OBJECT:
          case REJECTED_OTHER_REASON:
          default:
            throw new StorageException(
                String.format("Add star ref on ref %s failed: %s", refName, result.name()));
        }
      }
    }
  }

  private void deleteRef(Repository repo, String refName, ObjectId oldObjectId) throws IOException {
    if (ObjectId.zeroId().equals(oldObjectId)) {
      // ref doesn't exist
      return;
    }

    try (TraceTimer traceTimer =
        TraceContext.newTimer(
            "Delete star ref", Metadata.builder().noteDbRefName(refName).build())) {
      RefUpdate u = repo.updateRef(refName);
      u.setForceUpdate(true);
      u.setExpectedOldObjectId(oldObjectId);
      u.setRefLogIdent(serverIdent.get());
      u.setRefLogMessage("Unstar change", true);
      try (RefUpdateContext ctx = RefUpdateContext.open(CHANGE_MODIFICATION)) {
        RefUpdate.Result result = u.delete();
        switch (result) {
          case FORCED:
            gitRefUpdated.fire(allUsers, u, null);
            return;
          case LOCK_FAILURE:
            throw new LockFailureException(String.format("Delete star ref %s failed", refName), u);
          case NEW:
          case NO_CHANGE:
          case FAST_FORWARD:
          case IO_FAILURE:
          case NOT_ATTEMPTED:
          case REJECTED:
          case REJECTED_CURRENT_BRANCH:
          case RENAMED:
          case REJECTED_MISSING_OBJECT:
          case REJECTED_OTHER_REASON:
          default:
            throw new StorageException(
                String.format("Delete star ref %s failed: %s", refName, result.name()));
        }
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy