Merge branch 'stable-3.4'

* stable-3.4:
  Decrease size of serialized ParsingQueueTask
  TestEventPublisher: performance improvement

Solves: Jira GER-1554
Change-Id: I38c160f94e71c995a2dc19336bbd475785102371
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/Module.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/Module.java
index 9bdc0c4..c5e4c20 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/Module.java
@@ -30,6 +30,7 @@
 import com.googlesource.gerrit.plugins.eventseiffel.config.EventListenersConfig;
 import com.googlesource.gerrit.plugins.eventseiffel.config.EventMappingConfig;
 import com.googlesource.gerrit.plugins.eventseiffel.config.EventParsingConfig;
+import com.googlesource.gerrit.plugins.eventseiffel.config.EventsFilter;
 import com.googlesource.gerrit.plugins.eventseiffel.config.GraphQlApiConfig;
 import com.googlesource.gerrit.plugins.eventseiffel.config.RabbitMqConfig;
 import com.googlesource.gerrit.plugins.eventseiffel.eiffel.api.EiffelEventPublisher;
@@ -98,6 +99,7 @@
     bind(EventParsingConfig.class)
         .toProvider(EventParsingConfig.Provider.class)
         .in(Scopes.SINGLETON);
+    bind(EventsFilter.class).toProvider(EventsFilter.Provider.class);
     bind(EiffelEventParsingExecutor.Scheduled.class).in(Scopes.SINGLETON);
     bind(EiffelEventParsingExecutor.class).to(EiffelEventParsingExecutor.Scheduled.class);
     bind(EiffelEventParser.class).to(EiffelEventParserImpl.class);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/cache/EiffelEventIdCacheImpl.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/cache/EiffelEventIdCacheImpl.java
index 5377521..fa7d622 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/cache/EiffelEventIdCacheImpl.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/cache/EiffelEventIdCacheImpl.java
@@ -19,7 +19,6 @@
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
-import com.google.common.cache.Weigher;
 import com.google.gerrit.server.cache.CacheModule;
 import com.google.inject.Inject;
 import com.google.inject.Module;
@@ -53,7 +52,6 @@
         persist(CACHE_NAME, EventKey.class, new TypeLiteral<Optional<UUID>>() {})
             .maximumWeight(maxNbrOfEntries)
             .loader(EventLoader.class)
-            .weigher(CountWeigher.class)
             .keySerializer(new EventKeySerializer())
             .valueSerializer(new UUIDSerializer());
         bind(EiffelEventIdCacheImpl.class).asEagerSingleton();
@@ -155,12 +153,4 @@
       return es.getEventId(key);
     }
   }
-
-  static class CountWeigher implements Weigher<EventKey, Optional<UUID>> {
-
-    @Override
-    public int weigh(EventKey key, Optional<UUID> value) {
-      return 1;
-    }
-  }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/config/EventListenersConfig.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/config/EventListenersConfig.java
index 32f51c5..cc89f39 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/config/EventListenersConfig.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/config/EventListenersConfig.java
@@ -16,15 +16,11 @@
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.entities.AccessSection;
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.config.PluginConfigFactory;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.inject.Inject;
-import java.util.Arrays;
-import java.util.function.Function;
-import java.util.regex.Pattern;
 import org.eclipse.jgit.lib.Config;
 
 public class EventListenersConfig {
@@ -32,31 +28,10 @@
     private static final FluentLogger logger = FluentLogger.forEnclosingClass();
     static final String EVENT_LISTENERS = "EventListeners";
     static final String ENABLED = "enabled";
-    static final String BLOCKED_REFS = "blockedRef";
-    static final String BLOCKED_PROJECTS = "blockedProject";
-
-    @VisibleForTesting
-    static boolean filterMatches(String filter, String repoName) {
-      if (filter.startsWith(AccessSection.REGEX_PREFIX)) {
-        return Pattern.matches(filter, repoName);
-      }
-      if (filter.endsWith("*")) {
-        return repoName.startsWith(filter.substring(0, filter.length() - 1));
-      }
-      return repoName.equals(filter);
-    }
-
-    @VisibleForTesting
-    public static Function<String, Boolean> toCombinedMatcher(String... filters) {
-      return repoName -> Arrays.stream(filters).anyMatch(f -> filterMatches(f, repoName));
-    }
 
     @VisibleForTesting
     static EventListenersConfig fromConfig(Config cfg) {
-      return new EventListenersConfig(
-          cfg.getBoolean(EVENT_LISTENERS, ENABLED, true),
-          toCombinedMatcher(cfg.getStringList(EVENT_LISTENERS, null, BLOCKED_PROJECTS)),
-          toCombinedMatcher(cfg.getStringList(EVENT_LISTENERS, null, BLOCKED_REFS)));
+      return new EventListenersConfig(cfg.getBoolean(EVENT_LISTENERS, ENABLED, true));
     }
 
     private final PluginConfigFactory configFactory;
@@ -81,34 +56,19 @@
       } catch (NoSuchProjectException e) {
         logger.atSevere().withCause(e).log(
             "Unable to read project.config, using default EventListenersConfig");
-        return new EventListenersConfig(true, toCombinedMatcher(""), toCombinedMatcher(""));
+        return new EventListenersConfig(true);
       }
     }
   }
 
   private final boolean enabled;
-  private final Function<String, Boolean> blockedRefMatcher;
-  private final Function<String, Boolean> blockedProjectMatcher;
 
   @VisibleForTesting
-  public EventListenersConfig(
-      boolean enabled,
-      Function<String, Boolean> blockedProjectMatcher,
-      Function<String, Boolean> blockedRefMatcher) {
+  public EventListenersConfig(boolean enabled) {
     this.enabled = enabled;
-    this.blockedProjectMatcher = blockedProjectMatcher;
-    this.blockedRefMatcher = blockedRefMatcher;
   }
 
   public boolean isEnabled() {
     return this.enabled;
   }
-
-  public boolean refIsBlocked(String ref) {
-    return blockedRefMatcher.apply(ref);
-  }
-
-  public boolean projectIsBlocked(String repoName) {
-    return blockedProjectMatcher.apply(repoName);
-  }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/config/EventsFilter.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/config/EventsFilter.java
new file mode 100644
index 0000000..38f3acc
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/config/EventsFilter.java
@@ -0,0 +1,114 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed 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 com.googlesource.gerrit.plugins.eventseiffel.config;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.AccessSection;
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.config.PluginConfigFactory;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.inject.Inject;
+import java.util.Arrays;
+import java.util.function.Function;
+import java.util.regex.Pattern;
+import org.eclipse.jgit.lib.Config;
+
+public class EventsFilter {
+
+  public static class Provider implements com.google.inject.Provider<EventsFilter> {
+    private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+    static final String EVENTS_FILTER = "EventsFilter";
+    static final String BLOCKED_REFS = "blockedRef";
+    static final String BLOCKED_PROJECTS = "blockedProject";
+    static final String BLOCK_LIGHTWEIGHT_TAGS = "blockLightWeightTags";
+
+    @VisibleForTesting
+    static boolean filterMatches(String filter, String repoName) {
+      if (filter.startsWith(AccessSection.REGEX_PREFIX)) {
+        return Pattern.matches(filter, repoName);
+      }
+      if (filter.endsWith("*")) {
+        return repoName.startsWith(filter.substring(0, filter.length() - 1));
+      }
+      return repoName.equals(filter);
+    }
+
+    @VisibleForTesting
+    public static Function<String, Boolean> toCombinedMatcher(String... filters) {
+      return repoName -> Arrays.stream(filters).anyMatch(f -> filterMatches(f, repoName));
+    }
+
+    @VisibleForTesting
+    static EventsFilter fromConfig(Config cfg) {
+      return new EventsFilter(
+          toCombinedMatcher(cfg.getStringList(EVENTS_FILTER, null, BLOCKED_PROJECTS)),
+          toCombinedMatcher(cfg.getStringList(EVENTS_FILTER, null, BLOCKED_REFS)),
+          cfg.getBoolean(EVENTS_FILTER, BLOCK_LIGHTWEIGHT_TAGS, false));
+    }
+
+    private final PluginConfigFactory configFactory;
+    private final String pluginName;
+    private final AllProjectsName allProjects;
+
+    @Inject
+    public Provider(
+        PluginConfigFactory cfgFactory,
+        @PluginName String pluginName,
+        AllProjectsName allProjects) {
+      this.configFactory = cfgFactory;
+      this.pluginName = pluginName;
+      this.allProjects = allProjects;
+    }
+
+    @Override
+    public EventsFilter get() {
+      try {
+        Config cfg = configFactory.getProjectPluginConfig(allProjects, pluginName);
+        return fromConfig(cfg);
+      } catch (NoSuchProjectException e) {
+        logger.atSevere().withCause(e).log(
+            "Unable to read project.config, using default EventsFilter");
+        return new EventsFilter(toCombinedMatcher(""), toCombinedMatcher(""), false);
+      }
+    }
+  }
+
+  private final Function<String, Boolean> blockedRefMatcher;
+  private final Function<String, Boolean> blockedProjectMatcher;
+  private final boolean blockLightWeightTags;
+
+  @VisibleForTesting
+  public EventsFilter(
+      Function<String, Boolean> blockedProjectMatcher,
+      Function<String, Boolean> blockedRefMatcher,
+      boolean blockLightWeightTags) {
+    this.blockedProjectMatcher = blockedProjectMatcher;
+    this.blockedRefMatcher = blockedRefMatcher;
+    this.blockLightWeightTags = blockLightWeightTags;
+  }
+
+  public boolean refIsBlocked(String ref) {
+    return blockedRefMatcher.apply(ref);
+  }
+
+  public boolean projectIsBlocked(String repoName) {
+    return blockedProjectMatcher.apply(repoName);
+  }
+
+  public boolean blockLightWeightTags() {
+    return blockLightWeightTags;
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/eiffel/api/EiffelGraphQlClient.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/eiffel/api/EiffelGraphQlClient.java
index c3c9201..ff8bf2b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/eiffel/api/EiffelGraphQlClient.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/eiffel/api/EiffelGraphQlClient.java
@@ -14,6 +14,11 @@
 
 package com.googlesource.gerrit.plugins.eventseiffel.eiffel.api;
 
+import com.github.rholder.retry.RetryException;
+import com.github.rholder.retry.Retryer;
+import com.github.rholder.retry.RetryerBuilder;
+import com.github.rholder.retry.StopStrategies;
+import com.github.rholder.retry.WaitStrategies;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.Lists;
 import com.google.common.flogger.FluentLogger;
@@ -37,6 +42,8 @@
 import java.util.List;
 import java.util.Optional;
 import java.util.UUID;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 public class EiffelGraphQlClient implements EventStorage {
@@ -131,8 +138,14 @@
   private QueryResult query(String query) throws EventStorageException {
     HttpResponse<String> response;
     try {
-      response = post(query);
-    } catch (IOException | InterruptedException e) {
+      Retryer<HttpResponse<String>> retryer =
+          RetryerBuilder.<HttpResponse<String>>newBuilder()
+              .retryIfException()
+              .withWaitStrategy(WaitStrategies.fixedWait(10, TimeUnit.SECONDS))
+              .withStopStrategy(StopStrategies.stopAfterAttempt(2))
+              .build();
+      response = retryer.call(() -> post(query));
+    } catch (RetryException | ExecutionException e) {
       throw new EventStorageException(e, "Query \"%s\" failed.", query);
     }
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/listeners/PatchsetCreatedListener.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/listeners/PatchsetCreatedListener.java
index 4d07ca8..048f701 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/listeners/PatchsetCreatedListener.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/listeners/PatchsetCreatedListener.java
@@ -18,30 +18,35 @@
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.googlesource.gerrit.plugins.eventseiffel.config.EventListenersConfig;
+import com.googlesource.gerrit.plugins.eventseiffel.config.EventsFilter;
 import com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParsingQueue;
 
 public class PatchsetCreatedListener implements RevisionCreatedListener {
   private final EiffelEventParsingQueue queue;
   private final Provider<EventListenersConfig> configProvider;
+  private final Provider<EventsFilter> eventsFilter;
 
   @Inject
   public PatchsetCreatedListener(
-      EiffelEventParsingQueue queue, Provider<EventListenersConfig> config) {
+      EiffelEventParsingQueue queue,
+      Provider<EventListenersConfig> configProvider,
+      Provider<EventsFilter> eventsFilter) {
     this.queue = queue;
-    this.configProvider = config;
+    this.configProvider = configProvider;
+    this.eventsFilter = eventsFilter;
   }
 
   @Override
   public void onRevisionCreated(RevisionCreatedListener.Event event) {
-    EventListenersConfig config = configProvider.get();
-    if (!config.isEnabled()) {
+    if (!configProvider.get().isEnabled()) {
       return;
     }
     /* 'branch' is not the exact reference in this case.
      * Filtering for startsWith("refs/") to filter out refs/meta/config.*/
     if (!event.getChange().branch.startsWith(RefNames.REFS)) {
-      if (config.refIsBlocked(RefNames.REFS_HEADS + event.getChange().branch)
-          || config.projectIsBlocked(event.getChange().project)) {
+      EventsFilter filter = eventsFilter.get();
+      if (filter.refIsBlocked(RefNames.REFS_HEADS + event.getChange().branch)
+          || filter.projectIsBlocked(event.getChange().project)) {
         return;
       }
       queue.scheduleSccCreation(event);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/listeners/RefUpdateListener.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/listeners/RefUpdateListener.java
index 98ca829..3a04f53 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/listeners/RefUpdateListener.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/listeners/RefUpdateListener.java
@@ -18,18 +18,23 @@
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.googlesource.gerrit.plugins.eventseiffel.config.EventListenersConfig;
+import com.googlesource.gerrit.plugins.eventseiffel.config.EventsFilter;
 import com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParsingQueue;
 
 public class RefUpdateListener implements GitReferenceUpdatedListener {
 
   private final EiffelEventParsingQueue queue;
   private final Provider<EventListenersConfig> configProvider;
+  private final Provider<EventsFilter> eventsFilter;
 
   @Inject
   public RefUpdateListener(
-      EiffelEventParsingQueue queue, Provider<EventListenersConfig> configProvider) {
+      EiffelEventParsingQueue queue,
+      Provider<EventListenersConfig> configProvider,
+      Provider<EventsFilter> eventsFilter) {
     this.queue = queue;
     this.configProvider = configProvider;
+    this.eventsFilter = eventsFilter;
   }
 
   @Override
@@ -39,8 +44,10 @@
       return;
     }
 
-    if (config.refIsBlocked(event.getRefName())
-        || config.projectIsBlocked(event.getProjectName())) {
+    EventsFilter filter = eventsFilter.get();
+
+    if (filter.refIsBlocked(event.getRefName())
+        || filter.projectIsBlocked(event.getProjectName())) {
       return;
     }
     /* The reference was not deleted. */
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParserImpl.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParserImpl.java
index 5489a74..b2320b5 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParserImpl.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParserImpl.java
@@ -30,8 +30,10 @@
 import com.google.gerrit.extensions.common.AccountInfo;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import com.googlesource.gerrit.plugins.eventseiffel.EiffelEventHub;
 import com.googlesource.gerrit.plugins.eventseiffel.cache.EiffelEventIdLookupException;
+import com.googlesource.gerrit.plugins.eventseiffel.config.EventsFilter;
 import com.googlesource.gerrit.plugins.eventseiffel.eiffel.CompositionDefinedEventKey;
 import com.googlesource.gerrit.plugins.eventseiffel.eiffel.EventKey;
 import com.googlesource.gerrit.plugins.eventseiffel.eiffel.SourceChangeEventKey;
@@ -55,6 +57,8 @@
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.revwalk.RevWalkUtils;
 
 /** Creates and pushes missing Eiffel events to the Eiffel event queue. */
 public class EiffelEventParserImpl implements EiffelEventParser {
@@ -66,17 +70,20 @@
   private final GitRepositoryManager repoManager;
   private final UnprocessedCommitsWalker.Factory walkerFactory;
   private final EiffelEventMapper mapper;
+  private final Provider<EventsFilter> eventsFilter;
 
   @Inject
   public EiffelEventParserImpl(
       EiffelEventHub eventQueue,
       GitRepositoryManager repoManager,
       EiffelEventMapper mapper,
-      UnprocessedCommitsWalker.Factory walkerFactory) {
+      UnprocessedCommitsWalker.Factory walkerFactory,
+      Provider<EventsFilter> eventsFilter) {
     this.eventHub = eventQueue;
     this.repoManager = repoManager;
     this.walkerFactory = walkerFactory;
     this.mapper = mapper;
+    this.eventsFilter = eventsFilter;
   }
 
   /* (non-Javadoc)
@@ -203,15 +210,6 @@
             throw new EiffelEventIdLookupException(
                 "Unable to find SCC event id: %s", create.key.copy(SCC));
           }
-          List<UUID> scsParentEventIds = Lists.newArrayList();
-          for (RevCommit parent : create.commit.getParents()) {
-            Optional<UUID> parentUuid = eventHub.getExistingId(create.key.copy(parent.getName()));
-            if (parentUuid.isPresent()) {
-              scsParentEventIds.add(parentUuid.get());
-            } else {
-              exceptionForMissingParent(create, parent);
-            }
-          }
           pushToHub(
               mapper.toScs(
                   create.commit,
@@ -219,7 +217,7 @@
                   create.key.branch(),
                   create.submitter,
                   create.submittedAt,
-                  scsParentEventIds,
+                  getParentUuids(create.key, create.commit),
                   sccId.get()));
         }
       }
@@ -268,67 +266,116 @@
   }
 
   private void createAndScheduleCd(
-      String repoName, String tagName, Long creationTime, boolean force)
+      String projectName, String tagName, Long creationTime, boolean force)
       throws EventParsingException {
-    SourceChangeEventKey scs = null;
     Optional<UUID> scsId = Optional.empty();
+    List<Ref> refs = null;
 
     try {
-      String commitId = peelTag(repoName, tagName);
-      scs = SourceChangeEventKey.scsKey(repoName, RefNames.REFS_HEADS + "master", commitId);
+      EventsFilter filter = eventsFilter.get();
+      ObjectId objectId = peelTag(projectName, tagName, filter.blockLightWeightTags());
+      if (objectId == null) {
+        return;
+      }
+      String commitId = objectId.getName();
+
+      /* Check if an event for commit~master has been created. */
+      SourceChangeEventKey scs =
+          SourceChangeEventKey.scsKey(projectName, RefNames.REFS_HEADS + "master", commitId);
       scsId = eventHub.getExistingId(scs);
 
       if (scsId.isEmpty()) {
+        /* No event created for commit~master. Check if event is created for any
+        of the other branches. */
         Retryer<Optional<UUID>> retryer =
             RetryerBuilder.<Optional<UUID>>newBuilder()
                 .retryIfResult(Optional::isEmpty)
                 .withWaitStrategy(WaitStrategies.fixedWait(10, TimeUnit.SECONDS))
                 .withStopStrategy(StopStrategies.stopAfterAttempt(2))
                 .build();
+        try (Repository repo = repoManager.openRepository(Project.nameKey(projectName))) {
+          refs =
+              repo.getRefDatabase().getRefsByPrefix(RefNames.REFS_HEADS).stream()
+                  .collect(Collectors.toList());
+        } catch (IOException e) {
+          throw new EventParsingException(
+              e, "Unable to get branches for: %s:%s", projectName, tagName);
+        }
         try {
-          scsId = retryer.call(() -> findSourceChangeEventKey(repoName, commitId));
+          List<String> branches =
+              refs.stream()
+                  .map(Ref::getName)
+                  .filter(branch -> !branch.equals(RefNames.REFS_HEADS + "master"))
+                  .collect(Collectors.toList());
+          scsId = retryer.call(() -> findSourceChangeEventKey(projectName, commitId, branches));
         } catch (RetryException | ExecutionException e) {
-          throw new EventParsingException(e, "Failed to find SCS for %s in %s", commitId, repoName);
+          logger.atWarning().withCause(e).log(
+              "Failed to find SCS for %s in %s when trying to create CD for tag %s",
+              commitId, projectName, tagName);
         }
       }
+
       if (scsId.isEmpty()) {
-        throw new EventParsingException("Could not find SCS for: %s in %s", commitId, repoName);
+        /* No event has been created for the commit. Find any non-blocked branch that
+        commit is merged into and create SCS events for that branch. */
+        List<Ref> branches;
+        try (Repository repo = repoManager.openRepository(Project.nameKey(projectName))) {
+          RevWalk rw = new RevWalk(repo);
+          refs =
+              refs.stream()
+                  .filter(ref -> !filter.refIsBlocked(ref.getName()))
+                  .collect(Collectors.toList());
+          branches = RevWalkUtils.findBranchesReachableFrom(rw.parseCommit(objectId), rw, refs);
+        } catch (IOException e) {
+          throw new EventParsingException(
+              e, "Unable to get reachable branches for: %s:%s", projectName, tagName);
+        }
+        if (branches.isEmpty()) {
+          throw new EventParsingException(
+              "Could not find any unblocked branch for SCS with: %s in %s so CD could not be created for tag %s",
+              commitId, projectName, tagName);
+        }
+        String branch = branches.get(0).getName();
+        scs = SourceChangeEventKey.scsKey(projectName, branch, commitId);
+        createAndScheduleMissingScss(scs, null, null, null);
+        scsId = eventHub.getExistingId(scs);
       }
-      pushToHub(mapper.toCd(repoName, tagName, creationTime, scsId.get()), force);
+
+      if (scsId.isEmpty()) {
+        throw new EventParsingException(
+            "Could not find or create SCS for %s in %s so CD could not be created for tag %s",
+            commitId, projectName, tagName);
+      }
+      pushToHub(mapper.toCd(projectName, tagName, creationTime, scsId.get()), force);
     } catch (EiffelEventIdLookupException | InterruptedException e) {
       throw new EventParsingException(
           e,
           "Event creation failed for: %s",
-          CompositionDefinedEventKey.create(mapper.tagCompositionName(repoName), tagName));
+          CompositionDefinedEventKey.create(mapper.tagCompositionName(projectName), tagName));
     }
   }
 
-  private String peelTag(String repoName, String tagName) throws EventParsingException {
-    try (Repository repo = repoManager.openRepository(Project.nameKey(repoName))) {
+  private ObjectId peelTag(String projectName, String tagName, boolean blockLightWeightTags)
+      throws EventParsingException {
+    try (Repository repo = repoManager.openRepository(Project.nameKey(projectName))) {
       Ref tagRef = repo.getRefDatabase().exactRef(Constants.R_TAGS + tagName);
       if (tagRef != null) {
         ObjectId peeled = repo.getRefDatabase().peel(tagRef).getPeeledObjectId();
-        return peeled != null ? peeled.getName() : tagRef.getObjectId().getName();
+        if (peeled != null) return peeled;
+        if (!blockLightWeightTags) return tagRef.getObjectId();
+        logger.atInfo().log("Creation of CD is blocked for lightweight tags");
+        return null;
       }
-      throw new EventParsingException("Cannot find tag: %s:%s", repoName, tagName);
+      throw new EventParsingException("Cannot find tag: %s:%s", projectName, tagName);
     } catch (IOException e) {
-      throw new EventParsingException(e, "Unable to peel tag: %s:%s", repoName, tagName);
+      throw new EventParsingException(e, "Unable to peel tag: %s:%s", projectName, tagName);
     }
   }
 
-  private Optional<UUID> findSourceChangeEventKey(String repoName, String commitId)
+  private Optional<UUID> findSourceChangeEventKey(
+      String projectName, String commitId, List<String> branches)
       throws EiffelEventIdLookupException {
-    List<String> branches;
-    try (Repository repo = repoManager.openRepository(Project.nameKey(repoName))) {
-      branches =
-          repo.getRefDatabase().getRefsByPrefix(RefNames.REFS_HEADS).stream()
-              .map(Ref::getName)
-              .collect(Collectors.toList());
-    } catch (IOException ioe) {
-      logger.atSevere().withCause(ioe).log("Unable to find branches of %s.", repoName);
-      return Optional.empty();
-    }
-    return eventHub.getScsForCommit(repoName, commitId, branches);
+    return eventHub.getScsForCommit(projectName, commitId, branches);
   }
 
   private void pushToHub(EiffelEvent toPush) throws InterruptedException {
@@ -385,18 +432,10 @@
     while (commitFinder.hasNext()) {
       EventCreate job = commitFinder.next();
       logger.atFine().log("Processing event-creation for: %s", job.key);
-      List<UUID> parentIds = Lists.newArrayList();
-      for (RevCommit parent : job.commit.getParents()) {
-        SourceChangeEventKey parentKey = scc.copy(parent.getName());
-        Optional<UUID> parentId = eventHub.getExistingId(parentKey);
-        if (parentId.isPresent()) {
-          parentIds.add(parentId.get());
-        } else {
-          exceptionForMissingParent(job, parent);
-        }
-      }
       try {
-        pushToHub(mapper.toScc(job.commit, job.key.repo(), job.key.branch(), parentIds));
+        pushToHub(
+            mapper.toScc(
+                job.commit, job.key.repo(), job.key.branch(), getParentUuids(job.key, job.commit)));
       } catch (InterruptedException e) {
         logger.atSevere().log("Interrupted while pushing %s to EventHub.", job.key);
         throw e;
@@ -418,11 +457,26 @@
     }
   }
 
-  private void exceptionForMissingParent(EventCreate create, RevCommit parent)
+  private List<UUID> getParentUuids(SourceChangeEventKey key, RevCommit commit)
+      throws NoSuchEntityException, EiffelEventIdLookupException {
+    List<UUID> parentIds = Lists.newArrayList();
+    for (RevCommit parent : commit.getParents()) {
+      SourceChangeEventKey parentKey = key.copy(parent.getName());
+      Optional<UUID> parentId = eventHub.getExistingId(parentKey);
+      if (parentId.isPresent()) {
+        parentIds.add(parentId.get());
+      } else {
+        exceptionForMissingParent(key, parent);
+      }
+    }
+    return parentIds;
+  }
+
+  private void exceptionForMissingParent(SourceChangeEventKey key, RevCommit parent)
       throws NoSuchEntityException {
     throw new NoSuchEntityException(
         String.format(
             "Unable to lookup parent (%s) event UUID for %s even though it should exist.",
-            parent.abbreviate(7).name(), create.key));
+            parent.abbreviate(7).name(), key));
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/UnprocessedCommitsWalker.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/UnprocessedCommitsWalker.java
index fb46d82..1ddd0e2 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/UnprocessedCommitsWalker.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/UnprocessedCommitsWalker.java
@@ -90,14 +90,8 @@
   UnprocessedCommitsWalker(
       SourceChangeEventKey eventKey, EiffelEventHub eventHub, GitRepositoryManager repoManager)
       throws RepositoryNotFoundException, IOException, EiffelEventIdLookupException {
-    this.eventKey = eventKey;
-    this.eventHub = eventHub;
-    this.repo = repoManager.openRepository(Project.nameKey(eventKey.repo()));
-    this.rw = new RevWalk(repo);
-    this.toClose.add(repo);
-    this.toClose.add(rw);
-    rw.setRetainBody(false);
-    this.hasEvents = hasEvents();
+    this(eventKey, eventHub, repoManager.openRepository(Project.nameKey(eventKey.repo())));
+    this.toClose.add(this.repo);
   }
 
   UnprocessedCommitsWalker(SourceChangeEventKey eventKey, EiffelEventHub eventHub, Repository repo)
diff --git a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/rest/EventCreationResponse.java b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/rest/EventCreationResponse.java
index 8f43ba9..8303940 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/rest/EventCreationResponse.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/eventseiffel/rest/EventCreationResponse.java
@@ -43,7 +43,7 @@
   }
 
   static EventCreationResponse artc(TagResource res) {
-    return new EventCreationResponse(res, null, ARTC, CD);
+    return new EventCreationResponse(res, null, ARTC, CD, SCS, SCC);
   }
 
   private final EventCreationScheduled scheduled;
diff --git a/src/main/resources/Documentation/config-project-level.md b/src/main/resources/Documentation/config-project-level.md
index 844a965..ef8192c 100644
--- a/src/main/resources/Documentation/config-project-level.md
+++ b/src/main/resources/Documentation/config-project-level.md
@@ -11,6 +11,7 @@
       blockedRef = ^refs/heads/user/.*
       blockedProject = user/projects/*
       blockedProject = top/secret/project
+      blockLightWeightTags = True.
 ```
 
 ## Section "EventListeners" ##
@@ -30,3 +31,7 @@
 blockedProject
 : Pattern(s) that blocks event creation for all matching projects.
   Supported: REGEX - if starts with "^", WILDCARD - if ends with "*", EXACT - in other cases.
+
+blockLightWeightTags
+: Decides whether ArtC and CD events should be created for lightweight tags.
+  (Default: _False_).
diff --git a/src/main/resources/Documentation/rest-api-events-eiffel-artc-post.md b/src/main/resources/Documentation/rest-api-events-eiffel-artc-post.md
new file mode 100644
index 0000000..67189f1
--- /dev/null
+++ b/src/main/resources/Documentation/rest-api-events-eiffel-artc-post.md
@@ -0,0 +1,70 @@
+POST createArtcs
+==============================
+
+```
+POST /projects/{project-name}/tags/{tag-name}/@PLUGIN@~createArtcs
+```
+
+DESCRIPTION
+-----------
+Creates missing
+[ArtifactCreatedEvent(ARTC)](https://github.com/eiffel-community/eiffel/blob/edition-paris/eiffel-vocabulary/EiffelArtifactCreatedEvent.md)
+,
+[CompositionDefinedEvent(CD)](https://github.com/eiffel-community/eiffel/blob/edition-paris/eiffel-vocabulary/EiffelCompositionDefinedEvent.md)
+,
+[SourceChangeSubmitted(SCS)](https://github.com/eiffel-community/eiffel/blob/edition-paris/eiffel-vocabulary/EiffelSourceChangeSubmittedEvent.md)
+and
+[SourceChangeCreated(SCC)](https://github.com/eiffel-community/eiffel/blob/edition-paris/eiffel-vocabulary/EiffelSourceChangeCreatedEvent.md)
+events for commit pointed by {tag-name}.
+
+ACCESS
+------
+[Administrate Server](../../../Documentation/access-control.html#capability_administrateServer)
+capability is required.
+
+EXAMPLES
+--------
+
+#### Create missing ARTC events for the tag stable-3.5 of project my/project: ####
+
+```
+  curl -X POST --user janeadmin:secret \
+    -H "content-type:application/json" \
+    http://host:port/a/projects/my%2Fproject/tags/stable-3.5/@PLUGIN@~createArtcs
+```
+
+__Response:__
+
+```
+  )]}'
+  {
+   "types": [
+     "EiffelArtifactCreatedEvent",
+     "EiffelCompositionDefinedEvent",
+     "EiffelSourceChangeCreatedEvent",
+     "EiffelSourceChangeSubmittedEvent",
+   ],
+   "repo_name": "my/project",
+   "ref": "refs/tags/stable-3.5",
+   "status": "Event creation scheduled",
+  }
+```
+
+### Response object ###
+types
+: The types of events that may be created.
+
+repo_name
+: Name of the repository.
+
+ref
+: The ref of the tag from which we will create an ARTC event.
+
+SEE ALSO
+--------
+* [Plugin Development](../../../Documentation/dev-plugins.html)
+* [REST API Protocol Details](../../../Documentation/rest-api.html#_protocol_details)
+
+GERRIT
+------
+Part of [Gerrit Code Review](../../../Documentation/index.html)
diff --git a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventsTest.java b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventsTest.java
index c847bb0..0433b7a 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventsTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventsTest.java
@@ -15,6 +15,7 @@
 package com.googlesource.gerrit.plugins.eventseiffel;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.googlesource.gerrit.plugins.eventseiffel.config.EventsFilter.Provider.toCombinedMatcher;
 import static com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelEventType.SCC;
 import static com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelEventType.SCS;
 
@@ -24,9 +25,11 @@
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.extensions.api.projects.TagInput;
 import com.google.inject.Provider;
+import com.googlesource.gerrit.plugins.eventseiffel.config.EventsFilter;
 import com.googlesource.gerrit.plugins.eventseiffel.eiffel.EventKey;
 import com.googlesource.gerrit.plugins.eventseiffel.eiffel.SourceChangeEventKey;
 import com.googlesource.gerrit.plugins.eventseiffel.eiffel.api.EventStorageException;
+import com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelEventType;
 import com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelLinkInfo;
 import com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelLinkType;
 import com.googlesource.gerrit.plugins.eventseiffel.mapping.EiffelEventFactory;
@@ -115,6 +118,22 @@
         result.getCommit().getName());
   }
 
+  protected void setScsHandled() throws Exception {
+    markAsHandled(eventForHead(EiffelEventType.SCS), getHead(repo(), "HEAD"));
+  }
+
+  protected SourceChangeEventKey eventForHead(EiffelEventType type) throws Exception {
+    return SourceChangeEventKey.create(project.get(), getHead(), getHeadRevision(), type);
+  }
+
+  protected String getHead() throws Exception {
+    return gApi.projects().name(project.get()).head();
+  }
+
+  protected String getHeadRevision() throws Exception {
+    return getHead(repo(), "HEAD").getName();
+  }
+
   protected UUID markAsHandled(EventKey key) throws EventStorageException {
     Optional<UUID> eventId = TestEventStorage.INSTANCE.getEventId(key);
     if (eventId.isPresent()) {
@@ -184,6 +203,39 @@
     return gApi.projects().name(project.get()).tag(input.ref).create(input).get().ref;
   }
 
+  public static class TestEventsFilterProvider implements Provider<EventsFilter> {
+    private static String[] refPatterns;
+    private static String[] projectPatterns;
+    private static boolean blockLightweightTags;
+
+    static {
+      reset();
+    }
+
+    public static void reset() {
+      refPatterns = projectPatterns = new String[0];
+      blockLightweightTags = false;
+    }
+
+    public static void setBlockedRefPatterns(String... patterns) {
+      refPatterns = patterns;
+    }
+
+    public static void setBlockedProjectPatterns(String... patterns) {
+      projectPatterns = patterns;
+    }
+
+    public static void blockLightWeightTags() {
+      blockLightweightTags = true;
+    }
+
+    @Override
+    public EventsFilter get() {
+      return new EventsFilter(
+          toCombinedMatcher(projectPatterns), toCombinedMatcher(refPatterns), blockLightweightTags);
+    }
+  }
+
   public static class TestEventFactoryProvider implements Provider<EiffelEventFactory> {
 
     @Override
diff --git a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventsTestModule.java b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventsTestModule.java
index 17b5acd..345753d 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventsTestModule.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/EiffelEventsTestModule.java
@@ -20,9 +20,11 @@
 import com.google.inject.Singleton;
 import com.google.inject.internal.UniqueAnnotations;
 import com.googlesource.gerrit.plugins.eventseiffel.EiffelEventsTest.TestEventFactoryProvider;
+import com.googlesource.gerrit.plugins.eventseiffel.EiffelEventsTest.TestEventsFilterProvider;
 import com.googlesource.gerrit.plugins.eventseiffel.cache.EiffelEventIdCacheImpl;
 import com.googlesource.gerrit.plugins.eventseiffel.config.EventIdCacheConfig;
 import com.googlesource.gerrit.plugins.eventseiffel.config.EventMappingConfig;
+import com.googlesource.gerrit.plugins.eventseiffel.config.EventsFilter;
 import com.googlesource.gerrit.plugins.eventseiffel.eiffel.api.EventStorage;
 import com.googlesource.gerrit.plugins.eventseiffel.mapping.EiffelEventFactory;
 import com.googlesource.gerrit.plugins.eventseiffel.parsing.EiffelEventParser;
@@ -44,6 +46,8 @@
     bind(EiffelEventParsingExecutor.class).to(EiffelEventParsingExecutor.Direct.class);
     bind(TestEventPublisher.class).in(Scopes.SINGLETON);
     bind(EiffelEventHub.Consumer.class).to(TestEventPublisher.class);
+
+    bind(EventsFilter.class).toProvider(TestEventsFilterProvider.class);
     bind(EiffelEventParser.class).to(EiffelEventParserImpl.class);
     bind(EiffelEventParsingQueue.class);
     bind(EiffelEventFactory.class).toProvider(TestEventFactoryProvider.class);
diff --git a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/config/EventListenersConfigTest.java b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/config/EventsFilterTest.java
similarity index 66%
rename from src/test/java/com/googlesource/gerrit/plugins/eventseiffel/config/EventListenersConfigTest.java
rename to src/test/java/com/googlesource/gerrit/plugins/eventseiffel/config/EventsFilterTest.java
index ab2ca17..268cc2c 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/config/EventListenersConfigTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/config/EventsFilterTest.java
@@ -14,9 +14,9 @@
 
 package com.googlesource.gerrit.plugins.eventseiffel.config;
 
-import static com.googlesource.gerrit.plugins.eventseiffel.config.EventListenersConfig.Provider.filterMatches;
-import static com.googlesource.gerrit.plugins.eventseiffel.config.EventListenersConfig.Provider.fromConfig;
-import static com.googlesource.gerrit.plugins.eventseiffel.config.EventListenersConfig.Provider.toCombinedMatcher;
+import static com.googlesource.gerrit.plugins.eventseiffel.config.EventsFilter.Provider.filterMatches;
+import static com.googlesource.gerrit.plugins.eventseiffel.config.EventsFilter.Provider.fromConfig;
+import static com.googlesource.gerrit.plugins.eventseiffel.config.EventsFilter.Provider.toCombinedMatcher;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
@@ -24,51 +24,51 @@
 import org.eclipse.jgit.lib.Config;
 import org.junit.Test;
 
-public class EventListenersConfigTest {
+public class EventsFilterTest {
 
   @Test
   public void regexBlockRefPattern() throws Exception {
-    EventListenersConfig cfg = refsPatterns("^refs/.*/blocked");
-    assertTrue(cfg.refIsBlocked("refs/that/are/blocked"));
-    assertFalse(cfg.refIsBlocked("refs/allowed"));
+    EventsFilter filter = refsPatterns("^refs/.*/blocked");
+    assertTrue(filter.refIsBlocked("refs/that/are/blocked"));
+    assertFalse(filter.refIsBlocked("refs/allowed"));
 
-    cfg = refsPatterns("^refs/(heads|tags)");
-    assertTrue(cfg.refIsBlocked("refs/heads"));
-    assertTrue(cfg.refIsBlocked("refs/tags"));
-    assertFalse(cfg.refIsBlocked("refs/meta"));
+    filter = refsPatterns("^refs/(heads|tags)");
+    assertTrue(filter.refIsBlocked("refs/heads"));
+    assertTrue(filter.refIsBlocked("refs/tags"));
+    assertFalse(filter.refIsBlocked("refs/meta"));
   }
 
   @Test
   public void wildcardBlockRefPattern() throws Exception {
-    EventListenersConfig cfg = refsPatterns("");
-    assertFalse(cfg.refIsBlocked("refs/blocked/always"));
+    EventsFilter filter = refsPatterns("");
+    assertFalse(filter.refIsBlocked("refs/blocked/always"));
 
-    cfg = refsPatterns("refs/blocked/*");
-    assertTrue(cfg.refIsBlocked("refs/blocked/always"));
-    assertFalse(cfg.refIsBlocked("refs/allowed/always"));
+    filter = refsPatterns("refs/blocked/*");
+    assertTrue(filter.refIsBlocked("refs/blocked/always"));
+    assertFalse(filter.refIsBlocked("refs/allowed/always"));
   }
 
   @Test
   public void exactBlockRefPattern() throws Exception {
-    EventListenersConfig cfg = refsPatterns("");
-    assertFalse(cfg.refIsBlocked("refs/blocked/exact"));
+    EventsFilter filter = refsPatterns("");
+    assertFalse(filter.refIsBlocked("refs/blocked/exact"));
 
-    cfg = refsPatterns("refs/blocked/exact");
-    assertTrue(cfg.refIsBlocked("refs/blocked/exact"));
-    assertFalse(cfg.refIsBlocked("refs/blocked/exact2"));
+    filter = refsPatterns("refs/blocked/exact");
+    assertTrue(filter.refIsBlocked("refs/blocked/exact"));
+    assertFalse(filter.refIsBlocked("refs/blocked/exact2"));
   }
 
   @Test
   public void combinedBlockRefPattern() throws Exception {
-    EventListenersConfig cfg = refsPatterns("");
-    assertFalse(cfg.refIsBlocked("refs/certainly/blocked"));
-    assertFalse(cfg.refIsBlocked("refs/blocked/always"));
-    assertFalse(cfg.refIsBlocked("refs/not/allowed/at/all"));
+    EventsFilter filter = refsPatterns("");
+    assertFalse(filter.refIsBlocked("refs/certainly/blocked"));
+    assertFalse(filter.refIsBlocked("refs/blocked/always"));
+    assertFalse(filter.refIsBlocked("refs/not/allowed/at/all"));
 
-    cfg = refsPatterns("refs/blocked/*", "^refs/.*/blocked", "refs/not/allowed/at/all");
-    assertTrue(cfg.refIsBlocked("refs/certainly/blocked"));
-    assertTrue(cfg.refIsBlocked("refs/blocked/always"));
-    assertTrue(cfg.refIsBlocked("refs/not/allowed/at/all"));
+    filter = refsPatterns("refs/blocked/*", "^refs/.*/blocked", "refs/not/allowed/at/all");
+    assertTrue(filter.refIsBlocked("refs/certainly/blocked"));
+    assertTrue(filter.refIsBlocked("refs/blocked/always"));
+    assertTrue(filter.refIsBlocked("refs/not/allowed/at/all"));
   }
 
   @Test
@@ -132,26 +132,25 @@
   public void configParsedCorrectly() throws Exception {
     Config cfg = new Config();
     cfg.fromText(
-        "[EventListeners]\n"
+        "[EventsFilter]\n"
             + "blockedRef = refs/heads/blocked\n"
             + "blockedRef = refs/heads/also/blocked\n"
             + "blockedProject = blocked/project\n"
             + "blockedProject = blocked/projects/*\n"
             + "enabled = False\n");
-    EventListenersConfig fromText = fromConfig(cfg);
+    EventsFilter fromText = fromConfig(cfg);
 
     assertTrue(fromText.refIsBlocked("refs/heads/blocked"));
     assertTrue(fromText.refIsBlocked("refs/heads/also/blocked"));
     assertTrue(fromText.projectIsBlocked("blocked/project"));
     assertTrue(fromText.projectIsBlocked("blocked/projects/something"));
-    assertFalse(fromText.isEnabled());
   }
 
-  private EventListenersConfig refsPatterns(String... patterns) {
-    return new EventListenersConfig(true, toCombinedMatcher(), toCombinedMatcher(patterns));
+  private EventsFilter refsPatterns(String... patterns) {
+    return new EventsFilter(toCombinedMatcher(), toCombinedMatcher(patterns), false);
   }
 
-  private EventListenersConfig projectsPatterns(String... patterns) {
-    return new EventListenersConfig(true, toCombinedMatcher(patterns), toCombinedMatcher());
+  private EventsFilter projectsPatterns(String... patterns) {
+    return new EventsFilter(toCombinedMatcher(patterns), toCombinedMatcher(), false);
   }
 }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/listeners/GerritEventListenersIT.java b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/listeners/GerritEventListenersIT.java
index 3d93b80..4d0d44d 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/listeners/GerritEventListenersIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/listeners/GerritEventListenersIT.java
@@ -14,7 +14,6 @@
 
 package com.googlesource.gerrit.plugins.eventseiffel.listeners;
 
-import static com.googlesource.gerrit.plugins.eventseiffel.config.EventListenersConfig.Provider.toCombinedMatcher;
 import static com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelEventType.SCC;
 import static com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelEventType.SCS;
 import static org.junit.Assert.assertEquals;
@@ -37,6 +36,7 @@
 import com.googlesource.gerrit.plugins.eventseiffel.TestEventPublisher;
 import com.googlesource.gerrit.plugins.eventseiffel.config.EiffelConfig;
 import com.googlesource.gerrit.plugins.eventseiffel.config.EventListenersConfig;
+import com.googlesource.gerrit.plugins.eventseiffel.config.EventsFilter;
 import com.googlesource.gerrit.plugins.eventseiffel.eiffel.ArtifactEventKey;
 import com.googlesource.gerrit.plugins.eventseiffel.eiffel.CompositionDefinedEventKey;
 import com.googlesource.gerrit.plugins.eventseiffel.eiffel.EventKey;
@@ -63,7 +63,8 @@
   @Before
   public void before() {
     publisher = plugin.getSysInjector().getInstance(TestEventPublisher.class);
-    TestEventListenerConfigProvider.reset();
+    TestEventsFilterProvider.reset();
+    TestEventListenersConfigProvider.reset();
   }
 
   @After
@@ -110,13 +111,13 @@
     UUID masterSccEventId = markMasterAsHandled(SCC);
     RevCommit previousMaster = getMaster();
 
-    TestEventListenerConfigProvider.disable();
+    TestEventListenersConfigProvider.disable();
 
     PushOneCommit.Result res1 = createChange();
     SourceChangeEventKey sccKey1 = toSccKey(res1);
     merge(res1);
 
-    TestEventListenerConfigProvider.enable();
+    TestEventListenersConfigProvider.enable();
 
     PushOneCommit.Result res2 = createChange();
     SourceChangeEventKey sccKey2 = toSccKey(res2);
@@ -162,7 +163,7 @@
 
   @Test
   public void noEventsCreatedWhenDisabled() throws Exception {
-    TestEventListenerConfigProvider.disable();
+    TestEventListenersConfigProvider.disable();
     PushOneCommit.Result res = createChange();
     publisher.assertNotPublished(
         toSccKey(res), "Patch-set created shouldn't result in event when disabled");
@@ -173,7 +174,7 @@
 
   @Test
   public void noEventsCreatedWhenRefIsBlocked() throws Exception {
-    TestEventListenerConfigProvider.setBlockedRefPatterns("refs/heads/master");
+    TestEventsFilterProvider.setBlockedRefPatterns("refs/heads/master");
     PushOneCommit.Result res = createChange();
     publisher.assertNotPublished(
         toSccKey(res), "Patch-set created shouldn't result in event when target ref is blocked");
@@ -184,7 +185,7 @@
 
   @Test
   public void noEventsCreatedWhenProjectIsBlocked() throws Exception {
-    TestEventListenerConfigProvider.setBlockedProjectPatterns(project.get());
+    TestEventsFilterProvider.setBlockedProjectPatterns(project.get());
     PushOneCommit.Result res = createChange();
     publisher.assertNotPublished(
         toSccKey(res), "Patch-set created shouldn't result in event when project is blocked");
@@ -195,8 +196,16 @@
 
   @Test
   public void lightweightTagCreatedResultsInEvent() throws Exception {
-    UUID parentEventId = markMasterAsHandled(SCS);
-    String tagName = createTagRef(false).substring(RefNames.REFS_TAGS.length());
+    tagCreatedResultsInEvent(false);
+  }
+
+  @Test
+  public void annotatedTagCreatedResultsInEvent() throws Exception {
+    tagCreatedResultsInEvent(true);
+  }
+
+  private void tagCreatedResultsInEvent(boolean annotated) throws Exception {
+    String tagName = createTagRef(annotated).substring(RefNames.REFS_TAGS.length());
 
     EventKey artcKey = ArtifactEventKey.create(tagPURL(project.get(), tagName));
     EiffelEvent artcEvent = publisher.getPublished(artcKey);
@@ -208,13 +217,34 @@
     assertNotNull("Publisher did not find CD event", cdEvent);
     assertEquals(EventKey.fromEvent(cdEvent), cdKey);
 
+    EventKey scsKey = SourceChangeEventKey.scsKey(project.get(), "master", getMaster());
+    EiffelEvent scsEvent = publisher.getPublished(scsKey);
+    assertNotNull("Publisher did not find SCS event", scsEvent);
+    assertEquals(EventKey.fromEvent(scsEvent), scsKey);
+
     assertArtcLinks(cdEvent.meta.id, artcEvent.links);
-    assertCdLinks(parentEventId, cdEvent.links);
+    assertCdLinks(scsEvent.meta.id, cdEvent.links);
   }
 
   @Test
-  public void annotatedTagCreatedResultsInEvent() throws Exception {
+  public void tagCreatedResultsInNoEventWhenBranchIsBlocked() throws Exception {
+    TestEventsFilterProvider.setBlockedRefPatterns("refs/heads/master");
+    String tagName = createTagRef(true).substring(RefNames.REFS_TAGS.length());
+
+    EventKey artcKey = ArtifactEventKey.create(tagPURL(project.get(), tagName));
+    publisher.assertNotPublished(artcKey, "Publisher found ARTC event");
+
+    EventKey cdKey = CompositionDefinedEventKey.create(tagCompositionName(project.get()), tagName);
+    publisher.assertNotPublished(cdKey, "Publisher found CD event");
+
+    EventKey scsKey = SourceChangeEventKey.scsKey(project.get(), "master", getMaster());
+    publisher.assertNotPublished(scsKey, "Publisher found SCS event");
+  }
+
+  @Test
+  public void tagCreatedResultsInNoEventWhenBranchIsBlockedSCSHandled() throws Exception {
     UUID parentEventId = markMasterAsHandled(SCS);
+    TestEventsFilterProvider.setBlockedRefPatterns("refs/heads/master");
     String tagName = createTagRef(true).substring(RefNames.REFS_TAGS.length());
 
     EventKey artcKey = ArtifactEventKey.create(tagPURL(project.get(), tagName));
@@ -270,10 +300,8 @@
     return originMaster;
   }
 
-  public static class TestEventListenerConfigProvider implements Provider<EventListenersConfig> {
+  public static class TestEventListenersConfigProvider implements Provider<EventListenersConfig> {
     private static boolean enabled;
-    private static String[] refPatterns;
-    private static String[] projectPatterns;
 
     static {
       reset();
@@ -281,15 +309,6 @@
 
     static void reset() {
       enabled = true;
-      refPatterns = projectPatterns = new String[0];
-    }
-
-    static void setBlockedRefPatterns(String... patterns) {
-      refPatterns = patterns;
-    }
-
-    static void setBlockedProjectPatterns(String... patterns) {
-      projectPatterns = patterns;
     }
 
     static void disable() {
@@ -302,8 +321,7 @@
 
     @Override
     public EventListenersConfig get() {
-      return new EventListenersConfig(
-          enabled, toCombinedMatcher(projectPatterns), toCombinedMatcher(refPatterns));
+      return new EventListenersConfig(enabled);
     }
   }
 
@@ -312,7 +330,8 @@
     @Override
     protected void configure() {
       install(new EiffelEventsTestModule());
-      bind(EventListenersConfig.class).toProvider(TestEventListenerConfigProvider.class);
+      bind(EventListenersConfig.class).toProvider(TestEventListenersConfigProvider.class);
+      bind(EventsFilter.class).toProvider(TestEventsFilterProvider.class);
       DynamicSet.bind(binder(), RevisionCreatedListener.class).to(PatchsetCreatedListener.class);
       DynamicSet.bind(binder(), GitReferenceUpdatedListener.class).to(RefUpdateListener.class);
       bind(EiffelConfig.class).toProvider(EiffelConfig.Provider.class).in(Scopes.SINGLETON);
diff --git a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParserIT.java b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParserIT.java
index 668bb8e..e11ee3d 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParserIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParserIT.java
@@ -24,6 +24,7 @@
 import com.google.gerrit.acceptance.TestPlugin;
 import com.google.gerrit.acceptance.UseLocalDisk;
 import com.google.gerrit.acceptance.config.GlobalPluginConfig;
+import com.google.gerrit.entities.BranchNameKey;
 import com.google.gerrit.extensions.common.AccountInfo;
 import com.googlesource.gerrit.plugins.eventseiffel.TestEventHub;
 import com.googlesource.gerrit.plugins.eventseiffel.TestEventStorage;
@@ -48,6 +49,7 @@
   public void setUp() {
     eventParser = plugin.getSysInjector().getInstance(EiffelEventParserImpl.class);
     TestEventHub.EVENTS.clear();
+    TestEventsFilterProvider.reset();
   }
 
   @Test
@@ -225,14 +227,66 @@
   }
 
   @Test
-  public void artcNotCreatedWhenMissingScs() throws Exception {
-    String tag =
+  public void artcQueued() throws Exception {
+    String ref =
+        createTagRef(getHead(repo(), "HEAD").getName(), true).substring(Constants.R_TAGS.length());
+
+    SourceChangeEventKey scc =
+        SourceChangeEventKey.sccKey(project.get(), getHead(), getHeadRevision());
+    SourceChangeEventKey scs = scc.copy(SCS);
+    ArtifactEventKey artc = ArtifactEventKey.create(tagPURL(project.get(), ref, "localhost"));
+    CompositionDefinedEventKey cd =
+        CompositionDefinedEventKey.create(tagCompositionName(project.get(), "localhost"), ref);
+
+    eventParser.createAndScheduleArtc(project.get(), ref, EPOCH_MILLIS, false);
+    assertEquals(4, TestEventHub.EVENTS.size());
+    assertCorrectEvent(0, scc);
+    assertCorrectEvent(1, scs);
+    assertCorrectEvent(2, cd);
+    assertCorrectEvent(3, artc);
+  }
+
+  @Test
+  public void artcQueuedBlockBranch() throws Exception {
+    TestEventsFilterProvider.setBlockedRefPatterns("refs/heads/master");
+    String ref =
         createTagRef(getHead(repo(), "HEAD").getName(), true).substring(Constants.R_TAGS.length());
     EventParsingException thrown =
         assertThrows(
             EventParsingException.class,
-            () -> eventParser.createAndScheduleArtc(project.get(), tag, EPOCH_MILLIS, false));
-    assertThat(thrown).hasMessageThat().contains("Failed to find SCS for");
+            () -> eventParser.createAndScheduleArtc(project.get(), ref, EPOCH_MILLIS, false));
+    assertThat(thrown).hasMessageThat().startsWith("Could not find any unblocked branch for SCS");
+  }
+
+  @Test
+  public void artcQueuedBlockBranchButReachableFromOtherBranch() throws Exception {
+    TestEventsFilterProvider.setBlockedRefPatterns("refs/heads/master");
+    createBranch(BranchNameKey.create(project, "other-branch"));
+    String ref =
+        createTagRef(getHead(repo(), "HEAD").getName(), true).substring(Constants.R_TAGS.length());
+
+    SourceChangeEventKey scc =
+        SourceChangeEventKey.sccKey(project.get(), "other-branch", getHeadRevision());
+    SourceChangeEventKey scs = scc.copy(SCS);
+    ArtifactEventKey artc = ArtifactEventKey.create(tagPURL(project.get(), ref, "localhost"));
+    CompositionDefinedEventKey cd =
+        CompositionDefinedEventKey.create(tagCompositionName(project.get(), "localhost"), ref);
+
+    eventParser.createAndScheduleArtc(project.get(), ref, EPOCH_MILLIS, false);
+    assertEquals(4, TestEventHub.EVENTS.size());
+    assertCorrectEvent(0, scc);
+    assertCorrectEvent(1, scs);
+    assertCorrectEvent(2, cd);
+    assertCorrectEvent(3, artc);
+  }
+
+  @Test
+  public void artcQueuedNoTagCreated() throws Exception {
+    EventParsingException thrown =
+        assertThrows(
+            EventParsingException.class,
+            () -> eventParser.createAndScheduleArtc(project.get(), TAG_NAME, EPOCH_MILLIS, false));
+    assertThat(thrown).hasMessageThat().startsWith("Cannot find tag");
   }
 
   @Test
@@ -246,6 +300,23 @@
   }
 
   @Test
+  public void annotatedTagArtcQueuedscsHandledLightWeightBlocked() throws Exception {
+    TestEventsFilterProvider.blockLightWeightTags();
+    assertArtcQueuedScsHandled(true);
+  }
+
+  @Test
+  public void lightWeightTagArtcQueuedscsHandledLightWeightBlocked() throws Exception {
+    setScsHandled();
+    TestEventsFilterProvider.blockLightWeightTags();
+    String ref =
+        createTagRef(getHead(repo(), "HEAD").getName(), false).substring(Constants.R_TAGS.length());
+
+    eventParser.createAndScheduleArtc(project.get(), ref, EPOCH_MILLIS, false);
+    assertEquals(0, TestEventHub.EVENTS.size());
+  }
+
+  @Test
   public void artcQueuedCdHandled() throws Exception {
     String ref = setCdHandled();
     eventParser.createAndScheduleArtc(project.get(), ref, EPOCH_MILLIS, false);
diff --git a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParsingTest.java b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParsingTest.java
index a0e8d8a..a606e14 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParsingTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/EiffelEventParsingTest.java
@@ -5,8 +5,6 @@
 import com.googlesource.gerrit.plugins.eventseiffel.EiffelEventsTest;
 import com.googlesource.gerrit.plugins.eventseiffel.TestEventHub;
 import com.googlesource.gerrit.plugins.eventseiffel.eiffel.EventKey;
-import com.googlesource.gerrit.plugins.eventseiffel.eiffel.SourceChangeEventKey;
-import com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelEventType;
 import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
 import org.junit.Ignore;
@@ -41,20 +39,4 @@
     EventKey actual = EventKey.fromEvent(TestEventHub.EVENTS.get(order));
     assertEquals(expected, actual);
   }
-
-  protected void setScsHandled() throws Exception {
-    markAsHandled(eventForHead(EiffelEventType.SCS), getHead(repo(), "HEAD"));
-  }
-
-  protected SourceChangeEventKey eventForHead(EiffelEventType type) throws Exception {
-    return SourceChangeEventKey.create(project.get(), getHead(), getHeadRevision(), type);
-  }
-
-  protected String getHead() throws Exception {
-    return gApi.projects().name(project.get()).head();
-  }
-
-  protected String getHeadRevision() throws Exception {
-    return getHead(repo(), "HEAD").getName();
-  }
 }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/ParsingTestModule.java b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/ParsingTestModule.java
index f3fe7e5..b01640d 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/ParsingTestModule.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/parsing/ParsingTestModule.java
@@ -4,12 +4,14 @@
 import com.google.inject.Scopes;
 import com.google.inject.Singleton;
 import com.googlesource.gerrit.plugins.eventseiffel.EiffelEventHub;
+import com.googlesource.gerrit.plugins.eventseiffel.EiffelEventsTest.TestEventsFilterProvider;
 import com.googlesource.gerrit.plugins.eventseiffel.TestEventHub;
 import com.googlesource.gerrit.plugins.eventseiffel.TestEventStorage;
 import com.googlesource.gerrit.plugins.eventseiffel.cache.EiffelEventIdCacheImpl;
 import com.googlesource.gerrit.plugins.eventseiffel.config.EiffelConfig;
 import com.googlesource.gerrit.plugins.eventseiffel.config.EventIdCacheConfig;
 import com.googlesource.gerrit.plugins.eventseiffel.config.EventMappingConfig;
+import com.googlesource.gerrit.plugins.eventseiffel.config.EventsFilter;
 import com.googlesource.gerrit.plugins.eventseiffel.eiffel.api.EventStorage;
 import com.googlesource.gerrit.plugins.eventseiffel.mapping.EiffelEventFactory;
 import com.googlesource.gerrit.plugins.eventseiffel.mapping.EiffelEventMapper;
@@ -21,6 +23,7 @@
   protected void configure() {
     bind(EventStorage.class).toProvider(TestEventStorage.Provider.class).in(Singleton.class);
     bind(EventIdCacheConfig.class).toProvider(EventIdCacheConfig.Provider.class);
+    bind(EventsFilter.class).toProvider(TestEventsFilterProvider.class);
     install(EiffelEventIdCacheImpl.module());
     bind(EiffelEventHub.class).to(TestEventHub.class);
     bind(EventMappingConfig.class).toProvider(EventMappingConfig.Provider.class);
diff --git a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/rest/EventsEiffelRestIT.java b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/rest/EventsEiffelRestIT.java
index cb77c91..e1c4598 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/rest/EventsEiffelRestIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/eventseiffel/rest/EventsEiffelRestIT.java
@@ -120,7 +120,6 @@
 
   @Test
   public void createArtcAsAdmin() throws Exception {
-    createScss("master", true).assertStatus(202);
     String tagName = createTagRef(true).substring(RefNames.REFS_TAGS.length());
     createArtcs(tagName, true).assertStatus(202);
     ArtifactEventKey artc = ArtifactEventKey.create(tagPURL(project.get(), tagName));
@@ -130,7 +129,6 @@
 
   @Test
   public void createArtcAsNonAdminForbidden() throws Exception {
-    createScss("master", true).assertStatus(202);
     String tagName = createTagRef(true).substring(RefNames.REFS_TAGS.length());
     createArtcs(tagName, false).assertStatus(403);
     publisher.assertNotPublished(