Browse Source

SONAR-12197 completely drop "sonar.branch" code

tags/8.0
Michal Duda 4 years ago
parent
commit
05c8a2e004
69 changed files with 167 additions and 1119 deletions
  1. 1
    1
      server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/analysis/AnalysisMetadataHolderImpl.java
  2. 0
    6
      server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/analysis/Branch.java
  3. 2
    5
      server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/api/posttask/PostProjectAnalysisTasksExecutor.java
  4. 4
    14
      server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/BranchLoader.java
  5. 4
    26
      server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/DefaultBranchImpl.java
  6. 1
    1
      server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/notification/NotificationFactory.java
  7. 1
    1
      server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/BuildComponentTreeStep.java
  8. 3
    14
      server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStep.java
  9. 5
    22
      server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/analysis/AnalysisMetadataHolderImplTest.java
  10. 0
    12
      server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/analysis/ProjectConfigurationFactoryTest.java
  11. 2
    19
      server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/api/posttask/PostProjectAnalysisTasksExecutorTest.java
  12. 7
    35
      server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/BranchLoaderTest.java
  13. 0
    38
      server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/DefaultBranchImplTest.java
  14. 2
    10
      server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/notification/NotificationFactoryTest.java
  15. 1
    20
      server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/BuildComponentTreeStepTest.java
  16. 0
    28
      server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/QualityGateEventsStepTest.java
  17. 0
    5
      server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/ReportPersistComponentsStepTest.java
  18. 6
    25
      server/sonar-server/src/main/java/org/sonar/server/ce/queue/BranchSupport.java
  19. 3
    4
      server/sonar-server/src/main/java/org/sonar/server/ce/queue/ReportSubmitter.java
  20. 1
    9
      server/sonar-server/src/main/java/org/sonar/server/ce/ws/SubmitAction.java
  21. 3
    11
      server/sonar-server/src/main/java/org/sonar/server/component/ComponentUpdater.java
  22. 0
    15
      server/sonar-server/src/main/java/org/sonar/server/component/NewComponent.java
  23. 0
    21
      server/sonar-server/src/main/java/org/sonar/server/project/ws/CreateAction.java
  24. 6
    28
      server/sonar-server/src/test/java/org/sonar/server/ce/queue/BranchReportSubmitterTest.java
  25. 4
    47
      server/sonar-server/src/test/java/org/sonar/server/ce/queue/BranchSupportTest.java
  26. 16
    32
      server/sonar-server/src/test/java/org/sonar/server/ce/queue/ReportSubmitterTest.java
  27. 9
    20
      server/sonar-server/src/test/java/org/sonar/server/ce/ws/SubmitActionTest.java
  28. 0
    29
      server/sonar-server/src/test/java/org/sonar/server/component/ComponentUpdaterTest.java
  29. 1
    22
      server/sonar-server/src/test/java/org/sonar/server/project/ws/CreateActionTest.java
  30. 0
    19
      sonar-core/src/main/java/org/sonar/core/component/ComponentKeys.java
  31. 0
    12
      sonar-core/src/test/java/org/sonar/core/component/ComponentKeysTest.java
  32. 0
    16
      sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/fs/internal/AbstractProjectOrModule.java
  33. 0
    4
      sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/fs/internal/fs/DefaultInputModuleTest.java
  34. 0
    2
      sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/fs/internal/fs/DefaultInputProjectTest.java
  35. 0
    6
      sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java
  36. 0
    23
      sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectDefinition.java
  37. 0
    30
      sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectKey.java
  38. 2
    3
      sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectReactor.java
  39. 5
    27
      sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ProcessedScannerProperties.java
  40. 2
    7
      sonar-scanner-engine/src/main/java/org/sonar/scanner/cpd/CpdSettings.java
  41. 1
    5
      sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ComponentsPublisher.java
  42. 0
    4
      sonar-scanner-engine/src/main/java/org/sonar/scanner/report/MetadataPublisher.java
  43. 3
    4
      sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ReportPublisher.java
  44. 1
    1
      sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/ProjectRepositoriesSupplier.java
  45. 1
    1
      sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/QualityProfilesProvider.java
  46. 1
    1
      sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/settings/DefaultProjectSettingsLoader.java
  47. 1
    12
      sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectReactorValidator.java
  48. 4
    14
      sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java
  49. 1
    1
      sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/BranchParamsValidator.java
  50. 0
    30
      sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/DefaultBranchParamsValidator.java
  51. 9
    9
      sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/ProjectBranchesProvider.java
  52. 3
    3
      sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/ProjectPullRequestsProvider.java
  53. 1
    1
      sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/FileIndexer.java
  54. 1
    1
      sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmPublisher.java
  55. 1
    3
      sonar-scanner-engine/src/test/java/org/sonar/scanner/cpd/CpdSettingsTest.java
  56. 4
    87
      sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/branch/DeprecatedBranchMediumTest.java
  57. 0
    42
      sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/fs/FileSystemMediumTest.java
  58. 0
    40
      sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/fs/ProjectBuilderMediumTest.java
  59. 6
    59
      sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/scm/ScmMediumTest.java
  60. 1
    33
      sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ComponentsPublisherTest.java
  61. 1
    25
      sonar-scanner-engine/src/test/java/org/sonar/scanner/report/MetadataPublisherTest.java
  62. 2
    2
      sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/ProjectRepositoriesSupplierTest.java
  63. 1
    1
      sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/QualityProfileProviderTest.java
  64. 1
    1
      sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/settings/DefaultProjectSettingsLoaderTest.java
  65. 1
    7
      sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/ModuleIndexerTest.java
  66. 0
    44
      sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/ProjectReactorValidatorTest.java
  67. 10
    5
      sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/branch/ProjectBranchesProviderTest.java
  68. 10
    4
      sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/branch/ProjectPullRequestsProviderTest.java
  69. 11
    10
      sonar-scanner-protocol/src/main/protobuf/scanner_report.proto

+ 1
- 1
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/analysis/AnalysisMetadataHolderImpl.java View File

checkState(!this.branch.isInitialized(), "Branch has already been set"); checkState(!this.branch.isInitialized(), "Branch has already been set");
boolean isCommunityEdition = editionProvider.get().filter(t -> t == EditionProvider.Edition.COMMUNITY).isPresent(); boolean isCommunityEdition = editionProvider.get().filter(t -> t == EditionProvider.Edition.COMMUNITY).isPresent();
checkState( checkState(
!isCommunityEdition || branch.isMain() || branch.isLegacyFeature(),
!isCommunityEdition || branch.isMain(),
"Branches and Pull Requests are not supported in Community Edition"); "Branches and Pull Requests are not supported in Community Edition");
this.branch.setProperty(branch); this.branch.setProperty(branch);
return this; return this;

+ 0
- 6
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/analysis/Branch.java View File



boolean isMain(); boolean isMain();


/**
* Whether branch has been created through the legacy configuration
* (scanner parameter sonar.branch) or not
*/
boolean isLegacyFeature();

/** /**
* Name of the branch * Name of the branch
*/ */

+ 2
- 5
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/api/posttask/PostProjectAnalysisTasksExecutor.java View File

@CheckForNull @CheckForNull
private BranchImpl createBranch() { private BranchImpl createBranch() {
org.sonar.ce.task.projectanalysis.analysis.Branch analysisBranch = analysisMetadataHolder.getBranch(); org.sonar.ce.task.projectanalysis.analysis.Branch analysisBranch = analysisMetadataHolder.getBranch();
if (!analysisBranch.isLegacyFeature()) {
String branchKey = analysisBranch.getType() == PULL_REQUEST ? analysisBranch.getPullRequestKey() : analysisBranch.getName();
return new BranchImpl(analysisBranch.isMain(), branchKey, Branch.Type.valueOf(analysisBranch.getType().name()));
}
return null;
String branchKey = analysisBranch.getType() == PULL_REQUEST ? analysisBranch.getPullRequestKey() : analysisBranch.getName();
return new BranchImpl(analysisBranch.isMain(), branchKey, Branch.Type.valueOf(analysisBranch.getType().name()));
} }


private static QualityGate.Status convert(QualityGateStatus status) { private static QualityGate.Status convert(QualityGateStatus status) {

+ 4
- 14
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/BranchLoader.java View File

import org.sonar.ce.task.projectanalysis.analysis.MutableAnalysisMetadataHolder; import org.sonar.ce.task.projectanalysis.analysis.MutableAnalysisMetadataHolder;
import org.sonar.scanner.protocol.output.ScannerReport; import org.sonar.scanner.protocol.output.ScannerReport;


import static org.apache.commons.lang.StringUtils.trimToNull;
import static org.sonar.scanner.protocol.output.ScannerReport.Metadata.BranchType.UNSET; import static org.sonar.scanner.protocol.output.ScannerReport.Metadata.BranchType.UNSET;


public class BranchLoader { public class BranchLoader {
} }


public void load(ScannerReport.Metadata metadata) { public void load(ScannerReport.Metadata metadata) {
String deprecatedBranch = trimToNull(metadata.getDeprecatedBranch());
String branchName = trimToNull(metadata.getBranchName());

if (deprecatedBranch != null && branchName != null) {
throw MessageException.of("Properties sonar.branch and sonar.branch.name can't be set together");
}

if (delegate == null && hasBranchProperties(metadata)) {
throw MessageException.of("Current edition does not support branch feature");
}

if (delegate != null && deprecatedBranch == null) {
if (delegate != null) {
delegate.load(metadata); delegate.load(metadata);
} else if (hasBranchProperties(metadata)) {
throw MessageException.of("Current edition does not support branch feature");
} else { } else {
metadataHolder.setBranch(new DefaultBranchImpl(deprecatedBranch));
metadataHolder.setBranch(new DefaultBranchImpl());
} }
} }



+ 4
- 26
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/DefaultBranchImpl.java View File

package org.sonar.ce.task.projectanalysis.component; package org.sonar.ce.task.projectanalysis.component;


import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.sonar.api.utils.MessageException;
import org.sonar.ce.task.projectanalysis.analysis.Branch; import org.sonar.ce.task.projectanalysis.analysis.Branch;
import org.sonar.core.component.ComponentKeys; import org.sonar.core.component.ComponentKeys;
import org.sonar.db.component.BranchDto; import org.sonar.db.component.BranchDto;
import org.sonar.db.component.BranchType; import org.sonar.db.component.BranchType;


import static java.lang.String.format;
import static org.apache.commons.lang.StringUtils.isEmpty; import static org.apache.commons.lang.StringUtils.isEmpty;
import static org.apache.commons.lang.StringUtils.trimToNull; import static org.apache.commons.lang.StringUtils.trimToNull;


/** /**
* The default (and legacy) implementation of {@link Branch}. It is used
* when scanner is configured with parameter "sonar.branch" or when no branch is provided and the branch plugin is not installed.
* A legacy branch is implemented as a fork of the project, so any branch is considered as "main".
* Implementation of {@link Branch} for default/main branch. It is used
* when no branch is provided as a scanner parameter or if the branch plugin is not installed.
*/ */
public class DefaultBranchImpl implements Branch { public class DefaultBranchImpl implements Branch {
private final String branchName; private final String branchName;
private final boolean isLegacyBranch;


public DefaultBranchImpl() { public DefaultBranchImpl() {
this(null);
}

public DefaultBranchImpl(@Nullable String name) {
this.isLegacyBranch = (name != null);
this.branchName = (name == null) ? BranchDto.DEFAULT_MAIN_BRANCH_NAME : name;
if (!ComponentKeys.isValidLegacyBranch(branchName)) {
throw MessageException.of(format("\"%s\" is not a valid branch name. "
+ "Allowed characters are alphanumeric, '-', '_', '.' and '/'.", branchName));
}
this.branchName = BranchDto.DEFAULT_MAIN_BRANCH_NAME;
} }


@Override @Override
throw new IllegalStateException("Not valid for the main branch"); throw new IllegalStateException("Not valid for the main branch");
} }


@Override
public boolean isLegacyFeature() {
return isLegacyBranch;
}

@Override @Override
public String getName() { public String getName() {
return branchName; return branchName;


@Override @Override
public boolean supportsCrossProjectCpd() { public boolean supportsCrossProjectCpd() {
// only on regular project, not on branches
return !isLegacyBranch;
return true;
} }


@Override @Override


@Override @Override
public String generateKey(String projectKey, @Nullable String fileOrDirPath) { public String generateKey(String projectKey, @Nullable String fileOrDirPath) {
if (isLegacyBranch) {
projectKey = ComponentKeys.createKey(projectKey, branchName);
}
if (isEmpty(fileOrDirPath)) { if (isEmpty(fileOrDirPath)) {
return projectKey; return projectKey;
} }

+ 1
- 1
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/notification/NotificationFactory.java View File

Project.Builder builder = new Project.Builder(project.getUuid()) Project.Builder builder = new Project.Builder(project.getUuid())
.setKey(project.getKey()) .setKey(project.getKey())
.setProjectName(project.getName()); .setProjectName(project.getName());
if (!branch.isLegacyFeature() && branch.getType() != PULL_REQUEST && !branch.isMain()) {
if (branch.getType() != PULL_REQUEST && !branch.isMain()) {
builder.setBranchName(branch.getName()); builder.setBranchName(branch.getName());
} }
return builder.build(); return builder.build();

+ 1
- 1
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/BuildComponentTreeStep.java View File

Branch branch = analysisMetadataHolder.getBranch(); Branch branch = analysisMetadataHolder.getBranch();


// for non-legacy branches, the public key is different from the DB key. // for non-legacy branches, the public key is different from the DB key.
if (!branch.isLegacyFeature() && !branch.isMain()) {
if (!branch.isMain()) {
return new DefaultBranchImpl(); return new DefaultBranchImpl();
} }
return branch; return branch;

+ 3
- 14
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStep.java View File

import java.util.Optional; import java.util.Optional;
import javax.annotation.CheckForNull; import javax.annotation.CheckForNull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.utils.MessageException; import org.sonar.api.utils.MessageException;
import org.sonar.ce.task.CeTask; import org.sonar.ce.task.CeTask;
import org.sonar.ce.task.projectanalysis.analysis.MutableAnalysisMetadataHolder; import org.sonar.ce.task.projectanalysis.analysis.MutableAnalysisMetadataHolder;
import org.sonar.ce.task.projectanalysis.batch.BatchReportReader; import org.sonar.ce.task.projectanalysis.batch.BatchReportReader;
import org.sonar.ce.task.projectanalysis.component.BranchLoader; import org.sonar.ce.task.projectanalysis.component.BranchLoader;
import org.sonar.ce.task.step.ComputationStep; import org.sonar.ce.task.step.ComputationStep;
import org.sonar.core.component.ComponentKeys;
import org.sonar.core.platform.PluginRepository; import org.sonar.core.platform.PluginRepository;
import org.sonar.core.util.stream.MoreCollectors; import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.db.DbClient; import org.sonar.db.DbClient;
* @return a {@link Runnable} to execute some checks on the project at the end of the step * @return a {@link Runnable} to execute some checks on the project at the end of the step
*/ */
private Runnable loadProject(ScannerReport.Metadata reportMetadata, Organization organization) { private Runnable loadProject(ScannerReport.Metadata reportMetadata, Organization organization) {
String reportProjectKey = projectKeyFromReport(reportMetadata);
CeTask.Component mainComponent = mandatoryComponent(ceTask.getMainComponent()); CeTask.Component mainComponent = mandatoryComponent(ceTask.getMainComponent());
String mainComponentKey = mainComponent.getKey() String mainComponentKey = mainComponent.getKey()
.orElseThrow(() -> MessageException.of(format( .orElseThrow(() -> MessageException.of(format(
.orElseThrow(() -> MessageException.of(format( .orElseThrow(() -> MessageException.of(format(
"Compute Engine task component key is null. Project with UUID %s must have been deleted since report was uploaded. Can not proceed.", "Compute Engine task component key is null. Project with UUID %s must have been deleted since report was uploaded. Can not proceed.",
component.getUuid()))); component.getUuid())));
ComponentDto dto = toProject(reportProjectKey);
ComponentDto dto = toProject(reportMetadata.getProjectKey());


analysisMetadata.setProject(Project.from(dto)); analysisMetadata.setProject(Project.from(dto));
return () -> { return () -> {
if (!mainComponentKey.equals(reportProjectKey)) {
if (!mainComponentKey.equals(reportMetadata.getProjectKey())) {
throw MessageException.of(format( throw MessageException.of(format(
"ProjectKey in report (%s) is not consistent with projectKey under which the report has been submitted (%s)", "ProjectKey in report (%s) is not consistent with projectKey under which the report has been submitted (%s)",
reportProjectKey,
reportMetadata.getProjectKey(),
mainComponentKey)); mainComponentKey));
} }
if (!dto.getOrganizationUuid().equals(organization.getUuid())) { if (!dto.getOrganizationUuid().equals(organization.getUuid())) {
} }
} }


private static String projectKeyFromReport(ScannerReport.Metadata reportMetadata) {
String deprecatedBranch = reportMetadata.getDeprecatedBranch();
if (StringUtils.isNotEmpty(deprecatedBranch)) {
return ComponentKeys.createKey(reportMetadata.getProjectKey(), deprecatedBranch);
}
return reportMetadata.getProjectKey();
}

@Override @Override
public String getDescription() { public String getDescription() {
return "Load analysis metadata"; return "Load analysis metadata";

+ 5
- 22
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/analysis/AnalysisMetadataHolderImplTest.java View File

public void set_branch() { public void set_branch() {
AnalysisMetadataHolderImpl underTest = new AnalysisMetadataHolderImpl(editionProvider); AnalysisMetadataHolderImpl underTest = new AnalysisMetadataHolderImpl(editionProvider);


underTest.setBranch(new DefaultBranchImpl("master"));
underTest.setBranch(new DefaultBranchImpl());


assertThat(underTest.getBranch().getName()).isEqualTo("master"); assertThat(underTest.getBranch().getName()).isEqualTo("master");
} }
@Test @Test
public void setBranch_throws_ISE_when_called_twice() { public void setBranch_throws_ISE_when_called_twice() {
AnalysisMetadataHolderImpl underTest = new AnalysisMetadataHolderImpl(editionProvider); AnalysisMetadataHolderImpl underTest = new AnalysisMetadataHolderImpl(editionProvider);
underTest.setBranch(new DefaultBranchImpl("master"));
underTest.setBranch(new DefaultBranchImpl());


expectedException.expect(IllegalStateException.class); expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Branch has already been set"); expectedException.expectMessage("Branch has already been set");
underTest.setBranch(new DefaultBranchImpl("master"));
underTest.setBranch(new DefaultBranchImpl());
} }


@Test @Test
when(editionProvider.get()).thenReturn(Optional.ofNullable(edition)); when(editionProvider.get()).thenReturn(Optional.ofNullable(edition));
Branch branch = mock(Branch.class); Branch branch = mock(Branch.class);
when(branch.isMain()).thenReturn(true); when(branch.isMain()).thenReturn(true);
when(branch.isLegacyFeature()).thenReturn(false);
AnalysisMetadataHolderImpl underTest = new AnalysisMetadataHolderImpl(editionProvider);

underTest.setBranch(branch);

assertThat(underTest.getBranch()).isSameAs(branch);
}

@Test
@UseDataProvider("anyEditionIncludingNone")
public void setBranch_does_not_fail_if_legacy_branch_on_any_edition(@Nullable Edition edition) {
when(editionProvider.get()).thenReturn(Optional.ofNullable(edition));
Branch branch = mock(Branch.class);
when(branch.isMain()).thenReturn(false);
when(branch.isLegacyFeature()).thenReturn(true);
AnalysisMetadataHolderImpl underTest = new AnalysisMetadataHolderImpl(editionProvider); AnalysisMetadataHolderImpl underTest = new AnalysisMetadataHolderImpl(editionProvider);


underTest.setBranch(branch); underTest.setBranch(branch);


@Test @Test
@UseDataProvider("anyEditionIncludingNoneButCommunity") @UseDataProvider("anyEditionIncludingNoneButCommunity")
public void setBranch_does_not_fail_if_non_main_non_legacy_branch_on_any_edition_but_Community(@Nullable Edition edition) {
public void setBranch_does_not_fail_if_non_main_on_any_edition_but_Community(@Nullable Edition edition) {
when(editionProvider.get()).thenReturn(Optional.ofNullable(edition)); when(editionProvider.get()).thenReturn(Optional.ofNullable(edition));
Branch branch = mock(Branch.class); Branch branch = mock(Branch.class);
when(branch.isMain()).thenReturn(false); when(branch.isMain()).thenReturn(false);
when(branch.isLegacyFeature()).thenReturn(false);
AnalysisMetadataHolderImpl underTest = new AnalysisMetadataHolderImpl(editionProvider); AnalysisMetadataHolderImpl underTest = new AnalysisMetadataHolderImpl(editionProvider);


underTest.setBranch(branch); underTest.setBranch(branch);
} }


@Test @Test
public void setBranch_fails_if_non_main_non_legacy_branch_on_Community_edition() {
public void setBranch_fails_if_non_main_branch_on_Community_edition() {
when(editionProvider.get()).thenReturn(Optional.of(Edition.COMMUNITY)); when(editionProvider.get()).thenReturn(Optional.of(Edition.COMMUNITY));
Branch branch = mock(Branch.class); Branch branch = mock(Branch.class);
when(branch.isMain()).thenReturn(false); when(branch.isMain()).thenReturn(false);
when(branch.isLegacyFeature()).thenReturn(false);
AnalysisMetadataHolderImpl underTest = new AnalysisMetadataHolderImpl(editionProvider); AnalysisMetadataHolderImpl underTest = new AnalysisMetadataHolderImpl(editionProvider);


expectedException.expect(IllegalStateException.class); expectedException.expect(IllegalStateException.class);

+ 0
- 12
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/analysis/ProjectConfigurationFactoryTest.java View File

assertThat(config.get("sonar.leak.period")).hasValue("1"); assertThat(config.get("sonar.leak.period")).hasValue("1");
} }


@Test
public void legacy_branch() {
ComponentDto project = db.components().insertMainBranch();
db.properties().insertProperties(newComponentPropertyDto(project).setKey("sonar.leak.period").setValue("1"));
Branch branch = createBranch("legacy", true);
when(branch.isLegacyFeature()).thenReturn(true);

Configuration config = underTest.newProjectConfiguration(project.getKey(), createBranch(branch.getName(), true));

assertThat(config.get("sonar.leak.period")).hasValue("1");
}

private static Branch createBranch(String name, boolean isMain) { private static Branch createBranch(String name, boolean isMain) {
Branch branch = mock(Branch.class); Branch branch = mock(Branch.class);
when(branch.getName()).thenReturn(name); when(branch.getName()).thenReturn(name);

+ 2
- 19
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/api/posttask/PostProjectAnalysisTasksExecutorTest.java View File

import org.sonar.ce.task.projectanalysis.analysis.Branch; import org.sonar.ce.task.projectanalysis.analysis.Branch;
import org.sonar.ce.task.projectanalysis.analysis.Organization; import org.sonar.ce.task.projectanalysis.analysis.Organization;
import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule; import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule;
import org.sonar.ce.task.projectanalysis.component.DefaultBranchImpl;
import org.sonar.ce.task.projectanalysis.metric.Metric; import org.sonar.ce.task.projectanalysis.metric.Metric;
import org.sonar.ce.task.projectanalysis.qualitygate.Condition; import org.sonar.ce.task.projectanalysis.qualitygate.Condition;
import org.sonar.ce.task.projectanalysis.qualitygate.ConditionStatus; import org.sonar.ce.task.projectanalysis.qualitygate.ConditionStatus;
assertThat(taskContextCaptor.getValue().getProjectAnalysis().getAnalysis()).isEmpty(); assertThat(taskContextCaptor.getValue().getProjectAnalysis().getAnalysis()).isEmpty();
} }


@Test
public void branch_is_empty_when_legacy_branch_implementation_is_used() {
analysisMetadataHolder.setBranch(new DefaultBranchImpl("feature/foo"));

underTest.finished(true);

verify(postProjectAnalysisTask).finished(taskContextCaptor.capture());

assertThat(taskContextCaptor.getValue().getProjectAnalysis().getBranch()).isEmpty();
}

@Test @Test
public void branch_comes_from_AnalysisMetadataHolder_when_set() { public void branch_comes_from_AnalysisMetadataHolder_when_set() {
analysisMetadataHolder.setBranch(new Branch() { analysisMetadataHolder.setBranch(new Branch() {
return false; return false;
} }


@Override
public boolean isLegacyFeature() {
return false;
}

@Override @Override
public String getMergeBranchUuid() { public String getMergeBranchUuid() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
new PostProjectAnalysisTasksExecutor( new PostProjectAnalysisTasksExecutor(
ceTask, analysisMetadataHolder, qualityGateHolder, qualityGateStatusHolder, reportReader, ceTask, analysisMetadataHolder, qualityGateHolder, qualityGateStatusHolder, reportReader,
system2, new PostProjectAnalysisTask[] {logStatisticsTask}) system2, new PostProjectAnalysisTask[] {logStatisticsTask})
.finished(allStepsExecuted);
.finished(allStepsExecuted);


verify(logStatisticsTask).finished(taskContextCaptor.capture()); verify(logStatisticsTask).finished(taskContextCaptor.capture());


List<String> logs = logTester.logs(LoggerLevel.INFO); List<String> logs = logTester.logs(LoggerLevel.INFO);
assertThat(logs).hasSize(1); assertThat(logs).hasSize(1);
StringBuilder expectedLog = new StringBuilder("^PT1 "); StringBuilder expectedLog = new StringBuilder("^PT1 ");
stats.forEach((k,v) -> expectedLog.append("\\| " + k + "=" + v +" "));
stats.forEach((k, v) -> expectedLog.append("\\| " + k + "=" + v + " "));
expectedLog.append("\\| status=SUCCESS \\| time=\\d+ms$"); expectedLog.append("\\| status=SUCCESS \\| time=\\d+ms$");
assertThat(logs.get(0)).matches(expectedLog.toString()); assertThat(logs.get(0)).matches(expectedLog.toString());
} }

+ 7
- 35
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/BranchLoaderTest.java View File



import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;


public class BranchLoaderTest { public class BranchLoaderTest {
@Rule @Rule
public AnalysisMetadataHolderRule metadataHolder = new AnalysisMetadataHolderRule(); public AnalysisMetadataHolderRule metadataHolder = new AnalysisMetadataHolderRule();


@Test @Test
public void throw_ME_if_both_branch_properties_are_set() {
public void throw_ME_if_both_delegate_absent_and_has_branch_parameters() {
ScannerReport.Metadata metadata = ScannerReport.Metadata.newBuilder() ScannerReport.Metadata metadata = ScannerReport.Metadata.newBuilder()
.setDeprecatedBranch("foo")
.setBranchName("bar") .setBranchName("bar")
.build(); .build();


expectedException.expect(MessageException.class); expectedException.expect(MessageException.class);
expectedException.expectMessage("Properties sonar.branch and sonar.branch.name can't be set together");
expectedException.expectMessage("Current edition does not support branch feature");


new BranchLoader(metadataHolder).load(metadata); new BranchLoader(metadataHolder).load(metadata);
} }
} }


@Test @Test
public void default_support_of_branches_is_enabled_if_delegate_is_absent() {
public void default_support_of_branches_is_enabled_if_delegate_is_present_for_main_branch() {
ScannerReport.Metadata metadata = ScannerReport.Metadata.newBuilder() ScannerReport.Metadata metadata = ScannerReport.Metadata.newBuilder()
.setDeprecatedBranch("foo")
.build(); .build();
BranchLoaderDelegate delegate = mock(BranchLoaderDelegate.class);


new BranchLoader(metadataHolder).load(metadata);

assertThat(metadataHolder.getBranch()).isNotNull();

Branch branch = metadataHolder.getBranch();
assertThat(branch.isMain()).isTrue();
assertThat(branch.getName()).isEqualTo("foo");
}

@Test
public void default_support_of_branches_is_enabled_if_delegate_is_present() {
ScannerReport.Metadata metadata = ScannerReport.Metadata.newBuilder()
.setDeprecatedBranch("foo")
.build();

FakeDelegate delegate = new FakeDelegate();
new BranchLoader(metadataHolder, delegate).load(metadata); new BranchLoader(metadataHolder, delegate).load(metadata);


assertThat(metadataHolder.getBranch()).isNotNull();

Branch branch = metadataHolder.getBranch();
assertThat(branch.isMain()).isTrue();
assertThat(branch.getName()).isEqualTo("foo");
}

private class FakeDelegate implements BranchLoaderDelegate {
Branch branch = mock(Branch.class);

@Override
public void load(ScannerReport.Metadata metadata) {
metadataHolder.setBranch(branch);
}
verify(delegate, times(1)).load(metadata);
} }
} }

+ 0
- 38
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/DefaultBranchImplTest.java View File

*/ */
package org.sonar.ce.task.projectanalysis.component; package org.sonar.ce.task.projectanalysis.component;


import javax.annotation.Nullable;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.sonar.api.utils.MessageException;
import org.sonar.db.component.BranchDto; import org.sonar.db.component.BranchDto;
import org.sonar.db.component.BranchType; import org.sonar.db.component.BranchType;
import org.sonar.scanner.protocol.output.ScannerReport; import org.sonar.scanner.protocol.output.ScannerReport;
@Rule @Rule
public ExpectedException expectedException = ExpectedException.none(); public ExpectedException expectedException = ExpectedException.none();


@Test
public void throw_ME_if_name_contains_invalid_characters() {
assertThatNameIsCorrect("master");
assertThatNameIsCorrect("feature/foo");
assertThatNameIsCorrect("feature_foo");

assertThatNameIsNotCorrect("feature foo");
assertThatNameIsNotCorrect("feature#foo");
}

@Test @Test
public void default_branch_represents_the_project() { public void default_branch_represents_the_project() {
DefaultBranchImpl branch = new DefaultBranchImpl(); DefaultBranchImpl branch = new DefaultBranchImpl();
assertThat(branch.generateKey(PROJECT_KEY, null)).isEqualTo("P"); assertThat(branch.generateKey(PROJECT_KEY, null)).isEqualTo("P");
assertThat(branch.generateKey(PROJECT_KEY, FILE.getProjectRelativePath())).isEqualTo("P:src/Foo.js"); assertThat(branch.generateKey(PROJECT_KEY, FILE.getProjectRelativePath())).isEqualTo("P:src/Foo.js");
} }

@Test
public void branch_represents_a_forked_project_with_different_key() {
DefaultBranchImpl branch = new DefaultBranchImpl("bar");

// not a real branch. Parameter sonar.branch forks project.
assertThat(branch.isMain()).isTrue();
assertThat(branch.getType()).isEqualTo(BranchType.LONG);
assertThat(branch.getName()).isEqualTo("bar");
assertThat(branch.supportsCrossProjectCpd()).isFalse();

assertThat(branch.generateKey(PROJECT_KEY, null)).isEqualTo("P:bar");
assertThat(branch.generateKey(PROJECT_KEY, FILE.getProjectRelativePath())).isEqualTo("P:bar:src/Foo.js");
}

private void assertThatNameIsCorrect(@Nullable String name) {
DefaultBranchImpl branch = new DefaultBranchImpl(name);
assertThat(branch.getName()).isEqualTo(name);
}

private void assertThatNameIsNotCorrect(String name) {
expectedException.expect(MessageException.class);
expectedException.expectMessage("\"" + name + "\" is not a valid branch name. Allowed characters are alphanumeric, '-', '_', '.' and '/'.");

new DefaultBranchImpl(name);
}
} }

+ 2
- 10
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/notification/NotificationFactoryTest.java View File

public static Object[][] noBranchNameBranches() { public static Object[][] noBranchNameBranches() {
Branch mainBranch = mock(Branch.class); Branch mainBranch = mock(Branch.class);
when(mainBranch.isMain()).thenReturn(true); when(mainBranch.isMain()).thenReturn(true);
when(mainBranch.isLegacyFeature()).thenReturn(false);
when(mainBranch.getType()).thenReturn(BranchType.LONG); when(mainBranch.getType()).thenReturn(BranchType.LONG);
Branch legacyBranch = mock(Branch.class);
when(legacyBranch.isLegacyFeature()).thenReturn(true);
Branch shortBranch = mock(Branch.class); Branch shortBranch = mock(Branch.class);
when(shortBranch.isLegacyFeature()).thenReturn(false);
when(shortBranch.isMain()).thenReturn(false); when(shortBranch.isMain()).thenReturn(false);
when(shortBranch.getType()).thenReturn(BranchType.SHORT); when(shortBranch.getType()).thenReturn(BranchType.SHORT);
Branch pr = mock(Branch.class); Branch pr = mock(Branch.class);
when(pr.isLegacyFeature()).thenReturn(false);
when(pr.isMain()).thenReturn(false); when(pr.isMain()).thenReturn(false);
when(pr.getType()).thenReturn(BranchType.PULL_REQUEST); when(pr.getType()).thenReturn(BranchType.PULL_REQUEST);
return new Object[][] { return new Object[][] {
{mainBranch}, {mainBranch},
{legacyBranch},
{shortBranch}, {shortBranch},
{pr} {pr}
}; };


expectedException.expect(IllegalStateException.class); expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Can not find DTO for assignee uuid " + assigneeUuid); expectedException.expectMessage("Can not find DTO for assignee uuid " + assigneeUuid);
underTest.newIssuesChangesNotification(ImmutableSet.of(issue), assigneesByUuid); underTest.newIssuesChangesNotification(ImmutableSet.of(issue), assigneesByUuid);
} }


assertThat(changedIssue.getAssignee()).isEmpty(); assertThat(changedIssue.getAssignee()).isEmpty();
assertThat(changedIssue.getRule().getKey()).isEqualTo(issue.ruleKey()); assertThat(changedIssue.getRule().getKey()).isEqualTo(issue.ruleKey());
assertThat(changedIssue.getRule().getName()).isEqualTo(ruleRepository.getByKey(issue.ruleKey()).getName()); assertThat(changedIssue.getRule().getName()).isEqualTo(ruleRepository.getByKey(issue.ruleKey()).getName());
}
);
});
} }


private static Map<String, UserDto> nonEmptyAssigneesByUuid() { private static Map<String, UserDto> nonEmptyAssigneesByUuid() {


private static Branch newBranch(BranchType branchType, String branchName) { private static Branch newBranch(BranchType branchType, String branchName) {
Branch longBranch = mock(Branch.class); Branch longBranch = mock(Branch.class);
when(longBranch.isLegacyFeature()).thenReturn(false);
when(longBranch.isMain()).thenReturn(false); when(longBranch.isMain()).thenReturn(false);
when(longBranch.getType()).thenReturn(branchType); when(longBranch.getType()).thenReturn(branchType);
when(longBranch.getName()).thenReturn(branchName); when(longBranch.getName()).thenReturn(branchName);

+ 1
- 20
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/BuildComponentTreeStepTest.java View File

Branch branch = mock(Branch.class); Branch branch = mock(Branch.class);
when(branch.getName()).thenReturn("origin/feature"); when(branch.getName()).thenReturn("origin/feature");
when(branch.isMain()).thenReturn(false); when(branch.isMain()).thenReturn(false);
when(branch.isLegacyFeature()).thenReturn(false);
when(branch.generateKey(any(), any())).thenReturn("generated"); when(branch.generateKey(any(), any())).thenReturn("generated");
analysisMetadataHolder.setRootComponentRef(ROOT_REF) analysisMetadataHolder.setRootComponentRef(ROOT_REF)
.setAnalysisDate(ANALYSIS_DATE) .setAnalysisDate(ANALYSIS_DATE)
Branch branch = mock(Branch.class); Branch branch = mock(Branch.class);
when(branch.getName()).thenReturn(branchDto.getBranch()); when(branch.getName()).thenReturn(branchDto.getBranch());
when(branch.isMain()).thenReturn(false); when(branch.isMain()).thenReturn(false);
when(branch.isLegacyFeature()).thenReturn(false);
when(branch.generateKey(any(), any())).thenReturn(branchDto.getDbKey()); when(branch.generateKey(any(), any())).thenReturn(branchDto.getDbKey());
analysisMetadataHolder.setRootComponentRef(ROOT_REF) analysisMetadataHolder.setRootComponentRef(ROOT_REF)
.setAnalysisDate(ANALYSIS_DATE) .setAnalysisDate(ANALYSIS_DATE)
verifyComponentByRef(FILE_1_REF, REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1, REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1, REPORT_FILE_NAME_1, null); verifyComponentByRef(FILE_1_REF, REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1, REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1, REPORT_FILE_NAME_1, null);
} }


@Test
public void generate_keys_when_using_legacy_branch() {
analysisMetadataHolder.setRootComponentRef(ROOT_REF)
.setAnalysisDate(ANALYSIS_DATE)
.setProject(Project.from(newPrivateProjectDto(newOrganizationDto()).setDbKey(REPORT_PROJECT_KEY)))
.setBranch(new DefaultBranchImpl("origin/feature"));
BuildComponentTreeStep underTest = new BuildComponentTreeStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder, reportModulesPath);
reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF));
reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1));

underTest.execute(new TestComputationStepContext());

verifyComponentByRef(ROOT_REF, REPORT_PROJECT_KEY + ":origin/feature", analysisMetadataHolder.getProject().getName(), null);
verifyComponentByKey(REPORT_PROJECT_KEY + ":origin/feature:" + REPORT_DIR_PATH_1, REPORT_DIR_PATH_1);
verifyComponentByRef(FILE_1_REF, REPORT_PROJECT_KEY + ":origin/feature:" + REPORT_FILE_PATH_1, REPORT_FILE_NAME_1, null);
}

@Test @Test
public void compute_keys_and_uuids_on_project_having_module_and_directory() { public void compute_keys_and_uuids_on_project_having_module_and_directory() {
setAnalysisMetadataHolder(); setAnalysisMetadataHolder();
private void setAnalysisMetadataHolder() { private void setAnalysisMetadataHolder() {
analysisMetadataHolder.setRootComponentRef(ROOT_REF) analysisMetadataHolder.setRootComponentRef(ROOT_REF)
.setAnalysisDate(ANALYSIS_DATE) .setAnalysisDate(ANALYSIS_DATE)
.setBranch(new DefaultBranchImpl(null))
.setBranch(new DefaultBranchImpl())
.setProject(Project.from(newPrivateProjectDto(newOrganizationDto()).setDbKey(REPORT_PROJECT_KEY))); .setProject(Project.from(newPrivateProjectDto(newOrganizationDto()).setDbKey(REPORT_PROJECT_KEY)));
} }



+ 0
- 28
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/QualityGateEventsStepTest.java View File

reset(measureRepository, eventRepository, notificationService); reset(measureRepository, eventRepository, notificationService);
} }


@Test
public void verify_branch_name_is_set_in_notification_when_not_main() {
String branchName = "feature1";
analysisMetadataHolder.setBranch(new DefaultBranchImpl(branchName) {
@Override
public boolean isMain() {
return false;
}
});

when(measureRepository.getRawMeasure(PROJECT_COMPONENT, alertStatusMetric))
.thenReturn(of(Measure.newMeasureBuilder().setQualityGateStatus(OK_QUALITY_GATE_STATUS).createNoValue()));
when(measureRepository.getBaseMeasure(PROJECT_COMPONENT, alertStatusMetric)).thenReturn(
of(Measure.newMeasureBuilder().setQualityGateStatus(new QualityGateStatus(ERROR)).createNoValue()));

underTest.execute(new TestComputationStepContext());

verify(notificationService).deliver(notificationArgumentCaptor.capture());
Notification notification = notificationArgumentCaptor.getValue();
assertThat(notification.getType()).isEqualTo("alerts");
assertThat(notification.getFieldValue("projectKey")).isEqualTo(PROJECT_COMPONENT.getKey());
assertThat(notification.getFieldValue("projectName")).isEqualTo(PROJECT_COMPONENT.getName());
assertThat(notification.getFieldValue("projectVersion")).isEqualTo(PROJECT_COMPONENT.getProjectAttributes().getProjectVersion());
assertThat(notification.getFieldValue("branch")).isEqualTo(branchName);

reset(measureRepository, eventRepository, notificationService);
}

@Test @Test
public void verify_branch_name_is_not_set_in_notification_when_main() { public void verify_branch_name_is_not_set_in_notification_when_main() {
analysisMetadataHolder.setBranch(new DefaultBranchImpl()); analysisMetadataHolder.setBranch(new DefaultBranchImpl());

+ 0
- 5
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/ReportPersistComponentsStepTest.java View File

throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }


@Override
public boolean isLegacyFeature() {
return false;
}

@Override @Override
public String getName() { public String getName() {
return name; return name;

+ 6
- 25
server/sonar-server/src/main/java/org/sonar/server/ce/queue/BranchSupport.java View File

import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import org.sonar.api.server.ServerSide; import org.sonar.api.server.ServerSide;
import org.sonar.core.component.ComponentKeys;
import org.sonar.db.DbSession; import org.sonar.db.DbSession;
import org.sonar.db.component.BranchDto; import org.sonar.db.component.BranchDto;
import org.sonar.db.component.BranchType; import org.sonar.db.component.BranchType;
import org.sonar.db.component.ComponentDto; import org.sonar.db.component.ComponentDto;
import org.sonar.db.organization.OrganizationDto; import org.sonar.db.organization.OrganizationDto;


import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;


/** /**
* Branch code for {@link ReportSubmitter}. * Branch code for {@link ReportSubmitter}.
* <p> * <p>
* Does not support branches (except deprecated branch feature provided by "sonar.branch") unless an implementation of
* {@link BranchSupportDelegate} is available.
* Does not support branches unless an implementation of {@link BranchSupportDelegate} is available.
*/ */
@ServerSide @ServerSide
public class BranchSupport { public class BranchSupport {
this.delegate = delegate; this.delegate = delegate;
} }


ComponentKey createComponentKey(String projectKey, @Nullable String deprecatedBranch, Map<String, String> characteristics) {
ComponentKey createComponentKey(String projectKey, Map<String, String> characteristics) {
if (characteristics.isEmpty()) { if (characteristics.isEmpty()) {
return new ComponentKeyImpl(projectKey, deprecatedBranch, ComponentKeys.createKey(projectKey, deprecatedBranch));
return new ComponentKeyImpl(projectKey, projectKey);
} else { } else {
checkState(delegate != null, "Current edition does not support branch feature"); checkState(delegate != null, "Current edition does not support branch feature");
} }


checkArgument(deprecatedBranch == null, "Deprecated branch feature can't be used at the same time as new branch support");
return delegate.createComponentKey(projectKey, characteristics); return delegate.createComponentKey(projectKey, characteristics);
} }




public abstract String getDbKey(); public abstract String getDbKey();


public abstract Optional<String> getDeprecatedBranchName();

public abstract Optional<Branch> getBranch(); public abstract Optional<Branch> getBranch();


public abstract Optional<String> getPullRequestKey(); public abstract Optional<String> getPullRequestKey();


public final boolean isDeprecatedBranch() {
return getDeprecatedBranchName().isPresent();
}

public final boolean isMainBranch() { public final boolean isMainBranch() {
return !getBranch().isPresent() && !getPullRequestKey().isPresent(); return !getBranch().isPresent() && !getPullRequestKey().isPresent();
} }
private static final class ComponentKeyImpl extends ComponentKey { private static final class ComponentKeyImpl extends ComponentKey {
private final String key; private final String key;
private final String dbKey; private final String dbKey;
@CheckForNull
private final String deprecatedBranchName;


public ComponentKeyImpl(String key, @Nullable String deprecatedBranchName, String dbKey) {
public ComponentKeyImpl(String key, String dbKey) {
this.key = key; this.key = key;
this.deprecatedBranchName = deprecatedBranchName;
this.dbKey = dbKey; this.dbKey = dbKey;
} }


return dbKey; return dbKey;
} }


@Override
public Optional<String> getDeprecatedBranchName() {
return Optional.ofNullable(deprecatedBranchName);
}

@Override @Override
public Optional<Branch> getBranch() { public Optional<Branch> getBranch() {
return Optional.empty(); return Optional.empty();
} }
ComponentKeyImpl that = (ComponentKeyImpl) o; ComponentKeyImpl that = (ComponentKeyImpl) o;
return Objects.equals(key, that.key) && return Objects.equals(key, that.key) &&
Objects.equals(dbKey, that.dbKey) &&
Objects.equals(deprecatedBranchName, that.deprecatedBranchName);
Objects.equals(dbKey, that.dbKey);
} }


@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(key, dbKey, deprecatedBranchName);
return Objects.hash(key, dbKey);
} }
} }
} }

+ 3
- 4
server/sonar-server/src/main/java/org/sonar/server/ce/queue/ReportSubmitter.java View File

* @throws NotFoundException if the organization with the specified key does not exist * @throws NotFoundException if the organization with the specified key does not exist
* @throws IllegalArgumentException if the organization with the specified key is not the organization of the specified project (when it already exists in DB) * @throws IllegalArgumentException if the organization with the specified key is not the organization of the specified project (when it already exists in DB)
*/ */
public CeTask submit(String organizationKey, String projectKey, @Nullable String deprecatedBranch, @Nullable String projectName,
public CeTask submit(String organizationKey, String projectKey, @Nullable String projectName,
Map<String, String> characteristics, InputStream reportInput) { Map<String, String> characteristics, InputStream reportInput) {
try (DbSession dbSession = dbClient.openSession(false)) { try (DbSession dbSession = dbClient.openSession(false)) {
OrganizationDto organizationDto = getOrganizationDtoOrFail(dbSession, organizationKey); OrganizationDto organizationDto = getOrganizationDtoOrFail(dbSession, organizationKey);
BranchSupport.ComponentKey componentKey = branchSupport.createComponentKey(projectKey, deprecatedBranch, characteristics);
BranchSupport.ComponentKey componentKey = branchSupport.createComponentKey(projectKey, characteristics);
Optional<ComponentDto> existingComponent = dbClient.componentDao().selectByKey(dbSession, componentKey.getDbKey()); Optional<ComponentDto> existingComponent = dbClient.componentDao().selectByKey(dbSession, componentKey.getDbKey());
validateProject(dbSession, existingComponent, projectKey); validateProject(dbSession, existingComponent, projectKey);
ensureOrganizationIsConsistent(existingComponent, organizationDto); ensureOrganizationIsConsistent(existingComponent, organizationDto);
} }


private ComponentDto createComponent(DbSession dbSession, OrganizationDto organization, BranchSupport.ComponentKey componentKey, @Nullable String projectName) { private ComponentDto createComponent(DbSession dbSession, OrganizationDto organization, BranchSupport.ComponentKey componentKey, @Nullable String projectName) {
if (componentKey.isMainBranch() || componentKey.isDeprecatedBranch()) {
if (componentKey.isMainBranch()) {
ComponentDto project = createProject(dbSession, organization, componentKey, projectName); ComponentDto project = createProject(dbSession, organization, componentKey, projectName);
componentUpdater.commitAndIndex(dbSession, project); componentUpdater.commitAndIndex(dbSession, project);
return project; return project;
.setOrganizationUuid(organization.getUuid()) .setOrganizationUuid(organization.getUuid())
.setKey(componentKey.getKey()) .setKey(componentKey.getKey())
.setName(defaultIfBlank(projectName, componentKey.getKey())) .setName(defaultIfBlank(projectName, componentKey.getKey()))
.setDeprecatedBranch(componentKey.getDeprecatedBranchName().orElse(null))
.setQualifier(Qualifiers.PROJECT) .setQualifier(Qualifiers.PROJECT)
.setPrivate(newProjectPrivate) .setPrivate(newProjectPrivate)
.build(); .build();

+ 1
- 9
server/sonar-server/src/main/java/org/sonar/server/ce/ws/SubmitAction.java View File



private static final String PARAM_ORGANIZATION_KEY = "organization"; private static final String PARAM_ORGANIZATION_KEY = "organization";
private static final String PARAM_PROJECT_KEY = "projectKey"; private static final String PARAM_PROJECT_KEY = "projectKey";
private static final String PARAM_PROJECT_BRANCH = "projectBranch";
private static final String PARAM_PROJECT_NAME = "projectName"; private static final String PARAM_PROJECT_NAME = "projectName";
private static final String PARAM_REPORT_DATA = "report"; private static final String PARAM_REPORT_DATA = "report";
private static final String PARAM_ANALYSIS_CHARACTERISTIC = "characteristic"; private static final String PARAM_ANALYSIS_CHARACTERISTIC = "characteristic";
.setDescription("Key of project") .setDescription("Key of project")
.setExampleValue("my_project"); .setExampleValue("my_project");


// deprecated branch (see scanner parameter sonar.branch)
action
.createParam(PARAM_PROJECT_BRANCH)
.setDescription("Optional branch of project")
.setExampleValue("branch-1.x");

action action
.createParam(PARAM_PROJECT_NAME) .createParam(PARAM_PROJECT_NAME)
.setRequired(false) .setRequired(false)
.emptyAsNull() .emptyAsNull()
.or(defaultOrganizationProvider.get()::getKey); .or(defaultOrganizationProvider.get()::getKey);
String projectKey = wsRequest.mandatoryParam(PARAM_PROJECT_KEY); String projectKey = wsRequest.mandatoryParam(PARAM_PROJECT_KEY);
String deprecatedBranch = wsRequest.param(PARAM_PROJECT_BRANCH);
String projectName = abbreviate(defaultIfBlank(wsRequest.param(PARAM_PROJECT_NAME), projectKey), MAX_COMPONENT_NAME_LENGTH); String projectName = abbreviate(defaultIfBlank(wsRequest.param(PARAM_PROJECT_NAME), projectKey), MAX_COMPONENT_NAME_LENGTH);


Map<String, String> characteristics = parseTaskCharacteristics(wsRequest); Map<String, String> characteristics = parseTaskCharacteristics(wsRequest);


try (InputStream report = new BufferedInputStream(wsRequest.mandatoryParamAsPart(PARAM_REPORT_DATA).getInputStream())) { try (InputStream report = new BufferedInputStream(wsRequest.mandatoryParamAsPart(PARAM_REPORT_DATA).getInputStream())) {
CeTask task = reportSubmitter.submit(organizationKey, projectKey, deprecatedBranch, projectName, characteristics, report);
CeTask task = reportSubmitter.submit(organizationKey, projectKey, projectName, characteristics, report);
Ce.SubmitResponse submitResponse = Ce.SubmitResponse.newBuilder() Ce.SubmitResponse submitResponse = Ce.SubmitResponse.newBuilder()
.setTaskId(task.getUuid()) .setTaskId(task.getUuid())
.setProjectId(task.getComponent().get().getUuid()) .setProjectId(task.getComponent().get().getUuid())

+ 3
- 11
server/sonar-server/src/main/java/org/sonar/server/component/ComponentUpdater.java View File

import org.sonar.api.resources.Qualifiers; import org.sonar.api.resources.Qualifiers;
import org.sonar.api.resources.Scopes; import org.sonar.api.resources.Scopes;
import org.sonar.api.utils.System2; import org.sonar.api.utils.System2;
import org.sonar.core.component.ComponentKeys;
import org.sonar.core.i18n.I18n; import org.sonar.core.i18n.I18n;
import org.sonar.core.util.Uuids; import org.sonar.core.util.Uuids;
import org.sonar.db.DbClient; import org.sonar.db.DbClient;
} }


private ComponentDto createRootComponent(DbSession session, NewComponent newComponent) { private ComponentDto createRootComponent(DbSession session, NewComponent newComponent) {
checkLegacyBranchFormat(newComponent.qualifier(), newComponent.deprecatedBranch());
String keyWithBranch = ComponentKeys.createKey(newComponent.key(), newComponent.deprecatedBranch());
checkRequest(!dbClient.componentDao().selectByKey(session, keyWithBranch).isPresent(),
"Could not create %s, key already exists: %s", getQualifierToDisplay(newComponent.qualifier()), keyWithBranch);
checkRequest(!dbClient.componentDao().selectByKey(session, newComponent.key()).isPresent(),
"Could not create %s, key already exists: %s", getQualifierToDisplay(newComponent.qualifier()), newComponent.key());


String uuid = Uuids.create(); String uuid = Uuids.create();
ComponentDto component = new ComponentDto() ComponentDto component = new ComponentDto()
.setModuleUuid(null) .setModuleUuid(null)
.setModuleUuidPath(ComponentDto.UUID_PATH_SEPARATOR + uuid + ComponentDto.UUID_PATH_SEPARATOR) .setModuleUuidPath(ComponentDto.UUID_PATH_SEPARATOR + uuid + ComponentDto.UUID_PATH_SEPARATOR)
.setProjectUuid(uuid) .setProjectUuid(uuid)
.setDbKey(keyWithBranch)
.setDbKey(newComponent.key())
.setName(newComponent.name()) .setName(newComponent.name())
.setLongName(newComponent.name()) .setLongName(newComponent.name())
.setScope(Scopes.PROJECT) .setScope(Scopes.PROJECT)
checkRequest(isValidProjectKey(key), "Malformed key for %s: '%s'. It cannot be empty nor contain whitespaces.", getQualifierToDisplay(qualifier), key); checkRequest(isValidProjectKey(key), "Malformed key for %s: '%s'. It cannot be empty nor contain whitespaces.", getQualifierToDisplay(qualifier), key);
} }


private void checkLegacyBranchFormat(String qualifier, @Nullable String branch) {
checkRequest(branch == null || ComponentKeys.isValidLegacyBranch(branch),
"Malformed branch for %s: %s. Allowed characters are alphanumeric, '-', '_', '.' and '/', with at least one non-digit.", getQualifierToDisplay(qualifier), branch);
}

private String getQualifierToDisplay(String qualifier) { private String getQualifierToDisplay(String qualifier) {
return i18n.message(Locale.getDefault(), "qualifier." + qualifier, "Project"); return i18n.message(Locale.getDefault(), "qualifier." + qualifier, "Project");
} }

+ 0
- 15
server/sonar-server/src/main/java/org/sonar/server/component/NewComponent.java View File

*/ */
package org.sonar.server.component; package org.sonar.server.component;


import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;


import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
public class NewComponent { public class NewComponent {
private final String organizationUuid; private final String organizationUuid;
private final String key; private final String key;
private final String branch;
private final String qualifier; private final String qualifier;
private final String name; private final String name;
private final boolean isPrivate; private final boolean isPrivate;
private NewComponent(NewComponent.Builder builder) { private NewComponent(NewComponent.Builder builder) {
this.organizationUuid = builder.organizationUuid; this.organizationUuid = builder.organizationUuid;
this.key = builder.key; this.key = builder.key;
this.branch = builder.branch;
this.qualifier = builder.qualifier; this.qualifier = builder.qualifier;
this.name = builder.name; this.name = builder.name;
this.isPrivate = builder.isPrivate; this.isPrivate = builder.isPrivate;
return name; return name;
} }


@CheckForNull
public String deprecatedBranch() {
return branch;
}

public String qualifier() { public String qualifier() {
return qualifier; return qualifier;
} }
private String organizationUuid; private String organizationUuid;
private String key; private String key;
private String qualifier = PROJECT; private String qualifier = PROJECT;
private String branch;
private String name; private String name;
private boolean isPrivate = false; private boolean isPrivate = false;


return this; return this;
} }


public Builder setDeprecatedBranch(@Nullable String s) {
this.branch = s;
return this;
}

public Builder setQualifier(String qualifier) { public Builder setQualifier(String qualifier) {
this.qualifier = qualifier; this.qualifier = qualifier;
return this; return this;

+ 0
- 21
server/sonar-server/src/main/java/org/sonar/server/project/ws/CreateAction.java View File

import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
import static org.sonar.server.ws.WsUtils.writeProtobuf; import static org.sonar.server.ws.WsUtils.writeProtobuf;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.ACTION_CREATE; import static org.sonarqube.ws.client.project.ProjectsWsParameters.ACTION_CREATE;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_BRANCH;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NAME; import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NAME;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_PROJECT; import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_PROJECT;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_VISIBILITY; import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_VISIBILITY;
.setRequired(true) .setRequired(true)
.setExampleValue("SonarQube"); .setExampleValue("SonarQube");


action.createParam(PARAM_BRANCH)
.setDeprecatedSince("7.8")
.setDescription("SCM Branch of the project. The key of the project will become key:branch, for instance 'SonarQube:branch-5.0'")
.setExampleValue("branch-5.0");

action.createParam(PARAM_VISIBILITY) action.createParam(PARAM_VISIBILITY)
.setDescription("Whether the created project should be visible to everyone, or only specific user/groups.<br/>" + .setDescription("Whether the created project should be visible to everyone, or only specific user/groups.<br/>" +
"If no visibility is specified, the default project visibility of the organization will be used.") "If no visibility is specified, the default project visibility of the organization will be used.")
.setOrganizationUuid(organization.getUuid()) .setOrganizationUuid(organization.getUuid())
.setKey(request.getProjectKey()) .setKey(request.getProjectKey())
.setName(request.getName()) .setName(request.getName())
.setDeprecatedBranch(request.getBranch())
.setPrivate(changeToPrivate) .setPrivate(changeToPrivate)
.setQualifier(PROJECT) .setQualifier(PROJECT)
.build(), .build(),
.setOrganization(request.param(PARAM_ORGANIZATION)) .setOrganization(request.param(PARAM_ORGANIZATION))
.setProjectKey(request.mandatoryParam(PARAM_PROJECT)) .setProjectKey(request.mandatoryParam(PARAM_PROJECT))
.setName(abbreviate(request.mandatoryParam(PARAM_NAME), MAX_COMPONENT_NAME_LENGTH)) .setName(abbreviate(request.mandatoryParam(PARAM_NAME), MAX_COMPONENT_NAME_LENGTH))
.setBranch(request.param(PARAM_BRANCH))
.setVisibility(request.param(PARAM_VISIBILITY)) .setVisibility(request.param(PARAM_VISIBILITY))
.build(); .build();
} }
private final String organization; private final String organization;
private final String projectKey; private final String projectKey;
private final String name; private final String name;
private final String branch;
@CheckForNull @CheckForNull
private final String visibility; private final String visibility;


this.organization = builder.organization; this.organization = builder.organization;
this.projectKey = builder.projectKey; this.projectKey = builder.projectKey;
this.name = builder.name; this.name = builder.name;
this.branch = builder.branch;
this.visibility = builder.visibility; this.visibility = builder.visibility;
} }


return name; return name;
} }


@CheckForNull
public String getBranch() {
return branch;
}

@CheckForNull @CheckForNull
public String getVisibility() { public String getVisibility() {
return visibility; return visibility;
private String organization; private String organization;
private String projectKey; private String projectKey;
private String name; private String name;
private String branch;
@CheckForNull @CheckForNull
private String visibility; private String visibility;


return this; return this;
} }


public Builder setBranch(@Nullable String branch) {
this.branch = branch;
return this;
}

public Builder setVisibility(@Nullable String visibility) { public Builder setVisibility(@Nullable String visibility) {
this.visibility = visibility; this.visibility = visibility;
return this; return this;

+ 6
- 28
server/sonar-server/src/test/java/org/sonar/server/ce/queue/BranchReportSubmitterTest.java View File

import org.sonar.ce.queue.CeQueue; import org.sonar.ce.queue.CeQueue;
import org.sonar.ce.queue.CeQueueImpl; import org.sonar.ce.queue.CeQueueImpl;
import org.sonar.ce.queue.CeTaskSubmit; import org.sonar.ce.queue.CeTaskSubmit;
import org.sonar.core.component.ComponentKeys;
import org.sonar.db.DbSession; import org.sonar.db.DbSession;
import org.sonar.db.DbTester; import org.sonar.db.DbTester;
import org.sonar.db.ce.CeTaskTypes; import org.sonar.db.ce.CeTaskTypes;
mockSuccessfulPrepareSubmitCall(); mockSuccessfulPrepareSubmitCall();
InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8); InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8);


underTest.submit(organization.getKey(), project.getDbKey(), null, project.name(), emptyMap(), reportInput);
underTest.submit(organization.getKey(), project.getDbKey(), project.name(), emptyMap(), reportInput);


verifyZeroInteractions(branchSupportDelegate); verifyZeroInteractions(branchSupportDelegate);
} }
InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8); InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8);
String taskUuid = mockSuccessfulPrepareSubmitCall(); String taskUuid = mockSuccessfulPrepareSubmitCall();


underTest.submit(organization.getKey(), project.getDbKey(), null, project.name(), randomCharacteristics, reportInput);
underTest.submit(organization.getKey(), project.getDbKey(), project.name(), randomCharacteristics, reportInput);


verifyZeroInteractions(permissionTemplateService); verifyZeroInteractions(permissionTemplateService);
verifyZeroInteractions(favoriteUpdater); verifyZeroInteractions(favoriteUpdater);
verifyQueueSubmit(project, branch, user, randomCharacteristics, taskUuid); verifyQueueSubmit(project, branch, user, randomCharacteristics, taskUuid);
} }


@Test
public void submit_a_report_on_existing_deprecated_branch() {
OrganizationDto organization = db.organizations().insert();
String projectKey = randomAlphabetic(10);
String deprecatedBranchName = randomAlphabetic(11);
ComponentDto deprecatedBranch = db.components().insertMainBranch(organization, cpt -> cpt.setDbKey(ComponentKeys.createKey(projectKey, deprecatedBranchName)));
UserDto user = db.users().insertUser();
userSession.logIn(user).addProjectPermission(SCAN_EXECUTION, deprecatedBranch);
Map<String, String> noCharacteristics = emptyMap();
InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8);
String taskUuid = mockSuccessfulPrepareSubmitCall();

underTest.submit(organization.getKey(), deprecatedBranch.getDbKey(), null, deprecatedBranch.name(), noCharacteristics, reportInput);

verifyZeroInteractions(permissionTemplateService);
verifyZeroInteractions(favoriteUpdater);
verify(branchSupport, times(0)).createBranchComponent(any(), any(), any(), any(), any());
verifyZeroInteractions(branchSupportDelegate);
verifyQueueSubmit(deprecatedBranch, deprecatedBranch, user, noCharacteristics, taskUuid);
}

@Test @Test
public void submit_a_report_on_missing_branch_but_existing_project() { public void submit_a_report_on_missing_branch_but_existing_project() {
OrganizationDto organization = db.organizations().insert(); OrganizationDto organization = db.organizations().insert();
InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8); InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8);
String taskUuid = mockSuccessfulPrepareSubmitCall(); String taskUuid = mockSuccessfulPrepareSubmitCall();


underTest.submit(organization.getKey(), existingProject.getDbKey(), null, existingProject.name(), randomCharacteristics, reportInput);
underTest.submit(organization.getKey(), existingProject.getDbKey(), existingProject.name(), randomCharacteristics, reportInput);


verifyZeroInteractions(permissionTemplateService); verifyZeroInteractions(permissionTemplateService);
verifyZeroInteractions(favoriteUpdater); verifyZeroInteractions(favoriteUpdater);
String taskUuid = mockSuccessfulPrepareSubmitCall(); String taskUuid = mockSuccessfulPrepareSubmitCall();
InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8); InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8);


underTest.submit(organization.getKey(), nonExistingProject.getDbKey(), null, nonExistingProject.name(), randomCharacteristics, reportInput);
underTest.submit(organization.getKey(), nonExistingProject.getDbKey(), nonExistingProject.name(), randomCharacteristics, reportInput);


BranchDto exitingProjectMainBranch = db.getDbClient().branchDao().selectByUuid(db.getSession(), nonExistingProject.uuid()).get(); BranchDto exitingProjectMainBranch = db.getDbClient().branchDao().selectByUuid(db.getSession(), nonExistingProject.uuid()).get();
verify(branchSupport).createBranchComponent(any(DbSession.class), same(componentKey), eq(organization), eq(nonExistingProject), eq(exitingProjectMainBranch)); verify(branchSupport).createBranchComponent(any(DbSession.class), same(componentKey), eq(organization), eq(nonExistingProject), eq(exitingProjectMainBranch));
when(branchSupportDelegate.createComponentKey(any(), any())).thenThrow(expected); when(branchSupportDelegate.createComponentKey(any(), any())).thenThrow(expected);


try { try {
underTest.submit(organization.getKey(), project.getDbKey(), null, project.name(), randomCharacteristics, reportInput);
underTest.submit(organization.getKey(), project.getDbKey(), project.name(), randomCharacteristics, reportInput);
fail("exception should have been thrown"); fail("exception should have been thrown");
} catch (Exception e) { } catch (Exception e) {
assertThat(e).isSameAs(expected); assertThat(e).isSameAs(expected);
expectedException.expect(ForbiddenException.class); expectedException.expect(ForbiddenException.class);
expectedException.expectMessage("Insufficient privileges"); expectedException.expectMessage("Insufficient privileges");


underTest.submit(organization.getKey(), nonExistingProject.getDbKey(), null, nonExistingProject.name(), randomCharacteristics, reportInput);
underTest.submit(organization.getKey(), nonExistingProject.getDbKey(), nonExistingProject.name(), randomCharacteristics, reportInput);
} }


private static ComponentDto createButDoNotInsertBranch(ComponentDto project) { private static ComponentDto createButDoNotInsertBranch(ComponentDto project) {

+ 4
- 47
server/sonar-server/src/test/java/org/sonar/server/ce/queue/BranchSupportTest.java View File



import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner; import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.Random; import java.util.Random;
public void createComponentKey_of_main_branch() { public void createComponentKey_of_main_branch() {
String projectKey = randomAlphanumeric(12); String projectKey = randomAlphanumeric(12);


ComponentKey componentKey = underTestNoBranch.createComponentKey(projectKey, null, NO_CHARACTERISTICS);
ComponentKey componentKey = underTestNoBranch.createComponentKey(projectKey, NO_CHARACTERISTICS);


assertThat(componentKey) assertThat(componentKey)
.isEqualTo(underTestWithBranch.createComponentKey(projectKey, null, NO_CHARACTERISTICS));
.isEqualTo(underTestWithBranch.createComponentKey(projectKey, NO_CHARACTERISTICS));
assertThat(componentKey.getKey()).isEqualTo(projectKey); assertThat(componentKey.getKey()).isEqualTo(projectKey);
assertThat(componentKey.getDbKey()).isEqualTo(projectKey); assertThat(componentKey.getDbKey()).isEqualTo(projectKey);
assertThat(componentKey.isDeprecatedBranch()).isFalse();
assertThat(componentKey.getMainBranchComponentKey()).isSameAs(componentKey); assertThat(componentKey.getMainBranchComponentKey()).isSameAs(componentKey);
assertThat(componentKey.getBranch()).isEmpty(); assertThat(componentKey.getBranch()).isEmpty();
assertThat(componentKey.getPullRequestKey()).isEmpty(); assertThat(componentKey.getPullRequestKey()).isEmpty();
} }


@Test
public void createComponentKey_of_deprecated_branch() {
String projectKey = randomAlphanumeric(12);
String deprecatedBranchName = randomAlphanumeric(12);

ComponentKey componentKey = underTestNoBranch.createComponentKey(projectKey, deprecatedBranchName, NO_CHARACTERISTICS);

assertThat(componentKey)
.isEqualTo(underTestWithBranch.createComponentKey(projectKey, deprecatedBranchName, NO_CHARACTERISTICS));
assertThat(componentKey.getKey()).isEqualTo(projectKey);
assertThat(componentKey.getDbKey()).isEqualTo(projectKey + ":" + deprecatedBranchName);
assertThat(componentKey.isDeprecatedBranch()).isTrue();
assertThat(componentKey.getMainBranchComponentKey()).isSameAs(componentKey);
assertThat(componentKey.getBranch()).isEmpty();
assertThat(componentKey.getPullRequestKey()).isEmpty();
}

@Test
@UseDataProvider("nullOrNonEmpty")
public void createComponentKey_with_ISE_if_characteristics_is_not_empty_and_delegate_is_null(String deprecatedBranchName) {
String projectKey = randomAlphanumeric(12);
Map<String, String> nonEmptyMap = newRandomNonEmptyMap();

expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Current edition does not support branch feature");
underTestNoBranch.createComponentKey(projectKey, deprecatedBranchName, nonEmptyMap);
}

@Test
public void createComponentKey_fails_with_IAE_if_characteristics_is_not_empty_and_deprecatedBranchName_is_non_null() {
String projectKey = randomAlphanumeric(12);
String deprecatedBranchName = randomAlphanumeric(13);
Map<String, String> nonEmptyMap = newRandomNonEmptyMap();

expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Deprecated branch feature can't be used at the same time as new branch support");
underTestWithBranch.createComponentKey(projectKey, deprecatedBranchName, nonEmptyMap);
}

@Test @Test
public void createComponentKey_delegates_to_delegate_if_characteristics_is_not_empty() { public void createComponentKey_delegates_to_delegate_if_characteristics_is_not_empty() {
String projectKey = randomAlphanumeric(12); String projectKey = randomAlphanumeric(12);
ComponentKey expected = mock(ComponentKey.class); ComponentKey expected = mock(ComponentKey.class);
when(branchSupportDelegate.createComponentKey(projectKey, nonEmptyMap)).thenReturn(expected); when(branchSupportDelegate.createComponentKey(projectKey, nonEmptyMap)).thenReturn(expected);


ComponentKey componentKey = underTestWithBranch.createComponentKey(projectKey, null, nonEmptyMap);
ComponentKey componentKey = underTestWithBranch.createComponentKey(projectKey, nonEmptyMap);


assertThat(componentKey).isSameAs(expected); assertThat(componentKey).isSameAs(expected);
} }


expectedException.expect(IllegalStateException.class); expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Current edition does not support branch feature"); expectedException.expectMessage("Current edition does not support branch feature");
underTestNoBranch.createBranchComponent(dbSession, componentKey, organization, mainComponentDto, mainComponentBranchDto); underTestNoBranch.createBranchComponent(dbSession, componentKey, organization, mainComponentDto, mainComponentBranchDto);
} }



+ 16
- 32
server/sonar-server/src/test/java/org/sonar/server/ce/queue/ReportSubmitterTest.java View File

import org.sonar.ce.queue.CeQueueImpl; import org.sonar.ce.queue.CeQueueImpl;
import org.sonar.ce.queue.CeTaskSubmit; import org.sonar.ce.queue.CeTaskSubmit;
import org.sonar.core.i18n.I18n; import org.sonar.core.i18n.I18n;
import org.sonar.core.permission.GlobalPermissions;
import org.sonar.db.DbSession; import org.sonar.db.DbSession;
import org.sonar.db.DbTester; import org.sonar.db.DbTester;
import org.sonar.db.ce.CeTaskTypes; import org.sonar.db.ce.CeTaskTypes;
expectedException.expect(IllegalStateException.class); expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Current edition does not support branch feature"); expectedException.expectMessage("Current edition does not support branch feature");


underTest.submit(defaultOrganizationKey, PROJECT_KEY, null, PROJECT_NAME, nonEmptyCharacteristics, reportInput);
underTest.submit(defaultOrganizationKey, PROJECT_KEY, PROJECT_NAME, nonEmptyCharacteristics, reportInput);
} }


@Test @Test
when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(), eq(defaultOrganizationUuid), any(), eq(PROJECT_KEY))) when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(), eq(defaultOrganizationUuid), any(), eq(PROJECT_KEY)))
.thenReturn(true); .thenReturn(true);


underTest.submit(defaultOrganizationKey, PROJECT_KEY, null, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}", UTF_8));
underTest.submit(defaultOrganizationKey, PROJECT_KEY, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}", UTF_8));


verifyReportIsPersisted(TASK_UUID); verifyReportIsPersisted(TASK_UUID);
} }
userSession.logIn(user).addProjectPermission(SCAN_EXECUTION, project); userSession.logIn(user).addProjectPermission(SCAN_EXECUTION, project);
mockSuccessfulPrepareSubmitCall(); mockSuccessfulPrepareSubmitCall();


underTest.submit(defaultOrganizationKey, project.getDbKey(), null, project.name(), emptyMap(), IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8));
underTest.submit(defaultOrganizationKey, project.getDbKey(), project.name(), emptyMap(), IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8));


verifyReportIsPersisted(TASK_UUID); verifyReportIsPersisted(TASK_UUID);
verifyZeroInteractions(permissionTemplateService); verifyZeroInteractions(permissionTemplateService);
when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), eq(organization.getUuid()), any(), eq(PROJECT_KEY))).thenReturn(true); when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), eq(organization.getUuid()), any(), eq(PROJECT_KEY))).thenReturn(true);
when(permissionTemplateService.hasDefaultTemplateWithPermissionOnProjectCreator(any(DbSession.class), any(ComponentDto.class))).thenReturn(true); when(permissionTemplateService.hasDefaultTemplateWithPermissionOnProjectCreator(any(DbSession.class), any(ComponentDto.class))).thenReturn(true);


underTest.submit(organization.getKey(), PROJECT_KEY, null, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}"));
underTest.submit(organization.getKey(), PROJECT_KEY, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}"));


ComponentDto createdProject = db.getDbClient().componentDao().selectByKey(db.getSession(), PROJECT_KEY).get(); ComponentDto createdProject = db.getDbClient().componentDao().selectByKey(db.getSession(), PROJECT_KEY).get();
verifyReportIsPersisted(TASK_UUID); verifyReportIsPersisted(TASK_UUID);
when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), eq(organization.getUuid()), any(), eq(PROJECT_KEY))).thenReturn(true); when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), eq(organization.getUuid()), any(), eq(PROJECT_KEY))).thenReturn(true);
when(permissionTemplateService.hasDefaultTemplateWithPermissionOnProjectCreator(any(DbSession.class), any(ComponentDto.class))).thenReturn(true); when(permissionTemplateService.hasDefaultTemplateWithPermissionOnProjectCreator(any(DbSession.class), any(ComponentDto.class))).thenReturn(true);


underTest.submit(organization.getKey(), PROJECT_KEY, null, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}"));
underTest.submit(organization.getKey(), PROJECT_KEY, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}"));


ComponentDto createdProject = db.getDbClient().componentDao().selectByKey(db.getSession(), PROJECT_KEY).get(); ComponentDto createdProject = db.getDbClient().componentDao().selectByKey(db.getSession(), PROJECT_KEY).get();
assertThat(db.favorites().hasFavorite(createdProject, user.getId())).isTrue(); assertThat(db.favorites().hasFavorite(createdProject, user.getId())).isTrue();
when(permissionTemplateService.hasDefaultTemplateWithPermissionOnProjectCreator(any(DbSession.class), any(ComponentDto.class))).thenReturn(false); when(permissionTemplateService.hasDefaultTemplateWithPermissionOnProjectCreator(any(DbSession.class), any(ComponentDto.class))).thenReturn(false);
mockSuccessfulPrepareSubmitCall(); mockSuccessfulPrepareSubmitCall();


underTest.submit(defaultOrganizationKey, PROJECT_KEY, null, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}"));
underTest.submit(defaultOrganizationKey, PROJECT_KEY, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}"));


ComponentDto createdProject = db.getDbClient().componentDao().selectByKey(db.getSession(), PROJECT_KEY).get(); ComponentDto createdProject = db.getDbClient().componentDao().selectByKey(db.getSession(), PROJECT_KEY).get();
assertThat(db.favorites().hasNoFavorite(createdProject)).isTrue(); assertThat(db.favorites().hasNoFavorite(createdProject)).isTrue();
when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), eq(organization.getUuid()), any(), eq(PROJECT_KEY))).thenReturn(true); when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), eq(organization.getUuid()), any(), eq(PROJECT_KEY))).thenReturn(true);
when(permissionTemplateService.hasDefaultTemplateWithPermissionOnProjectCreator(any(DbSession.class), any(ComponentDto.class))).thenReturn(true); when(permissionTemplateService.hasDefaultTemplateWithPermissionOnProjectCreator(any(DbSession.class), any(ComponentDto.class))).thenReturn(true);


underTest.submit(organization.getKey(), PROJECT_KEY, null, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}"));
underTest.submit(organization.getKey(), PROJECT_KEY, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}"));


ComponentDto createdProject = db.getDbClient().componentDao().selectByKey(db.getSession(), PROJECT_KEY).get(); ComponentDto createdProject = db.getDbClient().componentDao().selectByKey(db.getSession(), PROJECT_KEY).get();
assertThat(db.favorites().hasNoFavorite(createdProject)).isTrue(); assertThat(db.favorites().hasNoFavorite(createdProject)).isTrue();
when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), eq(defaultOrganizationUuid), any(), eq(PROJECT_KEY))) when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), eq(defaultOrganizationUuid), any(), eq(PROJECT_KEY)))
.thenReturn(true); .thenReturn(true);


underTest.submit(defaultOrganizationKey, PROJECT_KEY, null, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}"));
underTest.submit(defaultOrganizationKey, PROJECT_KEY, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}"));


verify(queue).submit(any(CeTaskSubmit.class)); verify(queue).submit(any(CeTaskSubmit.class));
} }
userSession.addPermission(SCAN, org); userSession.addPermission(SCAN, org);
mockSuccessfulPrepareSubmitCall(); mockSuccessfulPrepareSubmitCall();


underTest.submit(org.getKey(), project.getDbKey(), null, project.name(), emptyMap(), IOUtils.toInputStream("{binary}"));
underTest.submit(org.getKey(), project.getDbKey(), project.name(), emptyMap(), IOUtils.toInputStream("{binary}"));


verify(queue).submit(any(CeTaskSubmit.class)); verify(queue).submit(any(CeTaskSubmit.class));
} }
userSession.addProjectPermission(SCAN_EXECUTION, project); userSession.addProjectPermission(SCAN_EXECUTION, project);
mockSuccessfulPrepareSubmitCall(); mockSuccessfulPrepareSubmitCall();


underTest.submit(defaultOrganizationKey, project.getDbKey(), null, project.name(), emptyMap(), IOUtils.toInputStream("{binary}"));
underTest.submit(defaultOrganizationKey, project.getDbKey(), project.name(), emptyMap(), IOUtils.toInputStream("{binary}"));


verify(queue).submit(any(CeTaskSubmit.class)); verify(queue).submit(any(CeTaskSubmit.class));
} }


/**
* SONAR-8757
*/
@Test
public void project_branch_must_not_benefit_from_the_scan_permission_on_main_project() {
String branchName = "branchFoo";
ComponentDto mainProject = db.components().insertPrivateProject();
userSession.addProjectPermission(GlobalPermissions.SCAN_EXECUTION, mainProject);
// user does not have the "scan" permission on the branch, so it can't scan it
ComponentDto branchProject = db.components().insertPrivateProject(p -> p.setDbKey(mainProject.getDbKey() + ":" + branchName));

expectedException.expect(ForbiddenException.class);
underTest.submit(defaultOrganizationKey, mainProject.getDbKey(), branchName, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}"));
}

@Test @Test
public void fail_with_NotFoundException_if_organization_with_specified_key_does_not_exist() { public void fail_with_NotFoundException_if_organization_with_specified_key_does_not_exist() {
expectedException.expect(NotFoundException.class); expectedException.expect(NotFoundException.class);
expectedException.expectMessage("Organization with key 'fop' does not exist"); expectedException.expectMessage("Organization with key 'fop' does not exist");


underTest.submit("fop", PROJECT_KEY, null, null, emptyMap(), null /* method will fail before parameter is used */);
underTest.submit("fop", PROJECT_KEY, null, emptyMap(), null /* method will fail before parameter is used */);
} }


@Test @Test
ComponentDto project = db.components().insertPrivateProject(organization); ComponentDto project = db.components().insertPrivateProject(organization);
mockSuccessfulPrepareSubmitCall(); mockSuccessfulPrepareSubmitCall();


underTest.submit(organization.getKey(), project.getDbKey(), null, project.name(), emptyMap(), IOUtils.toInputStream("{binary}"));
underTest.submit(organization.getKey(), project.getDbKey(), project.name(), emptyMap(), IOUtils.toInputStream("{binary}"));
} }


@Test @Test
expectedException.expect(BadRequestException.class); expectedException.expect(BadRequestException.class);
expectedException.expectMessage(format("Component '%s' is not a project", component.getKey())); expectedException.expectMessage(format("Component '%s' is not a project", component.getKey()));


underTest.submit(defaultOrganizationKey, component.getDbKey(), null, component.name(), emptyMap(), IOUtils.toInputStream("{binary}"));
underTest.submit(defaultOrganizationKey, component.getDbKey(), component.name(), emptyMap(), IOUtils.toInputStream("{binary}"));
} }


@Test @Test
mockSuccessfulPrepareSubmitCall(); mockSuccessfulPrepareSubmitCall();


try { try {
underTest.submit(defaultOrganizationKey, module.getDbKey(), null, module.name(), emptyMap(), IOUtils.toInputStream("{binary}"));
underTest.submit(defaultOrganizationKey, module.getDbKey(), module.name(), emptyMap(), IOUtils.toInputStream("{binary}"));
fail(); fail();
} catch (BadRequestException e) { } catch (BadRequestException e) {
assertThat(e.errors()).contains( assertThat(e.errors()).contains(
public void fail_with_forbidden_exception_when_no_scan_permission() { public void fail_with_forbidden_exception_when_no_scan_permission() {
expectedException.expect(ForbiddenException.class); expectedException.expect(ForbiddenException.class);


underTest.submit(defaultOrganizationKey, PROJECT_KEY, null, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}"));
underTest.submit(defaultOrganizationKey, PROJECT_KEY, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}"));
} }


@Test @Test
mockSuccessfulPrepareSubmitCall(); mockSuccessfulPrepareSubmitCall();


expectedException.expect(ForbiddenException.class); expectedException.expect(ForbiddenException.class);
underTest.submit(defaultOrganizationKey, PROJECT_KEY, null, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}"));
underTest.submit(defaultOrganizationKey, PROJECT_KEY, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}"));
} }


private void verifyReportIsPersisted(String taskUuid) { private void verifyReportIsPersisted(String taskUuid) {

+ 9
- 20
server/sonar-server/src/test/java/org/sonar/server/ce/ws/SubmitActionTest.java View File

import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;


@Test @Test
public void submit_task_to_the_queue_and_ask_for_immediate_processing() { public void submit_task_to_the_queue_and_ask_for_immediate_processing() {
when(reportSubmitter.submit(eq(organizationKey), eq("my_project"), isNull(), eq("My Project"),
anyMap(), any())).thenReturn(A_CE_TASK);
when(reportSubmitter.submit(eq(organizationKey), eq("my_project"), eq("My Project"), anyMap(), any())).thenReturn(A_CE_TASK);


Ce.SubmitResponse submitResponse = tester.newRequest() Ce.SubmitResponse submitResponse = tester.newRequest()
.setParam("projectKey", "my_project") .setParam("projectKey", "my_project")
.setMethod("POST") .setMethod("POST")
.executeProtobuf(Ce.SubmitResponse.class); .executeProtobuf(Ce.SubmitResponse.class);


verify(reportSubmitter).submit(eq(organizationKey), eq("my_project"), isNull(), eq("My Project"),
anyMap(), any());
verify(reportSubmitter).submit(eq(organizationKey), eq("my_project"), eq("My Project"), anyMap(), any());


assertThat(submitResponse.getTaskId()).isEqualTo("TASK_1"); assertThat(submitResponse.getTaskId()).isEqualTo("TASK_1");
assertThat(submitResponse.getProjectId()).isEqualTo(PROJECT_UUID); assertThat(submitResponse.getProjectId()).isEqualTo(PROJECT_UUID);


@Test @Test
public void submit_task_with_characteristics() { public void submit_task_with_characteristics() {
when(reportSubmitter.submit(eq(organizationKey), eq("my_project"), isNull(), eq("My Project"),
anyMap(), any())).thenReturn(A_CE_TASK);
when(reportSubmitter.submit(eq(organizationKey), eq("my_project"), eq("My Project"), anyMap(), any())).thenReturn(A_CE_TASK);


String[] characteristics = {"branch=foo", "pullRequest=123", "unsupported=bar"}; String[] characteristics = {"branch=foo", "pullRequest=123", "unsupported=bar"};
Ce.SubmitResponse submitResponse = tester.newRequest() Ce.SubmitResponse submitResponse = tester.newRequest()
.executeProtobuf(Ce.SubmitResponse.class); .executeProtobuf(Ce.SubmitResponse.class);


assertThat(submitResponse.getTaskId()).isEqualTo("TASK_1"); assertThat(submitResponse.getTaskId()).isEqualTo("TASK_1");
verify(reportSubmitter).submit(eq(organizationKey), eq("my_project"), isNull(), eq("My Project"),
map.capture(), any());
verify(reportSubmitter).submit(eq(organizationKey), eq("my_project"), eq("My Project"), map.capture(), any());


// unsupported characteristics are ignored // unsupported characteristics are ignored
assertThat(map.getValue()).containsExactly(entry("branch", "foo"), entry("pullRequest", "123")); assertThat(map.getValue()).containsExactly(entry("branch", "foo"), entry("pullRequest", "123"));
public void abbreviate_long_name() { public void abbreviate_long_name() {
String longName = Strings.repeat("a", 1_000); String longName = Strings.repeat("a", 1_000);
String expectedName = Strings.repeat("a", 497) + "..."; String expectedName = Strings.repeat("a", 497) + "...";
when(reportSubmitter.submit(eq(organizationKey), eq("my_project"), isNull(), eq(expectedName),
anyMap(), any())).thenReturn(A_CE_TASK);
when(reportSubmitter.submit(eq(organizationKey), eq("my_project"), eq(expectedName), anyMap(), any())).thenReturn(A_CE_TASK);


Ce.SubmitResponse submitResponse = tester.newRequest() Ce.SubmitResponse submitResponse = tester.newRequest()
.setParam("projectKey", "my_project") .setParam("projectKey", "my_project")
.setMethod("POST") .setMethod("POST")
.executeProtobuf(Ce.SubmitResponse.class); .executeProtobuf(Ce.SubmitResponse.class);


verify(reportSubmitter).submit(eq(organizationKey), eq("my_project"), isNull(), eq(expectedName),
anyMap(), any());
verify(reportSubmitter).submit(eq(organizationKey), eq("my_project"), eq(expectedName), anyMap(), any());


assertThat(submitResponse.getTaskId()).isEqualTo("TASK_1"); assertThat(submitResponse.getTaskId()).isEqualTo("TASK_1");
assertThat(submitResponse.getProjectId()).isEqualTo(PROJECT_UUID); assertThat(submitResponse.getProjectId()).isEqualTo(PROJECT_UUID);


@Test @Test
public void test_example_json_response() { public void test_example_json_response() {
when(reportSubmitter.submit(eq(organizationKey), eq("my_project"), isNull(), eq("My Project"),
anyMap(), any())).thenReturn(A_CE_TASK);
when(reportSubmitter.submit(eq(organizationKey), eq("my_project"), eq("My Project"), anyMap(), any())).thenReturn(A_CE_TASK);


TestResponse wsResponse = tester.newRequest() TestResponse wsResponse = tester.newRequest()
.setParam("projectKey", "my_project") .setParam("projectKey", "my_project")
*/ */
@Test @Test
public void project_name_is_optional() { public void project_name_is_optional() {
when(reportSubmitter.submit(eq(organizationKey), eq("my_project"), isNull(), eq("my_project"),
anyMap(), any())).thenReturn(A_CE_TASK);
when(reportSubmitter.submit(eq(organizationKey), eq("my_project"), eq("my_project"), anyMap(), any())).thenReturn(A_CE_TASK);


tester.newRequest() tester.newRequest()
.setParam("projectKey", "my_project") .setParam("projectKey", "my_project")
.setMethod("POST") .setMethod("POST")
.execute(); .execute();


verify(reportSubmitter).submit(eq(organizationKey), eq("my_project"), isNull(), eq("my_project"),
anyMap(), any());

verify(reportSubmitter).submit(eq(organizationKey), eq("my_project"), eq("my_project"), anyMap(), any());
} }
} }

+ 0
- 29
server/sonar-server/src/test/java/org/sonar/server/component/ComponentUpdaterTest.java View File

assertThat(loaded.isPrivate()).isEqualTo(project.isPrivate()); assertThat(loaded.isPrivate()).isEqualTo(project.isPrivate());
} }


@Test
public void create_project_with_deprecated_branch() {
ComponentDto project = underTest.create(db.getSession(),
NewComponent.newComponentBuilder()
.setKey(DEFAULT_PROJECT_KEY)
.setName(DEFAULT_PROJECT_NAME)
.setDeprecatedBranch("origin/master")
.setOrganizationUuid(db.getDefaultOrganization().getUuid())
.build(),
null);

assertThat(project.getDbKey()).isEqualTo("project-key:origin/master");
}

@Test @Test
public void create_view() { public void create_view() {
NewComponent view = NewComponent.newComponentBuilder() NewComponent view = NewComponent.newComponentBuilder()
.build(), .build(),
null); null);
} }

@Test
public void fail_to_create_new_component_on_invalid_branch() {
expectedException.expect(BadRequestException.class);
expectedException.expectMessage("Malformed branch for Project: origin?branch. Allowed characters are alphanumeric, '-', '_', '.' and '/', with at least one non-digit.");

underTest.create(db.getSession(),
NewComponent.newComponentBuilder()
.setKey(DEFAULT_PROJECT_KEY)
.setName(DEFAULT_PROJECT_NAME)
.setDeprecatedBranch("origin?branch")
.setOrganizationUuid(db.getDefaultOrganization().getUuid())
.build(),
null);
}
} }

+ 1
- 22
server/sonar-server/src/test/java/org/sonar/server/project/ws/CreateActionTest.java View File

import static org.sonar.server.project.ws.ProjectsWsSupport.PARAM_ORGANIZATION; import static org.sonar.server.project.ws.ProjectsWsSupport.PARAM_ORGANIZATION;
import static org.sonar.test.JsonAssert.assertJson; import static org.sonar.test.JsonAssert.assertJson;
import static org.sonarqube.ws.client.WsRequest.Method.POST; import static org.sonarqube.ws.client.WsRequest.Method.POST;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_BRANCH;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NAME; import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NAME;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_PROJECT; import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_PROJECT;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_VISIBILITY; import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_VISIBILITY;
.containsOnly(DEFAULT_PROJECT_KEY, DEFAULT_PROJECT_NAME, "TRK", "PRJ", false, null); .containsOnly(DEFAULT_PROJECT_KEY, DEFAULT_PROJECT_NAME, "TRK", "PRJ", false, null);
} }


@Test
public void create_project_with_branch() {
userSession.addPermission(PROVISION_PROJECTS, db.getDefaultOrganization());

CreateWsResponse response = call(CreateRequest.builder()
.setProjectKey(DEFAULT_PROJECT_KEY)
.setName(DEFAULT_PROJECT_NAME)
.setBranch("origin/master")
.build());

assertThat(response.getProject())
.extracting(Project::getKey, Project::getName, Project::getQualifier, Project::getVisibility)
.containsOnly(DEFAULT_PROJECT_KEY + ":origin/master", DEFAULT_PROJECT_NAME, "TRK", "public");
}

@Test @Test
public void apply_project_visibility_public() { public void apply_project_visibility_public() {
OrganizationDto organization = db.organizations().insert(); OrganizationDto organization = db.organizations().insert();
PARAM_VISIBILITY, PARAM_VISIBILITY,
PARAM_ORGANIZATION, PARAM_ORGANIZATION,
PARAM_NAME, PARAM_NAME,
PARAM_PROJECT,
PARAM_BRANCH);
PARAM_PROJECT);


WebService.Param organization = definition.param(PARAM_ORGANIZATION); WebService.Param organization = definition.param(PARAM_ORGANIZATION);
assertThat(organization.description()).isEqualTo("The key of the organization"); assertThat(organization.description()).isEqualTo("The key of the organization");
WebService.Param name = definition.param(PARAM_NAME); WebService.Param name = definition.param(PARAM_NAME);
assertThat(name.isRequired()).isTrue(); assertThat(name.isRequired()).isTrue();
assertThat(name.description()).isEqualTo("Name of the project. If name is longer than 500, it is abbreviated."); assertThat(name.description()).isEqualTo("Name of the project. If name is longer than 500, it is abbreviated.");

WebService.Param branch = definition.param(PARAM_BRANCH);
assertThat(branch.deprecatedSince()).isNotNull();
} }


private CreateWsResponse call(CreateRequest request) { private CreateWsResponse call(CreateRequest request) {
ofNullable(request.getOrganization()).ifPresent(org -> httpRequest.setParam("organization", org)); ofNullable(request.getOrganization()).ifPresent(org -> httpRequest.setParam("organization", org));
ofNullable(request.getProjectKey()).ifPresent(key -> httpRequest.setParam("project", key)); ofNullable(request.getProjectKey()).ifPresent(key -> httpRequest.setParam("project", key));
ofNullable(request.getName()).ifPresent(name -> httpRequest.setParam("name", name)); ofNullable(request.getName()).ifPresent(name -> httpRequest.setParam("name", name));
ofNullable(request.getBranch()).ifPresent(branch -> httpRequest.setParam("branch", branch));
return httpRequest.executeProtobuf(CreateWsResponse.class); return httpRequest.executeProtobuf(CreateWsResponse.class);
} }



+ 0
- 19
sonar-core/src/main/java/org/sonar/core/component/ComponentKeys.java View File

checkArgument(isValidProjectKey(keyCandidate), "Malformed key for '%s'. %s", keyCandidate, "Project key cannot be empty nor contain whitespaces."); checkArgument(isValidProjectKey(keyCandidate), "Malformed key for '%s'. %s", keyCandidate, "Project key cannot be empty nor contain whitespaces.");
} }


/**
* <p>Test if given parameter is valid for a branch. Valid format is:</p>
* <ul>
* <li>Allowed characters:
* <ul>
* <li>Uppercase ASCII letters A-Z</li>
* <li>Lowercase ASCII letters a-z</li>
* <li>ASCII digits 0-9</li>
* <li>Punctuation signs dash '-', underscore '_', period '.', and '/'</li>
* </ul>
* </li>
* </ul>
*
* @return <code>true</code> if <code>branchCandidate</code> can be used for a project
*/
public static boolean isValidLegacyBranch(String branchCandidate) {
return branchCandidate.matches(VALID_BRANCH_REGEXP);
}

/** /**
* Return the project key with potential branch * Return the project key with potential branch
*/ */

+ 0
- 12
sonar-core/src/test/java/org/sonar/core/component/ComponentKeysTest.java View File

assertThat(ComponentKeys.isValidProjectKey("ab ")).isFalse(); assertThat(ComponentKeys.isValidProjectKey("ab ")).isFalse();
} }


@Test
public void isValidBranchKey() {
assertThat(ComponentKeys.isValidLegacyBranch("")).isTrue();
assertThat(ComponentKeys.isValidLegacyBranch("abc")).isTrue();
assertThat(ComponentKeys.isValidLegacyBranch("0123")).isTrue();
assertThat(ComponentKeys.isValidLegacyBranch("ab 12")).isFalse();
assertThat(ComponentKeys.isValidLegacyBranch("ab_12")).isTrue();
assertThat(ComponentKeys.isValidLegacyBranch("ab/12")).isTrue();
assertThat(ComponentKeys.isValidLegacyBranch("ab\\12")).isFalse();
assertThat(ComponentKeys.isValidLegacyBranch("ab\n")).isFalse();
}

@Test @Test
public void checkProjectKey_with_correct_keys() { public void checkProjectKey_with_correct_keys() {
ComponentKeys.checkProjectKey("abc"); ComponentKeys.checkProjectKey("abc");

+ 0
- 16
sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/fs/internal/AbstractProjectOrModule.java View File

private final String name; private final String name;
private final String originalName; private final String originalName;
private final String description; private final String description;
private final String keyWithBranch;
private final String branch;
private final Map<String, String> properties; private final Map<String, String> properties;


private final String key; private final String key;
this.name = definition.getName(); this.name = definition.getName();
this.originalName = definition.getOriginalName(); this.originalName = definition.getOriginalName();
this.description = definition.getDescription(); this.description = definition.getDescription();
this.keyWithBranch = definition.getKeyWithBranch();
this.branch = definition.getBranch();
this.properties = Collections.unmodifiableMap(new HashMap<>(definition.properties())); this.properties = Collections.unmodifiableMap(new HashMap<>(definition.properties()));


this.definition = definition; this.definition = definition;
return workingDir; return workingDir;
} }


/**
* Module key without branch
*/
@Override @Override
public String key() { public String key() {
return key; return key;
return workDir; return workDir;
} }


public String getKeyWithBranch() {
return keyWithBranch;
}

@CheckForNull
public String getBranch() {
return branch;
}

public Map<String, String> properties() { public Map<String, String> properties() {
return properties; return properties;
} }

+ 0
- 4
sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/fs/internal/fs/DefaultInputModuleTest.java View File



assertThat(module.key()).isEqualTo("moduleKey"); assertThat(module.key()).isEqualTo("moduleKey");
assertThat(module.definition()).isEqualTo(def); assertThat(module.definition()).isEqualTo(def);
assertThat(module.getBranch()).isNull();
assertThat(module.getBaseDir()).isEqualTo(baseDir.toPath()); assertThat(module.getBaseDir()).isEqualTo(baseDir.toPath());
assertThat(module.getKeyWithBranch()).isEqualTo("moduleKey");
assertThat(module.getWorkDir()).isEqualTo(workDir.toPath()); assertThat(module.getWorkDir()).isEqualTo(workDir.toPath());
assertThat(module.getEncoding()).isEqualTo(Charset.defaultCharset()); assertThat(module.getEncoding()).isEqualTo(Charset.defaultCharset());
assertThat(module.getSourceDirsOrFiles().get()).containsExactlyInAnyOrder(src); assertThat(module.getSourceDirsOrFiles().get()).containsExactlyInAnyOrder(src);


assertThat(module.key()).isEqualTo("moduleKey"); assertThat(module.key()).isEqualTo("moduleKey");
assertThat(module.definition()).isEqualTo(def); assertThat(module.definition()).isEqualTo(def);
assertThat(module.getBranch()).isNull();
assertThat(module.getBaseDir()).isEqualTo(baseDir.toPath()); assertThat(module.getBaseDir()).isEqualTo(baseDir.toPath());
assertThat(module.getKeyWithBranch()).isEqualTo("moduleKey");
assertThat(module.getWorkDir()).isEqualTo(workDir.toPath()); assertThat(module.getWorkDir()).isEqualTo(workDir.toPath());
assertThat(module.getEncoding()).isEqualTo(Charset.defaultCharset()); assertThat(module.getEncoding()).isEqualTo(Charset.defaultCharset());
assertThat(module.getSourceDirsOrFiles()).isNotPresent(); assertThat(module.getSourceDirsOrFiles()).isNotPresent();

+ 0
- 2
sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/fs/internal/fs/DefaultInputProjectTest.java View File

assertThat(project.getName()).isEqualTo("projectName"); assertThat(project.getName()).isEqualTo("projectName");
assertThat(project.getOriginalName()).isEqualTo("projectName"); assertThat(project.getOriginalName()).isEqualTo("projectName");
assertThat(project.definition()).isEqualTo(def); assertThat(project.definition()).isEqualTo(def);
assertThat(project.getBranch()).isNull();
assertThat(project.getBaseDir()).isEqualTo(baseDir.toPath()); assertThat(project.getBaseDir()).isEqualTo(baseDir.toPath());
assertThat(project.getKeyWithBranch()).isEqualTo("projectKey");
assertThat(project.getDescription()).isEqualTo("desc"); assertThat(project.getDescription()).isEqualTo("desc");
assertThat(project.getWorkDir()).isEqualTo(workDir.toPath()); assertThat(project.getWorkDir()).isEqualTo(workDir.toPath());
assertThat(project.getEncoding()).isEqualTo(Charset.defaultCharset()); assertThat(project.getEncoding()).isEqualTo(Charset.defaultCharset());

+ 0
- 6
sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java View File

/* Global settings */ /* Global settings */
String SONAR_HOME = "SONAR_HOME"; String SONAR_HOME = "SONAR_HOME";


/**
* @deprecated since 6.7. This feature is deprecated in favor of the new branch feature.
* @see <a href="https://redirect.sonarsource.com/doc/branches.html">https://redirect.sonarsource.com/doc/branches.html/a>
*/
@Deprecated
String PROJECT_BRANCH_PROPERTY = "sonar.branch";
String PROJECT_VERSION_PROPERTY = "sonar.projectVersion"; String PROJECT_VERSION_PROPERTY = "sonar.projectVersion";
String BUILD_STRING_PROPERTY = "sonar.buildString"; String BUILD_STRING_PROPERTY = "sonar.buildString";



+ 0
- 23
sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectDefinition.java View File

return properties.get(CoreProperties.PROJECT_KEY_PROPERTY); return properties.get(CoreProperties.PROJECT_KEY_PROPERTY);
} }


/**
* @since 4.5
*/
public String getKeyWithBranch() {
String branch = getBranch();
String projectKey = getKey();
if (StringUtils.isNotBlank(branch)) {
projectKey = String.format("%s:%s", projectKey, branch);
}
return projectKey;
}

@CheckForNull
public String getBranch() {
String branch = properties.get(CoreProperties.PROJECT_BRANCH_PROPERTY);
if (StringUtils.isNotBlank(branch)) {
return branch;
} else if (parent != null) {
return parent.getBranch();
}
return null;
}

/** /**
* @deprecated since 7.7, use {@link #getOriginalProjectVersion()} instead * @deprecated since 7.7, use {@link #getOriginalProjectVersion()} instead
*/ */

+ 0
- 30
sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectKey.java View File

/*
* SonarQube
* Copyright (C) 2009-2019 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.api.batch.bootstrap;

/**
* Provides root project key with branch
* @deprecated since 7.6
*/
@Deprecated
@FunctionalInterface
public interface ProjectKey {
String get();
}

+ 2
- 3
sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectReactor.java View File

*/ */
package org.sonar.api.batch.bootstrap; package org.sonar.api.batch.bootstrap;


import org.sonar.api.scanner.ScannerSide;

import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.sonar.api.scanner.ScannerSide;


/** /**
* Mutable project definitions that can be modified by {@link ProjectBuilder} extensions. * Mutable project definitions that can be modified by {@link ProjectBuilder} extensions.


public String get() { public String get() {
if (root != null) { if (root != null) {
return root.getKeyWithBranch();
return root.getKey();
} }
return null; return null;
} }

+ 5
- 27
sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ProcessedScannerProperties.java View File

import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import javax.annotation.CheckForNull;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import org.sonar.api.CoreProperties;
import org.sonar.api.batch.bootstrap.ProjectKey;
import org.sonar.scanner.scan.ExternalProjectKeyAndOrganization; import org.sonar.scanner.scan.ExternalProjectKeyAndOrganization;


import static org.apache.commons.lang.StringUtils.trimToNull;
import static org.sonar.api.CoreProperties.PROJECT_KEY_PROPERTY;


/** /**
* Properties that are coming from scanner. * Properties that are coming from scanner.
*/ */
@Immutable @Immutable
public class ProcessedScannerProperties implements ProjectKey {
public class ProcessedScannerProperties {


private final Map<String, String> properties; private final Map<String, String> properties;


this.properties.putAll(rawScannerProperties.properties()); this.properties.putAll(rawScannerProperties.properties());


externalProjectKeyAndOrganization.getProjectKey() externalProjectKeyAndOrganization.getProjectKey()
.ifPresent(projectKey -> properties.put(CoreProperties.PROJECT_KEY_PROPERTY, projectKey));
.ifPresent(projectKey -> properties.put(PROJECT_KEY_PROPERTY, projectKey));
externalProjectKeyAndOrganization.getOrganization() externalProjectKeyAndOrganization.getOrganization()
.ifPresent(organization -> properties.put(org.sonar.core.config.ScannerProperties.ORGANIZATION, organization)); .ifPresent(organization -> properties.put(org.sonar.core.config.ScannerProperties.ORGANIZATION, organization));
} }
return properties.get(key); return properties.get(key);
} }


@Override
public String get() {
return getKeyWithBranch();
}

private String getKey() {
return properties.get(CoreProperties.PROJECT_KEY_PROPERTY);
}

public String getKeyWithBranch() {
String branch = getBranch();
String projectKey = getKey();
if (branch == null) {
return projectKey;
}
return String.format("%s:%s", projectKey, branch);
}

@CheckForNull
private String getBranch() {
return trimToNull(properties.get(CoreProperties.PROJECT_BRANCH_PROPERTY));
public String getProjectKey() {
return this.properties.get(PROJECT_KEY_PROPERTY);
} }
} }

+ 2
- 7
sonar-scanner-engine/src/main/java/org/sonar/scanner/cpd/CpdSettings.java View File

*/ */
package org.sonar.scanner.cpd; package org.sonar.scanner.cpd;


import org.apache.commons.lang.StringUtils;
import org.sonar.api.CoreProperties; import org.sonar.api.CoreProperties;
import org.sonar.api.config.Configuration; import org.sonar.api.config.Configuration;
import org.sonar.duplications.block.BlockChunker; import org.sonar.duplications.block.BlockChunker;


public class CpdSettings { public class CpdSettings {
private final Configuration settings; private final Configuration settings;
private final String branch;


public CpdSettings(Configuration config, DefaultInputProject project) {
public CpdSettings(Configuration config) {
this.settings = config; this.settings = config;
this.branch = project.getBranch();
} }


public boolean isCrossProjectDuplicationEnabled() { public boolean isCrossProjectDuplicationEnabled() {
return settings.getBoolean(CoreProperties.CPD_CROSS_PROJECT).orElse(false)
// No cross project duplication for branches
&& StringUtils.isBlank(branch);
return settings.getBoolean(CoreProperties.CPD_CROSS_PROJECT).orElse(false);
} }


/** /**

+ 1
- 5
sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ComponentsPublisher.java View File



@CheckForNull @CheckForNull
private static String getName(AbstractProjectOrModule module) { private static String getName(AbstractProjectOrModule module) {
if (StringUtils.isNotEmpty(module.definition().getBranch())) {
return module.definition().getName() + " " + module.definition().getBranch();
} else {
return module.definition().getOriginalName();
}
return module.definition().getOriginalName();
} }


@CheckForNull @CheckForNull

+ 0
- 4
sonar-scanner-engine/src/main/java/org/sonar/scanner/report/MetadataPublisher.java View File

import org.sonar.scanner.scm.ScmConfiguration; import org.sonar.scanner.scm.ScmConfiguration;
import org.sonar.scanner.scm.ScmRevision; import org.sonar.scanner.scm.ScmRevision;


import static java.util.Optional.ofNullable;

public class MetadataPublisher implements ReportPublisherStep { public class MetadataPublisher implements ReportPublisherStep {


private static final Logger LOG = Loggers.get(MetadataPublisher.class); private static final Logger LOG = Loggers.get(MetadataPublisher.class);
addBranchInformation(builder); addBranchInformation(builder);
} }


ofNullable(rootProject.getBranch()).ifPresent(builder::setDeprecatedBranch);

addScmInformation(builder); addScmInformation(builder);


for (QProfile qp : qProfiles.findAll()) { for (QProfile qp : qProfiles.findAll()) {

+ 3
- 4
sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ReportPublisher.java View File

.setParam("organization", properties.organizationKey().orElse(null)) .setParam("organization", properties.organizationKey().orElse(null))
.setParam("projectKey", moduleHierarchy.root().key()) .setParam("projectKey", moduleHierarchy.root().key())
.setParam("projectName", moduleHierarchy.root().getOriginalName()) .setParam("projectName", moduleHierarchy.root().getOriginalName())
.setParam("projectBranch", moduleHierarchy.root().getBranch())
.setPart("report", filePart); .setPart("report", filePart);


String branchName = branchConfiguration.branchName(); String branchName = branchConfiguration.branchName();
} else { } else {


Map<String, String> metadata = new LinkedHashMap<>(); Map<String, String> metadata = new LinkedHashMap<>();
String effectiveKey = moduleHierarchy.root().getKeyWithBranch();
properties.organizationKey().ifPresent(org -> metadata.put("organization", org)); properties.organizationKey().ifPresent(org -> metadata.put("organization", org));
metadata.put("projectKey", effectiveKey);
metadata.put("projectKey", moduleHierarchy.root().key());
metadata.put("serverUrl", server.getPublicRootUrl()); metadata.put("serverUrl", server.getPublicRootUrl());
metadata.put("serverVersion", server.getVersion()); metadata.put("serverVersion", server.getVersion());
properties.branch().ifPresent(branch -> metadata.put("branch", branch)); properties.branch().ifPresent(branch -> metadata.put("branch", branch));


URL dashboardUrl = buildDashboardUrl(server.getPublicRootUrl(), effectiveKey);
URL dashboardUrl = buildDashboardUrl(server.getPublicRootUrl(), moduleHierarchy.root().key());
metadata.put("dashboardUrl", dashboardUrl.toExternalForm()); metadata.put("dashboardUrl", dashboardUrl.toExternalForm());


URL taskUrl = HttpUrl.parse(server.getPublicRootUrl()).newBuilder() URL taskUrl = HttpUrl.parse(server.getPublicRootUrl()).newBuilder()

+ 1
- 1
sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/ProjectRepositoriesSupplier.java View File

public ProjectRepositories get() { public ProjectRepositories get() {
if (project == null) { if (project == null) {
Profiler profiler = Profiler.create(LOG).startInfo(LOG_MSG); Profiler profiler = Profiler.create(LOG).startInfo(LOG_MSG);
project = loader.load(scannerProperties.getKeyWithBranch(), branchConfig.longLivingSonarReferenceBranch());
project = loader.load(scannerProperties.getProjectKey(), branchConfig.longLivingSonarReferenceBranch());
profiler.stopInfo(); profiler.stopInfo();
} }



+ 1
- 1
sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/QualityProfilesProvider.java View File

public QualityProfiles provide(QualityProfileLoader loader, ProcessedScannerProperties props) { public QualityProfiles provide(QualityProfileLoader loader, ProcessedScannerProperties props) {
if (this.profiles == null) { if (this.profiles == null) {
Profiler profiler = Profiler.create(LOG).startInfo(LOG_MSG); Profiler profiler = Profiler.create(LOG).startInfo(LOG_MSG);
profiles = new QualityProfiles(loader.load(props.getKeyWithBranch()));
profiles = new QualityProfiles(loader.load(props.getProjectKey()));
profiler.stopInfo(); profiler.stopInfo();
} }



+ 1
- 1
sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/settings/DefaultProjectSettingsLoader.java View File



@Override @Override
public Map<String, String> loadProjectSettings() { public Map<String, String> loadProjectSettings() {
return load(scannerProperties.getKeyWithBranch());
return load(scannerProperties.getProjectKey());
} }
} }

+ 1
- 12
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectReactorValidator.java View File

validateModule(moduleDef, validationMessages); validateModule(moduleDef, validationMessages);
} }


String deprecatedBranchName = reactor.getRoot().getBranch();

if (isBranchFeatureAvailable()) { if (isBranchFeatureAvailable()) {
branchParamsValidator.validate(validationMessages, deprecatedBranchName);
branchParamsValidator.validate(validationMessages);
} else { } else {
validateBranchParamsWhenPluginAbsent(validationMessages); validateBranchParamsWhenPluginAbsent(validationMessages);
validatePullRequestParamsWhenPluginAbsent(validationMessages); validatePullRequestParamsWhenPluginAbsent(validationMessages);
} }


validateLegacyBranch(validationMessages, deprecatedBranchName);

if (!validationMessages.isEmpty()) { if (!validationMessages.isEmpty()) {
throw MessageException.of("Validation of project reactor failed:\n o " + throw MessageException.of("Validation of project reactor failed:\n o " +
String.join("\n o ", validationMessages)); String.join("\n o ", validationMessages));
} }
} }


private static void validateLegacyBranch(List<String> validationMessages, @Nullable String branch) {
if (isNotEmpty(branch) && !ComponentKeys.isValidLegacyBranch(branch)) {
validationMessages.add(format("\"%s\" is not a valid branch name. "
+ "Allowed characters are alphanumeric, '-', '_', '.' and '/'.", branch));
}
}

private boolean isBranchFeatureAvailable() { private boolean isBranchFeatureAvailable() {
return branchParamsValidator != null; return branchParamsValidator != null;
} }

+ 4
- 14
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java View File

GlobalAnalysisMode analysisMode = getComponentByType(GlobalAnalysisMode.class); GlobalAnalysisMode analysisMode = getComponentByType(GlobalAnalysisMode.class);
InputModuleHierarchy tree = getComponentByType(InputModuleHierarchy.class); InputModuleHierarchy tree = getComponentByType(InputModuleHierarchy.class);
ScanProperties properties = getComponentByType(ScanProperties.class); ScanProperties properties = getComponentByType(ScanProperties.class);
SonarRuntime sonarRuntime = getComponentByType(SonarRuntime.class);
properties.validate(); properties.validate();


properties.organizationKey().ifPresent(k -> LOG.info("Organization key: {}", k)); properties.organizationKey().ifPresent(k -> LOG.info("Organization key: {}", k));
if (sonarRuntime.getEdition() == SonarEdition.SONARCLOUD) {
String branch = tree.root().definition().getBranch();
if (branch != null) {
LOG.info("Branch key: {}", branch);
LOG.warn("The use of \"sonar.branch\" is deprecated and replaced by \"{}\". See {}.",
ScannerProperties.BRANCH_NAME, ScannerProperties.BRANCHES_DOC_LINK);
}
} else {
properties.get("sonar.branch").ifPresent(deprecatedBranch -> {
throw MessageException.of("The 'sonar.branch' parameter is no longer supported. You should stop using it. " +
"Branch analysis is available in Developer Edition and above. See https://redirect.sonarsource.com/editions/developer.html for more information.");
});
}
properties.get("sonar.branch").ifPresent(deprecatedBranch -> {
throw MessageException.of("The 'sonar.branch' parameter is no longer supported. You should stop using it. " +
"Branch analysis is available in Developer Edition and above. See https://redirect.sonarsource.com/editions/developer.html for more information.");
});


BranchConfiguration branchConfig = getComponentByType(BranchConfiguration.class); BranchConfiguration branchConfig = getComponentByType(BranchConfiguration.class);
if (branchConfig.branchType() == BranchType.PULL_REQUEST) { if (branchConfig.branchType() == BranchType.PULL_REQUEST) {

+ 1
- 1
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/BranchParamsValidator.java View File



@ScannerSide @ScannerSide
public interface BranchParamsValidator { public interface BranchParamsValidator {
void validate(List<String> validationMessages, @Nullable String deprecatedBranchName);
void validate(List<String> validationMessages);
} }

+ 0
- 30
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/DefaultBranchParamsValidator.java View File

/*
* SonarQube
* Copyright (C) 2009-2019 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.scanner.scan.branch;

import java.util.List;
import javax.annotation.Nullable;

public class DefaultBranchParamsValidator implements BranchParamsValidator {
@Override
public void validate(List<String> validationMessages, @Nullable String deprecatedBranchName) {
// no-op
}
}

+ 9
- 9
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/ProjectBranchesProvider.java View File

*/ */
package org.sonar.scanner.scan.branch; package org.sonar.scanner.scan.branch;


import java.util.Collections;
import com.google.common.collect.ImmutableList;
import org.picocontainer.annotations.Nullable; import org.picocontainer.annotations.Nullable;
import org.picocontainer.injectors.ProviderAdapter; import org.picocontainer.injectors.ProviderAdapter;
import org.sonar.api.batch.bootstrap.ProjectKey;
import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers; import org.sonar.api.utils.log.Loggers;
import org.sonar.api.utils.log.Profiler; import org.sonar.api.utils.log.Profiler;
import org.sonar.scanner.bootstrap.ProcessedScannerProperties;


public class ProjectBranchesProvider extends ProviderAdapter { public class ProjectBranchesProvider extends ProviderAdapter {




private ProjectBranches branches = null; private ProjectBranches branches = null;


public ProjectBranches provide(@Nullable ProjectBranchesLoader loader, ProjectKey projectKey) {
if (branches != null) {
return branches;
public ProjectBranches provide(@Nullable ProjectBranchesLoader loader, ProcessedScannerProperties scannerProperties) {
if (this.branches != null) {
return this.branches;
} }


if (loader == null) { if (loader == null) {
branches = new ProjectBranches(Collections.emptyList());
return branches;
this.branches = new ProjectBranches(ImmutableList.of());
return this.branches;
} }


Profiler profiler = Profiler.create(LOG).startInfo(LOG_MSG); Profiler profiler = Profiler.create(LOG).startInfo(LOG_MSG);
branches = loader.load(projectKey.get());
this.branches = loader.load(scannerProperties.getProjectKey());
profiler.stopInfo(); profiler.stopInfo();
return branches;
return this.branches;
} }
} }

+ 3
- 3
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/ProjectPullRequestsProvider.java View File



import java.util.Collections; import java.util.Collections;
import org.picocontainer.injectors.ProviderAdapter; import org.picocontainer.injectors.ProviderAdapter;
import org.sonar.api.batch.bootstrap.ProjectKey;
import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers; import org.sonar.api.utils.log.Loggers;
import org.sonar.api.utils.log.Profiler; import org.sonar.api.utils.log.Profiler;
import org.sonar.scanner.bootstrap.ProcessedScannerProperties;


public class ProjectPullRequestsProvider extends ProviderAdapter { public class ProjectPullRequestsProvider extends ProviderAdapter {




private ProjectPullRequests pullRequests = null; private ProjectPullRequests pullRequests = null;


public ProjectPullRequests provide(@org.picocontainer.annotations.Nullable ProjectPullRequestsLoader loader, ProjectKey projectKey) {
public ProjectPullRequests provide(@org.picocontainer.annotations.Nullable ProjectPullRequestsLoader loader, ProcessedScannerProperties scannerProperties) {
if (pullRequests != null) { if (pullRequests != null) {
return pullRequests; return pullRequests;
} }
} }


Profiler profiler = Profiler.create(LOG).startInfo(LOG_MSG); Profiler profiler = Profiler.create(LOG).startInfo(LOG_MSG);
pullRequests = loader.load(projectKey.get());
pullRequests = loader.load(scannerProperties.getProjectKey());
profiler.stopInfo(); profiler.stopInfo();
return pullRequests; return pullRequests;
} }

+ 1
- 1
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/FileIndexer.java View File

projectRelativePath.toString(), projectRelativePath.toString(),
moduleRelativePath.toString(), moduleRelativePath.toString(),
type, language, scannerComponentIdGenerator.getAsInt(), sensorStrategy); type, language, scannerComponentIdGenerator.getAsInt(), sensorStrategy);
DefaultInputFile inputFile = new DefaultInputFile(indexedFile, f -> metadataGenerator.setMetadata(module.getKeyWithBranch(), f, module.getEncoding()));
DefaultInputFile inputFile = new DefaultInputFile(indexedFile, f -> metadataGenerator.setMetadata(module.key(), f, module.getEncoding()));
if (language != null) { if (language != null) {
inputFile.setPublished(true); inputFile.setPublished(true);
} }

+ 1
- 1
sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmPublisher.java View File

if (configuration.forceReloadAll() || f.status() != Status.SAME) { if (configuration.forceReloadAll() || f.status() != Status.SAME) {
addIfNotEmpty(filesToBlame, f); addIfNotEmpty(filesToBlame, f);
} else if (!branchConfiguration.isShortOrPullRequest()) { } else if (!branchConfiguration.isShortOrPullRequest()) {
FileData fileData = projectRepositoriesSupplier.get().fileData(componentStore.findModule(f).getKeyWithBranch(), f);
FileData fileData = projectRepositoriesSupplier.get().fileData(componentStore.findModule(f).key(), f);
if (fileData == null || StringUtils.isEmpty(fileData.revision())) { if (fileData == null || StringUtils.isEmpty(fileData.revision())) {
addIfNotEmpty(filesToBlame, f); addIfNotEmpty(filesToBlame, f);
} else { } else {

+ 1
- 3
sonar-scanner-engine/src/test/java/org/sonar/scanner/cpd/CpdSettingsTest.java View File

public class CpdSettingsTest { public class CpdSettingsTest {
private CpdSettings cpdSettings; private CpdSettings cpdSettings;
private Configuration configuration; private Configuration configuration;
private DefaultInputProject project;


@Before @Before
public void setUp() { public void setUp() {
project = mock(DefaultInputProject.class);
configuration = mock(Configuration.class); configuration = mock(Configuration.class);
cpdSettings = new CpdSettings(configuration, project);
cpdSettings = new CpdSettings(configuration);
} }


@Test @Test

+ 4
- 87
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/branch/DeprecatedBranchMediumTest.java View File

import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map; import java.util.Map;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.junit.Before; import org.junit.Before;
import org.sonar.api.utils.MessageException; import org.sonar.api.utils.MessageException;
import org.sonar.scanner.mediumtest.AnalysisResult; import org.sonar.scanner.mediumtest.AnalysisResult;
import org.sonar.scanner.mediumtest.ScannerMediumTester; import org.sonar.scanner.mediumtest.ScannerMediumTester;
import org.sonar.api.utils.MessageException;
import org.sonar.scanner.mediumtest.ScannerMediumTester;
import org.sonar.xoo.XooPlugin; import org.sonar.xoo.XooPlugin;
import org.sonar.xoo.rule.XooRulesDefinition; import org.sonar.xoo.rule.XooRulesDefinition;


import static org.assertj.core.api.Assertions.assertThat;

public class DeprecatedBranchMediumTest { public class DeprecatedBranchMediumTest {


@Rule @Rule
.addActiveRule("xoo", "xoo:OneIssuePerFile", null, "One Issue Per File", null, null, null) .addActiveRule("xoo", "xoo:OneIssuePerFile", null, "One Issue Per File", null, null, null)
.addDefaultQProfile("xoo", "Sonar Way"); .addDefaultQProfile("xoo", "Sonar Way");


@Rule
public ScannerMediumTester testerSC = new ScannerMediumTester()
.setEdition(SonarEdition.SONARCLOUD)
.registerPlugin("xoo", new XooPlugin())
.addRules(new XooRulesDefinition())
// active a rule just to be sure that xoo files are published
.addActiveRule("xoo", "xoo:OneIssuePerFile", null, "One Issue Per File", null, null, null)
.addDefaultQProfile("xoo", "Sonar Way");

private File baseDir; private File baseDir;


private Map<String, String> commonProps; private Map<String, String> commonProps;


@Before @Before
public void prepare() throws IOException {
public void prepare() {
baseDir = temp.getRoot(); baseDir = temp.getRoot();


commonProps = ImmutableMap.<String, String>builder() commonProps = ImmutableMap.<String, String>builder()
} }


@Test @Test
public void scanProjectWithBranchOnSonarQube() throws IOException {
public void scanProjectWithBranch() throws IOException {
File srcDir = new File(baseDir, "src"); File srcDir = new File(baseDir, "src");
srcDir.mkdir(); srcDir.mkdir();


.build()) .build())
.execute(); .execute();
} }

@Test
public void scanProjectWithBranchOnSonarCloud() throws IOException {
File srcDir = new File(baseDir, "src");
srcDir.mkdir();

File xooFile = new File(srcDir, "sample.xoo");
FileUtils.write(xooFile, "Sample xoo\ncontent");

AnalysisResult result = testerSC.newAnalysis()
.properties(ImmutableMap.<String, String>builder()
.putAll(commonProps)
.put("sonar.branch", "branch")
.build())
.execute();

assertThat(result.inputFiles()).hasSize(1);
assertThat(result.inputFile("src/sample.xoo").key()).isEqualTo("com.foo.project:src/sample.xoo");

DefaultInputFile inputfile = (DefaultInputFile) result.inputFile("src/sample.xoo");
assertThat(result.getReportReader().readComponent(inputfile.scannerId()).getProjectRelativePath()).isEqualTo("src/sample.xoo");

assertThat(result.getReportReader().readMetadata().getDeprecatedBranch()).isEqualTo("branch");

result = testerSC.newAnalysis()
.properties(ImmutableMap.<String, String>builder()
.putAll(commonProps)
.put("sonar.branch", "")
.build())
.execute();

assertThat(result.inputFiles()).hasSize(1);
assertThat(result.inputFile("src/sample.xoo").key()).isEqualTo("com.foo.project:src/sample.xoo");
}

@Test
public void scanMultiModuleWithBranchOnSonarCloud() throws IOException {
Path srcDir = baseDir.toPath().resolve("moduleA").resolve("src");
Files.createDirectories(srcDir);

File xooFile = new File(srcDir.toFile(), "sample.xoo");
FileUtils.write(xooFile, "Sample xoo\ncontent");

AnalysisResult result = testerSC.newAnalysis()
.properties(ImmutableMap.<String, String>builder()
.putAll(commonProps)
.put("sonar.branch", "branch")
.put("sonar.modules", "moduleA")
.build())
.execute();

assertThat(result.inputFiles()).hasSize(1);
assertThat(result.inputFile("moduleA/src/sample.xoo").key()).isEqualTo("com.foo.project:moduleA/src/sample.xoo");

// no branch in the report
DefaultInputFile inputfile = (DefaultInputFile) result.inputFile("moduleA/src/sample.xoo");
assertThat(result.getReportReader().readComponent(inputfile.scannerId()).getProjectRelativePath()).isEqualTo("moduleA/src/sample.xoo");

assertThat(result.getReportReader().readMetadata().getDeprecatedBranch()).isEqualTo("branch");

result = testerSC.newAnalysis()
.properties(ImmutableMap.<String, String>builder()
.putAll(commonProps)
.put("sonar.branch", "")
.put("sonar.modules", "moduleA")
.build())
.execute();

assertThat(result.inputFiles()).hasSize(1);
assertThat(result.inputFile("moduleA/src/sample.xoo").key()).isEqualTo("com.foo.project:moduleA/src/sample.xoo");
}

} }

+ 0
- 42
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/fs/FileSystemMediumTest.java View File

assertThat(result.getReportComponent(file.scannerId())).isNotNull(); assertThat(result.getReportComponent(file.scannerId())).isNotNull();
} }


@Test
public void logProjectKeyAndOrganizationKey() throws IOException {
builder.put("sonar.organization", "my org");
builder.put("sonar.branch", "");
File srcDir = new File(baseDir, "src");
srcDir.mkdir();

File xooFile = new File(srcDir, "sample.xoo");
FileUtils.write(xooFile, "Sample xoo\ncontent", StandardCharsets.UTF_8);

tester.newAnalysis()
.properties(builder
.put("sonar.sources", "src")
.build())
.execute();

assertThat(logTester.logs()).contains("Project key: com.foo.project");
assertThat(logTester.logs()).contains("Organization key: my org");
assertThat(logTester.logs().stream().collect(joining("\n"))).doesNotContain("Branch key");
}

@Test
public void logBranchKey() throws IOException {
builder.put("sonar.branch", "my-branch");
File srcDir = new File(baseDir, "src");
assertThat(srcDir.mkdir()).isTrue();

File xooFile = new File(srcDir, "sample.xoo");
FileUtils.write(xooFile, "Sample xoo\ncontent", StandardCharsets.UTF_8);

tester.newAnalysis()
.properties(builder
.put("sonar.sources", "src")
.build())
.execute();

assertThat(logTester.logs()).contains("Project key: com.foo.project");
assertThat(logTester.logs()).contains("Branch key: my-branch");
assertThat(logTester.logs())
.contains("The use of \"sonar.branch\" is deprecated and replaced by \"sonar.branch.name\". See https://redirect.sonarsource.com/doc/branches.html.");
}

@Test @Test
public void logBranchNameAndType() { public void logBranchNameAndType() {
builder.put("sonar.branch.name", "my-branch"); builder.put("sonar.branch.name", "my-branch");

+ 0
- 40
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/fs/ProjectBuilderMediumTest.java View File



} }


@Test
// SONAR-6976
public void testProjectBuilderWithNewLine() throws IOException {
File baseDir = prepareProject();

exception.expect(MessageException.class);
exception.expectMessage("is not a valid branch name");
tester.newAnalysis()
.properties(ImmutableMap.<String, String>builder()
.put("sonar.projectBaseDir", baseDir.getAbsolutePath())
.put("sonar.projectKey", "com.foo.project")
.put("sonar.branch", "branch\n")
.put("sonar.sources", ".")
.put("sonar.xoo.enableProjectBuilder", "true")
.build())
.execute();
}

@Test
public void testProjectBuilderWithBranch() throws IOException {
File baseDir = prepareProject();

AnalysisResult result = tester.newAnalysis()
.properties(ImmutableMap.<String, String>builder()
.put("sonar.projectBaseDir", baseDir.getAbsolutePath())
.put("sonar.projectKey", "com.foo.project")
.put("sonar.branch", "my-branch")
.put("sonar.sources", ".")
.put("sonar.xoo.enableProjectBuilder", "true")
.build())
.execute();

List<Issue> issues = result.issuesFor(result.inputFile("module1/src/sample.xoo"));
assertThat(issues).hasSize(10);

assertThat(issues)
.extracting("msg", "textRange.startLine", "gap")
.contains(tuple("This issue is generated on each line", 1, 0.0));
}

private File prepareProject() throws IOException { private File prepareProject() throws IOException {
File baseDir = temp.newFolder(); File baseDir = temp.newFolder();
File module1Dir = new File(baseDir, "module1"); File module1Dir = new File(baseDir, "module1");

+ 6
- 59
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/scm/ScmMediumTest.java View File

// revision,author,dateTime // revision,author,dateTime
"1,foo,2013-01-04\n" + "1,foo,2013-01-04\n" +
"1,bar,2013-01-04\n" + "1,bar,2013-01-04\n" +
"2,biz,2014-01-04\n", StandardCharsets.UTF_8);
"2,biz,2014-01-04\n",
StandardCharsets.UTF_8);


File sameContentScmOnServer = new File(baseDir, SAME_CONTENT_SCM_ON_SERVER_XOO); File sameContentScmOnServer = new File(baseDir, SAME_CONTENT_SCM_ON_SERVER_XOO);
FileUtils.write(sameContentScmOnServer, SAMPLE_XOO_CONTENT, StandardCharsets.UTF_8); FileUtils.write(sameContentScmOnServer, SAMPLE_XOO_CONTENT, StandardCharsets.UTF_8);
FileUtils.write(xooScmFile, FileUtils.write(xooScmFile,
// revision,author,dateTime // revision,author,dateTime
"1,foo,2013-01-04\n" + "1,foo,2013-01-04\n" +
"1,bar,2013-01-04\n", StandardCharsets.UTF_8);
"1,bar,2013-01-04\n",
StandardCharsets.UTF_8);


tester.newAnalysis() tester.newAnalysis()
.properties(ImmutableMap.<String, String>builder() .properties(ImmutableMap.<String, String>builder()
assertThat(logTester.logs()).containsSubsequence(MISSING_BLAME_INFORMATION_FOR_THE_FOLLOWING_FILES, " * src/no_blame_scm_on_server.xoo"); assertThat(logTester.logs()).containsSubsequence(MISSING_BLAME_INFORMATION_FOR_THE_FOLLOWING_FILES, " * src/no_blame_scm_on_server.xoo");
} }


@Test
public void optimize_blame_for_deprecated_branch() throws IOException, URISyntaxException {

File baseDir = prepareProject();
File changedContentScmOnServer = new File(baseDir, CHANGED_CONTENT_SCM_ON_SERVER_XOO);
FileUtils.write(changedContentScmOnServer, SAMPLE_XOO_CONTENT + "\nchanged", StandardCharsets.UTF_8);
File xooScmFile = new File(baseDir, CHANGED_CONTENT_SCM_ON_SERVER_XOO + ".scm");
FileUtils.write(xooScmFile,
// revision,author,dateTime
"1,foo,2013-01-04\n" +
"1,bar,2013-01-04\n" +
"2,biz,2014-01-04\n", StandardCharsets.UTF_8);

File sameContentScmOnServer = new File(baseDir, SAME_CONTENT_SCM_ON_SERVER_XOO);
FileUtils.write(sameContentScmOnServer, SAMPLE_XOO_CONTENT, StandardCharsets.UTF_8);
// No need to write .scm file since this file should not be blamed

File noBlameScmOnServer = new File(baseDir, NO_BLAME_SCM_ON_SERVER_XOO);
FileUtils.write(noBlameScmOnServer, SAMPLE_XOO_CONTENT + "\nchanged", StandardCharsets.UTF_8);
// No .scm file to emulate a failure during blame

File sameContentNoScmOnServer = new File(baseDir, SAME_CONTENT_NO_SCM_ON_SERVER_XOO);
FileUtils.write(sameContentNoScmOnServer, SAMPLE_XOO_CONTENT, StandardCharsets.UTF_8);
xooScmFile = new File(baseDir, SAME_CONTENT_NO_SCM_ON_SERVER_XOO + ".scm");
FileUtils.write(xooScmFile,
// revision,author,dateTime
"1,foo,2013-01-04\n" +
"1,bar,2013-01-04\n", StandardCharsets.UTF_8);

tester.newAnalysis()
.properties(ImmutableMap.<String, String>builder()
.put("sonar.projectBaseDir", baseDir.getAbsolutePath())
.put("sonar.projectKey", "com.foo.project")
.put("sonar.branch", "mybranch")
.put("sonar.sources", "src")
.put("sonar.scm.provider", "xoo")
.build())
.execute();

assertThat(getChangesets(baseDir, "src/sample.xoo")).isNotNull();

assertThat(getChangesets(baseDir, CHANGED_CONTENT_SCM_ON_SERVER_XOO).getCopyFromPrevious()).isFalse();

assertThat(getChangesets(baseDir, SAME_CONTENT_SCM_ON_SERVER_XOO).getCopyFromPrevious()).isTrue();

assertThat(getChangesets(baseDir, SAME_CONTENT_NO_SCM_ON_SERVER_XOO).getCopyFromPrevious()).isFalse();

assertThat(getChangesets(baseDir, NO_BLAME_SCM_ON_SERVER_XOO)).isNull();

// 5 .xoo files + 3 .scm files, but only 4 marked for publishing. 1 file is SAME so not included in the total
assertThat(logTester.logs()).containsSubsequence("8 files indexed");
assertThat(logTester.logs()).containsSubsequence("SCM Publisher 4 source files to be analyzed");
assertThat(logTester.logs().stream().anyMatch(s -> Pattern.matches("SCM Publisher 3/4 source files have been analyzed \\(done\\) \\| time=[0-9]+ms", s))).isTrue();
assertThat(logTester.logs()).containsSubsequence(MISSING_BLAME_INFORMATION_FOR_THE_FOLLOWING_FILES, " * src/no_blame_scm_on_server.xoo");
}

@Test @Test
public void forceReload() throws IOException, URISyntaxException { public void forceReload() throws IOException, URISyntaxException {


FileUtils.write(xooScmFile, FileUtils.write(xooScmFile,
// revision,author,dateTime // revision,author,dateTime
"1,foo,2013-01-04\n" + "1,foo,2013-01-04\n" +
"1,bar,2013-01-04\n", StandardCharsets.UTF_8);
"1,bar,2013-01-04\n",
StandardCharsets.UTF_8);


AnalysisBuilder analysisBuilder = tester.newAnalysis() AnalysisBuilder analysisBuilder = tester.newAnalysis()
.properties(ImmutableMap.<String, String>builder() .properties(ImmutableMap.<String, String>builder()

+ 1
- 33
sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ComponentsPublisherTest.java View File

import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Collections;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.sonar.api.batch.fs.internal.DefaultInputProject; import org.sonar.api.batch.fs.internal.DefaultInputProject;
import org.sonar.api.batch.fs.internal.TestInputFileBuilder; import org.sonar.api.batch.fs.internal.TestInputFileBuilder;
import org.sonar.scanner.protocol.output.FileStructure; import org.sonar.scanner.protocol.output.FileStructure;
import org.sonar.scanner.protocol.output.ScannerReport;
import org.sonar.scanner.protocol.output.ScannerReport.Component; import org.sonar.scanner.protocol.output.ScannerReport.Component;
import org.sonar.scanner.protocol.output.ScannerReport.Component.FileStatus; import org.sonar.scanner.protocol.output.ScannerReport.Component.FileStatus;
import org.sonar.scanner.protocol.output.ScannerReport.ComponentLink.ComponentLinkType; import org.sonar.scanner.protocol.output.ScannerReport.ComponentLink.ComponentLinkType;


private File outputDir; private File outputDir;
private ScannerReportWriter writer; private ScannerReportWriter writer;
private ScannerReportReader reader;
private BranchConfiguration branchConfiguration; private BranchConfiguration branchConfiguration;


@Before @Before
branchConfiguration = mock(BranchConfiguration.class); branchConfiguration = mock(BranchConfiguration.class);
outputDir = temp.newFolder(); outputDir = temp.newFolder();
writer = new ScannerReportWriter(outputDir); writer = new ScannerReportWriter(outputDir);
reader = new ScannerReportReader(outputDir);
}

private void writeIssue(int componentId) {
writer.writeComponentIssues(componentId, Collections.singleton(ScannerReport.Issue.newBuilder().build()));
} }


@Test @Test
assertThat(reader.readComponent(7).getStatus()).isEqualTo(FileStatus.ADDED); assertThat(reader.readComponent(7).getStatus()).isEqualTo(FileStatus.ADDED);
} }


@Test
public void should_set_modified_name_with_branch() throws IOException {
ProjectInfo projectInfo = mock(ProjectInfo.class);
when(projectInfo.getAnalysisDate()).thenReturn(DateUtils.parseDate("2012-12-12"));

ProjectDefinition rootDef = ProjectDefinition.create()
.setKey("foo")
.setDescription("Root description")
.setBaseDir(temp.newFolder())
.setWorkDir(temp.newFolder())
.setProperty(CoreProperties.PROJECT_BRANCH_PROPERTY, "my_branch");

DefaultInputProject project = new DefaultInputProject(rootDef, 1);

InputComponentStore store = new InputComponentStore(branchConfiguration);

ComponentsPublisher publisher = new ComponentsPublisher(project, store);
publisher.publish(writer);
Component rootProtobuf = reader.readComponent(1);
assertThat(rootProtobuf.getKey()).isEqualTo("foo");
assertThat(rootProtobuf.getName()).isEqualTo("foo my_branch");
}

@Test @Test
public void publish_unchanged_components_even_in_short_branches() throws IOException { public void publish_unchanged_components_even_in_short_branches() throws IOException {
when(branchConfiguration.isShortOrPullRequest()).thenReturn(true); when(branchConfiguration.isShortOrPullRequest()).thenReturn(true);
} }


@Test @Test
public void publish_project_with_links_and_branch() throws Exception {
public void publish_project_with_links() throws Exception {
ProjectInfo projectInfo = mock(ProjectInfo.class); ProjectInfo projectInfo = mock(ProjectInfo.class);
when(projectInfo.getAnalysisDate()).thenReturn(DateUtils.parseDate("2012-12-12")); when(projectInfo.getAnalysisDate()).thenReturn(DateUtils.parseDate("2012-12-12"));


ProjectDefinition rootDef = ProjectDefinition.create() ProjectDefinition rootDef = ProjectDefinition.create()
.setKey("foo") .setKey("foo")
.setProperty(CoreProperties.PROJECT_VERSION_PROPERTY, "1.0") .setProperty(CoreProperties.PROJECT_VERSION_PROPERTY, "1.0")
.setProperty(CoreProperties.PROJECT_BRANCH_PROPERTY, "my_branch")
.setName("Root project") .setName("Root project")
.setProperty(CoreProperties.LINKS_HOME_PAGE, "http://home") .setProperty(CoreProperties.LINKS_HOME_PAGE, "http://home")
.setProperty(CoreProperties.LINKS_CI, "http://ci") .setProperty(CoreProperties.LINKS_CI, "http://ci")

+ 1
- 25
sonar-scanner-engine/src/test/java/org/sonar/scanner/report/MetadataPublisherTest.java View File

import org.junit.Test; import org.junit.Test;
import org.junit.rules.TemporaryFolder; import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.sonar.api.CoreProperties;
import org.sonar.api.batch.bootstrap.ProjectDefinition; import org.sonar.api.batch.bootstrap.ProjectDefinition;
import org.sonar.api.batch.scm.ScmProvider; import org.sonar.api.batch.scm.ScmProvider;
import org.sonar.scanner.ProjectInfo; import org.sonar.scanner.ProjectInfo;
import org.sonar.scanner.scm.ScmConfiguration; import org.sonar.scanner.scm.ScmConfiguration;
import org.sonar.scanner.scm.ScmRevision; import org.sonar.scanner.scm.ScmRevision;


import static java.util.Arrays.asList;
import static java.util.Collections.emptyMap; import static java.util.Collections.emptyMap;
import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@Test @Test
public void write_metadata() throws Exception { public void write_metadata() throws Exception {
Date date = new Date(); Date date = new Date();
when(qProfiles.findAll()).thenReturn(asList(new QProfile("q1", "Q1", "java", date)));
when(qProfiles.findAll()).thenReturn(Collections.singletonList(new QProfile("q1", "Q1", "java", date)));
when(pluginRepository.getPluginsByKey()).thenReturn(ImmutableMap.of( when(pluginRepository.getPluginsByKey()).thenReturn(ImmutableMap.of(
"java", new ScannerPlugin("java", 12345L, null), "java", new ScannerPlugin("java", 12345L, null),
"php", new ScannerPlugin("php", 45678L, null))); "php", new ScannerPlugin("php", 45678L, null)));
.build())); .build()));
} }


@Test
public void write_project_branch() throws Exception {
when(cpdSettings.isCrossProjectDuplicationEnabled()).thenReturn(false);

ProjectDefinition projectDef = ProjectDefinition.create()
.setKey("foo")
.setProperty(CoreProperties.PROJECT_BRANCH_PROPERTY, "myBranch");
createPublisher(projectDef);

File outputDir = temp.newFolder();
ScannerReportWriter writer = new ScannerReportWriter(outputDir);

underTest.publish(writer);

ScannerReportReader reader = new ScannerReportReader(outputDir);
ScannerReport.Metadata metadata = reader.readMetadata();
assertThat(metadata.getAnalysisDate()).isEqualTo(1234567L);
assertThat(metadata.getProjectKey()).isEqualTo("root");
assertThat(metadata.getDeprecatedBranch()).isEqualTo("myBranch");
assertThat(metadata.getCrossProjectDuplicationActivated()).isFalse();
}

@Test @Test
public void write_project_organization() throws Exception { public void write_project_organization() throws Exception {
when(properties.organizationKey()).thenReturn(Optional.of("SonarSource")); when(properties.organizationKey()).thenReturn(Optional.of("SonarSource"));

+ 2
- 2
sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/ProjectRepositoriesSupplierTest.java View File

underTest = new ProjectRepositoriesSupplier(loader, props, branchConfiguration); underTest = new ProjectRepositoriesSupplier(loader, props, branchConfiguration);
Map<String, FileData> fileMap = Maps.newHashMap(); Map<String, FileData> fileMap = Maps.newHashMap();
project = new SingleProjectRepository(fileMap); project = new SingleProjectRepository(fileMap);
when(props.getKeyWithBranch()).thenReturn("key");
when(props.getProjectKey()).thenReturn("key");
} }


@Test @Test


assertThat(repo.exists()).isEqualTo(true); assertThat(repo.exists()).isEqualTo(true);


verify(props).getKeyWithBranch();
verify(props).getProjectKey();
verify(loader).load(eq("key"), eq(null)); verify(loader).load(eq("key"), eq(null));
verifyNoMoreInteractions(loader, props); verifyNoMoreInteractions(loader, props);
} }

+ 1
- 1
sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/QualityProfileProviderTest.java View File

public void setUp() { public void setUp() {
qualityProfileProvider = new QualityProfilesProvider(); qualityProfileProvider = new QualityProfilesProvider();


when(props.getKeyWithBranch()).thenReturn("project");
when(props.getProjectKey()).thenReturn("project");


response = new ArrayList<>(1); response = new ArrayList<>(1);
response.add(QualityProfile.newBuilder().setKey("profile").setName("profile").setLanguage("lang").setRulesUpdatedAt(DateUtils.formatDateTime(new Date())).build()); response.add(QualityProfile.newBuilder().setKey("profile").setName("profile").setLanguage("lang").setRulesUpdatedAt(DateUtils.formatDateTime(new Date())).build());

+ 1
- 1
sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/settings/DefaultProjectSettingsLoaderTest.java View File

out.close(); out.close();
when(response.contentStream()).thenReturn(in); when(response.contentStream()).thenReturn(in);
when(wsClient.call(any())).thenReturn(response); when(wsClient.call(any())).thenReturn(response);
when(properties.getKeyWithBranch()).thenReturn("project_key");
when(properties.getProjectKey()).thenReturn("project_key");


Map<String, String> result = underTest.loadProjectSettings(); Map<String, String> result = underTest.loadProjectSettings();



+ 1
- 7
sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/ModuleIndexerTest.java View File

public class ModuleIndexerTest { public class ModuleIndexerTest {
private ModuleIndexer indexer; private ModuleIndexer indexer;
private DefaultInputModuleHierarchy moduleHierarchy; private DefaultInputModuleHierarchy moduleHierarchy;
private InputComponentStore componentStore;


public void createIndexer() { public void createIndexer() {
componentStore = new InputComponentStore(mock(BranchConfiguration.class));
InputComponentStore componentStore = new InputComponentStore(mock(BranchConfiguration.class));
moduleHierarchy = mock(DefaultInputModuleHierarchy.class); moduleHierarchy = mock(DefaultInputModuleHierarchy.class);
indexer = new ModuleIndexer(componentStore, moduleHierarchy); indexer = new ModuleIndexer(componentStore, moduleHierarchy);
} }
when(mod2.key()).thenReturn("mod2"); when(mod2.key()).thenReturn("mod2");
when(mod3.key()).thenReturn("mod3"); when(mod3.key()).thenReturn("mod3");


when(root.getKeyWithBranch()).thenReturn("root");
when(mod1.getKeyWithBranch()).thenReturn("mod1");
when(mod2.getKeyWithBranch()).thenReturn("mod2");
when(mod3.getKeyWithBranch()).thenReturn("mod3");

when(root.definition()).thenReturn(rootDef); when(root.definition()).thenReturn(rootDef);
when(mod1.definition()).thenReturn(def); when(mod1.definition()).thenReturn(def);
when(mod2.definition()).thenReturn(def); when(mod2.definition()).thenReturn(def);

+ 0
- 44
sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/ProjectReactorValidatorTest.java View File

underTest.validate(reactor); underTest.validate(reactor);
} }


@Test
@UseDataProvider("validBranches")
public void not_fail_with_valid_branch(String validBranch) {
ProjectReactor reactor = createProjectReactor("foo", validBranch);

underTest.validate(reactor);
}

@DataProvider
public static Object[][] validBranches() {
return new Object[][] {
{"branch"},
{"Branch2"},
{"bra.nch"},
{"bra-nch"},
{"1"},
{"bra_nch"}
};
}

@Test
@UseDataProvider("invalidBranches")
public void fail_with_invalid_branch(String invalidBranch) {
ProjectReactor reactor = createProjectReactor("foo", invalidBranch);

thrown.expect(MessageException.class);
thrown.expectMessage("\"" + invalidBranch + "\" is not a valid branch name");

underTest.validate(reactor);
}

@DataProvider
public static Object[][] invalidBranches() {
return new Object[][] {
{"bran#ch"},
{"bran:ch"}
};
}

@Test @Test
public void fail_when_branch_name_is_specified_but_branch_plugin_not_present() { public void fail_when_branch_name_is_specified_but_branch_plugin_not_present() {
ProjectDefinition def = ProjectDefinition.create().setProperty(CoreProperties.PROJECT_KEY_PROPERTY, "foo"); ProjectDefinition def = ProjectDefinition.create().setProperty(CoreProperties.PROJECT_KEY_PROPERTY, "foo");
}; };
} }


private ProjectReactor createProjectReactor(String projectKey, String branch) {
return createProjectReactor(projectKey, def -> def
.setProperty(CoreProperties.PROJECT_BRANCH_PROPERTY, branch));
}

private ProjectReactor createProjectReactor(String projectKey, Consumer<ProjectDefinition>... consumers) { private ProjectReactor createProjectReactor(String projectKey, Consumer<ProjectDefinition>... consumers) {
ProjectDefinition def = ProjectDefinition.create() ProjectDefinition def = ProjectDefinition.create()
.setProperty(CoreProperties.PROJECT_KEY_PROPERTY, projectKey); .setProperty(CoreProperties.PROJECT_KEY_PROPERTY, projectKey);

+ 10
- 5
sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/branch/ProjectBranchesProviderTest.java View File



import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.sonar.scanner.bootstrap.ProcessedScannerProperties;


import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;


private ProjectBranchesProvider provider = new ProjectBranchesProvider(); private ProjectBranchesProvider provider = new ProjectBranchesProvider();
private ProjectBranchesLoader mockLoader; private ProjectBranchesLoader mockLoader;
private ProjectBranches mockBranches; private ProjectBranches mockBranches;
private ProcessedScannerProperties scannerProperties;


@Before @Before
public void setUp() { public void setUp() {
mockLoader = mock(ProjectBranchesLoader.class); mockLoader = mock(ProjectBranchesLoader.class);
mockBranches = mock(ProjectBranches.class); mockBranches = mock(ProjectBranches.class);
scannerProperties = mock(ProcessedScannerProperties.class);

} }


@Test @Test
public void should_cache_branches() { public void should_cache_branches() {
ProjectBranches branches = provider.provide(null, () -> "project");
assertThat(provider.provide(null, () -> "project")).isSameAs(branches);
when(scannerProperties.getProjectKey()).thenReturn("project");
ProjectBranches branches = provider.provide(null, scannerProperties);
assertThat(provider.provide(null, scannerProperties)).isSameAs(branches);
} }


@Test @Test
public void should_use_loader() { public void should_use_loader() {
when(scannerProperties.getProjectKey()).thenReturn("key");
when(mockLoader.load("key")).thenReturn(mockBranches); when(mockLoader.load("key")).thenReturn(mockBranches);
ProjectBranches branches = provider.provide(mockLoader, () -> "key");
ProjectBranches branches = provider.provide(mockLoader, scannerProperties);


assertThat(branches).isSameAs(mockBranches); assertThat(branches).isSameAs(mockBranches);
} }


@Test @Test
public void should_return_default_if_no_loader() { public void should_return_default_if_no_loader() {
ProjectBranches branches = provider.provide(null, () -> "project");
ProjectBranches branches = provider.provide(null, scannerProperties);
assertThat(branches.isEmpty()).isTrue(); assertThat(branches.isEmpty()).isTrue();
} }
} }

+ 10
- 4
sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/branch/ProjectPullRequestsProviderTest.java View File



import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.sonar.scanner.bootstrap.ProcessedScannerProperties;


import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
private ProjectPullRequestsProvider provider = new ProjectPullRequestsProvider(); private ProjectPullRequestsProvider provider = new ProjectPullRequestsProvider();
private ProjectPullRequestsLoader mockLoader; private ProjectPullRequestsLoader mockLoader;
private ProjectPullRequests pullRequests; private ProjectPullRequests pullRequests;
private ProcessedScannerProperties scannerProperties;


@Before @Before
public void setUp() { public void setUp() {
mockLoader = mock(ProjectPullRequestsLoader.class); mockLoader = mock(ProjectPullRequestsLoader.class);
pullRequests = new ProjectPullRequests(emptyList()); pullRequests = new ProjectPullRequests(emptyList());
scannerProperties = mock(ProcessedScannerProperties.class);
} }


@Test @Test
public void cache_pull_requests() { public void cache_pull_requests() {
ProjectPullRequests pullRequests = provider.provide(null, () -> "project");
when(scannerProperties.getProjectKey()).thenReturn("project");
ProjectPullRequests pullRequests = provider.provide(null, scannerProperties);


assertThat(provider.provide(null, () -> "project")).isSameAs(pullRequests);
assertThat(provider.provide(null, scannerProperties)).isSameAs(pullRequests);
} }


@Test @Test
public void should_use_loader() { public void should_use_loader() {
when(scannerProperties.getProjectKey()).thenReturn("key");
when(mockLoader.load("key")).thenReturn(pullRequests); when(mockLoader.load("key")).thenReturn(pullRequests);


ProjectPullRequests result = provider.provide(mockLoader, () -> "key");
ProjectPullRequests result = provider.provide(mockLoader, scannerProperties);


assertThat(result).isSameAs(pullRequests); assertThat(result).isSameAs(pullRequests);
} }


@Test @Test
public void should_return_default_if_no_loader() { public void should_return_default_if_no_loader() {
ProjectPullRequests result = provider.provide(null, () -> "project");
when(scannerProperties.getProjectKey()).thenReturn("project");
ProjectPullRequests result = provider.provide(null, scannerProperties);


assertThat(result.isEmpty()).isTrue(); assertThat(result.isEmpty()).isTrue();
} }

+ 11
- 10
sonar-scanner-protocol/src/main/protobuf/scanner_report.proto View File

string organization_key = 2; string organization_key = 2;
// TODO should we keep this project_key here or not ? Because it's a duplication of Component.key // TODO should we keep this project_key here or not ? Because it's a duplication of Component.key
string project_key = 3; string project_key = 3;
// maps the property sonar.branch
string deprecated_branch = 4;

reserved 4; // deprecated_branch (legacy branches feature)

int32 root_component_ref = 5; int32 root_component_ref = 5;
bool cross_project_duplication_activated = 6; bool cross_project_duplication_activated = 6;
map<string, QProfile> qprofiles_per_language = 7; map<string, QProfile> qprofiles_per_language = 7;
string language = 3; string language = 3;
int64 rulesUpdatedAt = 4; int64 rulesUpdatedAt = 4;
} }
message Plugin { message Plugin {
string key = 1; string key = 1;
int64 updatedAt = 2; int64 updatedAt = 2;
message ComponentLink { message ComponentLink {
ComponentLinkType type = 1; ComponentLinkType type = 1;
string href = 2; string href = 2;
enum ComponentLinkType { enum ComponentLinkType {
UNSET = 0; UNSET = 0;
HOME = 1; HOME = 1;
DoubleValue double_value = 5; DoubleValue double_value = 5;
StringValue string_value = 6; StringValue string_value = 6;
} }
message BoolValue { message BoolValue {
bool value = 1; bool value = 1;
string data = 2; string data = 2;
} }
message IntValue { message IntValue {
int32 value = 1; int32 value = 1;
string data = 2; string data = 2;
} }
message LongValue { message LongValue {
int64 value = 1; int64 value = 1;
string data = 2; string data = 2;
} }
message DoubleValue { message DoubleValue {
double value = 1; double value = 1;
string data = 2; string data = 2;
} }
message StringValue { message StringValue {
string value = 1; string value = 1;
} }
message SyntaxHighlightingRule { message SyntaxHighlightingRule {
TextRange range = 1; TextRange range = 1;
HighlightingType type = 2; HighlightingType type = 2;
enum HighlightingType { enum HighlightingType {
UNSET = 0; UNSET = 0;
ANNOTATION = 1; ANNOTATION = 1;

Loading…
Cancel
Save