From 74801f89f98c3aca1de39a1e54d2e4336a039d77 Mon Sep 17 00:00:00 2001 From: Lukasz Jarocki Date: Wed, 10 Mar 2021 12:22:56 +0100 Subject: [PATCH] SONAR-14565 Update SonarQube main branch name during Bitbucket Server project onboarding --- .../BitbucketServerRestClient.java | 5 ++ .../alm/client/bitbucketserver/Branch.java | 50 +++++++++++ .../client/bitbucketserver/BranchesList.java | 54 +++++++++++ .../BitbucketServerRestClientTest.java | 79 ++++++++++++++++ .../bitbucketserver/BranchesListTest.java | 62 +++++++++++++ .../ImportBitbucketServerProjectAction.java | 28 ++++-- ...mportBitbucketServerProjectActionTest.java | 89 ++++++++++++++++++- 7 files changed, 360 insertions(+), 7 deletions(-) create mode 100644 server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucketserver/Branch.java create mode 100644 server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucketserver/BranchesList.java create mode 100644 server/sonar-alm-client/src/test/java/org/sonar/alm/client/bitbucketserver/BranchesListTest.java diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucketserver/BitbucketServerRestClient.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucketserver/BitbucketServerRestClient.java index e410ac25f56..344c1f1d917 100644 --- a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucketserver/BitbucketServerRestClient.java +++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucketserver/BitbucketServerRestClient.java @@ -102,6 +102,11 @@ public class BitbucketServerRestClient { return doGet(token, url, r -> buildGson().fromJson(r.body().charStream(), ProjectList.class)); } + public BranchesList getBranches(String serverUrl, String token, String projectSlug, String repositorySlug){ + HttpUrl url = buildUrl(serverUrl, format("/rest/api/1.0/projects/%s/repos/%s/branches", projectSlug, repositorySlug)); + return doGet(token, url, r -> buildGson().fromJson(r.body().charStream(), BranchesList.class)); + } + protected static HttpUrl buildUrl(@Nullable String serverUrl, String relativeUrl) { if (serverUrl == null || !(serverUrl.toLowerCase(ENGLISH).startsWith("http://") || serverUrl.toLowerCase(ENGLISH).startsWith("https://"))) { throw new IllegalArgumentException("url must start with http:// or https://"); diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucketserver/Branch.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucketserver/Branch.java new file mode 100644 index 00000000000..2d703950e9d --- /dev/null +++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucketserver/Branch.java @@ -0,0 +1,50 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.alm.client.bitbucketserver; + +import com.google.gson.annotations.SerializedName; + +public class Branch { + + @SerializedName("displayId") + private String name; + + @SerializedName("isDefault") + private boolean isDefault; + + public Branch(){ + // http://stackoverflow.com/a/18645370/229031 + } + + public Branch(String name, boolean isDefault) { + this.name = name; + this.isDefault = isDefault; + } + + public boolean isDefault() { + return isDefault; + } + + public String getName() { + return name; + } + +} diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucketserver/BranchesList.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucketserver/BranchesList.java new file mode 100644 index 00000000000..316c638b654 --- /dev/null +++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucketserver/BranchesList.java @@ -0,0 +1,54 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.alm.client.bitbucketserver; + +import com.google.gson.annotations.SerializedName; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class BranchesList { + + @SerializedName("values") + private List branches; + + public BranchesList() { + // http://stackoverflow.com/a/18645370/229031 + this(new ArrayList<>()); + } + + public BranchesList(List values) { + this.branches = values; + } + + public Optional findDefaultBranch() { + return branches.stream().filter(Branch::isDefault).findFirst(); + } + + public void addBranch(Branch branch) { + this.branches.add(branch); + } + + public List getBranches() { + return branches; + } +} diff --git a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/bitbucketserver/BitbucketServerRestClientTest.java b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/bitbucketserver/BitbucketServerRestClientTest.java index a4cafcf1941..ccfddb717d5 100644 --- a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/bitbucketserver/BitbucketServerRestClientTest.java +++ b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/bitbucketserver/BitbucketServerRestClientTest.java @@ -180,6 +180,85 @@ public class BitbucketServerRestClientTest { tuple(2L, "HOY", "hoy")); } + @Test + public void getBranches_given0Branches_returnEmptyList(){ + String bodyWith0Branches = "{\n" + + " \"size\": 0,\n" + + " \"limit\": 25,\n" + + " \"isLastPage\": true,\n" + + " \"values\": [],\n" + + " \"start\": 0\n" + + "}"; + server.enqueue(new MockResponse() + .setHeader("Content-Type", "application/json;charset=UTF-8") + .setBody(bodyWith0Branches)); + + BranchesList branches = underTest.getBranches(server.url("/").toString(), "token", "projectSlug", "repoSlug"); + + assertThat(branches.getBranches()).isEmpty(); + } + + @Test + public void getBranches_given1Branch_returnListWithOneBranch(){ + String bodyWith1Branch = "{\n" + + " \"size\": 1,\n" + + " \"limit\": 25,\n" + + " \"isLastPage\": true,\n" + + " \"values\": [{\n" + + " \"id\": \"refs/heads/demo\",\n" + + " \"displayId\": \"demo\",\n" + + " \"type\": \"BRANCH\",\n" + + " \"latestCommit\": \"3e30a6701af6f29f976e9a6609a6076b32a69ac3\",\n" + + " \"latestChangeset\": \"3e30a6701af6f29f976e9a6609a6076b32a69ac3\",\n" + + " \"isDefault\": false\n" + + " }],\n" + + " \"start\": 0\n" + + "}"; + server.enqueue(new MockResponse() + .setHeader("Content-Type", "application/json;charset=UTF-8") + .setBody(bodyWith1Branch)); + + BranchesList branches = underTest.getBranches(server.url("/").toString(), "token", "projectSlug", "repoSlug"); + assertThat(branches.getBranches()).hasSize(1); + + Branch branch = branches.getBranches().get(0); + assertThat(branch.getName()).isEqualTo("demo"); + assertThat(branch.isDefault()).isFalse(); + + } + + @Test + public void getBranches_given2Branches_returnListWithTwoBranches(){ + String bodyWith2Branches = "{\n" + + " \"size\": 2,\n" + + " \"limit\": 25,\n" + + " \"isLastPage\": true,\n" + + " \"values\": [{\n" + + " \"id\": \"refs/heads/demo\",\n" + + " \"displayId\": \"demo\",\n" + + " \"type\": \"BRANCH\",\n" + + " \"latestCommit\": \"3e30a6701af6f29f976e9a6609a6076b32a69ac3\",\n" + + " \"latestChangeset\": \"3e30a6701af6f29f976e9a6609a6076b32a69ac3\",\n" + + " \"isDefault\": false\n" + + " }, {\n" + + " \"id\": \"refs/heads/master\",\n" + + " \"displayId\": \"master\",\n" + + " \"type\": \"BRANCH\",\n" + + " \"latestCommit\": \"66633864d27c531ff43892f6dfea6d91632682fa\",\n" + + " \"latestChangeset\": \"66633864d27c531ff43892f6dfea6d91632682fa\",\n" + + " \"isDefault\": true\n" + + " }],\n" + + " \"start\": 0\n" + + "}"; + server.enqueue(new MockResponse() + .setHeader("Content-Type", "application/json;charset=UTF-8") + .setBody(bodyWith2Branches)); + + BranchesList branches = underTest.getBranches(server.url("/").toString(), "token", "projectSlug", "repoSlug"); + + assertThat(branches.getBranches()).hasSize(2); + } + @Test public void invalid_url() { assertThatThrownBy(() -> BitbucketServerRestClient.buildUrl("file://wrong-url", "")) diff --git a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/bitbucketserver/BranchesListTest.java b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/bitbucketserver/BranchesListTest.java new file mode 100644 index 00000000000..80fb549952a --- /dev/null +++ b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/bitbucketserver/BranchesListTest.java @@ -0,0 +1,62 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.alm.client.bitbucketserver; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.*; +import java.util.Optional; + +public class BranchesListTest { + + @Test + public void findDefaultBranch_givenNoBranches_returnEmptyOptional(){ + BranchesList branchesList = new BranchesList(); + + Optional defaultBranch = branchesList.findDefaultBranch(); + + assertThat(defaultBranch).isNotPresent(); + } + + @Test + public void findDefaultBranch_givenBranchesWithoutDefaultOne_returnEmptyOptional(){ + BranchesList branchesList = new BranchesList(); + branchesList.addBranch(new Branch("1", false)); + branchesList.addBranch(new Branch("2", false)); + + Optional defaultBranch = branchesList.findDefaultBranch(); + + assertThat(defaultBranch).isNotPresent(); + } + + @Test + public void findDefaultBranch_givenBranchesWithDefaultOne_returnOptionalWithThisBranch(){ + BranchesList branchesList = new BranchesList(); + branchesList.addBranch(new Branch("1", false)); + branchesList.addBranch(new Branch("2", false)); + branchesList.addBranch(new Branch("default", true)); + + Optional defaultBranchOptional = branchesList.findDefaultBranch(); + + assertThat(defaultBranchOptional).isPresent(); + assertThat(defaultBranchOptional.get().isDefault()).isTrue(); + assertThat(defaultBranchOptional.get().getName()).isEqualTo("default"); + } +} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectAction.java index 748230a781f..a1477a09e3d 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectAction.java @@ -21,6 +21,8 @@ package org.sonar.server.almintegration.ws.bitbucketserver; import java.util.Optional; import org.sonar.alm.client.bitbucketserver.BitbucketServerRestClient; +import org.sonar.alm.client.bitbucketserver.Branch; +import org.sonar.alm.client.bitbucketserver.BranchesList; import org.sonar.alm.client.bitbucketserver.Repository; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; @@ -34,6 +36,7 @@ import org.sonar.db.component.ComponentDto; import org.sonar.server.almintegration.ws.AlmIntegrationsWsAction; import org.sonar.server.almintegration.ws.ImportHelper; import org.sonar.server.component.ComponentUpdater; +import org.sonar.server.component.NewComponent; import org.sonar.server.project.ProjectDefaultVisibility; import org.sonar.server.user.UserSession; import org.sonarqube.ws.Projects; @@ -44,6 +47,7 @@ import static org.sonar.server.almintegration.ws.ImportHelper.PARAM_ALM_SETTING; import static org.sonar.server.almintegration.ws.ImportHelper.toCreateResponse; import static org.sonar.server.component.NewComponent.newComponentBuilder; import static org.sonar.server.ws.WsUtils.writeProtobuf; +import javax.annotation.Nullable; public class ImportBitbucketServerProjectAction implements AlmIntegrationsWsAction { @@ -117,22 +121,35 @@ public class ImportBitbucketServerProjectAction implements AlmIntegrationsWsActi String url = requireNonNull(almSettingDto.getUrl(), "ALM url cannot be null"); Repository repo = bitbucketServerRestClient.getRepo(url, pat, projectKey, repoSlug); - ComponentDto componentDto = createProject(dbSession, repo); + String defaultBranchName = getDefaultBranchName(pat, projectKey, repoSlug, url); + + ComponentDto componentDto = createProject(dbSession, repo, defaultBranchName); + populatePRSetting(dbSession, repo, componentDto, almSettingDto); + componentUpdater.commitAndIndex(dbSession, componentDto); + return toCreateResponse(componentDto); } } + + private String getDefaultBranchName(String pat, String projectKey, String repoSlug, String url) { + BranchesList branches = bitbucketServerRestClient.getBranches(url, pat, projectKey, repoSlug); + Optional defaultBranch = branches.findDefaultBranch(); + return defaultBranch.map(Branch::getName).orElse(null); + } - private ComponentDto createProject(DbSession dbSession, Repository repo) { + private ComponentDto createProject(DbSession dbSession, Repository repo, @Nullable String defaultBranchName) { boolean visibility = projectDefaultVisibility.get(dbSession).isPrivate(); - return componentUpdater.create(dbSession, newComponentBuilder() + NewComponent newProject = newComponentBuilder() .setKey(repo.getProject().getKey() + "_" + repo.getSlug()) .setName(repo.getName()) .setPrivate(visibility) .setQualifier(PROJECT) - .build(), - userSession.isLoggedIn() ? userSession.getUuid() : null); + .build(); + String userUuid = userSession.isLoggedIn() ? userSession.getUuid() : null; + + return componentUpdater.createWithoutCommit(dbSession, newProject, userUuid, defaultBranchName, p -> {}); } private void populatePRSetting(DbSession dbSession, Repository repo, ComponentDto componentDto, AlmSettingDto almSettingDto) { @@ -143,7 +160,6 @@ public class ImportBitbucketServerProjectAction implements AlmIntegrationsWsActi .setProjectUuid(componentDto.uuid()) .setMonorepo(false); dbClient.projectAlmSettingDao().insertOrUpdate(dbSession, projectAlmSettingDto); - dbSession.commit(); } } diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectActionTest.java index 811f79fad20..62d0fca00f8 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectActionTest.java @@ -19,12 +19,14 @@ */ package org.sonar.server.almintegration.ws.bitbucketserver; -import java.util.Optional; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.sonar.alm.client.bitbucketserver.BitbucketServerRestClient; +import org.sonar.alm.client.bitbucketserver.Branch; +import org.sonar.alm.client.bitbucketserver.BranchesList; import org.sonar.alm.client.bitbucketserver.Project; import org.sonar.alm.client.bitbucketserver.Repository; import org.sonar.api.server.ws.WebService; @@ -34,6 +36,7 @@ import org.sonar.core.util.SequenceUuidFactory; import org.sonar.db.DbTester; import org.sonar.db.alm.pat.AlmPatDto; import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.db.component.BranchDto; import org.sonar.db.project.ProjectDto; import org.sonar.db.user.UserDto; import org.sonar.server.almintegration.ws.ImportHelper; @@ -61,6 +64,11 @@ import static org.mockito.Mockito.when; import static org.sonar.db.alm.integration.pat.AlmPatsTesting.newAlmPatDto; import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS; import static org.sonar.db.permission.GlobalPermission.SCAN; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; public class ImportBitbucketServerProjectActionTest { @@ -81,6 +89,14 @@ public class ImportBitbucketServerProjectActionTest { private final WsActionTester ws = new WsActionTester(new ImportBitbucketServerProjectAction(db.getDbClient(), userSession, bitbucketServerRestClient, projectDefaultVisibility, componentUpdater, importHelper)); + private static BranchesList defaultBranchesList; + + @BeforeClass + public static void beforeAll() { + Branch defaultBranch = new Branch("default", true); + defaultBranchesList = new BranchesList(Collections.singletonList(defaultBranch)); + } + @Before public void before() { when(projectDefaultVisibility.get(any())).thenReturn(Visibility.PRIVATE); @@ -98,6 +114,7 @@ public class ImportBitbucketServerProjectActionTest { Project project = getGsonBBSProject(); Repository repo = getGsonBBSRepo(project); when(bitbucketServerRestClient.getRepo(any(), any(), any(), any())).thenReturn(repo); + when(bitbucketServerRestClient.getBranches(any(), any(), any(), any())).thenReturn(defaultBranchesList); Projects.CreateWsResponse response = ws.newRequest() .setParam("almSetting", almSetting.getKey()) @@ -132,6 +149,8 @@ public class ImportBitbucketServerProjectActionTest { expectedException.expectMessage("Could not create null, key already exists: " + projectKey); when(bitbucketServerRestClient.getRepo(any(), any(), any(), any())).thenReturn(repo); + when(bitbucketServerRestClient.getBranches(any(), any(), any(), any())).thenReturn(defaultBranchesList); + ws.newRequest() .setParam("almSetting", almSetting.getKey()) .setParam("projectKey", "projectKey") @@ -207,6 +226,74 @@ public class ImportBitbucketServerProjectActionTest { .execute(); } + @Test + public void handle_givenNoDefaultBranchFound_doNotUpdateDefaultBranchName() { + BranchesList branchesList = new BranchesList(); + Branch branch = new Branch("not_a_master", false); + branchesList.addBranch(branch); + + UserDto user = db.users().insertUser(); + userSession.logIn(user).addPermission(PROVISION_PROJECTS); + AlmSettingDto almSetting = db.almSettings().insertGitHubAlmSetting(); + db.almPats().insert(dto -> { + dto.setAlmSettingUuid(almSetting.getUuid()); + dto.setUserUuid(user.getUuid()); + }); + Project project = getGsonBBSProject(); + Repository repo = getGsonBBSRepo(project); + when(bitbucketServerRestClient.getRepo(any(), any(), any(), any())).thenReturn(repo); + when(bitbucketServerRestClient.getBranches(any(), any(), any(), any())).thenReturn(branchesList); + + Projects.CreateWsResponse response = ws.newRequest() + .setParam("almSetting", almSetting.getKey()) + .setParam("projectKey", "projectKey") + .setParam("repositorySlug", "repo-slug") + .executeProtobuf(Projects.CreateWsResponse.class); + + Projects.CreateWsResponse.Project result = response.getProject(); + + Optional projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey()); + + Collection branchDtos = db.getDbClient().branchDao().selectByProject(db.getSession(), projectDto.get()); + List collect = branchDtos.stream().filter(BranchDto::isMain).collect(Collectors.toList()); + String mainBranchName = collect.iterator().next().getKey(); + assertThat(mainBranchName).isEqualTo("master"); + } + + @Test + public void handle_givenDefaultBranchNamedDefault_updateDefaultBranchNameToDefault() { + BranchesList branchesList = new BranchesList(); + Branch branch = new Branch("default", true); + branchesList.addBranch(branch); + + UserDto user = db.users().insertUser(); + userSession.logIn(user).addPermission(PROVISION_PROJECTS); + AlmSettingDto almSetting = db.almSettings().insertGitHubAlmSetting(); + db.almPats().insert(dto -> { + dto.setAlmSettingUuid(almSetting.getUuid()); + dto.setUserUuid(user.getUuid()); + }); + Project project = getGsonBBSProject(); + Repository repo = getGsonBBSRepo(project); + when(bitbucketServerRestClient.getRepo(any(), any(), any(), any())).thenReturn(repo); + when(bitbucketServerRestClient.getBranches(any(), any(), any(), any())).thenReturn(branchesList); + + Projects.CreateWsResponse response = ws.newRequest() + .setParam("almSetting", almSetting.getKey()) + .setParam("projectKey", "projectKey") + .setParam("repositorySlug", "repo-slug") + .executeProtobuf(Projects.CreateWsResponse.class); + + Projects.CreateWsResponse.Project result = response.getProject(); + + Optional projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey()); + + Collection branchDtos = db.getDbClient().branchDao().selectByProject(db.getSession(), projectDto.get()); + List collect = branchDtos.stream().filter(BranchDto::isMain).collect(Collectors.toList()); + String mainBranchName = collect.iterator().next().getKey(); + assertThat(mainBranchName).isEqualTo("default"); + } + @Test public void definition() { WebService.Action def = ws.getDef(); -- 2.39.5