Merge changes I6a7f48e4,I3d0aacb0,I0b43eaef,I909a779b
* changes: DeleteRef: make sure permission and project state is checked DeleteRef: expose separate methods for deleting single/multi refs DeleteRef: remove its factory DeleteRef: move required input to method parameters
This commit is contained in:
		@@ -23,9 +23,7 @@ import com.google.gerrit.extensions.restapi.ResourceConflictException;
 | 
				
			|||||||
import com.google.gerrit.extensions.restapi.Response;
 | 
					import com.google.gerrit.extensions.restapi.Response;
 | 
				
			||||||
import com.google.gerrit.extensions.restapi.RestApiException;
 | 
					import com.google.gerrit.extensions.restapi.RestApiException;
 | 
				
			||||||
import com.google.gerrit.extensions.restapi.RestModifyView;
 | 
					import com.google.gerrit.extensions.restapi.RestModifyView;
 | 
				
			||||||
import com.google.gerrit.server.permissions.PermissionBackend;
 | 
					 | 
				
			||||||
import com.google.gerrit.server.permissions.PermissionBackendException;
 | 
					import com.google.gerrit.server.permissions.PermissionBackendException;
 | 
				
			||||||
import com.google.gerrit.server.permissions.RefPermission;
 | 
					 | 
				
			||||||
import com.google.gerrit.server.project.BranchResource;
 | 
					import com.google.gerrit.server.project.BranchResource;
 | 
				
			||||||
import com.google.gerrit.server.query.change.InternalChangeQuery;
 | 
					import com.google.gerrit.server.query.change.InternalChangeQuery;
 | 
				
			||||||
import com.google.gwtorm.server.OrmException;
 | 
					import com.google.gwtorm.server.OrmException;
 | 
				
			||||||
@@ -38,17 +36,12 @@ import java.io.IOException;
 | 
				
			|||||||
public class DeleteBranch implements RestModifyView<BranchResource, Input> {
 | 
					public class DeleteBranch implements RestModifyView<BranchResource, Input> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private final Provider<InternalChangeQuery> queryProvider;
 | 
					  private final Provider<InternalChangeQuery> queryProvider;
 | 
				
			||||||
  private final DeleteRef.Factory deleteRefFactory;
 | 
					  private final DeleteRef deleteRef;
 | 
				
			||||||
  private final PermissionBackend permissionBackend;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Inject
 | 
					  @Inject
 | 
				
			||||||
  DeleteBranch(
 | 
					  DeleteBranch(Provider<InternalChangeQuery> queryProvider, DeleteRef deleteRef) {
 | 
				
			||||||
      Provider<InternalChangeQuery> queryProvider,
 | 
					 | 
				
			||||||
      DeleteRef.Factory deleteRefFactory,
 | 
					 | 
				
			||||||
      PermissionBackend permissionBackend) {
 | 
					 | 
				
			||||||
    this.queryProvider = queryProvider;
 | 
					    this.queryProvider = queryProvider;
 | 
				
			||||||
    this.deleteRefFactory = deleteRefFactory;
 | 
					    this.deleteRef = deleteRef;
 | 
				
			||||||
    this.permissionBackend = permissionBackend;
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Override
 | 
					  @Override
 | 
				
			||||||
@@ -60,14 +53,11 @@ public class DeleteBranch implements RestModifyView<BranchResource, Input> {
 | 
				
			|||||||
          "not allowed to delete branch " + rsrc.getBranchKey().get());
 | 
					          "not allowed to delete branch " + rsrc.getBranchKey().get());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    permissionBackend.currentUser().ref(rsrc.getBranchKey()).check(RefPermission.DELETE);
 | 
					 | 
				
			||||||
    rsrc.getProjectState().checkStatePermitsWrite();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!queryProvider.get().setLimit(1).byBranchOpen(rsrc.getBranchKey()).isEmpty()) {
 | 
					    if (!queryProvider.get().setLimit(1).byBranchOpen(rsrc.getBranchKey()).isEmpty()) {
 | 
				
			||||||
      throw new ResourceConflictException("branch " + rsrc.getBranchKey() + " has open changes");
 | 
					      throw new ResourceConflictException("branch " + rsrc.getBranchKey() + " has open changes");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    deleteRefFactory.create(rsrc).ref(rsrc.getRef()).prefix(R_HEADS).delete();
 | 
					    deleteRef.deleteSingleRef(rsrc.getProjectState(), rsrc.getRef(), R_HEADS);
 | 
				
			||||||
    return Response.none();
 | 
					    return Response.none();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,6 +16,7 @@ package com.google.gerrit.server.restapi.project;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import static org.eclipse.jgit.lib.Constants.R_HEADS;
 | 
					import static org.eclipse.jgit.lib.Constants.R_HEADS;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.google.common.collect.ImmutableSet;
 | 
				
			||||||
import com.google.gerrit.extensions.api.projects.DeleteBranchesInput;
 | 
					import com.google.gerrit.extensions.api.projects.DeleteBranchesInput;
 | 
				
			||||||
import com.google.gerrit.extensions.restapi.BadRequestException;
 | 
					import com.google.gerrit.extensions.restapi.BadRequestException;
 | 
				
			||||||
import com.google.gerrit.extensions.restapi.Response;
 | 
					import com.google.gerrit.extensions.restapi.Response;
 | 
				
			||||||
@@ -30,11 +31,11 @@ import java.io.IOException;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@Singleton
 | 
					@Singleton
 | 
				
			||||||
public class DeleteBranches implements RestModifyView<ProjectResource, DeleteBranchesInput> {
 | 
					public class DeleteBranches implements RestModifyView<ProjectResource, DeleteBranchesInput> {
 | 
				
			||||||
  private final DeleteRef.Factory deleteRefFactory;
 | 
					  private final DeleteRef deleteRef;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Inject
 | 
					  @Inject
 | 
				
			||||||
  DeleteBranches(DeleteRef.Factory deleteRefFactory) {
 | 
					  DeleteBranches(DeleteRef deleteRef) {
 | 
				
			||||||
    this.deleteRefFactory = deleteRefFactory;
 | 
					    this.deleteRef = deleteRef;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Override
 | 
					  @Override
 | 
				
			||||||
@@ -43,7 +44,8 @@ public class DeleteBranches implements RestModifyView<ProjectResource, DeleteBra
 | 
				
			|||||||
    if (input == null || input.branches == null || input.branches.isEmpty()) {
 | 
					    if (input == null || input.branches == null || input.branches.isEmpty()) {
 | 
				
			||||||
      throw new BadRequestException("branches must be specified");
 | 
					      throw new BadRequestException("branches must be specified");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    deleteRefFactory.create(project).refs(input.branches).prefix(R_HEADS).delete();
 | 
					    deleteRef.deleteMultipleRefs(
 | 
				
			||||||
 | 
					        project.getProjectState(), ImmutableSet.copyOf(input.branches), R_HEADS);
 | 
				
			||||||
    return Response.none();
 | 
					    return Response.none();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,33 +14,35 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
package com.google.gerrit.server.restapi.project;
 | 
					package com.google.gerrit.server.restapi.project;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static com.google.common.collect.ImmutableSet.toImmutableSet;
 | 
				
			||||||
import static com.google.gerrit.reviewdb.client.RefNames.isConfigRef;
 | 
					import static com.google.gerrit.reviewdb.client.RefNames.isConfigRef;
 | 
				
			||||||
import static java.lang.String.format;
 | 
					import static java.lang.String.format;
 | 
				
			||||||
import static java.util.stream.Collectors.toList;
 | 
					 | 
				
			||||||
import static org.eclipse.jgit.lib.Constants.R_REFS;
 | 
					import static org.eclipse.jgit.lib.Constants.R_REFS;
 | 
				
			||||||
import static org.eclipse.jgit.lib.Constants.R_TAGS;
 | 
					import static org.eclipse.jgit.lib.Constants.R_TAGS;
 | 
				
			||||||
import static org.eclipse.jgit.transport.ReceiveCommand.Type.DELETE;
 | 
					import static org.eclipse.jgit.transport.ReceiveCommand.Type.DELETE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.google.common.collect.ImmutableSet;
 | 
				
			||||||
 | 
					import com.google.common.collect.Iterables;
 | 
				
			||||||
import com.google.common.flogger.FluentLogger;
 | 
					import com.google.common.flogger.FluentLogger;
 | 
				
			||||||
 | 
					import com.google.gerrit.common.Nullable;
 | 
				
			||||||
import com.google.gerrit.extensions.restapi.AuthException;
 | 
					import com.google.gerrit.extensions.restapi.AuthException;
 | 
				
			||||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
 | 
					import com.google.gerrit.extensions.restapi.ResourceConflictException;
 | 
				
			||||||
import com.google.gerrit.reviewdb.client.Branch;
 | 
					import com.google.gerrit.reviewdb.client.Branch;
 | 
				
			||||||
 | 
					import com.google.gerrit.reviewdb.client.Project;
 | 
				
			||||||
import com.google.gerrit.server.IdentifiedUser;
 | 
					import com.google.gerrit.server.IdentifiedUser;
 | 
				
			||||||
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 | 
					import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 | 
				
			||||||
import com.google.gerrit.server.git.GitRepositoryManager;
 | 
					import com.google.gerrit.server.git.GitRepositoryManager;
 | 
				
			||||||
import com.google.gerrit.server.permissions.PermissionBackend;
 | 
					import com.google.gerrit.server.permissions.PermissionBackend;
 | 
				
			||||||
import com.google.gerrit.server.permissions.PermissionBackendException;
 | 
					import com.google.gerrit.server.permissions.PermissionBackendException;
 | 
				
			||||||
import com.google.gerrit.server.permissions.RefPermission;
 | 
					import com.google.gerrit.server.permissions.RefPermission;
 | 
				
			||||||
import com.google.gerrit.server.project.ProjectResource;
 | 
					import com.google.gerrit.server.project.ProjectState;
 | 
				
			||||||
import com.google.gerrit.server.project.RefValidationHelper;
 | 
					import com.google.gerrit.server.project.RefValidationHelper;
 | 
				
			||||||
import com.google.gerrit.server.query.change.InternalChangeQuery;
 | 
					import com.google.gerrit.server.query.change.InternalChangeQuery;
 | 
				
			||||||
import com.google.gwtorm.server.OrmException;
 | 
					import com.google.gwtorm.server.OrmException;
 | 
				
			||||||
import com.google.inject.Inject;
 | 
					import com.google.inject.Inject;
 | 
				
			||||||
import com.google.inject.Provider;
 | 
					import com.google.inject.Provider;
 | 
				
			||||||
import com.google.inject.assistedinject.Assisted;
 | 
					import com.google.inject.Singleton;
 | 
				
			||||||
import java.io.IOException;
 | 
					import java.io.IOException;
 | 
				
			||||||
import java.util.ArrayList;
 | 
					 | 
				
			||||||
import java.util.List;
 | 
					 | 
				
			||||||
import org.eclipse.jgit.errors.LockFailedException;
 | 
					import org.eclipse.jgit.errors.LockFailedException;
 | 
				
			||||||
import org.eclipse.jgit.lib.BatchRefUpdate;
 | 
					import org.eclipse.jgit.lib.BatchRefUpdate;
 | 
				
			||||||
import org.eclipse.jgit.lib.NullProgressMonitor;
 | 
					import org.eclipse.jgit.lib.NullProgressMonitor;
 | 
				
			||||||
@@ -52,6 +54,7 @@ import org.eclipse.jgit.revwalk.RevWalk;
 | 
				
			|||||||
import org.eclipse.jgit.transport.ReceiveCommand;
 | 
					import org.eclipse.jgit.transport.ReceiveCommand;
 | 
				
			||||||
import org.eclipse.jgit.transport.ReceiveCommand.Result;
 | 
					import org.eclipse.jgit.transport.ReceiveCommand.Result;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Singleton
 | 
				
			||||||
public class DeleteRef {
 | 
					public class DeleteRef {
 | 
				
			||||||
  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 | 
					  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -64,13 +67,6 @@ public class DeleteRef {
 | 
				
			|||||||
  private final GitReferenceUpdated referenceUpdated;
 | 
					  private final GitReferenceUpdated referenceUpdated;
 | 
				
			||||||
  private final RefValidationHelper refDeletionValidator;
 | 
					  private final RefValidationHelper refDeletionValidator;
 | 
				
			||||||
  private final Provider<InternalChangeQuery> queryProvider;
 | 
					  private final Provider<InternalChangeQuery> queryProvider;
 | 
				
			||||||
  private final ProjectResource resource;
 | 
					 | 
				
			||||||
  private final List<String> refsToDelete;
 | 
					 | 
				
			||||||
  private String prefix;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  public interface Factory {
 | 
					 | 
				
			||||||
    DeleteRef create(ProjectResource r);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Inject
 | 
					  @Inject
 | 
				
			||||||
  DeleteRef(
 | 
					  DeleteRef(
 | 
				
			||||||
@@ -79,135 +75,164 @@ public class DeleteRef {
 | 
				
			|||||||
      GitRepositoryManager repoManager,
 | 
					      GitRepositoryManager repoManager,
 | 
				
			||||||
      GitReferenceUpdated referenceUpdated,
 | 
					      GitReferenceUpdated referenceUpdated,
 | 
				
			||||||
      RefValidationHelper.Factory refDeletionValidatorFactory,
 | 
					      RefValidationHelper.Factory refDeletionValidatorFactory,
 | 
				
			||||||
      Provider<InternalChangeQuery> queryProvider,
 | 
					      Provider<InternalChangeQuery> queryProvider) {
 | 
				
			||||||
      @Assisted ProjectResource resource) {
 | 
					 | 
				
			||||||
    this.identifiedUser = identifiedUser;
 | 
					    this.identifiedUser = identifiedUser;
 | 
				
			||||||
    this.permissionBackend = permissionBackend;
 | 
					    this.permissionBackend = permissionBackend;
 | 
				
			||||||
    this.repoManager = repoManager;
 | 
					    this.repoManager = repoManager;
 | 
				
			||||||
    this.referenceUpdated = referenceUpdated;
 | 
					    this.referenceUpdated = referenceUpdated;
 | 
				
			||||||
    this.refDeletionValidator = refDeletionValidatorFactory.create(DELETE);
 | 
					    this.refDeletionValidator = refDeletionValidatorFactory.create(DELETE);
 | 
				
			||||||
    this.queryProvider = queryProvider;
 | 
					    this.queryProvider = queryProvider;
 | 
				
			||||||
    this.resource = resource;
 | 
					 | 
				
			||||||
    this.refsToDelete = new ArrayList<>();
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public DeleteRef ref(String ref) {
 | 
					  /**
 | 
				
			||||||
    this.refsToDelete.add(ref);
 | 
					   * Deletes a single ref from the repository.
 | 
				
			||||||
    return this;
 | 
					   *
 | 
				
			||||||
 | 
					   * @param projectState the {@code ProjectState} of the project containing the target ref.
 | 
				
			||||||
 | 
					   * @param ref the ref to be deleted.
 | 
				
			||||||
 | 
					   * @throws IOException
 | 
				
			||||||
 | 
					   * @throws ResourceConflictException
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public void deleteSingleRef(ProjectState projectState, String ref)
 | 
				
			||||||
 | 
					      throws IOException, ResourceConflictException, AuthException, PermissionBackendException {
 | 
				
			||||||
 | 
					    deleteSingleRef(projectState, ref, null);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public DeleteRef refs(List<String> refs) {
 | 
					  /**
 | 
				
			||||||
    this.refsToDelete.addAll(refs);
 | 
					   * Deletes a single ref from the repository.
 | 
				
			||||||
    return this;
 | 
					   *
 | 
				
			||||||
  }
 | 
					   * @param projectState the {@code ProjectState} of the project containing the target ref.
 | 
				
			||||||
 | 
					   * @param ref the ref to be deleted.
 | 
				
			||||||
  public DeleteRef prefix(String prefix) {
 | 
					   * @param prefix the prefix of the ref.
 | 
				
			||||||
    this.prefix = prefix;
 | 
					   * @throws IOException
 | 
				
			||||||
    return this;
 | 
					   * @throws ResourceConflictException
 | 
				
			||||||
  }
 | 
					   */
 | 
				
			||||||
 | 
					  public void deleteSingleRef(ProjectState projectState, String ref, @Nullable String prefix)
 | 
				
			||||||
  public void delete()
 | 
					      throws IOException, ResourceConflictException, AuthException, PermissionBackendException {
 | 
				
			||||||
      throws OrmException, IOException, ResourceConflictException, PermissionBackendException {
 | 
					 | 
				
			||||||
    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 {
 | 
					 | 
				
			||||||
    String ref = refsToDelete.get(0);
 | 
					 | 
				
			||||||
    if (prefix != null && !ref.startsWith(R_REFS)) {
 | 
					    if (prefix != null && !ref.startsWith(R_REFS)) {
 | 
				
			||||||
      ref = prefix + ref;
 | 
					      ref = prefix + ref;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    RefUpdate.Result result;
 | 
					
 | 
				
			||||||
    RefUpdate u = r.updateRef(ref);
 | 
					    projectState.checkStatePermitsWrite();
 | 
				
			||||||
    u.setExpectedOldObjectId(r.exactRef(ref).getObjectId());
 | 
					    permissionBackend
 | 
				
			||||||
    u.setNewObjectId(ObjectId.zeroId());
 | 
					        .currentUser()
 | 
				
			||||||
    u.setForceUpdate(true);
 | 
					        .project(projectState.getNameKey())
 | 
				
			||||||
    refDeletionValidator.validateRefOperation(resource.getName(), identifiedUser.get(), u);
 | 
					        .ref(ref)
 | 
				
			||||||
    int remainingLockFailureCalls = MAX_LOCK_FAILURE_CALLS;
 | 
					        .check(RefPermission.DELETE);
 | 
				
			||||||
    for (; ; ) {
 | 
					
 | 
				
			||||||
      try {
 | 
					    try (Repository repository = repoManager.openRepository(projectState.getNameKey())) {
 | 
				
			||||||
        result = u.delete();
 | 
					      RefUpdate.Result result;
 | 
				
			||||||
      } catch (LockFailedException e) {
 | 
					      RefUpdate u = repository.updateRef(ref);
 | 
				
			||||||
        result = RefUpdate.Result.LOCK_FAILURE;
 | 
					      u.setExpectedOldObjectId(repository.exactRef(ref).getObjectId());
 | 
				
			||||||
      } catch (IOException e) {
 | 
					      u.setNewObjectId(ObjectId.zeroId());
 | 
				
			||||||
        logger.atSevere().withCause(e).log("Cannot delete %s", ref);
 | 
					      u.setForceUpdate(true);
 | 
				
			||||||
        throw e;
 | 
					      refDeletionValidator.validateRefOperation(projectState.getName(), identifiedUser.get(), u);
 | 
				
			||||||
      }
 | 
					      int remainingLockFailureCalls = MAX_LOCK_FAILURE_CALLS;
 | 
				
			||||||
      if (result == RefUpdate.Result.LOCK_FAILURE && --remainingLockFailureCalls > 0) {
 | 
					      for (; ; ) {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
          Thread.sleep(SLEEP_ON_LOCK_FAILURE_MS);
 | 
					          result = u.delete();
 | 
				
			||||||
        } catch (InterruptedException ie) {
 | 
					        } catch (LockFailedException e) {
 | 
				
			||||||
          // ignore
 | 
					          result = RefUpdate.Result.LOCK_FAILURE;
 | 
				
			||||||
 | 
					        } catch (IOException e) {
 | 
				
			||||||
 | 
					          logger.atSevere().withCause(e).log("Cannot delete %s", ref);
 | 
				
			||||||
 | 
					          throw e;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (result == RefUpdate.Result.LOCK_FAILURE && --remainingLockFailureCalls > 0) {
 | 
				
			||||||
 | 
					          try {
 | 
				
			||||||
 | 
					            Thread.sleep(SLEEP_ON_LOCK_FAILURE_MS);
 | 
				
			||||||
 | 
					          } catch (InterruptedException ie) {
 | 
				
			||||||
 | 
					            // ignore
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        break;
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    switch (result) {
 | 
					      switch (result) {
 | 
				
			||||||
      case NEW:
 | 
					        case NEW:
 | 
				
			||||||
      case NO_CHANGE:
 | 
					        case NO_CHANGE:
 | 
				
			||||||
      case FAST_FORWARD:
 | 
					        case FAST_FORWARD:
 | 
				
			||||||
      case FORCED:
 | 
					        case FORCED:
 | 
				
			||||||
        referenceUpdated.fire(
 | 
					          referenceUpdated.fire(
 | 
				
			||||||
            resource.getNameKey(), u, ReceiveCommand.Type.DELETE, identifiedUser.get().state());
 | 
					              projectState.getNameKey(),
 | 
				
			||||||
        break;
 | 
					              u,
 | 
				
			||||||
 | 
					              ReceiveCommand.Type.DELETE,
 | 
				
			||||||
 | 
					              identifiedUser.get().state());
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      case REJECTED_CURRENT_BRANCH:
 | 
					        case REJECTED_CURRENT_BRANCH:
 | 
				
			||||||
        logger.atSevere().log("Cannot delete %s: %s", ref, result.name());
 | 
					          logger.atSevere().log("Cannot delete %s: %s", ref, result.name());
 | 
				
			||||||
        throw new ResourceConflictException("cannot delete current branch");
 | 
					          throw new ResourceConflictException("cannot delete current branch");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      case IO_FAILURE:
 | 
					        case IO_FAILURE:
 | 
				
			||||||
      case LOCK_FAILURE:
 | 
					        case LOCK_FAILURE:
 | 
				
			||||||
      case NOT_ATTEMPTED:
 | 
					        case NOT_ATTEMPTED:
 | 
				
			||||||
      case REJECTED:
 | 
					        case REJECTED:
 | 
				
			||||||
      case RENAMED:
 | 
					        case RENAMED:
 | 
				
			||||||
      case REJECTED_MISSING_OBJECT:
 | 
					        case REJECTED_MISSING_OBJECT:
 | 
				
			||||||
      case REJECTED_OTHER_REASON:
 | 
					        case REJECTED_OTHER_REASON:
 | 
				
			||||||
      default:
 | 
					        default:
 | 
				
			||||||
        logger.atSevere().log("Cannot delete %s: %s", ref, result.name());
 | 
					          logger.atSevere().log("Cannot delete %s: %s", ref, result.name());
 | 
				
			||||||
        throw new ResourceConflictException("cannot delete: " + result.name());
 | 
					          throw new ResourceConflictException("cannot delete: " + result.name());
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private void deleteMultipleRefs(Repository r)
 | 
					  /**
 | 
				
			||||||
      throws OrmException, IOException, ResourceConflictException, PermissionBackendException {
 | 
					   * Deletes a set of refs from the repository.
 | 
				
			||||||
    BatchRefUpdate batchUpdate = r.getRefDatabase().newBatchUpdate();
 | 
					   *
 | 
				
			||||||
    batchUpdate.setAtomic(false);
 | 
					   * @param projectState the {@code ProjectState} of the project whose refs are to be deleted.
 | 
				
			||||||
    List<String> refs =
 | 
					   * @param refsToDelete the refs to be deleted.
 | 
				
			||||||
        prefix == null
 | 
					   * @param prefix the prefix of the refs.
 | 
				
			||||||
            ? refsToDelete
 | 
					   * @throws OrmException
 | 
				
			||||||
            : refsToDelete
 | 
					   * @throws IOException
 | 
				
			||||||
                .stream()
 | 
					   * @throws ResourceConflictException
 | 
				
			||||||
                .map(ref -> ref.startsWith(R_REFS) ? ref : prefix + ref)
 | 
					   * @throws PermissionBackendException
 | 
				
			||||||
                .collect(toList());
 | 
					   */
 | 
				
			||||||
    for (String ref : refs) {
 | 
					  public void deleteMultipleRefs(
 | 
				
			||||||
      batchUpdate.addCommand(createDeleteCommand(resource, r, ref));
 | 
					      ProjectState projectState, ImmutableSet<String> refsToDelete, String prefix)
 | 
				
			||||||
 | 
					      throws OrmException, IOException, ResourceConflictException, PermissionBackendException,
 | 
				
			||||||
 | 
					          AuthException {
 | 
				
			||||||
 | 
					    if (refsToDelete.isEmpty()) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    try (RevWalk rw = new RevWalk(r)) {
 | 
					
 | 
				
			||||||
      batchUpdate.execute(rw, NullProgressMonitor.INSTANCE);
 | 
					    if (refsToDelete.size() == 1) {
 | 
				
			||||||
 | 
					      deleteSingleRef(projectState, Iterables.getOnlyElement(refsToDelete), prefix);
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    StringBuilder errorMessages = new StringBuilder();
 | 
					
 | 
				
			||||||
    for (ReceiveCommand command : batchUpdate.getCommands()) {
 | 
					    try (Repository repository = repoManager.openRepository(projectState.getNameKey())) {
 | 
				
			||||||
      if (command.getResult() == Result.OK) {
 | 
					      BatchRefUpdate batchUpdate = repository.getRefDatabase().newBatchUpdate();
 | 
				
			||||||
        postDeletion(resource, command);
 | 
					      batchUpdate.setAtomic(false);
 | 
				
			||||||
      } else {
 | 
					      ImmutableSet<String> refs =
 | 
				
			||||||
        appendAndLogErrorMessage(errorMessages, command);
 | 
					          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());
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (errorMessages.length() > 0) {
 | 
					 | 
				
			||||||
      throw new ResourceConflictException(errorMessages.toString());
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private ReceiveCommand createDeleteCommand(ProjectResource project, Repository r, String refName)
 | 
					  private ReceiveCommand createDeleteCommand(
 | 
				
			||||||
 | 
					      ProjectState projectState, Repository r, String refName)
 | 
				
			||||||
      throws OrmException, IOException, ResourceConflictException, PermissionBackendException {
 | 
					      throws OrmException, IOException, ResourceConflictException, PermissionBackendException {
 | 
				
			||||||
    Ref ref = r.getRefDatabase().getRef(refName);
 | 
					    Ref ref = r.getRefDatabase().getRef(refName);
 | 
				
			||||||
    ReceiveCommand command;
 | 
					    ReceiveCommand command;
 | 
				
			||||||
@@ -227,7 +252,7 @@ public class DeleteRef {
 | 
				
			|||||||
      try {
 | 
					      try {
 | 
				
			||||||
        permissionBackend
 | 
					        permissionBackend
 | 
				
			||||||
            .currentUser()
 | 
					            .currentUser()
 | 
				
			||||||
            .project(project.getNameKey())
 | 
					            .project(projectState.getNameKey())
 | 
				
			||||||
            .ref(refName)
 | 
					            .ref(refName)
 | 
				
			||||||
            .check(RefPermission.DELETE);
 | 
					            .check(RefPermission.DELETE);
 | 
				
			||||||
      } catch (AuthException denied) {
 | 
					      } catch (AuthException denied) {
 | 
				
			||||||
@@ -237,12 +262,12 @@ public class DeleteRef {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!project.getProjectState().statePermitsWrite()) {
 | 
					    if (!projectState.statePermitsWrite()) {
 | 
				
			||||||
      command.setResult(Result.REJECTED_OTHER_REASON, "project state does not permit write");
 | 
					      command.setResult(Result.REJECTED_OTHER_REASON, "project state does not permit write");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!refName.startsWith(R_TAGS)) {
 | 
					    if (!refName.startsWith(R_TAGS)) {
 | 
				
			||||||
      Branch.NameKey branchKey = new Branch.NameKey(project.getNameKey(), ref.getName());
 | 
					      Branch.NameKey branchKey = new Branch.NameKey(projectState.getNameKey(), ref.getName());
 | 
				
			||||||
      if (!queryProvider.get().setLimit(1).byBranchOpen(branchKey).isEmpty()) {
 | 
					      if (!queryProvider.get().setLimit(1).byBranchOpen(branchKey).isEmpty()) {
 | 
				
			||||||
        command.setResult(Result.REJECTED_OTHER_REASON, "it has open changes");
 | 
					        command.setResult(Result.REJECTED_OTHER_REASON, "it has open changes");
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -252,7 +277,7 @@ public class DeleteRef {
 | 
				
			|||||||
    u.setForceUpdate(true);
 | 
					    u.setForceUpdate(true);
 | 
				
			||||||
    u.setExpectedOldObjectId(r.exactRef(refName).getObjectId());
 | 
					    u.setExpectedOldObjectId(r.exactRef(refName).getObjectId());
 | 
				
			||||||
    u.setNewObjectId(ObjectId.zeroId());
 | 
					    u.setNewObjectId(ObjectId.zeroId());
 | 
				
			||||||
    refDeletionValidator.validateRefOperation(project.getName(), identifiedUser.get(), u);
 | 
					    refDeletionValidator.validateRefOperation(projectState.getName(), identifiedUser.get(), u);
 | 
				
			||||||
    return command;
 | 
					    return command;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -281,7 +306,7 @@ public class DeleteRef {
 | 
				
			|||||||
    errorMessages.append("\n");
 | 
					    errorMessages.append("\n");
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private void postDeletion(ProjectResource project, ReceiveCommand cmd) {
 | 
					  private void postDeletion(Project.NameKey project, ReceiveCommand cmd) {
 | 
				
			||||||
    referenceUpdated.fire(project.getNameKey(), cmd, identifiedUser.get().state());
 | 
					    referenceUpdated.fire(project, cmd, identifiedUser.get().state());
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,9 +21,7 @@ import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
 | 
				
			|||||||
import com.google.gerrit.extensions.restapi.Response;
 | 
					import com.google.gerrit.extensions.restapi.Response;
 | 
				
			||||||
import com.google.gerrit.extensions.restapi.RestApiException;
 | 
					import com.google.gerrit.extensions.restapi.RestApiException;
 | 
				
			||||||
import com.google.gerrit.extensions.restapi.RestModifyView;
 | 
					import com.google.gerrit.extensions.restapi.RestModifyView;
 | 
				
			||||||
import com.google.gerrit.server.permissions.PermissionBackend;
 | 
					 | 
				
			||||||
import com.google.gerrit.server.permissions.PermissionBackendException;
 | 
					import com.google.gerrit.server.permissions.PermissionBackendException;
 | 
				
			||||||
import com.google.gerrit.server.permissions.RefPermission;
 | 
					 | 
				
			||||||
import com.google.gerrit.server.project.RefUtil;
 | 
					import com.google.gerrit.server.project.RefUtil;
 | 
				
			||||||
import com.google.gerrit.server.project.TagResource;
 | 
					import com.google.gerrit.server.project.TagResource;
 | 
				
			||||||
import com.google.gwtorm.server.OrmException;
 | 
					import com.google.gwtorm.server.OrmException;
 | 
				
			||||||
@@ -34,13 +32,11 @@ import java.io.IOException;
 | 
				
			|||||||
@Singleton
 | 
					@Singleton
 | 
				
			||||||
public class DeleteTag implements RestModifyView<TagResource, Input> {
 | 
					public class DeleteTag implements RestModifyView<TagResource, Input> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private final PermissionBackend permissionBackend;
 | 
					  private final DeleteRef deleteRef;
 | 
				
			||||||
  private final DeleteRef.Factory deleteRefFactory;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Inject
 | 
					  @Inject
 | 
				
			||||||
  DeleteTag(PermissionBackend permissionBackend, DeleteRef.Factory deleteRefFactory) {
 | 
					  DeleteTag(DeleteRef deleteRef) {
 | 
				
			||||||
    this.permissionBackend = permissionBackend;
 | 
					    this.deleteRef = deleteRef;
 | 
				
			||||||
    this.deleteRefFactory = deleteRefFactory;
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Override
 | 
					  @Override
 | 
				
			||||||
@@ -53,13 +49,7 @@ public class DeleteTag implements RestModifyView<TagResource, Input> {
 | 
				
			|||||||
      throw new MethodNotAllowedException("not allowed to delete " + tag);
 | 
					      throw new MethodNotAllowedException("not allowed to delete " + tag);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    permissionBackend
 | 
					    deleteRef.deleteSingleRef(resource.getProjectState(), tag);
 | 
				
			||||||
        .currentUser()
 | 
					 | 
				
			||||||
        .project(resource.getNameKey())
 | 
					 | 
				
			||||||
        .ref(tag)
 | 
					 | 
				
			||||||
        .check(RefPermission.DELETE);
 | 
					 | 
				
			||||||
    resource.getProjectState().checkStatePermitsWrite();
 | 
					 | 
				
			||||||
    deleteRefFactory.create(resource).ref(tag).delete();
 | 
					 | 
				
			||||||
    return Response.none();
 | 
					    return Response.none();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,6 +16,7 @@ package com.google.gerrit.server.restapi.project;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import static org.eclipse.jgit.lib.Constants.R_TAGS;
 | 
					import static org.eclipse.jgit.lib.Constants.R_TAGS;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.google.common.collect.ImmutableSet;
 | 
				
			||||||
import com.google.gerrit.extensions.api.projects.DeleteTagsInput;
 | 
					import com.google.gerrit.extensions.api.projects.DeleteTagsInput;
 | 
				
			||||||
import com.google.gerrit.extensions.restapi.BadRequestException;
 | 
					import com.google.gerrit.extensions.restapi.BadRequestException;
 | 
				
			||||||
import com.google.gerrit.extensions.restapi.Response;
 | 
					import com.google.gerrit.extensions.restapi.Response;
 | 
				
			||||||
@@ -30,11 +31,11 @@ import java.io.IOException;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@Singleton
 | 
					@Singleton
 | 
				
			||||||
public class DeleteTags implements RestModifyView<ProjectResource, DeleteTagsInput> {
 | 
					public class DeleteTags implements RestModifyView<ProjectResource, DeleteTagsInput> {
 | 
				
			||||||
  private final DeleteRef.Factory deleteRefFactory;
 | 
					  private final DeleteRef deleteRef;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Inject
 | 
					  @Inject
 | 
				
			||||||
  DeleteTags(DeleteRef.Factory deleteRefFactory) {
 | 
					  DeleteTags(DeleteRef deleteRef) {
 | 
				
			||||||
    this.deleteRefFactory = deleteRefFactory;
 | 
					    this.deleteRef = deleteRef;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Override
 | 
					  @Override
 | 
				
			||||||
@@ -43,7 +44,8 @@ public class DeleteTags implements RestModifyView<ProjectResource, DeleteTagsInp
 | 
				
			|||||||
    if (input == null || input.tags == null || input.tags.isEmpty()) {
 | 
					    if (input == null || input.tags == null || input.tags.isEmpty()) {
 | 
				
			||||||
      throw new BadRequestException("tags must be specified");
 | 
					      throw new BadRequestException("tags must be specified");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    deleteRefFactory.create(project).refs(input.tags).prefix(R_TAGS).delete();
 | 
					    deleteRef.deleteMultipleRefs(
 | 
				
			||||||
 | 
					        project.getProjectState(), ImmutableSet.copyOf(input.tags), R_TAGS);
 | 
				
			||||||
    return Response.none();
 | 
					    return Response.none();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -104,7 +104,6 @@ public class Module extends RestApiModule {
 | 
				
			|||||||
    put(PROJECT_KIND, "config").to(PutConfig.class);
 | 
					    put(PROJECT_KIND, "config").to(PutConfig.class);
 | 
				
			||||||
    post(COMMIT_KIND, "cherrypick").to(CherryPickCommit.class);
 | 
					    post(COMMIT_KIND, "cherrypick").to(CherryPickCommit.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    factory(DeleteRef.Factory.class);
 | 
					 | 
				
			||||||
    factory(ProjectNode.Factory.class);
 | 
					    factory(ProjectNode.Factory.class);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,6 +28,7 @@ import com.google.gerrit.common.data.Permission;
 | 
				
			|||||||
import com.google.gerrit.extensions.api.projects.BranchInput;
 | 
					import com.google.gerrit.extensions.api.projects.BranchInput;
 | 
				
			||||||
import com.google.gerrit.extensions.api.projects.DeleteBranchesInput;
 | 
					import com.google.gerrit.extensions.api.projects.DeleteBranchesInput;
 | 
				
			||||||
import com.google.gerrit.extensions.api.projects.ProjectApi;
 | 
					import com.google.gerrit.extensions.api.projects.ProjectApi;
 | 
				
			||||||
 | 
					import com.google.gerrit.extensions.restapi.AuthException;
 | 
				
			||||||
import com.google.gerrit.extensions.restapi.BadRequestException;
 | 
					import com.google.gerrit.extensions.restapi.BadRequestException;
 | 
				
			||||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
 | 
					import com.google.gerrit.extensions.restapi.ResourceConflictException;
 | 
				
			||||||
import com.google.gerrit.reviewdb.client.RefNames;
 | 
					import com.google.gerrit.reviewdb.client.RefNames;
 | 
				
			||||||
@@ -64,7 +65,24 @@ public class DeleteBranchesIT extends AbstractDaemonTest {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Test
 | 
					  @Test
 | 
				
			||||||
  public void deleteBranchesForbidden() throws Exception {
 | 
					  public void deleteOneBranchWithoutPermissionForbidden() throws Exception {
 | 
				
			||||||
 | 
					    ImmutableList<String> branchToDelete = ImmutableList.of("refs/heads/test-1");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    DeleteBranchesInput input = new DeleteBranchesInput();
 | 
				
			||||||
 | 
					    input.branches = branchToDelete;
 | 
				
			||||||
 | 
					    setApiUser(user);
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      project().deleteBranches(input);
 | 
				
			||||||
 | 
					      fail("Expected AuthException");
 | 
				
			||||||
 | 
					    } catch (AuthException e) {
 | 
				
			||||||
 | 
					      assertThat(e).hasMessageThat().isEqualTo("delete not permitted for refs/heads/test-1");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    setApiUser(admin);
 | 
				
			||||||
 | 
					    assertBranches(BRANCHES);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Test
 | 
				
			||||||
 | 
					  public void deleteMultiBranchesWithoutPermissionForbidden() throws Exception {
 | 
				
			||||||
    DeleteBranchesInput input = new DeleteBranchesInput();
 | 
					    DeleteBranchesInput input = new DeleteBranchesInput();
 | 
				
			||||||
    input.branches = BRANCHES;
 | 
					    input.branches = BRANCHES;
 | 
				
			||||||
    setApiUser(user);
 | 
					    setApiUser(user);
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user