diff options
12 files changed, 355 insertions, 12 deletions
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDao.java index 50ff5f66deb..2e4fcb111d4 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDao.java @@ -49,6 +49,11 @@ public class BranchDao implements Dao { } } + public int updateMainBranchName(DbSession dbSession, String projectUuid, String newBranchKey) { + long now = system2.now(); + return mapper(dbSession).updateMainBranchName(projectUuid, newBranchKey, now); + } + public Optional<BranchDto> selectByKey(DbSession dbSession, String projectUuid, BranchKeyType keyType, @Nullable String key) { String keyInDb = BranchDto.convertKeyToDb(key); return Optional.ofNullable(mapper(dbSession).selectByKey(projectUuid, keyType, keyInDb)); @@ -70,7 +75,6 @@ public class BranchDao implements Dao { return Optional.ofNullable(mapper(session).selectByUuid(uuid)); } - private static BranchMapper mapper(DbSession dbSession) { return dbSession.getMapper(BranchMapper.class); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchMapper.java index 0c978360aec..ae4fc73dc03 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchMapper.java @@ -29,6 +29,8 @@ public interface BranchMapper { int update(@Param("dto") BranchDto dto, @Param("now") long now); + int updateMainBranchName(@Param("projectUuid") String projectUuid, @Param("newBranchName") String newBranchName, @Param("now") long now); + BranchDto selectByKey(@Param("projectUuid") String projectUuid, @Param("keyType") BranchKeyType keyType, @Param("key") String key); diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/BranchMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/BranchMapper.xml index 9c39fff0ac1..a3a24d7913f 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/BranchMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/BranchMapper.xml @@ -36,6 +36,15 @@ ) </insert> + <update id="updateMainBranchName" parameterType="map"> + update project_branches + set + kee = #{newBranchName, jdbcType=VARCHAR}, + updated_at = #{now, jdbcType=BIGINT} + where + uuid = #{projectUuid, jdbcType=VARCHAR} + </update> + <update id="update" parameterType="map" useGeneratedKeys="false"> update project_branches set diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/component/BranchDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/component/BranchDaoTest.java index 616a6e1cde1..43b6f6aa14b 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/component/BranchDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/component/BranchDaoTest.java @@ -69,8 +69,34 @@ public class BranchDaoTest { entry("mergeBranchUuid", null), entry("pullRequestTitle", null), entry("createdAt", 1_000L), - entry("updatedAt", 1_000L) - ); + entry("updatedAt", 1_000L)); + } + + @Test + public void update_main_branch_name() { + BranchDto dto = new BranchDto(); + dto.setProjectUuid("U1"); + dto.setUuid("U1"); + dto.setBranchType(BranchType.LONG); + dto.setKeeType(BranchKeyType.BRANCH); + dto.setKey(null); + underTest.insert(dbSession, dto); + + BranchDto dto2 = new BranchDto(); + dto2.setProjectUuid("U2"); + dto2.setUuid("U2"); + dto2.setBranchType(BranchType.LONG); + dto2.setKeeType(BranchKeyType.BRANCH); + dto2.setKey("branch"); + underTest.insert(dbSession, dto2); + + underTest.updateMainBranchName(dbSession, "U1", "master"); + BranchDto loaded = underTest.selectByKey(dbSession, "U1", BranchKeyType.BRANCH, "master").get(); + assertThat(loaded.getMergeBranchUuid()).isNull(); + assertThat(loaded.getPullRequestTitle()).isNull(); + assertThat(loaded.getProjectUuid()).isEqualTo("U1"); + assertThat(loaded.getBranchType()).isEqualTo(BranchType.LONG); + assertThat(loaded.getKeeType()).isEqualTo(BranchKeyType.BRANCH); } @Test @@ -87,11 +113,11 @@ public class BranchDaoTest { underTest.insert(dbSession, dto); Map<String, Object> map = db.selectFirst(dbSession, SELECT_FROM + " where uuid='" + dto.getUuid() + "'"); - assertThat((String)map.get("projectUuid")).contains("a").isEqualTo(dto.getProjectUuid()); - assertThat((String)map.get("uuid")).contains("b").isEqualTo(dto.getUuid()); - assertThat((String)map.get("kee")).contains("c").isEqualTo(dto.getKey()); - assertThat((String)map.get("mergeBranchUuid")).contains("d").isEqualTo(dto.getMergeBranchUuid()); - assertThat((String)map.get("pullRequestTitle")).contains("e").isEqualTo(dto.getPullRequestTitle()); + assertThat((String) map.get("projectUuid")).contains("a").isEqualTo(dto.getProjectUuid()); + assertThat((String) map.get("uuid")).contains("b").isEqualTo(dto.getUuid()); + assertThat((String) map.get("kee")).contains("c").isEqualTo(dto.getKey()); + assertThat((String) map.get("mergeBranchUuid")).contains("d").isEqualTo(dto.getMergeBranchUuid()); + assertThat((String) map.get("pullRequestTitle")).contains("e").isEqualTo(dto.getPullRequestTitle()); } @Test @@ -183,7 +209,7 @@ public class BranchDaoTest { assertThat(underTest.selectByUuids(db.getSession(), asList(branch1.uuid(), branch2.uuid(), branch3.uuid()))) .extracting(BranchDto::getUuid) - .containsExactlyInAnyOrder(branch1.uuid(), branch2.uuid(), branch3.uuid()); + .containsExactlyInAnyOrder(branch1.uuid(), branch2.uuid(), branch3.uuid()); assertThat(underTest.selectByUuids(db.getSession(), singletonList(branch1.uuid()))) .extracting(BranchDto::getUuid) .containsExactlyInAnyOrder(branch1.uuid()); diff --git a/server/sonar-server/src/main/java/org/sonar/server/projectbranch/ws/BranchWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/projectbranch/ws/BranchWsModule.java index d47f197991c..270ceef8ad9 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/projectbranch/ws/BranchWsModule.java +++ b/server/sonar-server/src/main/java/org/sonar/server/projectbranch/ws/BranchWsModule.java @@ -28,6 +28,7 @@ public class BranchWsModule extends Module { ListAction.class, ShowAction.class, DeleteAction.class, + RenameAction.class, BranchesWs.class); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/projectbranch/ws/DeleteAction.java b/server/sonar-server/src/main/java/org/sonar/server/projectbranch/ws/DeleteAction.java index 89e3b9bb631..fa61c63d39e 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/projectbranch/ws/DeleteAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/projectbranch/ws/DeleteAction.java @@ -38,7 +38,7 @@ import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; import static org.sonar.server.ws.WsUtils.checkFoundWithOptional; import static org.sonarqube.ws.client.projectbranches.ProjectBranchesParameters.ACTION_DELETE; import static org.sonarqube.ws.client.projectbranches.ProjectBranchesParameters.PARAM_BRANCH; -import static org.sonarqube.ws.client.projectbranches.ProjectBranchesParameters.PARAM_PROJECT;; +import static org.sonarqube.ws.client.projectbranches.ProjectBranchesParameters.PARAM_PROJECT; public class DeleteAction implements BranchWsAction { private final DbClient dbClient; @@ -99,7 +99,7 @@ public class DeleteAction implements BranchWsAction { } private void checkPermission(ComponentDto project) { - userSession.hasComponentPermission(UserRole.ADMIN, project); + userSession.checkComponentPermission(UserRole.ADMIN, project); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/projectbranch/ws/RenameAction.java b/server/sonar-server/src/main/java/org/sonar/server/projectbranch/ws/RenameAction.java new file mode 100644 index 00000000000..3cf4e7d93fa --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/projectbranch/ws/RenameAction.java @@ -0,0 +1,99 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.projectbranch.ws; + +import java.util.Optional; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.api.server.ws.WebService.NewController; +import org.sonar.api.web.UserRole; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.component.BranchDto; +import org.sonar.db.component.BranchKeyType; +import org.sonar.db.component.ComponentDto; +import org.sonar.server.component.ComponentFinder; +import org.sonar.server.user.UserSession; + +import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; +import static org.sonarqube.ws.client.projectbranches.ProjectBranchesParameters.ACTION_RENAME; +import static org.sonarqube.ws.client.projectbranches.ProjectBranchesParameters.PARAM_BRANCH; +import static org.sonarqube.ws.client.projectbranches.ProjectBranchesParameters.PARAM_PROJECT; + +public class RenameAction implements BranchWsAction { + private final ComponentFinder componentFinder; + private final UserSession userSession; + private final DbClient dbClient; + + public RenameAction(DbClient dbClient, ComponentFinder componentFinder, UserSession userSession) { + this.dbClient = dbClient; + this.componentFinder = componentFinder; + this.userSession = userSession; + } + + @Override + public void define(NewController context) { + WebService.NewAction action = context.createAction(ACTION_RENAME) + .setSince("6.6") + .setDescription("Rename the main branch of a project. <br />" + + "Requires 'Administer' permission on the specified project.") + .setInternal(true) + .setPost(true) + .setHandler(this); + + action + .createParam(PARAM_PROJECT) + .setDescription("Project key") + .setExampleValue(KEY_PROJECT_EXAMPLE_001) + .setRequired(true); + action + .createParam(PARAM_BRANCH) + .setDescription("New name of the main branch") + .setExampleValue("master") + .setRequired(true); + } + + @Override + public void handle(Request request, Response response) throws Exception { + userSession.checkLoggedIn(); + String projectKey = request.mandatoryParam(PARAM_PROJECT); + String branchKey = request.mandatoryParam(PARAM_BRANCH); + + try (DbSession dbSession = dbClient.openSession(false)) { + ComponentDto project = componentFinder.getRootComponentByUuidOrKey(dbSession, null, projectKey); + checkPermission(project); + + Optional<BranchDto> branch = dbClient.branchDao().selectByKey(dbSession, project.uuid(), BranchKeyType.BRANCH, branchKey); + if (branch.isPresent() && !branch.get().isMain()) { + throw new IllegalArgumentException("Impossible to update branch name: a branch with name \"" + branchKey + "\" already exists in the project."); + } + + dbClient.branchDao().updateMainBranchName(dbSession, project.uuid(), branchKey); + dbSession.commit(); + response.noContent(); + } + } + + private void checkPermission(ComponentDto project) { + userSession.checkComponentPermission(UserRole.ADMIN, project); + } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/projectbranch/ws/BranchWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/projectbranch/ws/BranchWsModuleTest.java index 2ecb8a885a2..8018bcb7094 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/projectbranch/ws/BranchWsModuleTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/projectbranch/ws/BranchWsModuleTest.java @@ -30,6 +30,6 @@ public class BranchWsModuleTest { public void verify_count_of_added_components() { ComponentContainer container = new ComponentContainer(); new BranchWsModule().configure(container); - assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 4); + assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 5); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/projectbranch/ws/RenameActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/projectbranch/ws/RenameActionTest.java new file mode 100644 index 00000000000..bab33b6927e --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/projectbranch/ws/RenameActionTest.java @@ -0,0 +1,181 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.projectbranch.ws; + +import java.util.Optional; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.resources.ResourceTypes; +import org.sonar.api.server.ws.WebService; +import org.sonar.api.utils.System2; +import org.sonar.api.web.UserRole; +import org.sonar.db.DbTester; +import org.sonar.db.component.BranchDto; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.component.ResourceTypesRule; +import org.sonar.server.component.ComponentFinder; +import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.exceptions.UnauthorizedException; +import org.sonar.server.tester.UserSessionRule; +import org.sonar.server.ws.WsActionTester; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.api.resources.Qualifiers.PROJECT; + +public class RenameActionTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + @Rule + public DbTester db = DbTester.create(System2.INSTANCE); + @Rule + public UserSessionRule userSession = UserSessionRule.standalone(); + + private ResourceTypes resourceTypes = new ResourceTypesRule().setRootQualifiers(PROJECT); + private ComponentFinder componentFinder = new ComponentFinder(db.getDbClient(), resourceTypes); + private WsActionTester tester = new WsActionTester(new RenameAction(db.getDbClient(), componentFinder, userSession)); + + @Test + public void test_definition() { + WebService.Action definition = tester.getDef(); + assertThat(definition.key()).isEqualTo("rename"); + assertThat(definition.isPost()).isTrue(); + assertThat(definition.isInternal()).isTrue(); + assertThat(definition.params()).extracting(WebService.Param::key).containsExactlyInAnyOrder("project", "branch"); + assertThat(definition.since()).isEqualTo("6.6"); + } + + @Test + public void fail_if_missing_project_parameter() { + userSession.logIn(); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("The 'project' parameter is missing"); + + tester.newRequest().execute(); + } + + @Test + public void fail_if_missing_branch_parameter() { + userSession.logIn(); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("The 'branch' parameter is missing"); + + tester.newRequest().setParam("project", "projectName").execute(); + } + + @Test + public void fail_if_not_logged_in() { + expectedException.expect(UnauthorizedException.class); + expectedException.expectMessage("Authentication is required"); + + tester.newRequest().execute(); + } + + @Test + public void fail_if_no_administer_permission() { + userSession.logIn(); + ComponentDto project = db.components().insertMainBranch(); + + expectedException.expect(ForbiddenException.class); + expectedException.expectMessage("Insufficient privileges"); + + tester.newRequest() + .setParam("project", project.getKey()) + .setParam("branch", "branch1") + .execute(); + } + + @Test + public void successfully_rename() { + userSession.logIn(); + ComponentDto project = db.components().insertMainBranch(); + ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("branch")); + userSession.addProjectPermission(UserRole.ADMIN, project); + + tester.newRequest() + .setParam("project", project.getKey()) + .setParam("branch", "master") + .execute(); + + assertThat(db.countRowsOfTable("project_branches")).isEqualTo(2); + Optional<BranchDto> mainBranch = db.getDbClient().branchDao().selectByUuid(db.getSession(), project.uuid()); + assertThat(mainBranch.get().getKey()).isEqualTo("master"); + + Optional<BranchDto> unchangedBranch = db.getDbClient().branchDao().selectByUuid(db.getSession(), branch.uuid()); + assertThat(unchangedBranch.get().getKey()).isEqualTo("branch"); + } + + @Test + public void successfully_rename_with_same_name() { + userSession.logIn(); + ComponentDto project = db.components().insertMainBranch(); + ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("branch")); + userSession.addProjectPermission(UserRole.ADMIN, project); + + tester.newRequest() + .setParam("project", project.getKey()) + .setParam("branch", "master") + .execute(); + + tester.newRequest() + .setParam("project", project.getKey()) + .setParam("branch", "master") + .execute(); + + assertThat(db.countRowsOfTable("project_branches")).isEqualTo(2); + Optional<BranchDto> mainBranch = db.getDbClient().branchDao().selectByUuid(db.getSession(), project.uuid()); + assertThat(mainBranch.get().getKey()).isEqualTo("master"); + + Optional<BranchDto> unchangedBranch = db.getDbClient().branchDao().selectByUuid(db.getSession(), branch.uuid()); + assertThat(unchangedBranch.get().getKey()).isEqualTo("branch"); + } + + @Test + public void fail_if_name_already_used() { + userSession.logIn(); + ComponentDto project = db.components().insertMainBranch(); + userSession.addProjectPermission(UserRole.ADMIN, project); + db.components().insertProjectBranch(project, b -> b.setKey("branch")); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Impossible to update branch name: a branch with name \"branch\" already exists"); + + tester.newRequest() + .setParam("project", project.getKey()) + .setParam("branch", "branch") + .execute(); + } + + @Test + public void fail_if_project_does_not_exist() { + userSession.logIn(); + + expectedException.expect(NotFoundException.class); + expectedException.expectMessage("Project key 'foo' not found"); + + tester.newRequest() + .setParam("project", "foo") + .setParam("branch", "branch1") + .execute(); + } +} diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/projectbranches/ProjectBranchesParameters.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/projectbranches/ProjectBranchesParameters.java index c1370b92093..32501503293 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/projectbranches/ProjectBranchesParameters.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/projectbranches/ProjectBranchesParameters.java @@ -27,6 +27,7 @@ public class ProjectBranchesParameters { public static final String ACTION_LIST = "list"; public static final String ACTION_SHOW = "show"; public static final String ACTION_DELETE = "delete"; + public static final String ACTION_RENAME = "rename"; // parameters public static final String PARAM_PROJECT = "project"; diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/projectbranches/ProjectBranchesService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/projectbranches/ProjectBranchesService.java index 0a0b23a09aa..52d25fb4614 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/projectbranches/ProjectBranchesService.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/projectbranches/ProjectBranchesService.java @@ -29,6 +29,7 @@ import org.sonarqube.ws.client.WsConnector; import static org.sonarqube.ws.client.projectbranches.ProjectBranchesParameters.ACTION_LIST; import static org.sonarqube.ws.client.projectbranches.ProjectBranchesParameters.ACTION_SHOW; import static org.sonarqube.ws.client.projectbranches.ProjectBranchesParameters.ACTION_DELETE; +import static org.sonarqube.ws.client.projectbranches.ProjectBranchesParameters.ACTION_RENAME; import static org.sonarqube.ws.client.projectbranches.ProjectBranchesParameters.CONTROLLER; import static org.sonarqube.ws.client.projectbranches.ProjectBranchesParameters.PARAM_BRANCH; import static org.sonarqube.ws.client.projectbranches.ProjectBranchesParameters.PARAM_PROJECT; @@ -59,4 +60,11 @@ public class ProjectBranchesService extends BaseService { call(post); } + public void rename(String project, String branch) { + PostRequest post = new PostRequest(path(ACTION_RENAME)) + .setParam(PARAM_PROJECT, project) + .setParam(PARAM_BRANCH, branch); + call(post); + } + } diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/projectbranches/ProjectBranchesServiceTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/projectbranches/ProjectBranchesServiceTest.java index 6a5b894e035..a1596299511 100644 --- a/sonar-ws/src/test/java/org/sonarqube/ws/client/projectbranches/ProjectBranchesServiceTest.java +++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/projectbranches/ProjectBranchesServiceTest.java @@ -80,4 +80,16 @@ public class ProjectBranchesServiceTest { .andNoOtherParam(); } + @Test + public void rename() { + underTest.rename("projectKey", "my_branch"); + + PostRequest postRequest = serviceTester.getPostRequest(); + serviceTester.assertThat(postRequest) + .hasPath("rename") + .hasParam(PARAM_PROJECT, "projectKey") + .hasParam(PARAM_BRANCH, "my_branch") + .andNoOtherParam(); + } + } |