]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9616 Ability to set the name of the main branch
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Wed, 23 Aug 2017 12:07:23 +0000 (14:07 +0200)
committerJanos Gyerik <janos.gyerik@sonarsource.com>
Tue, 12 Sep 2017 09:34:49 +0000 (11:34 +0200)
12 files changed:
server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchMapper.java
server/sonar-db-dao/src/main/resources/org/sonar/db/component/BranchMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/component/BranchDaoTest.java
server/sonar-server/src/main/java/org/sonar/server/projectbranch/ws/BranchWsModule.java
server/sonar-server/src/main/java/org/sonar/server/projectbranch/ws/DeleteAction.java
server/sonar-server/src/main/java/org/sonar/server/projectbranch/ws/RenameAction.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/projectbranch/ws/BranchWsModuleTest.java
server/sonar-server/src/test/java/org/sonar/server/projectbranch/ws/RenameActionTest.java [new file with mode: 0644]
sonar-ws/src/main/java/org/sonarqube/ws/client/projectbranches/ProjectBranchesParameters.java
sonar-ws/src/main/java/org/sonarqube/ws/client/projectbranches/ProjectBranchesService.java
sonar-ws/src/test/java/org/sonarqube/ws/client/projectbranches/ProjectBranchesServiceTest.java

index 50ff5f66debae87124f502fd079a7600b9cddea9..2e4fcb111d4fe357211fc0aae3b44adae027434b 100644 (file)
@@ -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);
   }
index 0c978360aec069bde4807fd8ac3d0e59082d2f39..ae4fc73dc0355e6c8f8ea60eb327ef5b8371517d 100644 (file)
@@ -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);
 
index 9c39fff0ac14e3e3beca240b969c412e4dfd8576..a3a24d7913fa78b352820af73343bb2b0d696052 100644 (file)
     )
   </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
index 616a6e1cde115cbd2410fbd06a093b1969a4534c..43b6f6aa14b9e0eee4ed6421ad538341bf4a95e9 100644 (file)
@@ -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());
index d47f197991ccf1ef2ac5c61ec3372a501a0d4230..270ceef8ad9592c628a6fd634a24a0be1ee40d62 100644 (file)
@@ -28,6 +28,7 @@ public class BranchWsModule extends Module {
       ListAction.class,
       ShowAction.class,
       DeleteAction.class,
+      RenameAction.class,
       BranchesWs.class);
   }
 }
index 89e3b9bb63158d5ab4679fac74ed9fa2197cd757..fa61c63d39eeaaa2bbd2cbcc2ce03c3059070b3e 100644 (file)
@@ -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 (file)
index 0000000..3cf4e7d
--- /dev/null
@@ -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);
+  }
+
+}
index 2ecb8a885a29a83ea851296fce0cc64ac5dfe446..8018bcb70942ef8ff6d2669f346e0caa60ce7d4b 100644 (file)
@@ -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 (file)
index 0000000..bab33b6
--- /dev/null
@@ -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();
+  }
+}
index c1370b920930aa62930ef4367f8dfb68b2623492..32501503293c79344ff81e9bc0bffc6a3d41f3d6 100644 (file)
@@ -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";
index 0a0b23a09aa62591c8c87426abda89439f3989b1..52d25fb461423e00d53447ab338db2795be6bcb8 100644 (file)
@@ -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);
+  }
+
 }
index 6a5b894e035c2eda6fcf594329c937963106f05f..a15962995116cdc4e02e638170f89192613a6755 100644 (file)
@@ -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();
+  }
+
 }