Line data Source code
1 : // Copyright (C) 2018 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.project; 16 : 17 : import static com.google.gerrit.server.project.ProjectCache.illegalState; 18 : 19 : import com.google.common.base.MoreObjects; 20 : import com.google.common.base.Strings; 21 : import com.google.common.flogger.FluentLogger; 22 : import com.google.gerrit.entities.AccessSection; 23 : import com.google.gerrit.entities.AccountGroup; 24 : import com.google.gerrit.entities.BooleanProjectConfig; 25 : import com.google.gerrit.entities.GroupDescription; 26 : import com.google.gerrit.entities.GroupReference; 27 : import com.google.gerrit.entities.Permission; 28 : import com.google.gerrit.entities.PermissionRule; 29 : import com.google.gerrit.entities.Project; 30 : import com.google.gerrit.entities.RefNames; 31 : import com.google.gerrit.extensions.events.NewProjectCreatedListener; 32 : import com.google.gerrit.extensions.restapi.BadRequestException; 33 : import com.google.gerrit.extensions.restapi.ResourceConflictException; 34 : import com.google.gerrit.git.LockFailureException; 35 : import com.google.gerrit.server.GerritPersonIdent; 36 : import com.google.gerrit.server.IdentifiedUser; 37 : import com.google.gerrit.server.account.GroupBackend; 38 : import com.google.gerrit.server.config.RepositoryConfig; 39 : import com.google.gerrit.server.extensions.events.AbstractNoNotifyEvent; 40 : import com.google.gerrit.server.extensions.events.GitReferenceUpdated; 41 : import com.google.gerrit.server.git.GitRepositoryManager; 42 : import com.google.gerrit.server.git.GitRepositoryManager.Status; 43 : import com.google.gerrit.server.git.RepositoryExistsException; 44 : import com.google.gerrit.server.git.meta.MetaDataUpdate; 45 : import com.google.gerrit.server.plugincontext.PluginSetContext; 46 : import com.google.inject.Inject; 47 : import com.google.inject.Provider; 48 : import java.io.IOException; 49 : import java.util.List; 50 : import org.eclipse.jgit.errors.ConfigInvalidException; 51 : import org.eclipse.jgit.errors.RepositoryNotFoundException; 52 : import org.eclipse.jgit.lib.CommitBuilder; 53 : import org.eclipse.jgit.lib.Constants; 54 : import org.eclipse.jgit.lib.ObjectId; 55 : import org.eclipse.jgit.lib.ObjectInserter; 56 : import org.eclipse.jgit.lib.PersonIdent; 57 : import org.eclipse.jgit.lib.RefUpdate; 58 : import org.eclipse.jgit.lib.RefUpdate.Result; 59 : import org.eclipse.jgit.lib.Repository; 60 : import org.eclipse.jgit.transport.ReceiveCommand; 61 : 62 : /** 63 : * Business logic for creating projects. 64 : * 65 : * <p>This creates the repository, the underlying configuration in {@code refs/meta/config} and 66 : * initializes a first commit if necessary. 67 : */ 68 : public class ProjectCreator { 69 147 : private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 70 : 71 : private final GitRepositoryManager repoManager; 72 : private final PluginSetContext<NewProjectCreatedListener> createdListeners; 73 : private final ProjectCache projectCache; 74 : private final GroupBackend groupBackend; 75 : private final MetaDataUpdate.User metaDataUpdateFactory; 76 : private final GitReferenceUpdated referenceUpdated; 77 : private final RepositoryConfig repositoryCfg; 78 : private final Provider<PersonIdent> serverIdent; 79 : private final Provider<IdentifiedUser> identifiedUser; 80 : private final ProjectConfig.Factory projectConfigFactory; 81 : 82 : @Inject 83 : ProjectCreator( 84 : GitRepositoryManager repoManager, 85 : PluginSetContext<NewProjectCreatedListener> createdListeners, 86 : ProjectCache projectCache, 87 : GroupBackend groupBackend, 88 : MetaDataUpdate.User metaDataUpdateFactory, 89 : GitReferenceUpdated referenceUpdated, 90 : RepositoryConfig repositoryCfg, 91 : @GerritPersonIdent Provider<PersonIdent> serverIdent, 92 : Provider<IdentifiedUser> identifiedUser, 93 147 : ProjectConfig.Factory projectConfigFactory) { 94 147 : this.repoManager = repoManager; 95 147 : this.createdListeners = createdListeners; 96 147 : this.projectCache = projectCache; 97 147 : this.groupBackend = groupBackend; 98 147 : this.metaDataUpdateFactory = metaDataUpdateFactory; 99 147 : this.referenceUpdated = referenceUpdated; 100 147 : this.repositoryCfg = repositoryCfg; 101 147 : this.serverIdent = serverIdent; 102 147 : this.identifiedUser = identifiedUser; 103 147 : this.projectConfigFactory = projectConfigFactory; 104 147 : } 105 : 106 : public ProjectState createProject(CreateProjectArgs args) 107 : throws BadRequestException, ResourceConflictException, IOException, ConfigInvalidException { 108 144 : final Project.NameKey nameKey = args.getProject(); 109 : try { 110 144 : final String head = args.permissionsOnly ? RefNames.REFS_CONFIG : args.branch.get(0); 111 144 : Status status = repoManager.getRepositoryStatus(nameKey); 112 144 : if (!status.equals(Status.NON_EXISTENT)) { 113 1 : throw new RepositoryExistsException(nameKey, "Repository status: " + status); 114 : } 115 144 : try (Repository repo = repoManager.createRepository(nameKey)) { 116 144 : RefUpdate u = repo.updateRef(Constants.HEAD); 117 144 : u.disableRefLog(); 118 144 : u.link(head); 119 : 120 144 : createProjectConfig(args); 121 : 122 144 : if (!args.permissionsOnly && args.createEmptyCommit) { 123 135 : createEmptyCommits(repo, nameKey, args.branch); 124 : } 125 : 126 144 : fire(nameKey, head); 127 : 128 144 : return projectCache.get(nameKey).orElseThrow(illegalState(nameKey)); 129 : } 130 1 : } catch (RepositoryExistsException e) { 131 1 : throw new ResourceConflictException( 132 : "Cannot create " 133 1 : + nameKey.get() 134 : + " because the name is already occupied by another project.", 135 : e); 136 1 : } catch (RepositoryNotFoundException badName) { 137 1 : throw new BadRequestException("invalid project name: " + nameKey, badName); 138 : } 139 : } 140 : 141 : private void createProjectConfig(CreateProjectArgs args) 142 : throws IOException, ConfigInvalidException { 143 144 : try (MetaDataUpdate md = metaDataUpdateFactory.create(args.getProject())) { 144 144 : ProjectConfig config = projectConfigFactory.read(md); 145 : 146 144 : config.updateProject( 147 : newProject -> { 148 144 : newProject.setDescription(Strings.nullToEmpty(args.projectDescription)); 149 144 : newProject.setSubmitType( 150 144 : MoreObjects.firstNonNull( 151 144 : args.submitType, repositoryCfg.getDefaultSubmitType(args.getProject()))); 152 144 : newProject.setBooleanConfig( 153 : BooleanProjectConfig.USE_CONTRIBUTOR_AGREEMENTS, args.contributorAgreements); 154 144 : newProject.setBooleanConfig(BooleanProjectConfig.USE_SIGNED_OFF_BY, args.signedOffBy); 155 144 : newProject.setBooleanConfig(BooleanProjectConfig.USE_CONTENT_MERGE, args.contentMerge); 156 144 : newProject.setBooleanConfig( 157 : BooleanProjectConfig.CREATE_NEW_CHANGE_FOR_ALL_NOT_IN_TARGET, 158 : args.newChangeForAllNotInTarget); 159 144 : newProject.setBooleanConfig( 160 : BooleanProjectConfig.REQUIRE_CHANGE_ID, args.changeIdRequired); 161 144 : newProject.setBooleanConfig( 162 : BooleanProjectConfig.REJECT_EMPTY_COMMIT, args.rejectEmptyCommit); 163 144 : newProject.setMaxObjectSizeLimit(args.maxObjectSizeLimit); 164 144 : newProject.setBooleanConfig( 165 : BooleanProjectConfig.ENABLE_SIGNED_PUSH, args.enableSignedPush); 166 144 : newProject.setBooleanConfig( 167 : BooleanProjectConfig.REQUIRE_SIGNED_PUSH, args.requireSignedPush); 168 144 : if (args.newParent != null) { 169 143 : newProject.setParent(args.newParent); 170 : } 171 144 : }); 172 : 173 144 : if (!args.ownerIds.isEmpty()) { 174 4 : config.upsertAccessSection( 175 : AccessSection.ALL, 176 : all -> { 177 4 : for (AccountGroup.UUID ownerId : args.ownerIds) { 178 4 : GroupDescription.Basic g = groupBackend.get(ownerId); 179 4 : if (g != null) { 180 4 : GroupReference group = config.resolve(GroupReference.forGroup(g)); 181 4 : all.upsertPermission(Permission.OWNER).add(PermissionRule.builder(group)); 182 : } 183 4 : } 184 4 : }); 185 : } 186 : 187 144 : md.setMessage("Created project\n"); 188 144 : config.commit(md); 189 144 : md.getRepository().setGitwebDescription(args.projectDescription); 190 : } 191 144 : projectCache.onCreateProject(args.getProject()); 192 144 : } 193 : 194 : private void createEmptyCommits(Repository repo, Project.NameKey project, List<String> refs) 195 : throws IOException { 196 135 : try (ObjectInserter oi = repo.newObjectInserter()) { 197 135 : CommitBuilder cb = new CommitBuilder(); 198 135 : cb.setTreeId(oi.insert(Constants.OBJ_TREE, new byte[] {})); 199 135 : cb.setAuthor(metaDataUpdateFactory.getUserPersonIdent()); 200 135 : cb.setCommitter(serverIdent.get()); 201 135 : cb.setMessage("Initial empty repository\n"); 202 : 203 135 : ObjectId id = oi.insert(cb); 204 135 : oi.flush(); 205 : 206 135 : for (String ref : refs) { 207 135 : RefUpdate ru = repo.updateRef(ref); 208 135 : ru.setNewObjectId(id); 209 135 : Result result = ru.update(); 210 135 : switch (result) { 211 : case NEW: 212 135 : referenceUpdated.fire( 213 135 : project, ru, ReceiveCommand.Type.CREATE, identifiedUser.get().state()); 214 135 : break; 215 : case LOCK_FAILURE: 216 0 : throw new LockFailureException(String.format("Failed to create ref \"%s\"", ref), ru); 217 : case FAST_FORWARD: 218 : case FORCED: 219 : case IO_FAILURE: 220 : case NOT_ATTEMPTED: 221 : case NO_CHANGE: 222 : case REJECTED: 223 : case REJECTED_CURRENT_BRANCH: 224 : case RENAMED: 225 : case REJECTED_MISSING_OBJECT: 226 : case REJECTED_OTHER_REASON: 227 : default: 228 : { 229 0 : throw new IOException( 230 0 : String.format("Failed to create ref \"%s\": %s", ref, result.name())); 231 : } 232 : } 233 135 : } 234 0 : } catch (IOException e) { 235 0 : logger.atSevere().withCause(e).log("Cannot create empty commit for %s", project.get()); 236 0 : throw e; 237 135 : } 238 135 : } 239 : 240 : private void fire(Project.NameKey name, String head) { 241 144 : if (createdListeners.isEmpty()) { 242 9 : return; 243 : } 244 : 245 135 : ProjectCreator.Event event = new ProjectCreator.Event(name, head); 246 135 : createdListeners.runEach(l -> l.onNewProjectCreated(event)); 247 135 : } 248 : 249 : static class Event extends AbstractNoNotifyEvent implements NewProjectCreatedListener.Event { 250 : private final Project.NameKey name; 251 : private final String head; 252 : 253 135 : Event(Project.NameKey name, String head) { 254 135 : this.name = name; 255 135 : this.head = head; 256 135 : } 257 : 258 : @Override 259 : public String getProjectName() { 260 135 : return name.get(); 261 : } 262 : 263 : @Override 264 : public String getHeadName() { 265 135 : return head; 266 : } 267 : } 268 : }