LCOV - code coverage report
Current view: top level - server/restapi/project - CreateBranch.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 80 83 96.4 %
Date: 2022-11-19 15:00:39 Functions: 5 5 100.0 %

          Line data    Source code
       1             : // Copyright (C) 2013 The Android Open Source Project
       2             : //
       3             : // Licensed under the Apache License, Version 2.0 (the "License");
       4             : // you may not use this file except in compliance with the License.
       5             : // You may obtain a copy of the License at
       6             : //
       7             : // http://www.apache.org/licenses/LICENSE-2.0
       8             : //
       9             : // Unless required by applicable law or agreed to in writing, software
      10             : // distributed under the License is distributed on an "AS IS" BASIS,
      11             : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             : // See the License for the specific language governing permissions and
      13             : // limitations under the License.
      14             : 
      15             : package com.google.gerrit.server.restapi.project;
      16             : 
      17             : import static com.google.gerrit.entities.RefNames.isConfigRef;
      18             : 
      19             : import com.google.common.base.Strings;
      20             : import com.google.common.collect.ImmutableListMultimap;
      21             : import com.google.gerrit.common.Nullable;
      22             : import com.google.gerrit.entities.BranchNameKey;
      23             : import com.google.gerrit.entities.RefNames;
      24             : import com.google.gerrit.extensions.api.projects.BranchInfo;
      25             : import com.google.gerrit.extensions.api.projects.BranchInput;
      26             : import com.google.gerrit.extensions.restapi.AuthException;
      27             : import com.google.gerrit.extensions.restapi.BadRequestException;
      28             : import com.google.gerrit.extensions.restapi.IdString;
      29             : import com.google.gerrit.extensions.restapi.ResourceConflictException;
      30             : import com.google.gerrit.extensions.restapi.Response;
      31             : import com.google.gerrit.extensions.restapi.RestCollectionCreateView;
      32             : import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
      33             : import com.google.gerrit.git.LockFailureException;
      34             : import com.google.gerrit.server.IdentifiedUser;
      35             : import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
      36             : import com.google.gerrit.server.git.GitRepositoryManager;
      37             : import com.google.gerrit.server.permissions.PermissionBackend;
      38             : import com.google.gerrit.server.permissions.PermissionBackendException;
      39             : import com.google.gerrit.server.permissions.RefPermission;
      40             : import com.google.gerrit.server.project.BranchResource;
      41             : import com.google.gerrit.server.project.CreateRefControl;
      42             : import com.google.gerrit.server.project.NoSuchProjectException;
      43             : import com.google.gerrit.server.project.ProjectResource;
      44             : import com.google.gerrit.server.project.RefUtil;
      45             : import com.google.gerrit.server.project.RefValidationHelper;
      46             : import com.google.gerrit.server.util.MagicBranch;
      47             : import com.google.inject.Inject;
      48             : import com.google.inject.Provider;
      49             : import com.google.inject.Singleton;
      50             : import java.io.IOException;
      51             : import java.util.Map;
      52             : import org.eclipse.jgit.lib.Constants;
      53             : import org.eclipse.jgit.lib.ObjectId;
      54             : import org.eclipse.jgit.lib.Ref;
      55             : import org.eclipse.jgit.lib.RefUpdate;
      56             : import org.eclipse.jgit.lib.Repository;
      57             : import org.eclipse.jgit.revwalk.RevObject;
      58             : import org.eclipse.jgit.revwalk.RevWalk;
      59             : import org.eclipse.jgit.transport.ReceiveCommand;
      60             : 
      61             : @Singleton
      62             : public class CreateBranch
      63             :     implements RestCollectionCreateView<ProjectResource, BranchResource, BranchInput> {
      64             :   private final Provider<IdentifiedUser> identifiedUser;
      65             :   private final PermissionBackend permissionBackend;
      66             :   private final GitRepositoryManager repoManager;
      67             :   private final GitReferenceUpdated referenceUpdated;
      68             :   private final RefValidationHelper refCreationValidator;
      69             :   private final CreateRefControl createRefControl;
      70             : 
      71             :   @Inject
      72             :   CreateBranch(
      73             :       Provider<IdentifiedUser> identifiedUser,
      74             :       PermissionBackend permissionBackend,
      75             :       GitRepositoryManager repoManager,
      76             :       GitReferenceUpdated referenceUpdated,
      77             :       RefValidationHelper.Factory refHelperFactory,
      78         138 :       CreateRefControl createRefControl) {
      79         138 :     this.identifiedUser = identifiedUser;
      80         138 :     this.permissionBackend = permissionBackend;
      81         138 :     this.repoManager = repoManager;
      82         138 :     this.referenceUpdated = referenceUpdated;
      83         138 :     this.refCreationValidator = refHelperFactory.create(ReceiveCommand.Type.CREATE);
      84         138 :     this.createRefControl = createRefControl;
      85         138 :   }
      86             : 
      87             :   @Override
      88             :   public Response<BranchInfo> apply(ProjectResource rsrc, IdString id, BranchInput input)
      89             :       throws BadRequestException, AuthException, ResourceConflictException,
      90             :           UnprocessableEntityException, IOException, PermissionBackendException,
      91             :           NoSuchProjectException {
      92          29 :     String ref = id.get();
      93          29 :     if (input == null) {
      94           0 :       input = new BranchInput();
      95             :     }
      96          29 :     if (input.ref != null && !ref.equals(input.ref)) {
      97           1 :       throw new BadRequestException("ref must match URL");
      98             :     }
      99          29 :     if (input.revision != null) {
     100          13 :       input.revision = input.revision.trim();
     101             :     }
     102          29 :     if (Strings.isNullOrEmpty(input.revision)) {
     103          27 :       input.revision = Constants.HEAD;
     104             :     }
     105          29 :     while (ref.startsWith("/")) {
     106           1 :       ref = ref.substring(1);
     107             :     }
     108          29 :     ref = RefNames.fullName(ref);
     109          29 :     if (!Repository.isValidRefName(ref)) {
     110           1 :       throw new BadRequestException("invalid branch name \"" + ref + "\"");
     111             :     }
     112          29 :     if (MagicBranch.isMagicBranch(ref)) {
     113           1 :       throw new BadRequestException(
     114             :           "not allowed to create branches under \""
     115           1 :               + MagicBranch.getMagicRefNamePrefix(ref)
     116             :               + "\"");
     117             :     }
     118          29 :     if (!isBranchAllowed(ref)) {
     119           1 :       throw new BadRequestException(
     120             :           "Cannot create a branch with name \""
     121             :               + ref
     122             :               + "\". Not allowed to create branches under Gerrit internal or tags refs.");
     123             :     }
     124             : 
     125          29 :     BranchNameKey name = BranchNameKey.create(rsrc.getNameKey(), ref);
     126          29 :     try (Repository repo = repoManager.openRepository(rsrc.getNameKey())) {
     127          29 :       ObjectId revid = RefUtil.parseBaseRevision(repo, input.revision);
     128          29 :       RevWalk rw = RefUtil.verifyConnected(repo, revid);
     129          29 :       RevObject object = rw.parseAny(revid);
     130             : 
     131          29 :       if (ref.startsWith(Constants.R_HEADS)) {
     132             :         // Ensure that what we start the branch from is a commit. If we
     133             :         // were given a tag, dereference to the commit instead.
     134             :         //
     135          29 :         object = rw.parseCommit(object);
     136             :       }
     137             : 
     138          29 :       Ref sourceRef = repo.exactRef(input.revision);
     139          29 :       if (sourceRef == null) {
     140          13 :         createRefControl.checkCreateRef(identifiedUser, repo, name, object, /* forPush= */ false);
     141             :       } else {
     142          27 :         if (sourceRef.isSymbolic()) {
     143          27 :           sourceRef = sourceRef.getTarget();
     144             :         }
     145          27 :         createRefControl.checkCreateRef(
     146             :             identifiedUser,
     147             :             repo,
     148             :             name,
     149             :             object,
     150             :             /* forPush= */ false,
     151          27 :             BranchNameKey.create(rsrc.getNameKey(), sourceRef.getName()));
     152             :       }
     153             : 
     154          29 :       RefUpdate u = repo.updateRef(ref);
     155          29 :       u.setExpectedOldObjectId(ObjectId.zeroId());
     156          29 :       u.setNewObjectId(object.copy());
     157          29 :       u.setRefLogIdent(identifiedUser.get().newRefLogIdent());
     158          29 :       u.setRefLogMessage("created via REST from " + input.revision, false);
     159          29 :       refCreationValidator.validateRefOperation(
     160          29 :           rsrc.getName(),
     161          29 :           identifiedUser.get(),
     162             :           u,
     163          29 :           getValidateOptionsAsMultimap(input.validationOptions));
     164          29 :       RefUpdate.Result result = u.update(rw);
     165          29 :       switch (result) {
     166             :         case FAST_FORWARD:
     167             :         case NEW:
     168             :         case NO_CHANGE:
     169          29 :           referenceUpdated.fire(
     170          29 :               name.project(), u, ReceiveCommand.Type.CREATE, identifiedUser.get().state());
     171          29 :           break;
     172             :         case LOCK_FAILURE:
     173           2 :           if (repo.getRefDatabase().exactRef(ref) != null) {
     174           2 :             throw new ResourceConflictException("branch \"" + ref + "\" already exists");
     175             :           }
     176           1 :           String refPrefix = RefUtil.getRefPrefix(ref);
     177           1 :           while (!Constants.R_HEADS.equals(refPrefix)) {
     178           1 :             if (repo.getRefDatabase().exactRef(refPrefix) != null) {
     179           1 :               throw new ResourceConflictException(
     180             :                   "Cannot create branch \""
     181             :                       + ref
     182             :                       + "\" since it conflicts with branch \""
     183             :                       + refPrefix
     184             :                       + "\".");
     185             :             }
     186           1 :             refPrefix = RefUtil.getRefPrefix(refPrefix);
     187             :           }
     188           0 :           throw new LockFailureException(String.format("Failed to create %s", ref), u);
     189             :         case FORCED:
     190             :         case IO_FAILURE:
     191             :         case NOT_ATTEMPTED:
     192             :         case REJECTED:
     193             :         case REJECTED_CURRENT_BRANCH:
     194             :         case RENAMED:
     195             :         case REJECTED_MISSING_OBJECT:
     196             :         case REJECTED_OTHER_REASON:
     197             :         default:
     198           0 :           throw new IOException(String.format("Failed to create %s: %s", ref, result.name()));
     199             :       }
     200             : 
     201          29 :       BranchInfo info = new BranchInfo();
     202          29 :       info.ref = ref;
     203          29 :       info.revision = revid.getName();
     204             : 
     205          29 :       if (isConfigRef(name.branch())) {
     206             :         // Never allow to delete the meta config branch.
     207           1 :         info.canDelete = null;
     208             :       } else {
     209          29 :         info.canDelete =
     210          29 :             permissionBackend.currentUser().ref(name).testOrFalse(RefPermission.DELETE)
     211          29 :                     && rsrc.getProjectState().statePermitsWrite()
     212          29 :                 ? true
     213          29 :                 : null;
     214             :       }
     215          29 :       return Response.created(info);
     216             :     }
     217             :   }
     218             : 
     219             :   /** Branches cannot be created under any Gerrit internal or tags refs. */
     220             :   private boolean isBranchAllowed(String branch) {
     221          29 :     return !RefNames.isGerritRef(branch) && !branch.startsWith(RefNames.REFS_TAGS);
     222             :   }
     223             : 
     224             :   private static ImmutableListMultimap<String, String> getValidateOptionsAsMultimap(
     225             :       @Nullable Map<String, String> validationOptions) {
     226          29 :     if (validationOptions == null) {
     227          29 :       return ImmutableListMultimap.of();
     228             :     }
     229             : 
     230             :     ImmutableListMultimap.Builder<String, String> validationOptionsBuilder =
     231           1 :         ImmutableListMultimap.builder();
     232           1 :     validationOptions
     233           1 :         .entrySet()
     234           1 :         .forEach(e -> validationOptionsBuilder.put(e.getKey(), e.getValue()));
     235           1 :     return validationOptionsBuilder.build();
     236             :   }
     237             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750