You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

DefaultBlameOutput.java 5.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2019 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.scanner.scm;
  21. import java.util.HashMap;
  22. import java.util.LinkedHashSet;
  23. import java.util.List;
  24. import java.util.Locale;
  25. import java.util.Map;
  26. import java.util.Set;
  27. import java.util.concurrent.TimeUnit;
  28. import javax.annotation.Nullable;
  29. import org.apache.commons.lang.StringUtils;
  30. import org.sonar.api.batch.fs.InputFile;
  31. import org.sonar.api.batch.fs.internal.DefaultInputFile;
  32. import org.sonar.api.batch.scm.BlameCommand.BlameOutput;
  33. import org.sonar.api.batch.scm.BlameLine;
  34. import org.sonar.api.notifications.AnalysisWarnings;
  35. import org.sonar.api.utils.log.Logger;
  36. import org.sonar.api.utils.log.Loggers;
  37. import org.sonar.scanner.protocol.output.ScannerReport;
  38. import org.sonar.scanner.protocol.output.ScannerReport.Changesets.Builder;
  39. import org.sonar.scanner.protocol.output.ScannerReportWriter;
  40. import org.sonar.scanner.util.ProgressReport;
  41. import static org.sonar.api.utils.Preconditions.checkArgument;
  42. class DefaultBlameOutput implements BlameOutput {
  43. private static final Logger LOG = Loggers.get(DefaultBlameOutput.class);
  44. private final ScannerReportWriter writer;
  45. private AnalysisWarnings analysisWarnings;
  46. private final Set<InputFile> allFilesToBlame = new LinkedHashSet<>();
  47. private ProgressReport progressReport;
  48. private int count;
  49. private int total;
  50. DefaultBlameOutput(ScannerReportWriter writer, AnalysisWarnings analysisWarnings, List<InputFile> filesToBlame) {
  51. this.writer = writer;
  52. this.analysisWarnings = analysisWarnings;
  53. this.allFilesToBlame.addAll(filesToBlame);
  54. count = 0;
  55. total = filesToBlame.size();
  56. progressReport = new ProgressReport("Report about progress of SCM blame", TimeUnit.SECONDS.toMillis(10));
  57. progressReport.start("SCM Publisher " + total + " " + pluralize(total) + " to be analyzed");
  58. }
  59. @Override
  60. public synchronized void blameResult(InputFile file, List<BlameLine> lines) {
  61. checkNotNull(file);
  62. checkNotNull(lines);
  63. checkArgument(allFilesToBlame.contains(file), "It was not expected to blame file %s", file);
  64. if (lines.size() != file.lines()) {
  65. LOG.debug("Ignoring blame result since provider returned {} blame lines but file {} has {} lines", lines.size(), file, file.lines());
  66. return;
  67. }
  68. Builder scmBuilder = ScannerReport.Changesets.newBuilder();
  69. DefaultInputFile inputFile = (DefaultInputFile) file;
  70. scmBuilder.setComponentRef(inputFile.scannerId());
  71. Map<String, Integer> changesetsIdByRevision = new HashMap<>();
  72. int lineId = 1;
  73. for (BlameLine line : lines) {
  74. validateLine(line, lineId, file);
  75. Integer changesetId = changesetsIdByRevision.get(line.revision());
  76. if (changesetId == null) {
  77. addChangeset(scmBuilder, line);
  78. changesetId = scmBuilder.getChangesetCount() - 1;
  79. changesetsIdByRevision.put(line.revision(), changesetId);
  80. }
  81. scmBuilder.addChangesetIndexByLine(changesetId);
  82. lineId++;
  83. }
  84. writer.writeComponentChangesets(scmBuilder.build());
  85. allFilesToBlame.remove(file);
  86. count++;
  87. progressReport.message(count + "/" + total + " " + pluralize(count) + " have been analyzed");
  88. }
  89. private static void validateLine(BlameLine line, int lineId, InputFile file) {
  90. checkArgument(StringUtils.isNotBlank(line.revision()), "Blame revision is blank for file %s at line %s", file, lineId);
  91. checkArgument(line.date() != null, "Blame date is null for file %s at line %s", file, lineId);
  92. }
  93. private static void addChangeset(Builder scmBuilder, BlameLine line) {
  94. ScannerReport.Changesets.Changeset.Builder changesetBuilder = ScannerReport.Changesets.Changeset.newBuilder();
  95. changesetBuilder.setRevision(line.revision());
  96. changesetBuilder.setDate(line.date().getTime());
  97. if (StringUtils.isNotBlank(line.author())) {
  98. changesetBuilder.setAuthor(normalizeString(line.author()));
  99. }
  100. scmBuilder.addChangeset(changesetBuilder.build());
  101. }
  102. private static String normalizeString(@Nullable String inputString) {
  103. if (inputString == null) {
  104. return "";
  105. }
  106. return inputString.toLowerCase(Locale.US);
  107. }
  108. private static void checkNotNull(@Nullable Object obj) {
  109. if (obj == null) {
  110. throw new NullPointerException();
  111. }
  112. }
  113. public void finish(boolean success) {
  114. progressReport.stopAndLogTotalTime("SCM Publisher " + count + "/" + total + " " + pluralize(count) + " have been analyzed");
  115. if (success && !allFilesToBlame.isEmpty()) {
  116. LOG.warn("Missing blame information for the following files:");
  117. for (InputFile f : allFilesToBlame) {
  118. LOG.warn(" * " + f);
  119. }
  120. LOG.warn("This may lead to missing/broken features in SonarQube");
  121. analysisWarnings.addUnique(String.format("Missing blame information for %d %s. This may lead to some features not working correctly. Please check the analysis logs.",
  122. allFilesToBlame.size(),
  123. allFilesToBlame.size() > 1 ? "files" : "file"));
  124. }
  125. }
  126. private static String pluralize(long filesCount) {
  127. return filesCount == 1 ? "source file" : "source files";
  128. }
  129. }