+++ /dev/null
-/*
- * 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.ce.task.projectanalysis.component;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.component.KeyWithUuidDto;
-
-import static org.sonar.db.component.ComponentDto.removeBranchAndPullRequestFromKey;
-
-/**
- * Cache a map of component key -> uuid in short branches that have issues with status either RESOLVED or CONFIRMED.
- *
- */
-public class ShortBranchComponentsWithIssues {
- private final String uuid;
- private final DbClient dbClient;
-
- private Map<String, Set<String>> uuidsByKey;
-
- public ShortBranchComponentsWithIssues(TreeRootHolder treeRootHolder, DbClient dbClient) {
- this.uuid = treeRootHolder.getRoot().getUuid();
- this.dbClient = dbClient;
- }
-
- private void loadUuidsByKey() {
- uuidsByKey = new HashMap<>();
- try (DbSession dbSession = dbClient.openSession(false)) {
- List<KeyWithUuidDto> components = dbClient.componentDao().selectComponentKeysHavingIssuesToMerge(dbSession, uuid);
- for (KeyWithUuidDto dto : components) {
- uuidsByKey.computeIfAbsent(removeBranchAndPullRequestFromKey(dto.key()), s -> new HashSet<>()).add(dto.uuid());
- }
- }
- }
-
- public Set<String> getUuids(String componentKey) {
- if (uuidsByKey == null) {
- loadUuidsByKey();
- }
-
- return uuidsByKey.getOrDefault(componentKey, Collections.emptySet());
- }
-}
--- /dev/null
+/*
+ * 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.ce.task.projectanalysis.component;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.KeyWithUuidDto;
+
+import static org.sonar.db.component.ComponentDto.removeBranchAndPullRequestFromKey;
+
+/**
+ * Cache a map of component key -> set<uuid> in sibling branches/PR that have open issues
+ *
+ */
+public class SiblingComponentsWithOpenIssues {
+ private final DbClient dbClient;
+ private final AnalysisMetadataHolder metadataHolder;
+ private final TreeRootHolder treeRootHolder;
+
+ private Map<String, Set<String>> uuidsByKey;
+
+ public SiblingComponentsWithOpenIssues(TreeRootHolder treeRootHolder, AnalysisMetadataHolder metadataHolder, DbClient dbClient) {
+ this.treeRootHolder = treeRootHolder;
+ this.metadataHolder = metadataHolder;
+ this.dbClient = dbClient;
+ }
+
+ private void loadUuidsByKey() {
+ uuidsByKey = new HashMap<>();
+ String currentBranchUuid = treeRootHolder.getRoot().getUuid();
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ List<KeyWithUuidDto> components = dbClient.componentDao().selectAllSiblingComponentKeysHavingOpenIssues(dbSession,
+ metadataHolder.getBranch().getMergeBranchUuid().orElse(currentBranchUuid), currentBranchUuid);
+ for (KeyWithUuidDto dto : components) {
+ uuidsByKey.computeIfAbsent(removeBranchAndPullRequestFromKey(dto.key()), s -> new HashSet<>()).add(dto.uuid());
+ }
+ }
+ }
+
+ public Set<String> getUuids(String componentKey) {
+ if (uuidsByKey == null) {
+ loadUuidsByKey();
+ }
+
+ return uuidsByKey.getOrDefault(componentKey, Collections.emptySet());
+ }
+}
import org.sonar.ce.task.projectanalysis.component.DisabledComponentsHolderImpl;
import org.sonar.ce.task.projectanalysis.component.MergeBranchComponentUuids;
import org.sonar.ce.task.projectanalysis.component.ReportModulesPath;
-import org.sonar.ce.task.projectanalysis.component.ShortBranchComponentsWithIssues;
+import org.sonar.ce.task.projectanalysis.component.SiblingComponentsWithOpenIssues;
import org.sonar.ce.task.projectanalysis.component.TreeRootHolderImpl;
import org.sonar.ce.task.projectanalysis.dbmigration.DbMigrationModule;
import org.sonar.ce.task.projectanalysis.duplication.CrossProjectDuplicationStatusHolderImpl;
import org.sonar.ce.task.projectanalysis.issue.RuleTagsCopier;
import org.sonar.ce.task.projectanalysis.issue.ScmAccountToUser;
import org.sonar.ce.task.projectanalysis.issue.ScmAccountToUserLoader;
-import org.sonar.ce.task.projectanalysis.issue.ShortBranchIssueMerger;
-import org.sonar.ce.task.projectanalysis.issue.ShortBranchIssuesLoader;
+import org.sonar.ce.task.projectanalysis.issue.SiblingsIssueMerger;
+import org.sonar.ce.task.projectanalysis.issue.SiblingsIssuesLoader;
import org.sonar.ce.task.projectanalysis.issue.ShortBranchOrPullRequestTrackerExecution;
import org.sonar.ce.task.projectanalysis.issue.TrackerBaseInputFactory;
import org.sonar.ce.task.projectanalysis.issue.TrackerExecution;
MutableTaskResultHolderImpl.class,
BatchReportReaderImpl.class,
MergeBranchComponentUuids.class,
- ShortBranchComponentsWithIssues.class,
+ SiblingComponentsWithOpenIssues.class,
// repositories
LanguageRepositoryImpl.class,
BaseIssuesLoader.class,
IssueTrackingDelegator.class,
BranchPersisterImpl.class,
- ShortBranchIssuesLoader.class,
- ShortBranchIssueMerger.class,
+ SiblingsIssuesLoader.class,
+ SiblingsIssueMerger.class,
// filemove
ScoreMatrixDumperImpl.class,
private final IssueLifecycle issueLifecycle;
private final IssueVisitors issueVisitors;
private final IssueTrackingDelegator issueTracking;
- private final ShortBranchIssueMerger issueStatusCopier;
+ private final SiblingsIssueMerger issueStatusCopier;
private final AnalysisMetadataHolder analysisMetadataHolder;
private final MergeBranchComponentUuids mergeBranchComponentUuids;
public IntegrateIssuesVisitor(IssueCache issueCache, IssueLifecycle issueLifecycle, IssueVisitors issueVisitors,
- AnalysisMetadataHolder analysisMetadataHolder, IssueTrackingDelegator issueTracking, ShortBranchIssueMerger issueStatusCopier,
+ AnalysisMetadataHolder analysisMetadataHolder, IssueTrackingDelegator issueTracking, SiblingsIssueMerger issueStatusCopier,
MergeBranchComponentUuids mergeBranchComponentUuids) {
super(CrawlerDepthLimit.FILE, POST_ORDER);
this.issueCache = issueCache;
return;
}
- if (analysisMetadataHolder.isLongLivingBranch()) {
- issueStatusCopier.tryMerge(component, list);
- }
+ issueStatusCopier.tryMerge(component, list);
for (DefaultIssue issue : list) {
process(component, issue, cacheAppender);
public void mergeConfirmedOrResolvedFromShortLivingBranch(DefaultIssue raw, DefaultIssue base, String fromShortBranchName) {
copyAttributesOfIssueFromOtherBranch(raw, base);
- raw.setFieldChange(changeContext, IssueFieldsSetter.FROM_SHORT_BRANCH, fromShortBranchName, analysisMetadataHolder.getBranch().getName());
+ raw.setFieldChange(changeContext, IssueFieldsSetter.FROM_SHORT_BRANCH, fromShortBranchName, analysisMetadataHolder.isPullRequest() ? analysisMetadataHolder.getPullRequestKey() : analysisMetadataHolder.getBranch().getName());
}
private void copyAttributesOfIssueFromOtherBranch(DefaultIssue to, DefaultIssue from) {
+++ /dev/null
-/*
- * 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.ce.task.projectanalysis.issue;
-
-import java.util.Date;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-import javax.annotation.concurrent.Immutable;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.core.issue.tracking.Trackable;
-
-@Immutable
-public class ShortBranchIssue implements Trackable {
- private final String key;
- private final Integer line;
- private final String message;
- private final String lineHash;
- private final RuleKey ruleKey;
- private final String status;
- private final String branchName;
- private final Date creationDate;
-
- public ShortBranchIssue(String key, @Nullable Integer line, String message, @Nullable String lineHash, RuleKey ruleKey, String status, String branchName, Date creationDate) {
- this.key = key;
- this.line = line;
- this.message = message;
- this.lineHash = lineHash;
- this.ruleKey = ruleKey;
- this.status = status;
- this.branchName = branchName;
- this.creationDate = creationDate;
- }
-
- public String getKey() {
- return key;
- }
-
- @CheckForNull
- @Override
- public Integer getLine() {
- return line;
- }
-
- @Override
- public String getMessage() {
- return message;
- }
-
- @CheckForNull
- @Override
- public String getLineHash() {
- return lineHash;
- }
-
- @Override
- public RuleKey getRuleKey() {
- return ruleKey;
- }
-
- @Override
- public String getStatus() {
- return status;
- }
-
- public String getBranchName() {
- return branchName;
- }
-
- @Override
- public Date getCreationDate() {
- return creationDate;
- }
-
- @Override
- public int hashCode() {
- return key.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (getClass() != obj.getClass()) {
- return false;
- }
- ShortBranchIssue other = (ShortBranchIssue) obj;
- return key.equals(other.key);
- }
-
-}
+++ /dev/null
-/*
- * 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.ce.task.projectanalysis.issue;
-
-import java.util.Collection;
-import java.util.Map;
-import org.sonar.ce.task.projectanalysis.component.Component;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.tracking.SimpleTracker;
-import org.sonar.core.issue.tracking.Tracking;
-import org.sonar.ce.task.projectanalysis.component.Component;
-
-public class ShortBranchIssueMerger {
- private final ShortBranchIssuesLoader shortBranchIssuesLoader;
- private final SimpleTracker<DefaultIssue, ShortBranchIssue> tracker;
- private final IssueLifecycle issueLifecycle;
-
- public ShortBranchIssueMerger(ShortBranchIssuesLoader resolvedShortBranchIssuesLoader, IssueLifecycle issueLifecycle) {
- this(resolvedShortBranchIssuesLoader, new SimpleTracker<>(), issueLifecycle);
- }
-
- public ShortBranchIssueMerger(ShortBranchIssuesLoader shortBranchIssuesLoader, SimpleTracker<DefaultIssue, ShortBranchIssue> tracker, IssueLifecycle issueLifecycle) {
- this.shortBranchIssuesLoader = shortBranchIssuesLoader;
- this.tracker = tracker;
- this.issueLifecycle = issueLifecycle;
- }
-
- /**
- * Look for all resolved/confirmed issues in short living branches targeting the current long living branch, and run
- * a light issue tracking to find matches. Then merge issue attributes in the new issues.
- */
- public void tryMerge(Component component, Collection<DefaultIssue> newIssues) {
- Collection<ShortBranchIssue> shortBranchIssues = shortBranchIssuesLoader.loadCandidateIssuesForMergingInTargetBranch(component);
- Tracking<DefaultIssue, ShortBranchIssue> tracking = tracker.track(newIssues, shortBranchIssues);
-
- Map<DefaultIssue, ShortBranchIssue> matchedRaws = tracking.getMatchedRaws();
-
- Map<ShortBranchIssue, DefaultIssue> defaultIssues = shortBranchIssuesLoader.loadDefaultIssuesWithChanges(matchedRaws.values());
-
- for (Map.Entry<DefaultIssue, ShortBranchIssue> e : matchedRaws.entrySet()) {
- ShortBranchIssue issue = e.getValue();
- issueLifecycle.mergeConfirmedOrResolvedFromShortLivingBranch(e.getKey(), defaultIssues.get(issue), issue.getBranchName());
- }
- }
-}
+++ /dev/null
-/*
- * 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.ce.task.projectanalysis.issue;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-import org.sonar.ce.task.projectanalysis.component.Component;
-import org.sonar.ce.task.projectanalysis.component.ShortBranchComponentsWithIssues;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.issue.IssueDto;
-import org.sonar.db.issue.ShortBranchIssueDto;
-
-import static org.sonar.api.utils.DateUtils.longToDate;
-import static org.sonar.core.util.stream.MoreCollectors.toList;
-import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
-
-public class ShortBranchIssuesLoader {
-
- private final ShortBranchComponentsWithIssues shortBranchComponentsWithIssues;
- private final DbClient dbClient;
- private final ComponentIssuesLoader componentIssuesLoader;
-
- public ShortBranchIssuesLoader(ShortBranchComponentsWithIssues shortBranchComponentsWithIssues, DbClient dbClient,
- ComponentIssuesLoader componentIssuesLoader) {
- this.shortBranchComponentsWithIssues = shortBranchComponentsWithIssues;
- this.dbClient = dbClient;
- this.componentIssuesLoader = componentIssuesLoader;
- }
-
- public Collection<ShortBranchIssue> loadCandidateIssuesForMergingInTargetBranch(Component component) {
- String componentKey = ComponentDto.removeBranchAndPullRequestFromKey(component.getDbKey());
- Set<String> uuids = shortBranchComponentsWithIssues.getUuids(componentKey);
- if (uuids.isEmpty()) {
- return Collections.emptyList();
- }
-
- try (DbSession session = dbClient.openSession(false)) {
- return dbClient.issueDao().selectOpenByComponentUuids(session, uuids)
- .stream()
- .map(ShortBranchIssuesLoader::toShortBranchIssue)
- .collect(Collectors.toList());
- }
- }
-
- private static ShortBranchIssue toShortBranchIssue(ShortBranchIssueDto dto) {
- return new ShortBranchIssue(dto.getKey(), dto.getLine(), dto.getMessage(), dto.getChecksum(), dto.getRuleKey(), dto.getStatus(), dto.getBranchName(),
- longToDate(dto.getIssueCreationDate()));
- }
-
- public Map<ShortBranchIssue, DefaultIssue> loadDefaultIssuesWithChanges(Collection<ShortBranchIssue> lightIssues) {
- if (lightIssues.isEmpty()) {
- return Collections.emptyMap();
- }
-
- Map<String, ShortBranchIssue> issuesByKey = lightIssues.stream().collect(Collectors.toMap(ShortBranchIssue::getKey, i -> i));
- try (DbSession session = dbClient.openSession(false)) {
- List<DefaultIssue> issues = dbClient.issueDao().selectByKeys(session, issuesByKey.keySet())
- .stream()
- .map(IssueDto::toDefaultIssue)
- .collect(toList(issuesByKey.size()));
- componentIssuesLoader.loadChanges(session, issues);
- return issues.stream()
- .collect(uniqueIndex(i -> issuesByKey.get(i.key()), i -> i, issues.size()));
- }
- }
-
-}
--- /dev/null
+/*
+ * 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.ce.task.projectanalysis.issue;
+
+import java.util.Date;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.core.issue.tracking.Trackable;
+
+@Immutable
+public class SiblingIssue implements Trackable {
+ private final String key;
+ private final Integer line;
+ private final String message;
+ private final String lineHash;
+ private final RuleKey ruleKey;
+ private final String status;
+ private final String branchName;
+ private final Date updateDate;
+
+ public SiblingIssue(String key, @Nullable Integer line, String message, @Nullable String lineHash, RuleKey ruleKey, String status, String branchName, Date updateDate) {
+ this.key = key;
+ this.line = line;
+ this.message = message;
+ this.lineHash = lineHash;
+ this.ruleKey = ruleKey;
+ this.status = status;
+ this.branchName = branchName;
+ this.updateDate = updateDate;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ @CheckForNull
+ @Override
+ public Integer getLine() {
+ return line;
+ }
+
+ @Override
+ public String getMessage() {
+ return message;
+ }
+
+ @CheckForNull
+ @Override
+ public String getLineHash() {
+ return lineHash;
+ }
+
+ @Override
+ public RuleKey getRuleKey() {
+ return ruleKey;
+ }
+
+ @Override
+ public String getStatus() {
+ return status;
+ }
+
+ public String getBranchName() {
+ return branchName;
+ }
+
+ @Override
+ public Date getUpdateDate() {
+ return updateDate;
+ }
+
+ @Override
+ public int hashCode() {
+ return key.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ SiblingIssue other = (SiblingIssue) obj;
+ return key.equals(other.key);
+ }
+
+}
--- /dev/null
+/*
+ * 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.ce.task.projectanalysis.issue;
+
+import java.util.Collection;
+import java.util.Map;
+import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.tracking.SimpleTracker;
+import org.sonar.core.issue.tracking.Tracking;
+
+public class SiblingsIssueMerger {
+ private final SiblingsIssuesLoader siblingsIssuesLoader;
+ private final SimpleTracker<DefaultIssue, SiblingIssue> tracker;
+ private final IssueLifecycle issueLifecycle;
+
+ public SiblingsIssueMerger(SiblingsIssuesLoader resolvedSiblingsIssuesLoader, IssueLifecycle issueLifecycle) {
+ this(resolvedSiblingsIssuesLoader, new SimpleTracker<>(), issueLifecycle);
+ }
+
+ public SiblingsIssueMerger(SiblingsIssuesLoader siblingsIssuesLoader, SimpleTracker<DefaultIssue, SiblingIssue> tracker, IssueLifecycle issueLifecycle) {
+ this.siblingsIssuesLoader = siblingsIssuesLoader;
+ this.tracker = tracker;
+ this.issueLifecycle = issueLifecycle;
+ }
+
+ /**
+ * Look for all unclosed issues in branches/PR targeting the same long living branch, and run
+ * a light issue tracking to find matches. Then merge issue attributes in the new issues.
+ */
+ public void tryMerge(Component component, Collection<DefaultIssue> newIssues) {
+ Collection<SiblingIssue> siblingIssues = siblingsIssuesLoader.loadCandidateSiblingIssuesForMerging(component);
+ Tracking<DefaultIssue, SiblingIssue> tracking = tracker.track(newIssues, siblingIssues);
+
+ Map<DefaultIssue, SiblingIssue> matchedRaws = tracking.getMatchedRaws();
+
+ Map<SiblingIssue, DefaultIssue> defaultIssues = siblingsIssuesLoader.loadDefaultIssuesWithChanges(matchedRaws.values());
+
+ for (Map.Entry<DefaultIssue, SiblingIssue> e : matchedRaws.entrySet()) {
+ SiblingIssue issue = e.getValue();
+ issueLifecycle.mergeConfirmedOrResolvedFromShortLivingBranch(e.getKey(), defaultIssues.get(issue), issue.getBranchName());
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.ce.task.projectanalysis.issue;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.SiblingComponentsWithOpenIssues;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.issue.IssueDto;
+import org.sonar.db.issue.ShortBranchIssueDto;
+
+import static org.sonar.api.utils.DateUtils.longToDate;
+import static org.sonar.core.util.stream.MoreCollectors.toList;
+import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
+
+public class SiblingsIssuesLoader {
+
+ private final SiblingComponentsWithOpenIssues siblingComponentsWithOpenIssues;
+ private final DbClient dbClient;
+ private final ComponentIssuesLoader componentIssuesLoader;
+
+ public SiblingsIssuesLoader(SiblingComponentsWithOpenIssues siblingComponentsWithOpenIssues, DbClient dbClient,
+ ComponentIssuesLoader componentIssuesLoader) {
+ this.siblingComponentsWithOpenIssues = siblingComponentsWithOpenIssues;
+ this.dbClient = dbClient;
+ this.componentIssuesLoader = componentIssuesLoader;
+ }
+
+ public Collection<SiblingIssue> loadCandidateSiblingIssuesForMerging(Component component) {
+ String componentKey = ComponentDto.removeBranchAndPullRequestFromKey(component.getDbKey());
+ Set<String> uuids = siblingComponentsWithOpenIssues.getUuids(componentKey);
+ if (uuids.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ try (DbSession session = dbClient.openSession(false)) {
+ return dbClient.issueDao().selectOpenByComponentUuids(session, uuids)
+ .stream()
+ .map(SiblingsIssuesLoader::toSiblingIssue)
+ .collect(Collectors.toList());
+ }
+ }
+
+ private static SiblingIssue toSiblingIssue(ShortBranchIssueDto dto) {
+ return new SiblingIssue(dto.getKey(), dto.getLine(), dto.getMessage(), dto.getChecksum(), dto.getRuleKey(), dto.getStatus(), dto.getBranchName(),
+ longToDate(dto.getIssueUpdateDate()));
+ }
+
+ public Map<SiblingIssue, DefaultIssue> loadDefaultIssuesWithChanges(Collection<SiblingIssue> lightIssues) {
+ if (lightIssues.isEmpty()) {
+ return Collections.emptyMap();
+ }
+
+ Map<String, SiblingIssue> issuesByKey = lightIssues.stream().collect(Collectors.toMap(SiblingIssue::getKey, i -> i));
+ try (DbSession session = dbClient.openSession(false)) {
+ List<DefaultIssue> issues = dbClient.issueDao().selectByKeys(session, issuesByKey.keySet())
+ .stream()
+ .map(IssueDto::toDefaultIssue)
+ .collect(toList(issuesByKey.size()));
+ componentIssuesLoader.loadChanges(session, issues);
+ return issues.stream()
+ .collect(uniqueIndex(i -> issuesByKey.get(i.key()), i -> i, issues.size()));
+ }
+ }
+
+}
+++ /dev/null
-/*
- * 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.ce.task.projectanalysis.component;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.BranchType;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
-import org.sonar.db.issue.IssueTesting;
-import org.sonar.db.rule.RuleDefinitionDto;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class ShortBranchComponentsWithIssuesTest {
- @Rule
- public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
-
- @Rule
- public DbTester db = DbTester.create();
-
- private ShortBranchComponentsWithIssues underTest;
-
- private ComponentDto long1;
- private ComponentDto fileWithNoIssues;
- private ComponentDto fileWithOneOpenIssue;
- private ComponentDto fileWithOneResolvedIssue;
- private ComponentDto fileWithOneOpenTwoResolvedIssues;
- private ComponentDto fileWithOneResolvedIssueInLong1Short1;
- private ComponentDto fileWithOneResolvedIssueInLong1Short2;
-
- private ComponentDto long2;
- private ComponentDto fileWithOneOpenIssueOnLong2;
- private ComponentDto fileWithOneResolvedIssueOnLong2;
-
- @Before
- public void setUp() {
- ComponentDto project = db.components().insertMainBranch();
-
- long1 = db.components().insertProjectBranch(project, b -> b.setKey("long1"), b -> b.setBranchType(BranchType.LONG));
- ComponentDto long1short1 = db.components().insertProjectBranch(project,
- b -> b.setKey("long1short1"),
- b -> b.setBranchType(BranchType.SHORT),
- b -> b.setMergeBranchUuid(long1.uuid()));
- ComponentDto long1short2 = db.components().insertProjectBranch(project,
- b -> b.setKey("long1short2"),
- b -> b.setBranchType(BranchType.SHORT),
- b -> b.setMergeBranchUuid(long1.uuid()));
-
- fileWithNoIssues = db.components().insertComponent(ComponentTesting.newFileDto(long1, null));
-
- RuleDefinitionDto rule = db.rules().insert();
-
- fileWithOneOpenIssue = db.components().insertComponent(ComponentTesting.newFileDto(long1short1, null));
- db.issues().insertIssue(IssueTesting.newIssue(rule, long1short1, fileWithOneOpenIssue));
-
- fileWithOneResolvedIssue = db.components().insertComponent(ComponentTesting.newFileDto(long1short1, null));
- db.issues().insertIssue(IssueTesting.newIssue(rule, long1short1, fileWithOneResolvedIssue).setStatus("RESOLVED"));
-
- fileWithOneOpenTwoResolvedIssues = db.components().insertComponent(ComponentTesting.newFileDto(long1short1, null));
- db.issues().insertIssue(IssueTesting.newIssue(rule, long1short1, fileWithOneOpenTwoResolvedIssues));
- db.issues().insertIssue(IssueTesting.newIssue(rule, long1short1, fileWithOneOpenTwoResolvedIssues).setStatus("RESOLVED"));
-
- String fileKey = "file-x";
- fileWithOneResolvedIssueInLong1Short1 = db.components().insertComponent(ComponentTesting.newFileDto(long1short1, null)
- .setDbKey(fileKey + ":BRANCH:long1short1"));
- db.issues().insertIssue(IssueTesting.newIssue(rule, long1short1, fileWithOneResolvedIssueInLong1Short1).setStatus("RESOLVED"));
- fileWithOneResolvedIssueInLong1Short2 = db.components().insertComponent(ComponentTesting.newFileDto(long1short2, null)
- .setDbKey(fileKey + ":BRANCH:long1short2"));
- db.issues().insertIssue(IssueTesting.newIssue(rule, long1short2, fileWithOneResolvedIssueInLong1Short2).setStatus("RESOLVED"));
-
- long2 = db.components().insertProjectBranch(project, b -> b.setKey("long2"), b -> b.setBranchType(BranchType.LONG));
- ComponentDto long2short1 = db.components().insertProjectBranch(project,
- b -> b.setKey("long2short1"),
- b -> b.setBranchType(BranchType.SHORT),
- b -> b.setMergeBranchUuid(long2.uuid()));
-
- fileWithOneOpenIssueOnLong2 = db.components().insertComponent(ComponentTesting.newFileDto(long2short1, null));
- db.issues().insertIssue(IssueTesting.newIssue(rule, long2short1, fileWithOneOpenIssueOnLong2));
-
- fileWithOneResolvedIssueOnLong2 = db.components().insertComponent(ComponentTesting.newFileDto(long2short1, null));
- db.issues().insertIssue(IssueTesting.newIssue(rule, long2short1, fileWithOneResolvedIssueOnLong2).setStatus("RESOLVED"));
-
- setRootUuid(long1.uuid());
- underTest = new ShortBranchComponentsWithIssues(treeRootHolder, db.getDbClient());
- }
-
- @Test
- public void should_find_components_with_issues_to_merge_on_long1() {
- setRootUuid(long1.uuid());
-
- assertThat(underTest.getUuids(fileWithNoIssues.getKey())).isEmpty();
- assertThat(underTest.getUuids(fileWithOneOpenIssue.getKey())).containsOnly(fileWithOneOpenIssue.uuid());
- assertThat(underTest.getUuids(fileWithOneResolvedIssue.getKey())).containsOnly(fileWithOneResolvedIssue.uuid());
- assertThat(underTest.getUuids(fileWithOneOpenTwoResolvedIssues.getKey())).containsOnly(fileWithOneOpenTwoResolvedIssues.uuid());
-
- assertThat(fileWithOneResolvedIssueInLong1Short1.getKey()).isEqualTo(fileWithOneResolvedIssueInLong1Short2.getKey());
- assertThat(underTest.getUuids(fileWithOneResolvedIssueInLong1Short1.getKey())).containsOnly(
- fileWithOneResolvedIssueInLong1Short1.uuid(),
- fileWithOneResolvedIssueInLong1Short2.uuid());
- }
-
- @Test
- public void should_find_components_with_issues_to_merge_on_long2() {
- setRootUuid(long2.uuid());
- underTest = new ShortBranchComponentsWithIssues(treeRootHolder, db.getDbClient());
-
- assertThat(underTest.getUuids(fileWithOneResolvedIssue.getKey())).isEmpty();
- assertThat(underTest.getUuids(fileWithOneResolvedIssueOnLong2.getKey())).containsOnly(fileWithOneResolvedIssueOnLong2.uuid());
- assertThat(underTest.getUuids(fileWithOneOpenIssueOnLong2.getKey())).containsOnly(fileWithOneOpenIssueOnLong2.uuid());
- }
-
- @Test
- public void should_find_components_with_issues_to_merge_on_derived_short() {
- ComponentDto project = db.components().insertMainBranch();
- setRootUuid(project.uuid());
-
- ComponentDto branch = db.components().insertProjectBranch(project,
- b -> b.setBranchType(BranchType.SHORT),
- b -> b.setMergeBranchUuid(project.uuid()));
-
- RuleDefinitionDto rule = db.rules().insert();
-
- ComponentDto fileWithResolvedIssue = db.components().insertComponent(ComponentTesting.newFileDto(branch, null));
- db.issues().insertIssue(IssueTesting.newIssue(rule, branch, fileWithResolvedIssue).setStatus("RESOLVED"));
-
- underTest = new ShortBranchComponentsWithIssues(treeRootHolder, db.getDbClient());
-
- assertThat(underTest.getUuids(fileWithResolvedIssue.getKey())).hasSize(1);
- }
-
- @Test
- public void should_find_components_with_issues_to_merge_on_derived_pull_request() {
- ComponentDto project = db.components().insertMainBranch();
- setRootUuid(project.uuid());
-
- ComponentDto pullRequest = db.components().insertProjectBranch(project,
- b -> b.setBranchType(BranchType.PULL_REQUEST),
- b -> b.setMergeBranchUuid(project.uuid()));
-
- RuleDefinitionDto rule = db.rules().insert();
-
- ComponentDto fileWithResolvedIssue = db.components().insertComponent(ComponentTesting.newFileDto(pullRequest, null));
- db.issues().insertIssue(IssueTesting.newIssue(rule, pullRequest, fileWithResolvedIssue).setStatus("RESOLVED"));
-
- underTest = new ShortBranchComponentsWithIssues(treeRootHolder, db.getDbClient());
-
- assertThat(underTest.getUuids(fileWithResolvedIssue.getKey())).hasSize(1);
- }
-
- @Test
- public void should_not_find_components_with_issues_to_merge_on_derived_long() {
- ComponentDto project = db.components().insertMainBranch();
- setRootUuid(project.uuid());
-
- ComponentDto branch = db.components().insertProjectBranch(project,
- b -> b.setBranchType(BranchType.LONG),
- b -> b.setMergeBranchUuid(project.uuid()));
-
- RuleDefinitionDto rule = db.rules().insert();
-
- ComponentDto fileWithResolvedIssue = db.components().insertComponent(ComponentTesting.newFileDto(branch, null));
- db.issues().insertIssue(IssueTesting.newIssue(rule, branch, fileWithResolvedIssue).setStatus("RESOLVED"));
-
- underTest = new ShortBranchComponentsWithIssues(treeRootHolder, db.getDbClient());
-
- assertThat(underTest.getUuids(fileWithResolvedIssue.getKey())).isEmpty();
- }
-
- private void setRootUuid(String uuid) {
- Component root = mock(Component.class);
- when(root.getUuid()).thenReturn(uuid);
- treeRootHolder.setRoot(root);
- }
-}
--- /dev/null
+/*
+ * 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.ce.task.projectanalysis.component;
+
+import java.util.Optional;
+import javax.annotation.Nullable;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
+import org.sonar.ce.task.projectanalysis.analysis.Branch;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.BranchType;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.issue.IssueTesting;
+import org.sonar.db.rule.RuleDefinitionDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class SiblingComponentsWithOpenIssuesTest {
+ @Rule
+ public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
+
+ @Rule
+ public AnalysisMetadataHolderRule metadataHolder = new AnalysisMetadataHolderRule();
+
+ @Rule
+ public DbTester db = DbTester.create();
+
+ private SiblingComponentsWithOpenIssues underTest;
+
+ private ComponentDto long1;
+ private ComponentDto fileWithNoIssuesOnLong1;
+ private ComponentDto fileWithOneOpenIssueOnLong1Short1;
+ private ComponentDto fileWithOneResolvedIssueOnLong1Short1;
+ private ComponentDto fileWithOneOpenTwoResolvedIssuesOnLong1Short1;
+ private ComponentDto fileXWithOneResolvedIssueOnLong1Short1;
+ private ComponentDto fileXWithOneResolvedIssueOnLong1Short2;
+
+ private ComponentDto long2;
+ private ComponentDto fileWithOneOpenIssueOnLong2Short1;
+ private ComponentDto fileWithOneResolvedIssueOnLong2Short1;
+ private ComponentDto long1short1;
+
+ @Before
+ public void setUp() {
+ ComponentDto project = db.components().insertMainBranch();
+
+ long1 = db.components().insertProjectBranch(project, b -> b.setKey("long1"), b -> b.setBranchType(BranchType.LONG));
+ long1short1 = db.components().insertProjectBranch(project,
+ b -> b.setKey("long1short1"),
+ b -> b.setBranchType(BranchType.SHORT),
+ b -> b.setMergeBranchUuid(long1.uuid()));
+ ComponentDto long1short2 = db.components().insertProjectBranch(project,
+ b -> b.setKey("long1short2"),
+ b -> b.setBranchType(BranchType.SHORT),
+ b -> b.setMergeBranchUuid(long1.uuid()));
+
+ fileWithNoIssuesOnLong1 = db.components().insertComponent(ComponentTesting.newFileDto(long1, null));
+
+ RuleDefinitionDto rule = db.rules().insert();
+
+ fileWithOneOpenIssueOnLong1Short1 = db.components().insertComponent(ComponentTesting.newFileDto(long1short1, null));
+ db.issues().insertIssue(IssueTesting.newIssue(rule, long1short1, fileWithOneOpenIssueOnLong1Short1));
+
+ fileWithOneResolvedIssueOnLong1Short1 = db.components().insertComponent(ComponentTesting.newFileDto(long1short1, null));
+ db.issues().insertIssue(IssueTesting.newIssue(rule, long1short1, fileWithOneResolvedIssueOnLong1Short1).setStatus("RESOLVED"));
+
+ fileWithOneOpenTwoResolvedIssuesOnLong1Short1 = db.components().insertComponent(ComponentTesting.newFileDto(long1short1, null));
+ db.issues().insertIssue(IssueTesting.newIssue(rule, long1short1, fileWithOneOpenTwoResolvedIssuesOnLong1Short1));
+ db.issues().insertIssue(IssueTesting.newIssue(rule, long1short1, fileWithOneOpenTwoResolvedIssuesOnLong1Short1).setStatus("RESOLVED"));
+
+ String fileKey = "file-x";
+ fileXWithOneResolvedIssueOnLong1Short1 = db.components().insertComponent(ComponentTesting.newFileDto(long1short1, null)
+ .setDbKey(fileKey + ":BRANCH:long1short1"));
+ db.issues().insertIssue(IssueTesting.newIssue(rule, long1short1, fileXWithOneResolvedIssueOnLong1Short1).setStatus("RESOLVED"));
+ fileXWithOneResolvedIssueOnLong1Short2 = db.components().insertComponent(ComponentTesting.newFileDto(long1short2, null)
+ .setDbKey(fileKey + ":BRANCH:long1short2"));
+ db.issues().insertIssue(IssueTesting.newIssue(rule, long1short2, fileXWithOneResolvedIssueOnLong1Short2).setStatus("RESOLVED"));
+
+ long2 = db.components().insertProjectBranch(project, b -> b.setKey("long2"), b -> b.setBranchType(BranchType.LONG));
+ ComponentDto long2short1 = db.components().insertProjectBranch(project,
+ b -> b.setKey("long2short1"),
+ b -> b.setBranchType(BranchType.SHORT),
+ b -> b.setMergeBranchUuid(long2.uuid()));
+
+ fileWithOneOpenIssueOnLong2Short1 = db.components().insertComponent(ComponentTesting.newFileDto(long2short1, null));
+ db.issues().insertIssue(IssueTesting.newIssue(rule, long2short1, fileWithOneOpenIssueOnLong2Short1));
+
+ fileWithOneResolvedIssueOnLong2Short1 = db.components().insertComponent(ComponentTesting.newFileDto(long2short1, null));
+ db.issues().insertIssue(IssueTesting.newIssue(rule, long2short1, fileWithOneResolvedIssueOnLong2Short1).setStatus("RESOLVED"));
+
+ setCurrentBranchUuid(long1.uuid());
+ underTest = new SiblingComponentsWithOpenIssues(treeRootHolder, metadataHolder, db.getDbClient());
+ }
+
+ @Test
+ public void should_find_sibling_components_with_open_issues_for_long1() {
+ setCurrentBranchUuid(long1.uuid());
+ setReferenceBranchUuid(null);
+
+ assertThat(underTest.getUuids(fileWithNoIssuesOnLong1.getKey())).isEmpty();
+ assertThat(underTest.getUuids(fileWithOneOpenIssueOnLong1Short1.getKey())).containsOnly(fileWithOneOpenIssueOnLong1Short1.uuid());
+ assertThat(underTest.getUuids(fileWithOneResolvedIssueOnLong1Short1.getKey())).containsOnly(fileWithOneResolvedIssueOnLong1Short1.uuid());
+ assertThat(underTest.getUuids(fileWithOneOpenTwoResolvedIssuesOnLong1Short1.getKey())).containsOnly(fileWithOneOpenTwoResolvedIssuesOnLong1Short1.uuid());
+
+ assertThat(fileXWithOneResolvedIssueOnLong1Short1.getKey()).isEqualTo(fileXWithOneResolvedIssueOnLong1Short2.getKey());
+ assertThat(underTest.getUuids(fileXWithOneResolvedIssueOnLong1Short1.getKey())).containsOnly(
+ fileXWithOneResolvedIssueOnLong1Short1.uuid(),
+ fileXWithOneResolvedIssueOnLong1Short2.uuid());
+ }
+
+ @Test
+ public void should_find_sibling_components_with_open_issues_for_short1() {
+ setCurrentBranchUuid(long1short1.uuid());
+ setReferenceBranchUuid(long1.uuid());
+
+ assertThat(underTest.getUuids(fileWithNoIssuesOnLong1.getKey())).isEmpty();
+ assertThat(underTest.getUuids(fileWithOneOpenIssueOnLong1Short1.getKey())).isEmpty();
+ assertThat(underTest.getUuids(fileWithOneResolvedIssueOnLong1Short1.getKey())).isEmpty();
+ assertThat(underTest.getUuids(fileWithOneOpenTwoResolvedIssuesOnLong1Short1.getKey())).isEmpty();
+
+ assertThat(underTest.getUuids(fileXWithOneResolvedIssueOnLong1Short1.getKey())).containsOnly(
+ fileXWithOneResolvedIssueOnLong1Short2.uuid());
+ }
+
+ @Test
+ public void should_find_sibling_components_with_open_issues_for_long2() {
+ setCurrentBranchUuid(long2.uuid());
+ setReferenceBranchUuid(null);
+ underTest = new SiblingComponentsWithOpenIssues(treeRootHolder, metadataHolder, db.getDbClient());
+
+ assertThat(underTest.getUuids(fileWithOneResolvedIssueOnLong1Short1.getKey())).isEmpty();
+ assertThat(underTest.getUuids(fileWithOneResolvedIssueOnLong2Short1.getKey())).containsOnly(fileWithOneResolvedIssueOnLong2Short1.uuid());
+ assertThat(underTest.getUuids(fileWithOneOpenIssueOnLong2Short1.getKey())).containsOnly(fileWithOneOpenIssueOnLong2Short1.uuid());
+ }
+
+ @Test
+ public void should_find_sibling_components_with_open_issues_from_short() {
+ ComponentDto project = db.components().insertMainBranch();
+ setCurrentBranchUuid(project.uuid());
+ setReferenceBranchUuid(null);
+
+ ComponentDto branch = db.components().insertProjectBranch(project,
+ b -> b.setBranchType(BranchType.SHORT),
+ b -> b.setMergeBranchUuid(project.uuid()));
+
+ RuleDefinitionDto rule = db.rules().insert();
+
+ ComponentDto fileWithResolvedIssueOnShort = db.components().insertComponent(ComponentTesting.newFileDto(branch, null));
+ db.issues().insertIssue(IssueTesting.newIssue(rule, branch, fileWithResolvedIssueOnShort).setStatus("RESOLVED"));
+
+ underTest = new SiblingComponentsWithOpenIssues(treeRootHolder, metadataHolder, db.getDbClient());
+
+ assertThat(underTest.getUuids(fileWithResolvedIssueOnShort.getKey())).hasSize(1);
+ }
+
+ @Test
+ public void should_find_sibling_components_with_open_issues_from_pullrequest() {
+ ComponentDto project = db.components().insertMainBranch();
+ setCurrentBranchUuid(project.uuid());
+ setReferenceBranchUuid(null);
+
+ ComponentDto pullRequest = db.components().insertProjectBranch(project,
+ b -> b.setBranchType(BranchType.PULL_REQUEST),
+ b -> b.setMergeBranchUuid(project.uuid()));
+
+ RuleDefinitionDto rule = db.rules().insert();
+
+ ComponentDto fileWithResolvedIssueOnPullrequest = db.components().insertComponent(ComponentTesting.newFileDto(pullRequest, null));
+ db.issues().insertIssue(IssueTesting.newIssue(rule, pullRequest, fileWithResolvedIssueOnPullrequest).setStatus("RESOLVED"));
+
+ underTest = new SiblingComponentsWithOpenIssues(treeRootHolder, metadataHolder, db.getDbClient());
+
+ assertThat(underTest.getUuids(fileWithResolvedIssueOnPullrequest.getKey())).hasSize(1);
+ }
+
+ @Test
+ public void should_not_find_sibling_components_on_derived_long() {
+ ComponentDto project = db.components().insertMainBranch();
+ setCurrentBranchUuid(project.uuid());
+ setReferenceBranchUuid(null);
+
+ ComponentDto derivedLongBranch = db.components().insertProjectBranch(project,
+ b -> b.setBranchType(BranchType.LONG),
+ b -> b.setMergeBranchUuid(project.uuid()));
+
+ RuleDefinitionDto rule = db.rules().insert();
+
+ ComponentDto fileWithResolvedIssueOnDerivedLongBranch = db.components().insertComponent(ComponentTesting.newFileDto(derivedLongBranch, null));
+ db.issues().insertIssue(IssueTesting.newIssue(rule, derivedLongBranch, fileWithResolvedIssueOnDerivedLongBranch).setStatus("RESOLVED"));
+
+ underTest = new SiblingComponentsWithOpenIssues(treeRootHolder, metadataHolder, db.getDbClient());
+
+ assertThat(underTest.getUuids(fileWithResolvedIssueOnDerivedLongBranch.getKey())).isEmpty();
+ }
+
+ private void setCurrentBranchUuid(String uuid) {
+ Component root = mock(Component.class);
+ when(root.getUuid()).thenReturn(uuid);
+ treeRootHolder.setRoot(root);
+ }
+
+ private void setReferenceBranchUuid(@Nullable String uuid) {
+ Branch branch = mock(Branch.class);
+ when(branch.getMergeBranchUuid()).thenReturn(Optional.ofNullable(uuid));
+ metadataHolder.setBranch(branch);
+ }
+}
private IssueLifecycle issueLifecycle = mock(IssueLifecycle.class);
private IssueVisitor issueVisitor = mock(IssueVisitor.class);
private MergeBranchComponentUuids mergeBranchComponentsUuids = mock(MergeBranchComponentUuids.class);
- private ShortBranchIssueMerger issueStatusCopier = mock(ShortBranchIssueMerger.class);
+ private SiblingsIssueMerger issueStatusCopier = mock(SiblingsIssueMerger.class);
private MergeBranchComponentUuids mergeBranchComponentUuids = mock(MergeBranchComponentUuids.class);
private SourceLinesHashRepository sourceLinesHash = mock(SourceLinesHashRepository.class);
private NewLinesRepository newLinesRepository = mock(NewLinesRepository.class);
+++ /dev/null
-/*
- * 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.ce.task.projectanalysis.issue;
-
-import java.time.Instant;
-import java.time.temporal.ChronoUnit;
-import java.util.Collections;
-import java.util.Date;
-import javax.annotation.Nullable;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.sonar.api.config.internal.MapSettings;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.utils.System2;
-import org.sonar.ce.task.projectanalysis.component.ShortBranchComponentsWithIssues;
-import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.FieldDiffs;
-import org.sonar.core.issue.tracking.SimpleTracker;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.BranchType;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.issue.IssueDto;
-import org.sonar.db.issue.IssueTesting;
-import org.sonar.db.rule.RuleDefinitionDto;
-import org.sonar.db.user.UserDto;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
-import static org.sonar.db.component.ComponentTesting.newFileDto;
-
-public class ShortBranchIssueMergerTest {
- @Mock
- private IssueLifecycle issueLifecycle;
-
- @Rule
- public DbTester db = DbTester.create();
-
- @Rule
- public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule()
- .setRoot(builder(org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT, PROJECT_REF).setKey(PROJECT_KEY).setUuid(PROJECT_UUID)
- .addChildren(FILE_1)
- .build());
-
- private static final String PROJECT_KEY = "project";
- private static final int PROJECT_REF = 1;
- private static final String PROJECT_UUID = "projectUuid";
- private static final int FILE_1_REF = 12341;
- private static final String FILE_1_KEY = "fileKey";
- private static final String FILE_1_UUID = "fileUuid";
-
- private static final org.sonar.ce.task.projectanalysis.component.Component FILE_1 = builder(
- org.sonar.ce.task.projectanalysis.component.Component.Type.FILE, FILE_1_REF)
- .setKey(FILE_1_KEY)
- .setUuid(FILE_1_UUID)
- .build();
-
- private SimpleTracker<DefaultIssue, ShortBranchIssue> tracker = new SimpleTracker<>();
- private ShortBranchIssueMerger copier;
- private ComponentDto fileOnBranch1Dto;
- private ComponentDto fileOnBranch2Dto;
- private ComponentDto fileOnBranch3Dto;
- private ComponentDto projectDto;
- private ComponentDto branch1Dto;
- private ComponentDto branch2Dto;
- private ComponentDto branch3Dto;
- private RuleDefinitionDto rule;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- DbClient dbClient = db.getDbClient();
- ComponentIssuesLoader componentIssuesLoader = new ComponentIssuesLoader(dbClient, null, null, new MapSettings().asConfig(), System2.INSTANCE);
- copier = new ShortBranchIssueMerger(new ShortBranchIssuesLoader(new ShortBranchComponentsWithIssues(treeRootHolder, dbClient), dbClient, componentIssuesLoader), tracker,
- issueLifecycle);
- projectDto = db.components().insertMainBranch(p -> p.setDbKey(PROJECT_KEY).setUuid(PROJECT_UUID));
- branch1Dto = db.components().insertProjectBranch(projectDto, b -> b.setKey("myBranch1")
- .setBranchType(BranchType.SHORT)
- .setMergeBranchUuid(projectDto.uuid()));
- branch2Dto = db.components().insertProjectBranch(projectDto, b -> b.setKey("myBranch2")
- .setBranchType(BranchType.SHORT)
- .setMergeBranchUuid(projectDto.uuid()));
- branch3Dto = db.components().insertProjectBranch(projectDto, b -> b.setKey("myBranch3")
- .setBranchType(BranchType.SHORT)
- .setMergeBranchUuid(projectDto.uuid()));
- fileOnBranch1Dto = db.components().insertComponent(newFileDto(branch1Dto).setDbKey(FILE_1_KEY + ":BRANCH:myBranch1"));
- fileOnBranch2Dto = db.components().insertComponent(newFileDto(branch2Dto).setDbKey(FILE_1_KEY + ":BRANCH:myBranch2"));
- fileOnBranch3Dto = db.components().insertComponent(newFileDto(branch3Dto).setDbKey(FILE_1_KEY + ":BRANCH:myBranch3"));
- rule = db.rules().insert();
- }
-
- @Test
- public void do_nothing_if_no_match() {
- DefaultIssue i = createIssue("issue1", rule.getKey(), Issue.STATUS_CONFIRMED, null, new Date());
- copier.tryMerge(FILE_1, Collections.singleton(i));
-
- verifyZeroInteractions(issueLifecycle);
- }
-
- @Test
- public void do_nothing_if_no_new_issue() {
- db.issues().insertIssue(IssueTesting.newIssue(rule, branch1Dto, fileOnBranch1Dto).setKee("issue1").setStatus(Issue.STATUS_CONFIRMED).setLine(1).setChecksum("checksum"));
- copier.tryMerge(FILE_1, Collections.emptyList());
-
- verifyZeroInteractions(issueLifecycle);
- }
-
- @Test
- public void merge_confirmed_issues() {
- db.issues().insertIssue(IssueTesting.newIssue(rule, branch1Dto, fileOnBranch1Dto).setKee("issue1").setStatus(Issue.STATUS_CONFIRMED).setLine(1).setChecksum("checksum"));
- DefaultIssue newIssue = createIssue("issue2", rule.getKey(), Issue.STATUS_OPEN, null, new Date());
-
- copier.tryMerge(FILE_1, Collections.singleton(newIssue));
-
- ArgumentCaptor<DefaultIssue> issueToMerge = ArgumentCaptor.forClass(DefaultIssue.class);
- verify(issueLifecycle).mergeConfirmedOrResolvedFromShortLivingBranch(eq(newIssue), issueToMerge.capture(), eq("myBranch1"));
-
- assertThat(issueToMerge.getValue().key()).isEqualTo("issue1");
- }
-
- @Test
- public void prefer_resolved_issues() {
- db.issues().insertIssue(IssueTesting.newIssue(rule, branch1Dto, fileOnBranch1Dto).setKee("issue1").setStatus(Issue.STATUS_REOPENED).setLine(1).setChecksum("checksum"));
- db.issues().insertIssue(IssueTesting.newIssue(rule, branch2Dto, fileOnBranch2Dto).setKee("issue2").setStatus(Issue.STATUS_CONFIRMED).setLine(1).setChecksum("checksum"));
- db.issues().insertIssue(IssueTesting.newIssue(rule, branch3Dto, fileOnBranch3Dto).setKee("issue3").setStatus(Issue.STATUS_RESOLVED)
- .setResolution(Issue.RESOLUTION_FALSE_POSITIVE).setLine(1).setChecksum("checksum"));
- DefaultIssue newIssue = createIssue("newIssue", rule.getKey(), Issue.STATUS_OPEN, null, new Date());
-
- copier.tryMerge(FILE_1, Collections.singleton(newIssue));
-
- ArgumentCaptor<DefaultIssue> issueToMerge = ArgumentCaptor.forClass(DefaultIssue.class);
- verify(issueLifecycle).mergeConfirmedOrResolvedFromShortLivingBranch(eq(newIssue), issueToMerge.capture(), eq("myBranch3"));
-
- assertThat(issueToMerge.getValue().key()).isEqualTo("issue3");
- }
-
- @Test
- public void prefer_confirmed_issues_if_no_resolved() {
- db.issues().insertIssue(IssueTesting.newIssue(rule, branch1Dto, fileOnBranch1Dto).setKee("issue1").setStatus(Issue.STATUS_REOPENED).setLine(1).setChecksum("checksum"));
- db.issues().insertIssue(IssueTesting.newIssue(rule, branch2Dto, fileOnBranch2Dto).setKee("issue2").setStatus(Issue.STATUS_OPEN).setLine(1).setChecksum("checksum"));
- db.issues().insertIssue(IssueTesting.newIssue(rule, branch3Dto, fileOnBranch3Dto).setKee("issue3").setStatus(Issue.STATUS_CONFIRMED).setLine(1).setChecksum("checksum"));
- DefaultIssue newIssue = createIssue("newIssue", rule.getKey(), Issue.STATUS_OPEN, null, new Date());
-
- copier.tryMerge(FILE_1, Collections.singleton(newIssue));
-
- ArgumentCaptor<DefaultIssue> issueToMerge = ArgumentCaptor.forClass(DefaultIssue.class);
- verify(issueLifecycle).mergeConfirmedOrResolvedFromShortLivingBranch(eq(newIssue), issueToMerge.capture(), eq("myBranch3"));
-
- assertThat(issueToMerge.getValue().key()).isEqualTo("issue3");
- }
-
- @Test
- public void prefer_older_issues() {
- Instant now = Instant.now();
- db.issues().insertIssue(IssueTesting.newIssue(rule, branch1Dto, fileOnBranch1Dto).setKee("issue1").setStatus(Issue.STATUS_REOPENED).setLine(1).setChecksum("checksum")
- .setIssueCreationDate(Date.from(now.plus(2, ChronoUnit.SECONDS))));
- db.issues().insertIssue(IssueTesting.newIssue(rule, branch2Dto, fileOnBranch2Dto).setKee("issue2").setStatus(Issue.STATUS_OPEN).setLine(1).setChecksum("checksum")
- .setIssueCreationDate(Date.from(now.plus(1, ChronoUnit.SECONDS))));
- db.issues().insertIssue(IssueTesting.newIssue(rule, branch3Dto, fileOnBranch3Dto).setKee("issue3").setStatus(Issue.STATUS_OPEN).setLine(1).setChecksum("checksum")
- .setIssueCreationDate(Date.from(now)));
- DefaultIssue newIssue = createIssue("newIssue", rule.getKey(), Issue.STATUS_OPEN, null, new Date());
-
- copier.tryMerge(FILE_1, Collections.singleton(newIssue));
-
- ArgumentCaptor<DefaultIssue> issueToMerge = ArgumentCaptor.forClass(DefaultIssue.class);
- verify(issueLifecycle).mergeConfirmedOrResolvedFromShortLivingBranch(eq(newIssue), issueToMerge.capture(), eq("myBranch3"));
-
- assertThat(issueToMerge.getValue().key()).isEqualTo("issue3");
- }
-
- @Test
- public void lazy_load_changes() {
- UserDto user1 = db.users().insertUser();
- IssueDto issue1 = db.issues()
- .insertIssue(IssueTesting.newIssue(rule, branch1Dto, fileOnBranch1Dto).setKee("issue1").setStatus(Issue.STATUS_REOPENED).setLine(1).setChecksum("checksum"));
- db.issues().insertComment(issue1, user1, "A comment 1");
- db.issues().insertFieldDiffs(issue1, FieldDiffs.parse("severity=BLOCKER|INFO,assignee=toto|titi").setCreationDate(new Date()));
- UserDto user2 = db.users().insertUser();
- IssueDto issue2 = db.issues()
- .insertIssue(IssueTesting.newIssue(rule, branch2Dto, fileOnBranch2Dto).setKee("issue2").setStatus(Issue.STATUS_CONFIRMED).setLine(1).setChecksum("checksum"));
- db.issues().insertComment(issue2, user2, "A comment 2");
- db.issues().insertFieldDiffs(issue2, FieldDiffs.parse("severity=BLOCKER|MINOR,assignee=foo|bar").setCreationDate(new Date()));
- DefaultIssue newIssue = createIssue("newIssue", rule.getKey(), Issue.STATUS_OPEN, null, new Date());
-
- copier.tryMerge(FILE_1, Collections.singleton(newIssue));
-
- ArgumentCaptor<DefaultIssue> issueToMerge = ArgumentCaptor.forClass(DefaultIssue.class);
- verify(issueLifecycle).mergeConfirmedOrResolvedFromShortLivingBranch(eq(newIssue), issueToMerge.capture(), eq("myBranch2"));
-
- assertThat(issueToMerge.getValue().key()).isEqualTo("issue2");
- assertThat(issueToMerge.getValue().defaultIssueComments()).isNotEmpty();
- assertThat(issueToMerge.getValue().changes()).isNotEmpty();
- }
-
- private static DefaultIssue createIssue(String key, RuleKey ruleKey, String status, @Nullable String resolution, Date creationDate) {
- DefaultIssue issue = new DefaultIssue();
- issue.setKey(key);
- issue.setRuleKey(ruleKey);
- issue.setMessage("msg");
- issue.setLine(1);
- issue.setStatus(status);
- issue.setResolution(resolution);
- issue.setCreationDate(creationDate);
- issue.setChecksum("checksum");
- return issue;
- }
-
-}
--- /dev/null
+/*
+ * 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.ce.task.projectanalysis.issue;
+
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Optional;
+import javax.annotation.Nullable;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
+import org.sonar.ce.task.projectanalysis.analysis.Branch;
+import org.sonar.ce.task.projectanalysis.component.SiblingComponentsWithOpenIssues;
+import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.FieldDiffs;
+import org.sonar.core.issue.tracking.SimpleTracker;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.BranchType;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.issue.IssueDto;
+import org.sonar.db.issue.IssueTesting;
+import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.db.user.UserDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
+import static org.sonar.db.component.ComponentTesting.newFileDto;
+
+public class SiblingsIssueMergerTest {
+ @Mock
+ private IssueLifecycle issueLifecycle;
+
+ @Mock
+ private Branch branch;
+
+ @Rule
+ public DbTester db = DbTester.create();
+
+ @Rule
+ public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule()
+ .setRoot(builder(org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT, PROJECT_REF).setKey(PROJECT_KEY).setUuid(PROJECT_UUID)
+ .addChildren(FILE_1)
+ .build());
+
+ @Rule
+ public AnalysisMetadataHolderRule metadataHolder = new AnalysisMetadataHolderRule();
+
+ private static final String PROJECT_KEY = "project";
+ private static final int PROJECT_REF = 1;
+ private static final String PROJECT_UUID = "projectUuid";
+ private static final int FILE_1_REF = 12341;
+ private static final String FILE_1_KEY = "fileKey";
+ private static final String FILE_1_UUID = "fileUuid";
+
+ private static final org.sonar.ce.task.projectanalysis.component.Component FILE_1 = builder(
+ org.sonar.ce.task.projectanalysis.component.Component.Type.FILE, FILE_1_REF)
+ .setKey(FILE_1_KEY)
+ .setUuid(FILE_1_UUID)
+ .build();
+
+ private SimpleTracker<DefaultIssue, SiblingIssue> tracker = new SimpleTracker<>();
+ private SiblingsIssueMerger copier;
+ private ComponentDto fileOnBranch1Dto;
+ private ComponentDto fileOnBranch2Dto;
+ private ComponentDto fileOnBranch3Dto;
+ private ComponentDto projectDto;
+ private ComponentDto branch1Dto;
+ private ComponentDto branch2Dto;
+ private ComponentDto branch3Dto;
+ private RuleDefinitionDto rule;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(branch.getMergeBranchUuid()).thenReturn(Optional.empty());
+ metadataHolder.setBranch(branch);
+ DbClient dbClient = db.getDbClient();
+ ComponentIssuesLoader componentIssuesLoader = new ComponentIssuesLoader(dbClient, null, null, new MapSettings().asConfig(), System2.INSTANCE);
+ copier = new SiblingsIssueMerger(new SiblingsIssuesLoader(new SiblingComponentsWithOpenIssues(treeRootHolder, metadataHolder, dbClient), dbClient, componentIssuesLoader), tracker,
+ issueLifecycle);
+ projectDto = db.components().insertMainBranch(p -> p.setDbKey(PROJECT_KEY).setUuid(PROJECT_UUID));
+ branch1Dto = db.components().insertProjectBranch(projectDto, b -> b.setKey("myBranch1")
+ .setBranchType(BranchType.SHORT)
+ .setMergeBranchUuid(projectDto.uuid()));
+ branch2Dto = db.components().insertProjectBranch(projectDto, b -> b.setKey("myBranch2")
+ .setBranchType(BranchType.SHORT)
+ .setMergeBranchUuid(projectDto.uuid()));
+ branch3Dto = db.components().insertProjectBranch(projectDto, b -> b.setKey("myBranch3")
+ .setBranchType(BranchType.SHORT)
+ .setMergeBranchUuid(projectDto.uuid()));
+ fileOnBranch1Dto = db.components().insertComponent(newFileDto(branch1Dto).setDbKey(FILE_1_KEY + ":BRANCH:myBranch1"));
+ fileOnBranch2Dto = db.components().insertComponent(newFileDto(branch2Dto).setDbKey(FILE_1_KEY + ":BRANCH:myBranch2"));
+ fileOnBranch3Dto = db.components().insertComponent(newFileDto(branch3Dto).setDbKey(FILE_1_KEY + ":BRANCH:myBranch3"));
+ rule = db.rules().insert();
+ }
+
+ @Test
+ public void do_nothing_if_no_match() {
+ DefaultIssue i = createIssue("issue1", rule.getKey(), Issue.STATUS_CONFIRMED, null, new Date());
+ copier.tryMerge(FILE_1, Collections.singleton(i));
+
+ verifyZeroInteractions(issueLifecycle);
+ }
+
+ @Test
+ public void do_nothing_if_no_new_issue() {
+ db.issues().insertIssue(IssueTesting.newIssue(rule, branch1Dto, fileOnBranch1Dto).setKee("issue1").setStatus(Issue.STATUS_CONFIRMED).setLine(1).setChecksum("checksum"));
+ copier.tryMerge(FILE_1, Collections.emptyList());
+
+ verifyZeroInteractions(issueLifecycle);
+ }
+
+ @Test
+ public void merge_confirmed_issues() {
+ db.issues().insertIssue(IssueTesting.newIssue(rule, branch1Dto, fileOnBranch1Dto).setKee("issue1").setStatus(Issue.STATUS_CONFIRMED).setLine(1).setChecksum("checksum"));
+ DefaultIssue newIssue = createIssue("issue2", rule.getKey(), Issue.STATUS_OPEN, null, new Date());
+
+ copier.tryMerge(FILE_1, Collections.singleton(newIssue));
+
+ ArgumentCaptor<DefaultIssue> issueToMerge = ArgumentCaptor.forClass(DefaultIssue.class);
+ verify(issueLifecycle).mergeConfirmedOrResolvedFromShortLivingBranch(eq(newIssue), issueToMerge.capture(), eq("myBranch1"));
+
+ assertThat(issueToMerge.getValue().key()).isEqualTo("issue1");
+ }
+
+ @Test
+ public void prefer_more_recently_updated_issues() {
+ Instant now = Instant.now();
+ db.issues().insertIssue(IssueTesting.newIssue(rule, branch1Dto, fileOnBranch1Dto).setKee("issue1").setStatus(Issue.STATUS_REOPENED).setLine(1).setChecksum("checksum")
+ .setIssueUpdateDate(Date.from(now.plus(2, ChronoUnit.SECONDS))));
+ db.issues().insertIssue(IssueTesting.newIssue(rule, branch2Dto, fileOnBranch2Dto).setKee("issue2").setStatus(Issue.STATUS_OPEN).setLine(1).setChecksum("checksum")
+ .setIssueUpdateDate(Date.from(now.plus(1, ChronoUnit.SECONDS))));
+ db.issues().insertIssue(IssueTesting.newIssue(rule, branch3Dto, fileOnBranch3Dto).setKee("issue3").setStatus(Issue.STATUS_OPEN).setLine(1).setChecksum("checksum")
+ .setIssueUpdateDate(Date.from(now)));
+ DefaultIssue newIssue = createIssue("newIssue", rule.getKey(), Issue.STATUS_OPEN, null, new Date());
+
+ copier.tryMerge(FILE_1, Collections.singleton(newIssue));
+
+ ArgumentCaptor<DefaultIssue> issueToMerge = ArgumentCaptor.forClass(DefaultIssue.class);
+ verify(issueLifecycle).mergeConfirmedOrResolvedFromShortLivingBranch(eq(newIssue), issueToMerge.capture(), eq("myBranch1"));
+
+ assertThat(issueToMerge.getValue().key()).isEqualTo("issue1");
+ }
+
+ @Test
+ public void lazy_load_changes() {
+ UserDto user1 = db.users().insertUser();
+ IssueDto issue1 = db.issues()
+ .insertIssue(IssueTesting.newIssue(rule, branch1Dto, fileOnBranch1Dto).setKee("issue1").setStatus(Issue.STATUS_REOPENED).setLine(1).setChecksum("checksum"));
+ db.issues().insertComment(issue1, user1, "A comment 1");
+ db.issues().insertFieldDiffs(issue1, FieldDiffs.parse("severity=BLOCKER|INFO,assignee=toto|titi").setCreationDate(new Date()));
+ UserDto user2 = db.users().insertUser();
+ IssueDto issue2 = db.issues()
+ .insertIssue(IssueTesting.newIssue(rule, branch2Dto, fileOnBranch2Dto).setKee("issue2").setStatus(Issue.STATUS_CONFIRMED).setLine(1).setChecksum("checksum"));
+ db.issues().insertComment(issue2, user2, "A comment 2");
+ db.issues().insertFieldDiffs(issue2, FieldDiffs.parse("severity=BLOCKER|MINOR,assignee=foo|bar").setCreationDate(new Date()));
+ DefaultIssue newIssue = createIssue("newIssue", rule.getKey(), Issue.STATUS_OPEN, null, new Date());
+
+ copier.tryMerge(FILE_1, Collections.singleton(newIssue));
+
+ ArgumentCaptor<DefaultIssue> issueToMerge = ArgumentCaptor.forClass(DefaultIssue.class);
+ verify(issueLifecycle).mergeConfirmedOrResolvedFromShortLivingBranch(eq(newIssue), issueToMerge.capture(), eq("myBranch2"));
+
+ assertThat(issueToMerge.getValue().key()).isEqualTo("issue2");
+ assertThat(issueToMerge.getValue().defaultIssueComments()).isNotEmpty();
+ assertThat(issueToMerge.getValue().changes()).isNotEmpty();
+ }
+
+ private static DefaultIssue createIssue(String key, RuleKey ruleKey, String status, @Nullable String resolution, Date creationDate) {
+ DefaultIssue issue = new DefaultIssue();
+ issue.setKey(key);
+ issue.setRuleKey(ruleKey);
+ issue.setMessage("msg");
+ issue.setLine(1);
+ issue.setStatus(status);
+ issue.setResolution(resolution);
+ issue.setCreationDate(creationDate);
+ issue.setChecksum("checksum");
+ return issue;
+ }
+
+}
return mapper(dbSession).selectProjectsByNameQuery(nameQueryForSql, includeModules);
}
- public List<KeyWithUuidDto> selectComponentKeysHavingIssuesToMerge(DbSession dbSession, String mergeBranchUuid) {
- return mapper(dbSession).selectComponentKeysHavingIssuesToMerge(mergeBranchUuid);
+ public List<KeyWithUuidDto> selectAllSiblingComponentKeysHavingOpenIssues(DbSession dbSession, String referenceBranchUuid, String currentBranchUuid) {
+ return mapper(dbSession).selectAllSiblingComponentKeysHavingOpenIssues(referenceBranchUuid, currentBranchUuid);
}
/**
void updateTags(ComponentDto component);
- List<KeyWithUuidDto> selectComponentKeysHavingIssuesToMerge(@Param("mergeBranchUuid") String mergeBranchUuid);
+ List<KeyWithUuidDto> selectAllSiblingComponentKeysHavingOpenIssues(@Param("referenceBranchUuid") String referenceBranchUuid,
+ @Param("currentBranchUuid") String currentBranchUuid);
List<ProjectNclocDistributionDto> selectPrivateProjectsWithNcloc(@Param("organizationUuid") String organizationUuid);
private Integer line;
private String checksum;
private String status;
- private Long issueCreationDate;
+ private Long issueUpdateDate;
// joins
private String ruleKey;
return RuleKey.of(ruleRepo, ruleKey);
}
- public Long getIssueCreationDate() {
- return issueCreationDate;
+ public Long getIssueUpdateDate() {
+ return issueUpdateDate;
}
- public ShortBranchIssueDto setIssueCreationDate(Long issueCreationDate) {
- this.issueCreationDate = issueCreationDate;
+ public ShortBranchIssueDto setIssueUpdateDate(Long issueUpdateDate) {
+ this.issueUpdateDate = issueUpdateDate;
return this;
}
DELETE FROM projects WHERE id=#{id,jdbcType=BIGINT}
</delete>
- <select id="selectComponentKeysHavingIssuesToMerge" resultType="KeyWithUuid">
+ <select id="selectAllSiblingComponentKeysHavingOpenIssues" resultType="KeyWithUuid">
SELECT DISTINCT p.kee as kee, p.uuid as uuid FROM projects p
JOIN issues i
ON p.uuid = i.component_uuid
JOIN project_branches b
ON i.project_uuid = b.uuid
AND (b.branch_type = 'SHORT' OR b.branch_type = 'PULL_REQUEST')
- AND b.merge_branch_uuid = #{mergeBranchUuid,jdbcType=VARCHAR}
+ AND b.merge_branch_uuid = #{referenceBranchUuid,jdbcType=VARCHAR}
+ AND b.uuid != #{currentBranchUuid,jdbcType=VARCHAR}
AND i.status != 'CLOSED'
</select>
i.line as line,
i.status as status,
i.checksum as checksum,
- i.issue_creation_date as issueCreationDate,
+ i.issue_update_date as issueUpdateDate,
r.plugin_rule_key as ruleKey,
r.plugin_name as ruleRepo,
b.kee as branchName
assertThat(fp.getRuleKey()).isNotNull();
assertThat(fp.getStatus()).isNotNull();
assertThat(fp.getBranchName()).isEqualTo("feature/foo");
- assertThat(fp.getIssueCreationDate()).isNotNull();
+ assertThat(fp.getIssueUpdateDate()).isNotNull();
}
@Test
}
@Override
- public Date getCreationDate() {
- return creationDate;
+ public Date getUpdateDate() {
+ return updateDate;
}
}
SearchKey rawKey = searchKeyFactory.apply(raw);
Collection<BASE> bases = baseSearch.get(rawKey);
bases.stream()
- .sorted(comparing(this::statusRank).reversed()
- .thenComparing(comparing(Trackable::getCreationDate)))
+ // Choose the more recently updated issue first to get the latest changes in siblings
+ .sorted(comparing(Trackable::getUpdateDate).reversed())
.findFirst()
.ifPresent(match -> {
tracking.match(raw, match);
String getStatus();
/**
- * Functional creation date for the issue. See {@link DefaultIssue#creationDate()}
+ * Functional update date for the issue. See {@link DefaultIssue#updateDate()}
*/
- Date getCreationDate();
+ Date getUpdateDate();
}
private final Integer line;
private final String message, lineHash;
private final String status;
- private final Date creationDate;
+ private final Date updateDate;
- Issue(@Nullable Integer line, String lineHash, RuleKey ruleKey, String message, String status, Date creationDate) {
+ Issue(@Nullable Integer line, String lineHash, RuleKey ruleKey, String message, String status, Date updateDate) {
this.line = line;
this.lineHash = lineHash;
this.ruleKey = ruleKey;
this.status = status;
- this.creationDate = creationDate;
+ this.updateDate = updateDate;
this.message = trim(message);
}
}
@Override
- public Date getCreationDate() {
- return creationDate;
+ public Date getUpdateDate() {
+ return updateDate;
}
}