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 : }