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

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

There is a newer version: 3.10.0-rc4
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.project;

import static java.lang.String.format;
import static java.util.stream.Collectors.toList;
import static org.eclipse.jgit.lib.Constants.R_TAGS;
import static org.eclipse.jgit.transport.ReceiveCommand.Type.DELETE;

import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.reviewdb.client.Branch;
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.query.change.InternalChangeQuery;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jgit.errors.LockFailedException;
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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DeleteRef {
  private static final Logger log = LoggerFactory.getLogger(DeleteRef.class);

  private static final int MAX_LOCK_FAILURE_CALLS = 10;
  private static final long SLEEP_ON_LOCK_FAILURE_MS = 15;

  private final Provider identifiedUser;
  private final GitRepositoryManager repoManager;
  private final GitReferenceUpdated referenceUpdated;
  private final RefValidationHelper refDeletionValidator;
  private final Provider queryProvider;
  private final ProjectResource resource;
  private final List refsToDelete;
  private String prefix;

  public interface Factory {
    DeleteRef create(ProjectResource r);
  }

  @AssistedInject
  DeleteRef(
      Provider identifiedUser,
      GitRepositoryManager repoManager,
      GitReferenceUpdated referenceUpdated,
      RefValidationHelper.Factory refDeletionValidatorFactory,
      Provider queryProvider,
      @Assisted ProjectResource resource) {
    this.identifiedUser = identifiedUser;
    this.repoManager = repoManager;
    this.referenceUpdated = referenceUpdated;
    this.refDeletionValidator = refDeletionValidatorFactory.create(DELETE);
    this.queryProvider = queryProvider;
    this.resource = resource;
    this.refsToDelete = new ArrayList<>();
  }

  public DeleteRef ref(String ref) {
    this.refsToDelete.add(ref);
    return this;
  }

  public DeleteRef refs(List refs) {
    this.refsToDelete.addAll(refs);
    return this;
  }

  public DeleteRef prefix(String prefix) {
    this.prefix = prefix;
    return this;
  }

  public void delete() throws OrmException, IOException, ResourceConflictException, AuthException {
    if (!refsToDelete.isEmpty()) {
      try (Repository r = repoManager.openRepository(resource.getNameKey())) {
        if (refsToDelete.size() == 1) {
          deleteSingleRef(r);
        } else {
          deleteMultipleRefs(r);
        }
      }
    }
  }

  private void deleteSingleRef(Repository r)
      throws IOException, ResourceConflictException, AuthException {
    String ref = refsToDelete.get(0);
    if (prefix != null && !ref.startsWith(prefix)) {
      ref = prefix + ref;
    }

    if (!resource.getControl().controlForRef(ref).canDelete()) {
      throw new AuthException("delete not permitted for " + ref);
    }

    RefUpdate.Result result;
    RefUpdate u = r.updateRef(ref);
    u.setExpectedOldObjectId(r.exactRef(ref).getObjectId());
    u.setNewObjectId(ObjectId.zeroId());
    u.setForceUpdate(true);
    refDeletionValidator.validateRefOperation(resource.getName(), identifiedUser.get(), u);
    int remainingLockFailureCalls = MAX_LOCK_FAILURE_CALLS;
    for (; ; ) {
      try {
        result = u.delete();
      } catch (LockFailedException e) {
        result = RefUpdate.Result.LOCK_FAILURE;
      } catch (IOException e) {
        log.error("Cannot delete " + ref, e);
        throw e;
      }
      if (result == RefUpdate.Result.LOCK_FAILURE && --remainingLockFailureCalls > 0) {
        try {
          Thread.sleep(SLEEP_ON_LOCK_FAILURE_MS);
        } catch (InterruptedException ie) {
          // ignore
        }
      } else {
        break;
      }
    }

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

      case REJECTED_CURRENT_BRANCH:
        log.error("Cannot delete " + ref + ": " + result.name());
        throw new ResourceConflictException("cannot delete current branch");

      case IO_FAILURE:
      case LOCK_FAILURE:
      case NOT_ATTEMPTED:
      case REJECTED:
      case RENAMED:
      default:
        log.error("Cannot delete " + ref + ": " + result.name());
        throw new ResourceConflictException("cannot delete: " + result.name());
    }
  }

  private void deleteMultipleRefs(Repository r)
      throws OrmException, IOException, ResourceConflictException {
    BatchRefUpdate batchUpdate = r.getRefDatabase().newBatchUpdate();
    List refs =
        prefix == null
            ? refsToDelete
            : refsToDelete
                .stream()
                .map(ref -> ref.startsWith(prefix) ? ref : prefix + ref)
                .collect(toList());
    for (String ref : refs) {
      batchUpdate.addCommand(createDeleteCommand(resource, r, ref));
    }
    try (RevWalk rw = new RevWalk(r)) {
      batchUpdate.execute(rw, NullProgressMonitor.INSTANCE);
    }
    StringBuilder errorMessages = new StringBuilder();
    for (ReceiveCommand command : batchUpdate.getCommands()) {
      if (command.getResult() == Result.OK) {
        postDeletion(resource, command);
      } else {
        appendAndLogErrorMessage(errorMessages, command);
      }
    }
    if (errorMessages.length() > 0) {
      throw new ResourceConflictException(errorMessages.toString());
    }
  }

  private ReceiveCommand createDeleteCommand(ProjectResource project, Repository r, String refName)
      throws OrmException, IOException, ResourceConflictException {
    Ref ref = r.getRefDatabase().getRef(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 (!project.getControl().controlForRef(refName).canDelete()) {
      command.setResult(
          Result.REJECTED_OTHER_REASON,
          "it doesn't exist or you do not have permission to delete it");
    }

    if (!refName.startsWith(R_TAGS)) {
      Branch.NameKey branchKey = new Branch.NameKey(project.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(r.exactRef(refName).getObjectId());
    u.setNewObjectId(ObjectId.zeroId());
    refDeletionValidator.validateRefOperation(project.getName(), identifiedUser.get(), u);
    return command;
  }

  private void appendAndLogErrorMessage(StringBuilder errorMessages, ReceiveCommand cmd) {
    String msg = null;
    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;
    }
    log.error(msg);
    errorMessages.append(msg);
    errorMessages.append("\n");
  }

  private void postDeletion(ProjectResource project, ReceiveCommand cmd) {
    referenceUpdated.fire(project.getNameKey(), cmd, identifiedUser.get().getAccount());
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy