You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by sk...@apache.org on 2020/12/16 13:08:13 UTC

[ignite-teamcity-bot] branch master updated: Fixed collecting issues under the same topic on the MTCGA board.

This is an automated email from the ASF dual-hosted git repository.

sk0x50 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite-teamcity-bot.git


The following commit(s) were added to refs/heads/master by this push:
     new b807230  Fixed collecting issues under the same topic on the MTCGA board.
b807230 is described below

commit b8072301946ba5e3709d607ae802dd78f4f63e9a
Author: sergeyuttsel <ut...@gmail.com>
AuthorDate: Wed Dec 16 16:06:45 2020 +0300

    Fixed collecting issues under the same topic on the MTCGA board.
    
    Signed-off-by: Slava Koptilin <sl...@gmail.com>
---
 .../org/apache/ignite/ci/jobs/CheckQueueJob.java   |   2 +
 .../tcbot/engine/board/BoardServiceTest.java       | 375 +++++++++++++++++++++
 .../{cleaner => board}/TeamcityIgnitedModule.java  |  13 +-
 .../engine/cleaner/TeamcityIgnitedModule.java      |   1 -
 .../tcbot/engine/defect/DefectCompacted.java       |  26 +-
 .../ignite/tcbot/engine/defect/DefectsStorage.java |  39 ++-
 .../teamcity/ignited/change/RevisionCompacted.java |   7 +
 7 files changed, 452 insertions(+), 11 deletions(-)

diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jobs/CheckQueueJob.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jobs/CheckQueueJob.java
index 418f7e5..0baa5f8 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jobs/CheckQueueJob.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jobs/CheckQueueJob.java
@@ -128,6 +128,8 @@ public class CheckQueueJob implements Runnable {
     @AutoProfiling
     @MonitoredTask(name = "Check Servers Queue (Triggering)")
     protected String runEx() {
+        logger.info("Build triggering task is started");
+
         if (Boolean.valueOf(System.getProperty(AUTO_TRIGGERING_BUILD_DISABLED))) {
             final String msg = "Automatic build triggering was disabled.";
             logger.info(msg);
diff --git a/ignite-tc-helper-web/src/test/java/org/apache/ignite/tcbot/engine/board/BoardServiceTest.java b/ignite-tc-helper-web/src/test/java/org/apache/ignite/tcbot/engine/board/BoardServiceTest.java
new file mode 100644
index 0000000..d9cfa67
--- /dev/null
+++ b/ignite-tc-helper-web/src/test/java/org/apache/ignite/tcbot/engine/board/BoardServiceTest.java
@@ -0,0 +1,375 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.tcbot.engine.board;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.internal.SingletonScope;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.ci.db.TcHelperDb;
+import org.apache.ignite.ci.issue.Issue;
+import org.apache.ignite.ci.issue.IssueKey;
+import org.apache.ignite.ci.teamcity.ignited.buildcondition.BuildConditionDao;
+import org.apache.ignite.ci.teamcity.ignited.change.ChangeCompacted;
+import org.apache.ignite.ci.teamcity.ignited.change.ChangeDao;
+import org.apache.ignite.ci.teamcity.ignited.change.RevisionCompacted;
+import org.apache.ignite.ci.teamcity.ignited.fatbuild.FatBuildCompacted;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
+import org.apache.ignite.tcbot.common.conf.IDataSourcesConfigSupplier;
+import org.apache.ignite.tcbot.engine.conf.ITcBotConfig;
+import org.apache.ignite.tcbot.engine.defect.DefectCompacted;
+import org.apache.ignite.tcbot.engine.defect.DefectsStorage;
+import org.apache.ignite.tcbot.engine.issue.IssuesStorage;
+import org.apache.ignite.tcbot.persistence.IStringCompactor;
+import org.apache.ignite.tcbot.persistence.TcBotPersistenceModule;
+import org.apache.ignite.tcbot.persistence.scheduler.DirectExecNoWaitScheduler;
+import org.apache.ignite.tcbot.persistence.scheduler.IScheduler;
+import org.apache.ignite.tcignited.build.FatBuildDao;
+import org.apache.ignite.tcignited.buildlog.BuildLogCheckResultDao;
+import org.apache.ignite.tcignited.buildref.BuildRefDao;
+import org.apache.ignite.tcignited.history.BuildStartTimeStorage;
+import org.apache.ignite.tcignited.history.SuiteInvocationHistoryDao;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import static org.apache.ignite.tcbot.engine.issue.IssueType.newFailure;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class BoardServiceTest {
+    /** Test ignite port. */
+    public static final int TEST_IGNITE_PORT = 64124;
+
+    /** Ignite. */
+    private static Ignite ignite;
+
+    private static Injector injector;
+    private static FatBuildDao fatBuildDao;
+    private static DefectsStorage defectsStorage;
+    private static IssuesStorage issuesStorage;
+    private static ChangeDao changeDao;
+    private static BoardService boardService;
+
+    private static AtomicInteger defectCounter = new AtomicInteger();
+
+    private final static String tc1 = "apache";
+    private final static String tc2 = "private";
+    private final static int tcId1 = Math.abs(tc1.hashCode());
+    private final static int tcId2 = Math.abs(tc2.hashCode());
+    private static int tcSrvCodeCid1;
+    private static int tcSrvCodeCid2;
+    private final static String branch1 = "branch1";
+    private final static String branch2 = "branch2";
+    private final static String issueName1 = "issue1";
+    private final static String issueName2 = "issue2";
+    private final static String issueName3 = "issue3";
+    private final static String issueName4 = "issue4";
+    private final static String issueName5 = "issue5";
+    private final static long nowTime = System.currentTimeMillis();
+
+    private static byte[] commit1 = new byte[] {4, 101, -45};
+    private static byte[] commit2 = new byte[] {82, 65, 1};
+    private static byte[] commit3 = new byte[] {-120, 0, 37};
+    private static byte[] commit4 = new byte[] {0, -7, 37};
+    private static byte[] commit5 = new byte[] {-8, -128, 127};
+
+    //issues
+    private static Issue issue1 = new Issue(new IssueKey(tc1, 1, issueName1), newFailure, nowTime);
+    private static Issue issue2 = new Issue(new IssueKey(tc1, 1, issueName2), newFailure, nowTime);
+    private static Issue issue3 = new Issue(new IssueKey(tc1, 2, issueName3), newFailure, nowTime);
+    private static Issue issue4 = new Issue(new IssueKey(tc2, 1, issueName4), newFailure, nowTime);
+    private static Issue issue5 = new Issue(new IssueKey(tc2, 4, issueName5), newFailure, nowTime);
+
+    //change
+    private static ChangeCompacted change1 = createChange(commit1);
+    private static ChangeCompacted change2 = createChange(commit2);
+    private static ChangeCompacted change3 = createChange(commit3);
+    private static ChangeCompacted change4 = createChange(commit4);
+
+    //revision
+    private static RevisionCompacted revision1 = createRevision(commit1);
+    private static RevisionCompacted revision2 = createRevision(commit2);
+    private static RevisionCompacted revision3 = createRevision(commit3);
+    private static RevisionCompacted revision4 = createRevision(commit4);
+
+    //changes
+    private static Map<Integer, ChangeCompacted> emptyBuildChanges = new HashMap<>();
+    private static Map<Integer, ChangeCompacted> buildChanges1 = new HashMap<>();
+    private static Map<Integer, ChangeCompacted> buildChanges2 = new HashMap<>();
+
+    private static List<RevisionCompacted> buildRevisions1 = new ArrayList<>();
+    private static List<RevisionCompacted> buildRevisions2 = new ArrayList<>();
+    private static List<RevisionCompacted> buildRevisions3 = new ArrayList<>();
+
+    private static FatBuildCompacted fatBuildCompacted1 = mock(FatBuildCompacted.class);
+    private static FatBuildCompacted fatBuildCompacted2 = mock(FatBuildCompacted.class);
+    private static FatBuildCompacted fatBuildCompacted3 = mock(FatBuildCompacted.class);
+    //fatBuildCompacted4 with the same revisions as the fatBuildCompacted1
+    private static FatBuildCompacted fatBuildCompacted4 = mock(FatBuildCompacted.class);
+
+    static {
+        buildChanges1.put(1, change1);
+
+        buildChanges1.put(2, change2);
+
+        buildRevisions1.add(revision1);
+        buildRevisions1.add(revision2);
+
+        buildRevisions2.add(revision3);
+        buildRevisions2.add(revision4);
+
+        buildRevisions3.add(revision3);
+        buildRevisions3.add(revision4);
+
+        when(fatBuildCompacted1.revisions()).thenReturn(buildRevisions1);
+        when(fatBuildCompacted1.id()).thenReturn(1);
+
+        when(fatBuildCompacted2.revisions()).thenReturn(buildRevisions2);
+        when(fatBuildCompacted2.id()).thenReturn(2);
+
+        when(fatBuildCompacted3.revisions()).thenReturn(buildRevisions3);
+        when(fatBuildCompacted3.id()).thenReturn(3);
+
+        when(fatBuildCompacted4.revisions()).thenReturn(buildRevisions1);
+        when(fatBuildCompacted4.id()).thenReturn(4);
+    }
+
+    @BeforeClass
+    public static void startIgnite() {
+        IgniteConfiguration cfg = new IgniteConfiguration();
+        final TcpDiscoverySpi spi = new TcpDiscoverySpi();
+        int locPort = TEST_IGNITE_PORT;
+
+        spi.setLocalPort(locPort);
+        spi.setLocalPortRange(1);
+        spi.setIpFinder(new TcHelperDb.LocalOnlyTcpDiscoveryIpFinder(locPort));
+
+        cfg.setDiscoverySpi(spi);
+
+        ignite = Ignition.start(cfg);
+
+        TeamcityIgnitedModule module = new TeamcityIgnitedModule();
+
+        injector = Guice.createInjector(module, new IgniteTestModule());
+
+        fatBuildDao = injector.getInstance(FatBuildDao.class);
+        defectsStorage = injector.getInstance(DefectsStorage.class);
+        issuesStorage = injector.getInstance(IssuesStorage.class);
+        changeDao = injector.getInstance(ChangeDao.class);
+        boardService = injector.getInstance(BoardService.class);
+        IStringCompactor compactor = injector.getInstance(IStringCompactor.class);
+
+        fatBuildDao.init();
+        injector.getInstance(SuiteInvocationHistoryDao.class).init();
+        injector.getInstance(BuildLogCheckResultDao.class).init();
+        injector.getInstance(BuildRefDao.class).init();
+        injector.getInstance(BuildStartTimeStorage.class).init();
+        injector.getInstance(BuildConditionDao.class).init();
+
+        //save issue names in compactor cache
+        compactor.getStringId(issueName1);
+        compactor.getStringId(issueName2);
+        compactor.getStringId(issueName3);
+        compactor.getStringId(issueName4);
+        compactor.getStringId(issueName5);
+
+        tcSrvCodeCid1 = compactor.getStringId(tc1);
+        tcSrvCodeCid2 = compactor.getStringId(tc2);
+
+        fatBuildDao.putFatBuild(tcId1, fatBuildCompacted1.id(), fatBuildCompacted1);
+        fatBuildDao.putFatBuild(tcId1, fatBuildCompacted2.id(), fatBuildCompacted2);
+        fatBuildDao.putFatBuild(tcId1, fatBuildCompacted3.id(), fatBuildCompacted3);
+        fatBuildDao.putFatBuild(tcId2, fatBuildCompacted1.id(), fatBuildCompacted1);
+        fatBuildDao.putFatBuild(tcId2, fatBuildCompacted4.id(), fatBuildCompacted4);
+
+        when(changeDao.getAll(eq(tcId1), any())).thenReturn(buildChanges1);
+        when(changeDao.getAll(eq(tcId2), any())).thenReturn(emptyBuildChanges);
+    }
+
+    @AfterClass
+    public static void stopIgnite() {
+        if (ignite != null)
+            ignite.close();
+    }
+
+    @After
+    public void clear() {
+        DefectsStorage defectsStorage = injector.getInstance(DefectsStorage.class);
+        IssuesStorage issuesStorage = injector.getInstance(IssuesStorage.class);
+
+        defectsStorage.loadAllDefects().stream().forEach(defect0 -> {
+            defect0.resolvedByUsernameId(1);
+            defectsStorage.save(defect0);
+        });
+
+        issuesStorage.removeOldIssues(System.currentTimeMillis(), Integer.MAX_VALUE);
+        defectsStorage.removeOldDefects(System.currentTimeMillis(), Integer.MAX_VALUE);
+    }
+
+    private static ChangeCompacted createChange(byte[] commit) {
+        ChangeCompacted change = mock(ChangeCompacted.class);
+        when(change.commitVersion()).thenReturn(commit);
+        return change;
+    }
+
+    private static RevisionCompacted createRevision(byte[] commit) {
+        RevisionCompacted revision = mock(RevisionCompacted.class);
+        when(revision.revision()).thenReturn(commit);
+        return revision;
+    }
+
+    /**
+     * Test that convert the same issue to defect does not create new defect
+     */
+    @Test
+    public void testMergingTheSameIssue() {
+        issuesStorage.saveIssue(issue1);
+
+        boardService.issuesToDefects();
+
+        DefectCompacted defect = defectsStorage.load(defectCounter.incrementAndGet());
+
+        assertEquals(1, defect.buildsInvolved().size());
+        assertEquals((int)issue1.issueKey().getBuildId(), defect.buildsInvolved().get(issue1.issueKey().getBuildId()).build().id());
+        assertEquals(1, defectsStorage.loadAllDefects().size());
+
+        boardService.issuesToDefects();
+
+        defect = defectsStorage.load(defectCounter.get());
+
+        assertEquals(1, defect.buildsInvolved().size());
+        assertEquals((int)issue1.issueKey().buildId, defect.buildsInvolved().get((int)issue1.issueKey().buildId).build().id());
+        assertEquals(1, defectsStorage.loadAllDefects().size());
+
+    }
+
+    /**
+     * Test that two issues from the same build converted to one defect
+     */
+    @Test
+    public void testMergingIssuesFromTheSameBuild() {
+        issuesStorage.saveIssue(issue1);
+        issuesStorage.saveIssue(issue2);
+
+        boardService.issuesToDefects();
+
+        DefectCompacted defect = defectsStorage.load(defectCounter.incrementAndGet());
+
+        assertEquals(1, defect.buildsInvolved().size());
+        assertEquals((int)issue2.issueKey().buildId, defect.buildsInvolved().get(issue2.issueKey().buildId).build().id());
+        assertEquals(1, defectsStorage.loadAllDefects().size());
+
+    }
+
+    /**
+     * Test that two issues from different build and different revisions but with the same changes converted to one defect
+     */
+    @Test
+    public void testMergingIssuesWithTheSameChanges() {
+        issuesStorage.saveIssue(issue2);
+        issuesStorage.saveIssue(issue3);
+
+        boardService.issuesToDefects();
+
+        DefectCompacted defect = defectsStorage.load(defectCounter.incrementAndGet());
+
+        assertEquals(2, defect.buildsInvolved().size());
+        assertEquals((int)issue2.issueKey().buildId, defect.buildsInvolved().get(issue2.issueKey().buildId).build().id());
+        assertEquals((int)issue3.issueKey().buildId, defect.buildsInvolved().get(issue3.issueKey().buildId).build().id());
+        assertEquals(1, defectsStorage.loadAllDefects().size());
+
+    }
+
+    /**
+     * Test that two issues from different build and no changes but with the same revisions converted to one defect
+     */
+    @Test
+    public void testMergingIssuesWithTheSameRevisions() {
+        issuesStorage.saveIssue(issue4);
+
+        boardService.issuesToDefects();
+
+        issuesStorage.removeOldIssues(System.currentTimeMillis(), Integer.MAX_VALUE);
+
+        issuesStorage.saveIssue(issue5);
+
+        boardService.issuesToDefects();
+
+        DefectCompacted defect = defectsStorage.load(defectCounter.incrementAndGet());
+
+        assertEquals(2, defect.buildsInvolved().size());
+        assertEquals((int)issue4.issueKey().buildId, defect.buildsInvolved().get(issue4.issueKey().buildId).build().id());
+        assertEquals((int)issue5.issueKey().buildId, defect.buildsInvolved().get(issue5.issueKey().buildId).build().id());
+        assertEquals(1, defectsStorage.loadAllDefects().size());
+    }
+
+    /**
+     * Test that two issues converted to two defects
+     */
+    @Test
+    public void testConvertIssuesToDifferentDefects() {
+        issuesStorage.saveIssue(issue1);
+
+        boardService.issuesToDefects();
+
+        issuesStorage.removeOldIssues(System.currentTimeMillis(), Integer.MAX_VALUE);
+
+        issuesStorage.saveIssue(issue4);
+
+        boardService.issuesToDefects();
+
+        DefectCompacted defect1 = defectsStorage.load(defectCounter.incrementAndGet());
+        DefectCompacted defect2 = defectsStorage.load(defectCounter.incrementAndGet());
+
+        assertEquals(2, defectsStorage.loadAllDefects().size());
+
+        assertEquals(1, defect1.buildsInvolved().size());
+        assertEquals((int)issue1.issueKey().buildId, defect1.buildsInvolved().get(issue1.issueKey().buildId).build().id());
+
+        assertEquals(1, defect2.buildsInvolved().size());
+        assertEquals((int)issue4.issueKey().buildId, defect2.buildsInvolved().get(issue4.issueKey().buildId).build().id());
+    }
+
+    private static class IgniteTestModule extends AbstractModule {
+        /** {@inheritDoc} */
+        @Override protected void configure() {
+            bind(Ignite.class).toInstance(ignite);
+            bind(IScheduler.class).to(DirectExecNoWaitScheduler.class).in(new SingletonScope());
+
+            ITcBotConfig cfg = Mockito.mock(ITcBotConfig.class);
+            bind(ITcBotConfig.class).toInstance(cfg);
+            bind(IDataSourcesConfigSupplier.class).toInstance(cfg);
+
+            install(new TcBotPersistenceModule());
+        }
+    }
+}
diff --git a/ignite-tc-helper-web/src/test/java/org/apache/ignite/tcbot/engine/cleaner/TeamcityIgnitedModule.java b/ignite-tc-helper-web/src/test/java/org/apache/ignite/tcbot/engine/board/TeamcityIgnitedModule.java
similarity index 87%
copy from ignite-tc-helper-web/src/test/java/org/apache/ignite/tcbot/engine/cleaner/TeamcityIgnitedModule.java
copy to ignite-tc-helper-web/src/test/java/org/apache/ignite/tcbot/engine/board/TeamcityIgnitedModule.java
index 388b43a..e0ef73e 100644
--- a/ignite-tc-helper-web/src/test/java/org/apache/ignite/tcbot/engine/cleaner/TeamcityIgnitedModule.java
+++ b/ignite-tc-helper-web/src/test/java/org/apache/ignite/tcbot/engine/board/TeamcityIgnitedModule.java
@@ -15,20 +15,22 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.tcbot.engine.cleaner;
+package org.apache.ignite.tcbot.engine.board;
 
 import com.google.inject.AbstractModule;
 import com.google.inject.internal.SingletonScope;
-import javax.annotation.Nullable;
 import org.apache.ignite.ci.teamcity.ignited.buildcondition.BuildConditionDao;
 import org.apache.ignite.ci.teamcity.ignited.buildtype.BuildTypeDao;
 import org.apache.ignite.ci.teamcity.ignited.buildtype.BuildTypeRefDao;
 import org.apache.ignite.ci.teamcity.ignited.buildtype.BuildTypeSync;
 import org.apache.ignite.ci.teamcity.ignited.change.ChangeDao;
 import org.apache.ignite.ci.teamcity.ignited.change.ChangeSync;
+import org.apache.ignite.tcbot.engine.cleaner.Cleaner;
 import org.apache.ignite.tcbot.engine.defect.DefectsStorage;
 import org.apache.ignite.tcbot.engine.issue.IIssuesStorage;
 import org.apache.ignite.tcbot.engine.issue.IssuesStorage;
+import org.apache.ignite.tcbot.engine.user.IUserStorage;
+import org.apache.ignite.tcignited.ITeamcityIgnitedProvider;
 import org.apache.ignite.tcignited.build.FatBuildDao;
 import org.apache.ignite.tcignited.build.ProactiveFatBuildSync;
 import org.apache.ignite.tcignited.build.UpdateCountersStorage;
@@ -45,6 +47,8 @@ import org.apache.ignite.tcignited.mute.MuteDao;
 import org.apache.ignite.tcignited.mute.MuteSync;
 import org.apache.ignite.tcservice.TcRealConnectionModule;
 
+import static org.mockito.Mockito.mock;
+
 public class TeamcityIgnitedModule extends AbstractModule {
     /** {@inheritDoc} */
     @Override protected void configure() {
@@ -54,7 +58,7 @@ public class TeamcityIgnitedModule extends AbstractModule {
         bind(FatBuildDao.class).in(new SingletonScope());
         bind(ProactiveFatBuildSync.class).in(new SingletonScope());
         bind(ChangeSync.class).in(new SingletonScope());
-        bind(ChangeDao.class).in(new SingletonScope());
+        bind(ChangeDao.class).toInstance(mock(ChangeDao.class));
         bind(BuildTypeRefDao.class).in(new SingletonScope());
         bind(BuildTypeDao.class).in(new SingletonScope());
         bind(BuildTypeSync.class).in(new SingletonScope());
@@ -69,6 +73,9 @@ public class TeamcityIgnitedModule extends AbstractModule {
         bind(Cleaner.class).in(new SingletonScope());
         bind(DefectsStorage.class).in(new SingletonScope());
         bind(IIssuesStorage.class).to(IssuesStorage.class).in(new SingletonScope());
+        bind(BoardService.class).in(new SingletonScope());
+        bind(ITeamcityIgnitedProvider.class).toInstance(mock(ITeamcityIgnitedProvider.class));
+        bind(IUserStorage.class).toInstance(mock(IUserStorage.class));
 
         TcRealConnectionModule module = new TcRealConnectionModule();
 
diff --git a/ignite-tc-helper-web/src/test/java/org/apache/ignite/tcbot/engine/cleaner/TeamcityIgnitedModule.java b/ignite-tc-helper-web/src/test/java/org/apache/ignite/tcbot/engine/cleaner/TeamcityIgnitedModule.java
index 388b43a..79834ce 100644
--- a/ignite-tc-helper-web/src/test/java/org/apache/ignite/tcbot/engine/cleaner/TeamcityIgnitedModule.java
+++ b/ignite-tc-helper-web/src/test/java/org/apache/ignite/tcbot/engine/cleaner/TeamcityIgnitedModule.java
@@ -19,7 +19,6 @@ package org.apache.ignite.tcbot.engine.cleaner;
 
 import com.google.inject.AbstractModule;
 import com.google.inject.internal.SingletonScope;
-import javax.annotation.Nullable;
 import org.apache.ignite.ci.teamcity.ignited.buildcondition.BuildConditionDao;
 import org.apache.ignite.ci.teamcity.ignited.buildtype.BuildTypeDao;
 import org.apache.ignite.ci.teamcity.ignited.buildtype.BuildTypeRefDao;
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/DefectCompacted.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/DefectCompacted.java
index 4252667..5f5dc38 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/DefectCompacted.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/DefectCompacted.java
@@ -52,6 +52,9 @@ public class DefectCompacted {
     /** Commits hashes involved. */
     private List<CommitCompacted> commits = new ArrayList<>();
 
+    /** Commits hashes of revisions involved. */
+    private List<CommitCompacted> revisions = new ArrayList<>();
+
     /** Blame candidates. */
     private List<BlameCandidate> blameCandidates = new ArrayList<>();
 
@@ -83,6 +86,26 @@ public class DefectCompacted {
         return this;
     }
 
+    /**
+     * @param collect Collected revisions, should be sorted.
+     */
+    public boolean sameRevisions(List<CommitCompacted> collect) {
+        if (revisions == null)
+            return false;
+
+        return revisions.equals(collect);
+    }
+
+    /**
+     * @param collect Collected revisions, should be sorted.
+     */
+    public DefectCompacted revisions(List<CommitCompacted> collect) {
+        revisions.clear();
+        revisions.addAll(collect);
+
+        return this;
+    }
+
     public Map<Integer, DefectFirstBuild> buildsInvolved() {
         return Collections.unmodifiableMap(buildsInvolved);
     }
@@ -197,6 +220,7 @@ public class DefectCompacted {
             resolvedByUsernameId == compacted.resolvedByUsernameId &&
             resolvedTs == compacted.resolvedTs &&
             Objects.equals(commits, compacted.commits) &&
+            Objects.equals(revisions, compacted.revisions) &&
             Objects.equals(blameCandidates, compacted.blameCandidates) &&
             Objects.equals(buildsInvolved, compacted.buildsInvolved) &&
             Objects.equals(changes, compacted.changes);
@@ -205,6 +229,6 @@ public class DefectCompacted {
     /** {@inheritDoc} */
     @Override public int hashCode() {
         return Objects.hash(id, tcBranch, tcSrvId, tcSrvCodeCid, trackedBranchCid, resolvedByUsernameId, resolvedTs,
-            commits, blameCandidates, buildsInvolved, changes);
+            commits, revisions, blameCandidates, buildsInvolved, changes);
     }
 }
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/DefectsStorage.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/DefectsStorage.java
index 78804ee..84bf0a8 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/DefectsStorage.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/DefectsStorage.java
@@ -36,6 +36,7 @@ import org.apache.ignite.cache.query.QueryCursor;
 import org.apache.ignite.cache.query.ScanQuery;
 import org.apache.ignite.ci.teamcity.ignited.change.ChangeCompacted;
 import org.apache.ignite.ci.teamcity.ignited.change.ChangeDao;
+import org.apache.ignite.ci.teamcity.ignited.change.RevisionCompacted;
 import org.apache.ignite.ci.teamcity.ignited.fatbuild.FatBuildCompacted;
 import org.apache.ignite.configuration.CacheConfiguration;
 import org.apache.ignite.tcbot.persistence.CacheConfigs;
@@ -109,13 +110,38 @@ public class DefectsStorage {
             .sorted(CommitCompacted::compareTo)
             .collect(toList());
 
-        try (QueryCursor<Cache.Entry<Integer, DefectCompacted>> qry = cache.query(new ScanQuery<Integer, DefectCompacted>()
-            .setFilter((k, v) -> v.resolvedByUsernameId() < 1 && v.tcSrvId() == srvId))) {
-            for (Cache.Entry<Integer, DefectCompacted> next : qry) {
-                DefectCompacted openDefect = next.getValue();
+        if (!commitsToUse.isEmpty()) {
+            try (QueryCursor<Cache.Entry<Integer, DefectCompacted>> qry = cache.query(new ScanQuery<Integer, DefectCompacted>()
+                .setFilter((k, v) -> v.resolvedByUsernameId() < 1 && v.tcSrvId() == srvId))) {
+                for (Cache.Entry<Integer, DefectCompacted> next : qry) {
+                    DefectCompacted openDefect = next.getValue();
 
-                if (openDefect.sameCommits(commitsToUse))
-                    return processExisting(function, cache, next.getKey(), openDefect);
+                    if (openDefect.sameCommits(commitsToUse))
+                        return processExisting(function, cache, next.getKey(), openDefect);
+                }
+            }
+        }
+
+        List<RevisionCompacted> buildRevisions = fatBuild.revisions();
+
+        if (buildRevisions == null)
+            buildRevisions = new ArrayList<>();
+
+        List<CommitCompacted> revisionsToUse = buildRevisions.stream()
+            .map(RevisionCompacted::revision)
+            .map(CommitCompacted::new)
+            .sorted(CommitCompacted::compareTo)
+            .collect(toList());
+
+        if (commitsToUse.isEmpty() && !buildRevisions.isEmpty()) {
+            try (QueryCursor<Cache.Entry<Integer, DefectCompacted>> qry = cache.query(new ScanQuery<Integer, DefectCompacted>()
+                .setFilter((k, v) -> v.resolvedByUsernameId() < 1 && v.tcSrvId() == srvId))) {
+                for (Cache.Entry<Integer, DefectCompacted> next : qry) {
+                    DefectCompacted openDefect = next.getValue();
+
+                    if (openDefect.sameRevisions(revisionsToUse))
+                        return processExisting(function, cache, next.getKey(), openDefect);
+                }
             }
         }
 
@@ -125,6 +151,7 @@ public class DefectsStorage {
 
         DefectCompacted defect = new DefectCompacted(id)
             .commits(commitsToUse)
+            .revisions(revisionsToUse)
             .changeMap(changeList)
             .tcBranch(fatBuild.branchName())
             .tcSrvId(srvId)
diff --git a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/ci/teamcity/ignited/change/RevisionCompacted.java b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/ci/teamcity/ignited/change/RevisionCompacted.java
index 08703c3..5d98d15 100644
--- a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/ci/teamcity/ignited/change/RevisionCompacted.java
+++ b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/ci/teamcity/ignited/change/RevisionCompacted.java
@@ -116,6 +116,13 @@ public class RevisionCompacted implements IVersionedEntity {
         return DatatypeConverter.printHexBinary(version).toLowerCase();
     }
 
+    /**
+     *
+     */
+    public byte[] revision() {
+        return version;
+    }
+
     /** {@inheritDoc} */
     @Override public boolean equals(Object o) {
         if (this == o)