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

com.google.gerrit.server.restapi.project.DeleteRef Maven / Gradle / Ivy

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

import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.gerrit.entities.RefNames.isConfigRef;
import static com.google.gerrit.server.update.context.RefUpdateContext.RefUpdateType.BRANCH_MODIFICATION;
import static java.lang.String.format;
import static org.eclipse.jgit.lib.Constants.R_REFS;
import static org.eclipse.jgit.lib.Constants.R_TAGS;
import static org.eclipse.jgit.transport.ReceiveCommand.Type.DELETE;

import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.git.LockFailureException;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.RefPermission;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.project.RefValidationHelper;
import com.google.gerrit.server.query.change.InternalChangeQuery;
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 org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
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.eclipse.jgit.transport.ReceiveCommand.Result;

@Singleton
public class DeleteRef {
  private static final FluentLogger logger = FluentLogger.forEnclosingClass();

  private final Provider identifiedUser;
  private final PermissionBackend permissionBackend;
  private final GitRepositoryManager repoManager;
  private final GitReferenceUpdated referenceUpdated;
  private final RefValidationHelper refDeletionValidator;
  private final Provider queryProvider;

  @Inject
  DeleteRef(
      Provider identifiedUser,
      PermissionBackend permissionBackend,
      GitRepositoryManager repoManager,
      GitReferenceUpdated referenceUpdated,
      RefValidationHelper.Factory refDeletionValidatorFactory,
      Provider queryProvider) {
    this.identifiedUser = identifiedUser;
    this.permissionBackend = permissionBackend;
    this.repoManager = repoManager;
    this.referenceUpdated = referenceUpdated;
    this.refDeletionValidator = refDeletionValidatorFactory.create(DELETE);
    this.queryProvider = queryProvider;
  }

  /**
   * Deletes a single ref from the repository.
   *
   * @param projectState the {@code ProjectState} of the project containing the target ref.
   * @param ref the ref to be deleted.
   */
  public void deleteSingleRef(ProjectState projectState, String ref)
      throws IOException, ResourceConflictException, AuthException, PermissionBackendException {
    deleteSingleRef(projectState, ref, null);
  }

  /**
   * Deletes a single ref from the repository.
   *
   * @param projectState the {@code ProjectState} of the project containing the target ref.
   * @param ref the ref to be deleted.
   * @param prefix the prefix of the ref.
   */
  public void deleteSingleRef(ProjectState projectState, String ref, @Nullable String prefix)
      throws IOException, ResourceConflictException, AuthException, PermissionBackendException {
    try (RefUpdateContext ctx = RefUpdateContext.open(BRANCH_MODIFICATION)) {
      if (prefix != null && !ref.startsWith(R_REFS)) {
        ref = prefix + ref;
      }

      projectState.checkStatePermitsWrite();
      permissionBackend
          .currentUser()
          .project(projectState.getNameKey())
          .ref(ref)
          .check(RefPermission.DELETE);

      try (Repository repository = repoManager.openRepository(projectState.getNameKey())) {
        Ref refObj = repository.exactRef(ref);
        if (refObj == null) {
          throw new ResourceConflictException(String.format("ref %s doesn't exist", ref));
        }
        RefUpdate u = repository.updateRef(ref);
        u.setExpectedOldObjectId(refObj.getObjectId());
        u.setNewObjectId(ObjectId.zeroId());
        u.setForceUpdate(true);
        refDeletionValidator.validateRefOperation(
            projectState.getName(),
            identifiedUser.get(),
            u,
            /* pushOptions */ ImmutableListMultimap.of());
        RefUpdate.Result result = u.delete();

        switch (result) {
          case NEW:
          case NO_CHANGE:
          case FAST_FORWARD:
          case FORCED:
            referenceUpdated.fire(
                projectState.getNameKey(),
                u,
                ReceiveCommand.Type.DELETE,
                identifiedUser.get().state());
            break;

          case REJECTED_CURRENT_BRANCH:
            logger.atFine().log("Cannot delete current branch %s: %s", ref, result.name());
            throw new ResourceConflictException("cannot delete current branch");

          case LOCK_FAILURE:
            throw new LockFailureException(String.format("Cannot delete %s", ref), u);
          case IO_FAILURE:
          case NOT_ATTEMPTED:
          case REJECTED:
          case RENAMED:
          case REJECTED_MISSING_OBJECT:
          case REJECTED_OTHER_REASON:
          default:
            throw new StorageException(String.format("Cannot delete %s: %s", ref, result.name()));
        }
      }
    }
  }

  /**
   * Deletes a set of refs from the repository.
   *
   * @param projectState the {@code ProjectState} of the project whose refs are to be deleted.
   * @param refsToDelete the refs to be deleted.
   * @param prefix the prefix to add to abbreviated refs, eg. "refs/heads/".
   */
  public void deleteMultipleRefs(
      ProjectState projectState, ImmutableSet refsToDelete, String prefix)
      throws IOException, ResourceConflictException, PermissionBackendException, AuthException {
    if (refsToDelete.isEmpty()) {
      return;
    }

    if (refsToDelete.size() == 1) {
      deleteSingleRef(projectState, Iterables.getOnlyElement(refsToDelete), prefix);
      return;
    }

    try (Repository repository = repoManager.openRepository(projectState.getNameKey())) {
      BatchRefUpdate batchUpdate = repository.getRefDatabase().newBatchUpdate();
      batchUpdate.setAtomic(false);
      ImmutableSet refs =
          prefix == null
              ? refsToDelete
              : refsToDelete.stream()
                  .map(ref -> ref.startsWith(R_REFS) ? ref : prefix + ref)
                  .collect(toImmutableSet());
      for (String ref : refs) {
        batchUpdate.addCommand(createDeleteCommand(projectState, repository, ref));
      }
      try (RevWalk rw = new RevWalk(repository)) {
        batchUpdate.execute(rw, NullProgressMonitor.INSTANCE);
      }
      StringBuilder errorMessages = new StringBuilder();
      for (ReceiveCommand command : batchUpdate.getCommands()) {
        if (command.getResult() == Result.OK) {
          postDeletion(projectState.getNameKey(), command);
        } else {
          appendAndLogErrorMessage(errorMessages, command);
        }
      }
      if (errorMessages.length() > 0) {
        throw new ResourceConflictException(errorMessages.toString());
      }
    }
  }

  private ReceiveCommand createDeleteCommand(
      ProjectState projectState, Repository r, String refName)
      throws IOException, ResourceConflictException, PermissionBackendException {
    Ref ref = r.getRefDatabase().exactRef(refName);
    ReceiveCommand command;
    if (ref == null) {
      command = new ReceiveCommand(ObjectId.zeroId(), ObjectId.zeroId(), refName);
      command.setResult(
          Result.REJECTED_OTHER_REASON,
          "it doesn't exist or you do not have permission to delete it");
      return command;
    }
    command = new ReceiveCommand(ref.getObjectId(), ObjectId.zeroId(), ref.getName());

    if (isConfigRef(refName)) {
      // Never allow to delete the meta config branch.
      command.setResult(Result.REJECTED_OTHER_REASON, "not allowed to delete branch " + refName);
    } else {
      try {
        permissionBackend
            .currentUser()
            .project(projectState.getNameKey())
            .ref(refName)
            .check(RefPermission.DELETE);
      } catch (AuthException denied) {
        command.setResult(
            Result.REJECTED_OTHER_REASON,
            "it doesn't exist or you do not have permission to delete it");
      }
    }

    if (!projectState.statePermitsWrite()) {
      command.setResult(Result.REJECTED_OTHER_REASON, "project state does not permit write");
    }

    if (!refName.startsWith(R_TAGS)) {
      BranchNameKey branchKey = BranchNameKey.create(projectState.getNameKey(), ref.getName());
      if (!queryProvider.get().setLimit(1).byBranchOpen(branchKey).isEmpty()) {
        command.setResult(Result.REJECTED_OTHER_REASON, "it has open changes");
      }
    }

    RefUpdate u = r.updateRef(refName);
    u.setForceUpdate(true);
    u.setExpectedOldObjectId(ref.getObjectId());
    u.setNewObjectId(ObjectId.zeroId());
    refDeletionValidator.validateRefOperation(
        projectState.getName(),
        identifiedUser.get(),
        u,
        /* pushOptions */ ImmutableListMultimap.of());
    return command;
  }

  private void appendAndLogErrorMessage(StringBuilder errorMessages, ReceiveCommand cmd) {
    String msg;
    switch (cmd.getResult()) {
      case REJECTED_CURRENT_BRANCH:
        msg = format("Cannot delete %s: it is the current branch", cmd.getRefName());
        break;
      case REJECTED_OTHER_REASON:
        msg = format("Cannot delete %s: %s", cmd.getRefName(), cmd.getMessage());
        break;
      case LOCK_FAILURE:
      case NOT_ATTEMPTED:
      case OK:
      case REJECTED_MISSING_OBJECT:
      case REJECTED_NOCREATE:
      case REJECTED_NODELETE:
      case REJECTED_NONFASTFORWARD:
      default:
        msg = format("Cannot delete %s: %s", cmd.getRefName(), cmd.getResult());
        break;
    }
    logger.atSevere().log("%s", msg);
    errorMessages.append(msg);
    errorMessages.append("\n");
  }

  private void postDeletion(Project.NameKey project, ReceiveCommand cmd) {
    referenceUpdated.fire(project, cmd, identifiedUser.get().state());
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy