blob: 1343ad5c6893596d9e6ed558c5319e9b23508c50 [file] [log] [blame]
// Copyright (C) 2013 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.acceptance.rest.project;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.gerrit.acceptance.rest.project.ProjectAssert.assertProjectInfo;
import static com.google.gerrit.acceptance.rest.project.ProjectAssert.assertProjectOwners;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.capabilityKey;
import static com.google.gerrit.server.project.ProjectConfig.PROJECT_CONFIG;
import static com.google.gerrit.server.update.context.RefUpdateContext.RefUpdateType.BRANCH_MODIFICATION;
import static com.google.gerrit.server.update.context.RefUpdateContext.RefUpdateType.INIT_REPO;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jgit.lib.Constants.R_HEADS;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.net.HttpHeaders;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.UseLocalDisk;
import com.google.gerrit.acceptance.config.GerritConfig;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.BooleanProjectConfig;
import com.google.gerrit.entities.InternalGroup;
import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.projects.BranchInfo;
import com.google.gerrit.extensions.api.projects.ConfigInfo;
import com.google.gerrit.extensions.api.projects.ConfigInput;
import com.google.gerrit.extensions.api.projects.ProjectInput;
import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.project.ProjectConfig;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.restapi.project.DeleteRef;
import com.google.gerrit.server.update.context.RefUpdateContext;
import com.google.gerrit.server.validators.ValidationException;
import com.google.inject.Inject;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.apache.http.HttpStatus;
import org.apache.http.message.BasicHeader;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.junit.Test;
public class CreateProjectIT extends AbstractDaemonTest {
@Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
@Inject private DeleteRef deleteRef;
@Test
public void createProjectHttp() throws Exception {
String newProjectName = name("newProject");
RestResponse r = adminRestSession.put("/projects/" + newProjectName);
r.assertCreated();
ProjectInfo p = newGson().fromJson(r.getReader(), ProjectInfo.class);
assertThat(p.name).isEqualTo(newProjectName);
// Check that we populate the label data in the HTTP path. See GetProjectIT#getProject
// for more extensive coverage of the LabelTypeInfo.
assertThat(p.labels).hasSize(1);
Optional<ProjectState> projectState = projectCache.get(Project.nameKey(newProjectName));
assertThat(projectState).isPresent();
assertProjectInfo(projectState.get().getProject(), p);
assertHead(newProjectName, "refs/heads/master");
}
@Test
public void createProjectHttpWhenProjectAlreadyExists_conflict() throws Exception {
adminRestSession.put("/projects/" + allProjects.get()).assertConflict();
}
@Test
public void createProjectHttpWhenProjectAlreadyExists_preconditionFailed() throws Exception {
adminRestSession
.putWithHeaders(
"/projects/" + allProjects.get(), new BasicHeader(HttpHeaders.IF_NONE_MATCH, "*"))
.assertPreconditionFailed();
}
@Test
public void createSameProjectFromTwoConcurrentRequests() throws Exception {
try (ExecutorService executor = Executors.newFixedThreadPool(2)) {
for (int i = 0; i < 10; i++) {
String newProjectName = name("foo" + i);
CyclicBarrier sync = new CyclicBarrier(2);
Callable<RestResponse> createProjectFoo =
() -> {
sync.await();
return adminRestSession.put("/projects/" + newProjectName);
};
Future<RestResponse> r1 = executor.submit(createProjectFoo);
Future<RestResponse> r2 = executor.submit(createProjectFoo);
assertThat(ImmutableList.of(r1.get().getStatusCode(), r2.get().getStatusCode()))
.containsAtLeast(HttpStatus.SC_CREATED, HttpStatus.SC_CONFLICT);
}
}
}
@Test
@UseLocalDisk
public void createProjectHttpWithUnreasonableName_badRequest() throws Exception {
ImmutableList<String> forbiddenStrings =
ImmutableList.of(
"/../", "/./", "//", ".git/", "?", "%", "*", ":", "<", ">", "|", "$", "/+", "~");
for (String s : forbiddenStrings) {
String projectName = name("invalid" + s + "name");
assertWithMessage("Expected status code for " + projectName + " to be 400.")
.that(adminRestSession.put("/projects/" + Url.encode(projectName)).getStatusCode())
.isEqualTo(HttpStatus.SC_BAD_REQUEST);
}
}
@Test
public void createProjectHttpWithNameMismatch_badRequest() throws Exception {
ProjectInput in = new ProjectInput();
in.name = name("otherName");
adminRestSession.put("/projects/" + name("someName"), in).assertBadRequest();
}
@Test
public void createProjectHttpWithInvalidRefName_badRequest() throws Exception {
ProjectInput in = new ProjectInput();
in.branches = Collections.singletonList(name("invalid ref name"));
adminRestSession.put("/projects/" + name("newProject"), in).assertBadRequest();
}
@Test
public void createProject() throws Exception {
String newProjectName = name("newProject");
ProjectInfo p = gApi.projects().create(newProjectName).get();
assertThat(p.name).isEqualTo(newProjectName);
Optional<ProjectState> projectState = projectCache.get(Project.nameKey(newProjectName));
assertThat(projectState).isPresent();
assertProjectInfo(projectState.get().getProject(), p);
assertHead(newProjectName, "refs/heads/master");
assertThat(readProjectConfig(newProjectName))
.hasValue("[access]\n\tinheritFrom = All-Projects\n[submit]\n\taction = inherit\n");
}
@Test
public void createProjectWithGitSuffix() throws Exception {
String newProjectName = name("newProject");
ProjectInfo p = gApi.projects().create(newProjectName + ".git").get();
assertThat(p.name).isEqualTo(newProjectName);
Optional<ProjectState> projectState = projectCache.get(Project.nameKey(newProjectName));
assertThat(projectState).isPresent();
assertProjectInfo(projectState.get().getProject(), p);
assertHead(newProjectName, "refs/heads/master");
}
@Test
public void createProjectThatEndsWithSlash() throws Exception {
String newProjectName = name("newProject");
ProjectInfo p = gApi.projects().create(newProjectName + "/").get();
assertThat(p.name).isEqualTo(newProjectName);
Optional<ProjectState> projectState = projectCache.get(Project.nameKey(newProjectName));
assertThat(projectState).isPresent();
assertProjectInfo(projectState.get().getProject(), p);
assertHead(newProjectName, "refs/heads/master");
}
@Test
public void createProjectThatContainsSlash() throws Exception {
String newProjectName = name("newProject/newProject");
ProjectInfo p = gApi.projects().create(newProjectName).get();
assertThat(p.name).isEqualTo(newProjectName);
Optional<ProjectState> projectState = projectCache.get(Project.nameKey(newProjectName));
assertThat(projectState).isPresent();
assertProjectInfo(projectState.get().getProject(), p);
assertHead(newProjectName, "refs/heads/master");
}
@Test
public void createProjectWithProperties() throws Exception {
String newProjectName = name("newProject");
ProjectInput in = new ProjectInput();
in.name = newProjectName;
in.description = "Test description";
in.submitType = SubmitType.CHERRY_PICK;
in.useContributorAgreements = InheritableBoolean.TRUE;
in.useSignedOffBy = InheritableBoolean.TRUE;
in.useContentMerge = InheritableBoolean.TRUE;
in.requireChangeId = InheritableBoolean.TRUE;
ProjectInfo p = gApi.projects().create(in).get();
assertThat(p.name).isEqualTo(newProjectName);
Project project = projectCache.get(Project.nameKey(newProjectName)).get().getProject();
assertProjectInfo(project, p);
assertThat(project.getDescription()).isEqualTo(in.description);
assertThat(project.getSubmitType()).isEqualTo(in.submitType);
assertThat(project.getBooleanConfig(BooleanProjectConfig.USE_CONTRIBUTOR_AGREEMENTS))
.isEqualTo(in.useContributorAgreements);
assertThat(project.getBooleanConfig(BooleanProjectConfig.USE_SIGNED_OFF_BY))
.isEqualTo(in.useSignedOffBy);
assertThat(project.getBooleanConfig(BooleanProjectConfig.USE_CONTENT_MERGE))
.isEqualTo(in.useContentMerge);
assertThat(project.getBooleanConfig(BooleanProjectConfig.REQUIRE_CHANGE_ID))
.isEqualTo(in.requireChangeId);
}
@Test
public void createChildProject() throws Exception {
String parentName = name("parent");
ProjectInput in = new ProjectInput();
in.name = parentName;
gApi.projects().create(in);
String childName = name("child");
in = new ProjectInput();
in.name = childName;
in.parent = parentName;
gApi.projects().create(in);
Project project = projectCache.get(Project.nameKey(childName)).get().getProject();
assertThat(project.getParentName()).isEqualTo(in.parent);
}
@Test
public void createChildProjectUnderNonExistingParent_UnprocessableEntity() throws Exception {
ProjectInput in = new ProjectInput();
in.name = name("newProjectName");
in.parent = "non-existing-project";
assertCreateFails(in, UnprocessableEntityException.class);
}
@Test
public void createProjectWithOwner() throws Exception {
String newProjectName = name("newProject");
ProjectInput in = new ProjectInput();
in.name = newProjectName;
in.owners = Lists.newArrayListWithCapacity(3);
in.owners.add("Anonymous Users"); // by name
in.owners.add(SystemGroupBackend.REGISTERED_USERS.get()); // by UUID
Optional<InternalGroup> group = groupCache.get(AccountGroup.nameKey("Administrators"));
if (group.isPresent()) {
in.owners.add(Integer.toString(group.get().getId().get())); // by ID
}
gApi.projects().create(in);
Optional<ProjectState> projectState = projectCache.get(Project.nameKey(newProjectName));
Set<AccountGroup.UUID> expectedOwnerIds = Sets.newHashSetWithExpectedSize(3);
expectedOwnerIds.add(SystemGroupBackend.ANONYMOUS_USERS);
expectedOwnerIds.add(SystemGroupBackend.REGISTERED_USERS);
expectedOwnerIds.add(groupUuid("Administrators"));
assertThat(projectState).isPresent();
assertProjectOwners(expectedOwnerIds, projectState.get());
}
@Test
public void createProjectWithNonExistingOwner_UnprocessableEntity() throws Exception {
ProjectInput in = new ProjectInput();
in.name = name("newProjectName");
in.owners = Collections.singletonList("non-existing-group");
assertCreateFails(in, UnprocessableEntityException.class);
}
@Test
public void createPermissionOnlyProject() throws Exception {
String newProjectName = name("newProject");
ProjectInput in = new ProjectInput();
in.name = newProjectName;
in.permissionsOnly = true;
gApi.projects().create(in);
assertHead(newProjectName, RefNames.REFS_CONFIG);
}
@Test
@GerritConfig(name = "gerrit.defaultBranch", value = "main")
public void createPermissionOnlyProject_WhenDefaultBranchIsSet() throws Exception {
String newProjectName = name("newProject");
ProjectInput in = new ProjectInput();
in.name = newProjectName;
in.permissionsOnly = true;
gApi.projects().create(in);
// For permissionOnly, don't use host-level default branch.
assertHead(newProjectName, RefNames.REFS_CONFIG);
}
@Test
public void createProjectWithBranches() throws Exception {
String newProjectName = name("newProject");
ProjectInput in = new ProjectInput();
in.name = newProjectName;
in.createEmptyCommit = true;
in.branches = Lists.newArrayListWithCapacity(3);
in.branches.add("refs/heads/test");
in.branches.add("refs/heads/master");
in.branches.add("release"); // without 'refs/heads' prefix
gApi.projects().create(in);
assertHead(newProjectName, "refs/heads/test");
assertEmptyCommit(newProjectName, "refs/heads/test", "refs/heads/master", "refs/heads/release");
}
@Test
public void createProjectWithInvalidBranch() throws Exception {
String newProjectName = name("newProject");
ProjectInput in = new ProjectInput();
in.name = newProjectName;
in.createEmptyCommit = true;
in.branches = ImmutableList.of("refs/heads/test", "refs/changes/34/1234");
Throwable thrown =
assertThrows(ResourceConflictException.class, () -> gApi.projects().create(in));
assertThat(thrown).hasCauseThat().isInstanceOf(ValidationException.class);
assertThat(thrown)
.hasMessageThat()
.contains("Cannot create a project with branch refs/changes/34/1234");
}
@Test
@GerritConfig(name = "gerrit.defaultBranch", value = "main")
public void createProject_WhenDefaultBranchIsSet() throws Exception {
String newProjectName = name("newProject");
gApi.projects().create(newProjectName);
ImmutableMap<String, BranchInfo> branches = getProjectBranches(newProjectName);
// HEAD symbolic ref is set to the default, but the actual ref is not created.
assertThat(branches.keySet()).containsExactly("HEAD", "refs/meta/config");
assertHead(newProjectName, "refs/heads/main");
}
@Test
@GerritConfig(name = "gerrit.defaultBranch", value = "main")
public void createProjectWithEmptyCommit_WhenDefaultBranchIsSet() throws Exception {
String newProjectName = name("newProject");
ProjectInput in = new ProjectInput();
in.name = newProjectName;
in.createEmptyCommit = true;
gApi.projects().create(in);
ImmutableMap<String, BranchInfo> branches = getProjectBranches(newProjectName);
// HEAD symbolic ref is set to the default, and the actual ref is created.
assertThat(branches.keySet()).containsExactly("HEAD", "refs/meta/config", "refs/heads/main");
assertHead(newProjectName, "refs/heads/main");
assertEmptyCommit(newProjectName, "HEAD", "refs/heads/main");
}
@Test
@GerritConfig(name = "gerrit.defaultBranch", value = "refs/heads/main")
public void createProject_WhenDefaultBranchIsSet_WithBranches() throws Exception {
// Host-level default only applies if no branches were passed in the input
String newProjectName = name("newProject");
ProjectInput in = new ProjectInput();
in.name = newProjectName;
in.createEmptyCommit = true;
in.branches = ImmutableList.of("refs/heads/test", "release");
gApi.projects().create(in);
ImmutableMap<String, BranchInfo> branches = getProjectBranches(newProjectName);
assertThat(branches.keySet())
.containsExactly("HEAD", "refs/meta/config", "refs/heads/test", "refs/heads/release");
assertHead(newProjectName, "refs/heads/test");
assertEmptyCommit(newProjectName, "refs/heads/test", "refs/heads/release");
}
@Test
@GerritConfig(name = "gerrit.defaultBranch", value = "refs/users/self")
public void createProject_WhenDefaultBranchIsSet_ToGerritRef() throws Exception {
String newProjectName = name("newProject");
Throwable thrown =
assertThrows(ResourceConflictException.class, () -> gApi.projects().create(newProjectName));
assertThat(thrown).hasCauseThat().isInstanceOf(ValidationException.class);
assertThat(thrown)
.hasMessageThat()
.contains("Cannot create a project with branch refs/users/self");
}
@Test
@GerritConfig(name = "gerrit.defaultBranch", value = "refs~main")
public void createProject_WhenDefaultBranchIsSet_ToInvalidBranch() throws Exception {
String newProjectName = name("newProject");
Throwable thrown =
assertThrows(BadRequestException.class, () -> gApi.projects().create(newProjectName));
assertThat(thrown)
.hasMessageThat()
.isEqualTo("Branch \"refs/heads/refs~main\" is not a valid name.");
}
@Test
public void createProjectWithCapability() throws Exception {
projectOperations
.allProjectsForUpdate()
.add(
allowCapability(GlobalCapability.CREATE_PROJECT)
.group(SystemGroupBackend.REGISTERED_USERS))
.update();
try {
requestScopeOperations.setApiUser(user.id());
ProjectInput in = new ProjectInput();
in.name = name("newProject");
ProjectInfo p = gApi.projects().create(in).get();
assertThat(p.name).isEqualTo(in.name);
} finally {
projectOperations
.allProjectsForUpdate()
.remove(
capabilityKey(GlobalCapability.CREATE_PROJECT)
.group(SystemGroupBackend.REGISTERED_USERS))
.update();
}
}
@Test
public void createProjectWithoutCapability_Forbidden() throws Exception {
requestScopeOperations.setApiUser(user.id());
ProjectInput in = new ProjectInput();
in.name = name("newProject");
assertCreateFails(in, AuthException.class);
}
@Test
public void createProjectWhenProjectAlreadyExists_Conflict() throws Exception {
ProjectInput in = new ProjectInput();
in.name = allProjects.get();
assertCreateFails(in, ResourceConflictException.class);
}
@Test
public void createProjectWithCreateProjectCapabilityAndParentNotVisible() throws Exception {
try (ProjectConfigUpdate u = updateProject(allProjects)) {
u.getConfig()
.updateProject(p -> p.setState(com.google.gerrit.extensions.client.ProjectState.HIDDEN));
u.save();
}
projectOperations
.allProjectsForUpdate()
.add(
allowCapability(GlobalCapability.CREATE_PROJECT)
.group(SystemGroupBackend.REGISTERED_USERS))
.update();
try {
requestScopeOperations.setApiUser(user.id());
ProjectInput in = new ProjectInput();
in.name = name("newProject");
ProjectInfo p = gApi.projects().create(in).get();
assertThat(p.name).isEqualTo(in.name);
} finally {
try (ProjectConfigUpdate u = updateProject(allProjects)) {
u.getConfig()
.updateProject(
p -> p.setState(com.google.gerrit.extensions.client.ProjectState.ACTIVE));
u.save();
}
projectOperations
.allProjectsForUpdate()
.remove(
capabilityKey(GlobalCapability.CREATE_PROJECT)
.group(SystemGroupBackend.REGISTERED_USERS))
.update();
}
}
@SuppressWarnings("deprecation")
@Test
public void createProjectWithDefaultInheritedSubmitType() throws Exception {
String parent = name("parent");
ProjectInput pin = new ProjectInput();
pin.name = parent;
ConfigInfo cfg = gApi.projects().create(pin).config();
assertThat(cfg.submitType).isEqualTo(SubmitType.MERGE_IF_NECESSARY);
assertThat(cfg.defaultSubmitType.value).isEqualTo(SubmitType.MERGE_IF_NECESSARY);
assertThat(cfg.defaultSubmitType.configuredValue).isEqualTo(SubmitType.INHERIT);
assertThat(cfg.defaultSubmitType.inheritedValue).isEqualTo(SubmitType.MERGE_IF_NECESSARY);
ConfigInput cin = new ConfigInput();
cin.submitType = SubmitType.CHERRY_PICK;
gApi.projects().name(parent).config(cin);
cfg = gApi.projects().name(parent).config();
assertThat(cfg.submitType).isEqualTo(SubmitType.CHERRY_PICK);
assertThat(cfg.defaultSubmitType.value).isEqualTo(SubmitType.CHERRY_PICK);
assertThat(cfg.defaultSubmitType.configuredValue).isEqualTo(SubmitType.CHERRY_PICK);
assertThat(cfg.defaultSubmitType.inheritedValue).isEqualTo(SubmitType.MERGE_IF_NECESSARY);
String child = name("child");
pin = new ProjectInput();
pin.submitType = SubmitType.INHERIT;
pin.parent = parent;
pin.name = child;
cfg = gApi.projects().create(pin).config();
assertThat(cfg.submitType).isEqualTo(SubmitType.CHERRY_PICK);
assertThat(cfg.defaultSubmitType.value).isEqualTo(SubmitType.CHERRY_PICK);
assertThat(cfg.defaultSubmitType.configuredValue).isEqualTo(SubmitType.INHERIT);
assertThat(cfg.defaultSubmitType.inheritedValue).isEqualTo(SubmitType.CHERRY_PICK);
cin = new ConfigInput();
cin.submitType = SubmitType.REBASE_IF_NECESSARY;
gApi.projects().name(parent).config(cin);
cfg = gApi.projects().name(parent).config();
assertThat(cfg.submitType).isEqualTo(SubmitType.REBASE_IF_NECESSARY);
assertThat(cfg.defaultSubmitType.value).isEqualTo(SubmitType.REBASE_IF_NECESSARY);
assertThat(cfg.defaultSubmitType.configuredValue).isEqualTo(SubmitType.REBASE_IF_NECESSARY);
assertThat(cfg.defaultSubmitType.inheritedValue).isEqualTo(SubmitType.MERGE_IF_NECESSARY);
cfg = gApi.projects().name(child).config();
assertThat(cfg.submitType).isEqualTo(SubmitType.REBASE_IF_NECESSARY);
assertThat(cfg.defaultSubmitType.value).isEqualTo(SubmitType.REBASE_IF_NECESSARY);
assertThat(cfg.defaultSubmitType.configuredValue).isEqualTo(SubmitType.INHERIT);
assertThat(cfg.defaultSubmitType.inheritedValue).isEqualTo(SubmitType.REBASE_IF_NECESSARY);
}
@SuppressWarnings("deprecation")
@Test
@GerritConfig(
name = "repository.testinheritedsubmittype/*.defaultSubmitType",
value = "CHERRY_PICK")
public void repositoryConfigTakesPrecedenceOverInheritedSubmitType() throws Exception {
// Can't use name() since we need to specify this project name in gerrit.config prior to
// startup. Pick something reasonably unique instead.
String parent = "testinheritedsubmittype";
ProjectInput pin = new ProjectInput();
pin.name = parent;
pin.submitType = SubmitType.MERGE_ALWAYS;
ConfigInfo cfg = gApi.projects().create(pin).config();
assertThat(cfg.submitType).isEqualTo(SubmitType.MERGE_ALWAYS);
assertThat(cfg.defaultSubmitType.value).isEqualTo(SubmitType.MERGE_ALWAYS);
assertThat(cfg.defaultSubmitType.configuredValue).isEqualTo(SubmitType.MERGE_ALWAYS);
assertThat(cfg.defaultSubmitType.inheritedValue).isEqualTo(SubmitType.MERGE_IF_NECESSARY);
String child = parent + "/child";
pin = new ProjectInput();
pin.parent = parent;
pin.name = child;
cfg = gApi.projects().create(pin).config();
assertThat(cfg.submitType).isEqualTo(SubmitType.CHERRY_PICK);
assertThat(cfg.defaultSubmitType.value).isEqualTo(SubmitType.CHERRY_PICK);
assertThat(cfg.defaultSubmitType.configuredValue).isEqualTo(SubmitType.CHERRY_PICK);
assertThat(cfg.defaultSubmitType.inheritedValue).isEqualTo(SubmitType.MERGE_ALWAYS);
}
@Test
public void initOnly_noOpIfProjectHasAlreadyBeenInitialized() throws Exception {
Project.NameKey projectName = Project.nameKey(name("newProject"));
ProjectInput projectInput = new ProjectInput();
projectInput.name = projectName.get();
projectInput.createEmptyCommit = true;
projectInput.branches = ImmutableList.of("refs/heads/master", "refs/heads/stable");
gApi.projects().create(projectInput);
assertHead(projectName.get(), "refs/heads/master");
assertThat(readProjectConfig(projectName.get()))
.hasValue("[access]\n\tinheritFrom = All-Projects\n[submit]\n\taction = inherit\n");
assertEmptyCommit(projectName.get(), "refs/heads/master", "refs/heads/stable");
RevCommit headRefsMetaConfig =
projectOperations.project(projectName).getHead("refs/meta/config");
RevCommit headMaster = projectOperations.project(projectName).getHead("refs/heads/master");
RevCommit headStable = projectOperations.project(projectName).getHead("refs/heads/stable");
// Test that rerunning the project init doesn't change anything.
// We must make the create project request via REST since the gApi refuses to do the request if
// the project already exists.
projectInput.initOnly = true;
RestResponse r = adminRestSession.put("/projects/" + projectName.get(), projectInput);
r.assertOK();
assertHead(projectName.get(), "refs/heads/master");
assertThat(readProjectConfig(projectName.get()))
.hasValue("[access]\n\tinheritFrom = All-Projects\n[submit]\n\taction = inherit\n");
assertEmptyCommit(projectName.get(), "refs/heads/master", "refs/heads/stable");
assertThat(projectOperations.project(projectName).getHead("refs/meta/config"))
.isEqualTo(headRefsMetaConfig);
assertThat(projectOperations.project(projectName).getHead("refs/heads/master"))
.isEqualTo(headMaster);
assertThat(projectOperations.project(projectName).getHead("refs/heads/stable"))
.isEqualTo(headStable);
}
@Test
public void initOnly_cannotResetHead() throws Exception {
ProjectInput projectInput = new ProjectInput();
projectInput.name = name("newProject");
projectInput.createEmptyCommit = true;
projectInput.branches = ImmutableList.of("refs/heads/master", "refs/heads/stable");
gApi.projects().create(projectInput);
assertHead(projectInput.name, "refs/heads/master");
// Set HEAD to another branch.
try (RefUpdateContext ctx = RefUpdateContext.open(INIT_REPO);
Repository repo = repoManager.openRepository(Project.nameKey(projectInput.name))) {
RefUpdate u = repo.updateRef(Constants.HEAD);
u.disableRefLog();
u.link("refs/heads/stable");
}
assertHead(projectInput.name, "refs/heads/stable");
// Rerunning the project doesn't override the existing head.
// We must make the create project request via REST since the gApi refuses to do the request if
// the project already exists.
projectInput.initOnly = true;
RestResponse r = adminRestSession.put("/projects/" + projectInput.name, projectInput);
r.assertConflict();
assertHead(projectInput.name, "refs/heads/stable");
}
@Test
public void initOnly_createMissingRefsMetaConfig() throws Exception {
ProjectInput projectInput = new ProjectInput();
projectInput.name = name("newProject");
projectInput.createEmptyCommit = true;
projectInput.branches = ImmutableList.of("refs/heads/master", "refs/heads/stable");
gApi.projects().create(projectInput);
assertThat(readProjectConfig(projectInput.name))
.hasValue("[access]\n\tinheritFrom = All-Projects\n[submit]\n\taction = inherit\n");
// Delete refs/meta/config.
try (RefUpdateContext ctx = RefUpdateContext.open(BRANCH_MODIFICATION)) {
deleteRef.deleteSingleRef(
projectCache.get(Project.nameKey(projectInput.name)).get(), "refs/meta/config", R_HEADS);
}
// Rerunning the project init creates refs/meta/config.
// We must make the create project request via REST since the gApi refuses to do the request if
// the project already exists.
projectInput.initOnly = true;
RestResponse r = adminRestSession.put("/projects/" + projectInput.name, projectInput);
r.assertOK();
assertThat(readProjectConfig(projectInput.name))
.hasValue("[access]\n\tinheritFrom = All-Projects\n[submit]\n\taction = inherit\n");
}
@Test
public void initOnly_cannotResetProjectConfig() throws Exception {
ProjectInput projectInput = new ProjectInput();
projectInput.name = name("newProject");
projectInput.createEmptyCommit = true;
projectInput.branches = ImmutableList.of("refs/heads/master", "refs/heads/stable");
gApi.projects().create(projectInput);
assertThat(readProjectConfig(projectInput.name))
.hasValue("[access]\n\tinheritFrom = All-Projects\n[submit]\n\taction = inherit\n");
// Update project.config
try (MetaDataUpdate md = metaDataUpdateFactory.create(Project.nameKey(projectInput.name))) {
ProjectConfig config = projectConfigFactory.read(md);
config.updateProject(p -> p.setParent(project));
config.commit(md, false);
}
assertThat(readProjectConfig(projectInput.name))
.hasValue(
String.format("[access]\n\tinheritFrom = %s\n[submit]\n\taction = inherit\n", project));
// Rerunning the project init doesn't update refs/meta/config.
// We must make the create project request via REST since the gApi refuses to do the request if
// the project already exists.
projectInput.initOnly = true;
RestResponse r = adminRestSession.put("/projects/" + projectInput.name, projectInput);
r.assertConflict();
assertThat(readProjectConfig(projectInput.name))
.hasValue(
String.format("[access]\n\tinheritFrom = %s\n[submit]\n\taction = inherit\n", project));
}
@Test
public void initOnly_createMissingBranches() throws Exception {
ProjectInput projectInput = new ProjectInput();
projectInput.name = name("newProject");
projectInput.createEmptyCommit = true;
projectInput.branches = ImmutableList.of("refs/heads/master", "refs/heads/stable");
gApi.projects().create(projectInput);
assertEmptyCommit(projectInput.name, "refs/heads/master", "refs/heads/stable");
// Delete the branches.
try (RefUpdateContext ctx = RefUpdateContext.open(BRANCH_MODIFICATION)) {
deleteRef.deleteSingleRef(
projectCache.get(Project.nameKey(projectInput.name)).get(), "refs/heads/master", R_HEADS);
deleteRef.deleteSingleRef(
projectCache.get(Project.nameKey(projectInput.name)).get(), "refs/heads/stable", R_HEADS);
}
// Rerunning the project init creates the branches.
// We must make the create project request via REST since the gApi refuses to do the request if
// the project already exists.
projectInput.initOnly = true;
RestResponse r = adminRestSession.put("/projects/" + projectInput.name, projectInput);
r.assertOK();
assertEmptyCommit(projectInput.name, "refs/heads/master", "refs/heads/stable");
}
@Test
public void initOnly_cannotCreateAdditionalBranches() throws Exception {
ProjectInput projectInput = new ProjectInput();
projectInput.name = name("newProject");
projectInput.createEmptyCommit = true;
projectInput.branches = ImmutableList.of("refs/heads/master", "refs/heads/stable");
gApi.projects().create(projectInput);
assertEmptyCommit(projectInput.name, "refs/heads/master", "refs/heads/stable");
// Rerunning the project init doesn't create additional branches.
// We must make the create project request via REST since the gApi refuses to do the request if
// the project already exists.
projectInput.initOnly = true;
projectInput.branches =
ImmutableList.of("refs/heads/master", "refs/heads/stable", "refs/heads/test");
RestResponse r = adminRestSession.put("/projects/" + projectInput.name, projectInput);
r.assertConflict();
assertEmptyCommit(projectInput.name, "refs/heads/master", "refs/heads/stable");
assertThat(
projectOperations
.project(Project.nameKey(projectInput.name))
.hasHead("refs/heads/test"))
.isFalse();
}
@Test
public void initOnly_requiresCreateProjectCapability() throws Exception {
ProjectInput projectInput = new ProjectInput();
projectInput.name = name("newProject");
projectInput.createEmptyCommit = true;
projectInput.branches = ImmutableList.of("refs/heads/master", "refs/heads/stable");
gApi.projects().create(projectInput);
assertHead(projectInput.name, "refs/heads/master");
assertThat(readProjectConfig(projectInput.name))
.hasValue("[access]\n\tinheritFrom = All-Projects\n[submit]\n\taction = inherit\n");
assertEmptyCommit(projectInput.name, "refs/heads/master", "refs/heads/stable");
// Test that rerunning the project init is rejected for non-admin user.
// We must make the create project request via REST since the gApi refuses to do the request if
// the project already exists.
projectInput.initOnly = true;
RestResponse r = userRestSession.put("/projects/" + projectInput.name, projectInput);
r.assertForbidden();
}
private void assertEmptyCommit(String projectName, String... refs) throws Exception {
Project.NameKey projectKey = Project.nameKey(projectName);
try (Repository repo = repoManager.openRepository(projectKey);
RevWalk rw = new RevWalk(repo);
TreeWalk tw = new TreeWalk(rw.getObjectReader())) {
for (String ref : refs) {
RevCommit commit = rw.lookupCommit(repo.exactRef(ref).getObjectId());
rw.parseBody(commit);
tw.addTree(commit.getTree());
assertThat(tw.next()).isFalse();
tw.reset();
}
}
}
private void assertCreateFails(ProjectInput in, Class<? extends RestApiException> errType)
throws Exception {
assertThrows(errType, () -> gApi.projects().create(in));
}
private Optional<String> readProjectConfig(String projectName) throws Exception {
try (Repository repo = repoManager.openRepository(Project.nameKey(projectName));
TestRepository<Repository> tr = new TestRepository<>(repo)) {
RevWalk rw = tr.getRevWalk();
Ref ref = repo.exactRef(RefNames.REFS_CONFIG);
if (ref == null) {
return Optional.empty();
}
ObjectLoader obj =
rw.getObjectReader()
.open(tr.get(rw.parseTree(ref.getObjectId()), PROJECT_CONFIG), Constants.OBJ_BLOB);
return Optional.of(new String(obj.getCachedBytes(Integer.MAX_VALUE), UTF_8));
}
}
}