[ 
https://issues.apache.org/jira/browse/SLING-8104?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16694497#comment-16694497
 ] 

ASF GitHub Bot commented on SLING-8104:
---------------------------------------

bosschaert closed pull request #9: SLING-8104 Avoid magic when merging features
URL: https://github.com/apache/sling-org-apache-sling-feature/pull/9
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/src/main/java/org/apache/sling/feature/builder/BuilderContext.java 
b/src/main/java/org/apache/sling/feature/builder/BuilderContext.java
index 1dd100c..61acba5 100644
--- a/src/main/java/org/apache/sling/feature/builder/BuilderContext.java
+++ b/src/main/java/org/apache/sling/feature/builder/BuilderContext.java
@@ -29,10 +29,6 @@
  */
 public class BuilderContext {
 
-    enum ArtifactMergeAlgorithm {
-        LATEST, HIGHEST
-    }
-
     /** The required feature provider */
     private final FeatureProvider provider;
 
@@ -42,10 +38,10 @@
     private final Map<String, Map<String,String>> extensionConfiguration = new 
HashMap<>();
     private final List<MergeHandler> mergeExtensions = new ArrayList<>();
     private final List<PostProcessHandler> postProcessExtensions = new 
ArrayList<>();
+    private final List<String> artifactsOverrides = new ArrayList<>();
     private final Map<String,String> variables = new HashMap<>();
     private final Map<String,String> frameworkProperties = new HashMap<>();
 
-    private ArtifactMergeAlgorithm merge = ArtifactMergeAlgorithm.HIGHEST;
 
     /**
      * Create a new context
@@ -72,27 +68,38 @@ public BuilderContext setArtifactProvider(final 
ArtifactProvider ap) {
     }
 
     /**
-     * Add overwrites for the variables
+     * Add overrides for the variables
      *
-     * @param vars The overwrites
+     * @param vars The overrides
      * @return The builder context
      */
-    public BuilderContext addVariablesOverwrites(final Map<String,String> 
vars) {
+    public BuilderContext addVariablesOverrides(final Map<String,String> vars) 
{
         this.variables.putAll(vars);
         return this;
     }
 
     /**
-     * Add overwrites for the framework properties
+     * Add overrides for the framework properties
      *
-     * @param props The overwrites
+     * @param props The overrides
      * @return The builder context
      */
-    public BuilderContext addFrameworkPropertiesOverwrites(final 
Map<String,String> props) {
+    public BuilderContext addFrameworkPropertiesOverrides(final 
Map<String,String> props) {
         this.frameworkProperties.putAll(props);
         return this;
     }
 
+    /**
+     * Add overrides for artifact clashes
+     *
+     * @param overrides The overrides
+     * @return The builder context
+     */
+    public BuilderContext addArtifactsOverrides(final List<String> overrides) {
+        this.artifactsOverrides.addAll(overrides);
+        return this;
+    }
+
     /**
      * Add merge extensions
      *
@@ -115,17 +122,6 @@ public BuilderContext addPostProcessExtensions(final 
PostProcessHandler... exten
         return this;
     }
 
-    /**
-     * Set the merge algorithm
-     *
-     * @param alg The algorithm
-     * @return The builder context
-     */
-    public BuilderContext setMergeAlgorithm(final ArtifactMergeAlgorithm alg) {
-        this.merge = alg;
-        return this;
-    }
-
     /**
      * Set a handler configuration
      *
@@ -152,11 +148,15 @@ ArtifactProvider getArtifactProvider() {
         return this.artifactProvider;
     }
 
-    Map<String,String> getVariablesOverwrites() {
-        return  this.variables;
+    List<String> getArtifactOverrides() {
+        return this.artifactsOverrides;
+    }
+
+    Map<String,String> getVariablesOverrides() {
+        return this.variables;
     }
 
-    Map<String,String> getFrameworkPropertiesOverwrites() {
+    Map<String,String> getFrameworkPropertiesOverrides() {
         return this.frameworkProperties;
     }
 
@@ -168,10 +168,6 @@ FeatureProvider getFeatureProvider() {
         return this.provider;
     }
 
-    ArtifactMergeAlgorithm getMergeAlgorithm() {
-        return this.merge;
-    }
-
     /**
      * Get the list of merge extensions
      * @return The list of merge extensions
@@ -196,11 +192,11 @@ ArtifactMergeAlgorithm getMergeAlgorithm() {
     BuilderContext clone(final FeatureProvider featureProvider) {
         final BuilderContext ctx = new BuilderContext(featureProvider);
         ctx.setArtifactProvider(this.artifactProvider);
+        ctx.artifactsOverrides.addAll(this.artifactsOverrides);
         ctx.variables.putAll(this.variables);
         ctx.frameworkProperties.putAll(this.frameworkProperties);
         ctx.mergeExtensions.addAll(mergeExtensions);
         ctx.postProcessExtensions.addAll(postProcessExtensions);
-        ctx.merge = this.merge;
         return ctx;
     }
 }
diff --git a/src/main/java/org/apache/sling/feature/builder/BuilderUtil.java 
b/src/main/java/org/apache/sling/feature/builder/BuilderUtil.java
index 4dd5b82..6399487 100644
--- a/src/main/java/org/apache/sling/feature/builder/BuilderUtil.java
+++ b/src/main/java/org/apache/sling/feature/builder/BuilderUtil.java
@@ -16,9 +16,22 @@
  */
 package org.apache.sling.feature.builder;
 
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.Bundles;
+import org.apache.sling.feature.Configuration;
+import org.apache.sling.feature.Configurations;
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.FeatureConstants;
+import org.osgi.framework.Version;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+
 import java.io.StringReader;
 import java.io.StringWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.List;
@@ -35,21 +48,11 @@
 import javax.json.JsonValue.ValueType;
 import javax.json.JsonWriter;
 
-import org.apache.sling.feature.Artifact;
-import org.apache.sling.feature.Bundles;
-import org.apache.sling.feature.Configuration;
-import org.apache.sling.feature.Configurations;
-import org.apache.sling.feature.Extension;
-import org.apache.sling.feature.Feature;
-import org.apache.sling.feature.FeatureConstants;
-import org.apache.sling.feature.builder.BuilderContext.ArtifactMergeAlgorithm;
-import org.osgi.resource.Capability;
-import org.osgi.resource.Requirement;
-
 /**
  * Utility methods for the builders
  */
 class BuilderUtil {
+    static final String CATCHALL_OVERRIDE = "*:*:";
 
     static boolean contains(String key, Iterable<Map.Entry<String, String>> 
iterable) {
         if (iterable != null) {
@@ -73,7 +76,7 @@ static String get(String key, Iterable<Map.Entry<String, 
String>> iterable) {
         return null;
     }
 
-    private static void mergeWithContextOverwrite(String type, 
Map<String,String> target, Map<String,String> source, 
Iterable<Map.Entry<String,String>> context) {
+    private static void mergeWithContextOverride(String type, 
Map<String,String> target, Map<String,String> source, 
Iterable<Map.Entry<String,String>> context) {
         Map<String,String> result = new HashMap<>();
         for (Map.Entry<String, String> entry : target.entrySet()) {
             result.put(entry.getKey(), contains(entry.getKey(), context) ? 
get(entry.getKey(), context) : entry.getValue());
@@ -88,7 +91,7 @@ private static void mergeWithContextOverwrite(String type, 
Map<String,String> ta
                     String targetValue = target.get(entry.getKey());
                     if (targetValue != null) {
                         if (!value.equals(targetValue)) {
-                            throw new 
IllegalStateException(String.format("Can't merge %s '%s' defined twice (as '%s' 
v.s. '%s') and not overwritten.", type, entry.getKey(), value, targetValue));
+                            throw new 
IllegalStateException(String.format("Can't merge %s '%s' defined twice (as '%s' 
v.s. '%s') and not overridden.", type, entry.getKey(), value, targetValue));
                         }
                     }
                     else {
@@ -106,8 +109,8 @@ else if (!contains(entry.getKey(), target.entrySet())) {
 
     // variables
     static void mergeVariables(Map<String,String> target, Map<String,String> 
source, BuilderContext context) {
-        mergeWithContextOverwrite("Variable", target, source,
-                (null != context) ? 
context.getVariablesOverwrites().entrySet() : null);
+        mergeWithContextOverride("Variable", target, source,
+                (null != context) ? context.getVariablesOverrides().entrySet() 
: null);
     }
 
     /**
@@ -115,32 +118,40 @@ static void mergeVariables(Map<String,String> target, 
Map<String,String> source,
      *
      * @param target             The target bundles
      * @param source             The source bundles
-     * @param originatingFeature Optional, if set origin will be recorded
+     * @param sourceFeature Optional, if set origin will be recorded
      * @param artifactMergeAlg   Algorithm used to merge the artifacts
      */
     static void mergeBundles(final Bundles target,
         final Bundles source,
-        final Feature originatingFeature,
-            final ArtifactMergeAlgorithm artifactMergeAlg) {
+        final Feature sourceFeature,
+        final List<String> artifactOverrides) {
         for(final Map.Entry<Integer, List<Artifact>> entry : 
source.getBundlesByStartOrder().entrySet()) {
             for(final Artifact a : entry.getValue()) {
-                // version handling - use provided algorithm
-                boolean replace = true;
-                if (artifactMergeAlg == ArtifactMergeAlgorithm.HIGHEST) {
-                    final Artifact existing = target.getSame(a.getId());
-                    if ( existing != null && 
existing.getId().getOSGiVersion().compareTo(a.getId().getOSGiVersion()) > 0 ) {
-                        replace = false;
+                Artifact existing = target.getSame(a.getId());
+                List<Artifact> selectedArtifacts = null;
+                if (existing != null) {
+                    if (sourceFeature.getId().toMvnId().equals(
+                            
existing.getMetadata().get(FeatureConstants.ARTIFACT_ATTR_ORIGINAL_FEATURE))) {
+                        // If the source artifact came from the same feature, 
keep them side-by-side
+                        selectedArtifacts = Arrays.asList(existing, a);
+                    } else {
+                        selectedArtifacts = selectArtifactOverride(existing, 
a, artifactOverrides);
+                        while(target.removeSame(existing.getId())) {
+                            // Keep executing removeSame() which ignores the 
version until last one was removed
+                        }
                     }
+                } else {
+                    selectedArtifacts = Collections.singletonList(a);
                 }
-                if ( replace ) {
-                    target.removeSame(a.getId());
+
+                for (Artifact sa : selectedArtifacts) {
                     // create a copy to detach artifact from source
-                    final Artifact cp = a.copy(a.getId());
+                    final Artifact cp = sa.copy(sa.getId());
                     // Record the original feature of the bundle
-                    if (originatingFeature != null
-                            && 
a.getMetadata().get(FeatureConstants.ARTIFACT_ATTR_ORIGINAL_FEATURE) == null) {
+                    if (sourceFeature != null && source.contains(sa)
+                            && 
sa.getMetadata().get(FeatureConstants.ARTIFACT_ATTR_ORIGINAL_FEATURE) == null) {
                         
cp.getMetadata().put(FeatureConstants.ARTIFACT_ATTR_ORIGINAL_FEATURE,
-                                originatingFeature.getId().toMvnId());
+                                sourceFeature.getId().toMvnId());
                     }
                     target.add(cp);
                 }
@@ -148,6 +159,51 @@ static void mergeBundles(final Bundles target,
         }
     }
 
+    static List<Artifact> selectArtifactOverride(Artifact a1, Artifact a2, 
List<String> artifactOverrides) {
+        if (a1.getId().equals(a2.getId())) {
+            // They're the same so return one of them
+            return Collections.singletonList(a2);
+        }
+
+        String a1gid = a1.getId().getGroupId();
+        String a1aid = a1.getId().getArtifactId();
+        String a2gid = a2.getId().getGroupId();
+        String a2aid = a2.getId().getArtifactId();
+
+        if (!a1gid.equals(a2gid))
+            throw new IllegalStateException("Artifacts must have the same 
group ID: " + a1 + " and " + a2);
+        if (!a1aid.equals(a2aid))
+            throw new IllegalStateException("Artifacts must have the same 
artifact ID: " + a1 + " and " + a2);
+
+        String prefix = a1gid + ":" + a1aid + ":";
+        for (String o : artifactOverrides) {
+            if (o.startsWith(prefix) || o.startsWith(CATCHALL_OVERRIDE)) {
+                int idx = o.lastIndexOf(':');
+                if (idx <= 0 || o.length() <= idx)
+                    continue;
+
+                String rule = o.substring(idx+1).trim();
+
+                if ("ALL".equals(rule)) {
+                    return Arrays.asList(a1, a2);
+                } else if ("HIGHEST".equals(rule)) {
+                    Version a1v = a1.getId().getOSGiVersion();
+                    Version a2v = a2.getId().getOSGiVersion();
+                    return a1v.compareTo(a2v) > 0 ? 
Collections.singletonList(a1) : Collections.singletonList(a2);
+                } else if ("LATEST".equals(rule)) {
+                    return Collections.singletonList(a2);
+                } else if (a1.getId().getVersion().equals(rule)) {
+                    return Collections.singletonList(a1);
+                } else if (a2.getId().getVersion().equals(rule)) {
+                    return Collections.singletonList(a2);
+                }
+                throw new IllegalStateException("Override rule " + o + " not 
applicable to artifacts " + a1 + " and " + a2);
+            }
+        }
+        throw new IllegalStateException("Artifact override rule required to 
select between these two artifacts " +
+                a1 + " and " + a2);
+    }
+
     // configurations - merge / override
     static void mergeConfigurations(final Configurations target, final 
Configurations source, final Feature origin) {
         for(final Configuration cfg : source) {
@@ -181,8 +237,8 @@ static void mergeConfigurations(final Configurations 
target, final Configuration
 
     // framework properties (add/merge)
     static void mergeFrameworkProperties(final Map<String,String> target, 
final Map<String,String> source, BuilderContext context) {
-        mergeWithContextOverwrite("Property", target, source,
-                context != null ? 
context.getFrameworkPropertiesOverwrites().entrySet() : null);
+        mergeWithContextOverride("Property", target, source,
+                context != null ? 
context.getFrameworkPropertiesOverrides().entrySet() : null);
     }
 
     // requirements (add)
@@ -208,13 +264,13 @@ static void mergeCapabilities(final List<Capability> 
target, final List<Capabili
      *
      * @param target             The target extension
      * @param source             The source extension
-     * @param originatingFeature Optional, if set origin will be recorded for
-     *                           artifacts
+     * @param originatingFeature Optional, if set origin will be recorded for 
artifacts
      * @param artifactMergeAlg   The merge algorithm for artifacts
      */
     static void mergeExtensions(final Extension target,
-            final Extension source, final Feature originatingFeature,
-            final ArtifactMergeAlgorithm artifactMergeAlg) {
+            final Extension source,
+            final Feature sourceFeature,
+            final List<String> artifactOverrides) {
         switch ( target.getType() ) {
             case TEXT : // simply append
                 target.setText(target.getText() + "\n" + source.getText());
@@ -254,25 +310,32 @@ static void mergeExtensions(final Extension target,
                 break;
 
         case ARTIFACTS:
-            for (final Artifact a : source.getArtifacts()) {
-                // use artifactMergeAlg
-                boolean replace = true;
-                if (artifactMergeAlg == ArtifactMergeAlgorithm.HIGHEST) {
-                    final Artifact existing = 
target.getArtifacts().getSame(a.getId());
-                    if (existing != null
-                            && 
existing.getId().getOSGiVersion().compareTo(a.getId().getOSGiVersion()) > 0) {
-                        replace = false;
+            for(final Artifact a : source.getArtifacts()) {
+                Artifact existing = target.getArtifacts().getSame(a.getId());
+                List<Artifact> selectedArtifacts = null;
+                if (existing != null) {
+                    if (sourceFeature.getId().toMvnId().equals(
+                            
existing.getMetadata().get(FeatureConstants.ARTIFACT_ATTR_ORIGINAL_FEATURE))) {
+                        // If the source artifact came from the same feature, 
keep them side-by-side
+                        selectedArtifacts = Arrays.asList(existing, a);
+                    } else {
+                        selectedArtifacts = selectArtifactOverride(existing, 
a, artifactOverrides);
+                        
while(target.getArtifacts().removeSame(existing.getId())) {
+                            // Keep executing removeSame() which ignores the 
version until last one was removed
+                        }
                     }
+                } else {
+                    selectedArtifacts = Collections.singletonList(a);
                 }
-                if (replace) {
-                    target.getArtifacts().removeSame(a.getId());
+
+                for (Artifact sa : selectedArtifacts) {
                     // create a copy to detach artifact from source
-                    final Artifact cp = a.copy(a.getId());
-                    // Record the original feature of the artifact
-                    if (originatingFeature != null
-                            && 
a.getMetadata().get(FeatureConstants.ARTIFACT_ATTR_ORIGINAL_FEATURE) == null) {
+                    final Artifact cp = sa.copy(sa.getId());
+                    // Record the original feature of the bundle
+                    if (sourceFeature != null && 
source.getArtifacts().contains(sa)
+                            && 
sa.getMetadata().get(FeatureConstants.ARTIFACT_ATTR_ORIGINAL_FEATURE) == null) {
                         
cp.getMetadata().put(FeatureConstants.ARTIFACT_ATTR_ORIGINAL_FEATURE,
-                                originatingFeature.getId().toMvnId());
+                                sourceFeature.getId().toMvnId());
                     }
                     target.getArtifacts().add(cp);
                 }
@@ -284,7 +347,8 @@ static void mergeExtensions(final Extension target,
     // extensions (add/merge)
     static void mergeExtensions(final Feature target,
         final Feature source,
-            final ArtifactMergeAlgorithm artifactMergeAlg, final 
BuilderContext context, final boolean recordOrigin) {
+        final BuilderContext context,
+        final List<String> artifactOverrides) {
         for(final Extension ext : source.getExtensions()) {
             boolean found = false;
 
@@ -306,7 +370,7 @@ static void mergeExtensions(final Feature target,
                     }
                     if ( !handled ) {
                         // default merge
-                        mergeExtensions(current, ext, recordOrigin ? source : 
null, artifactMergeAlg);
+                        mergeExtensions(current, ext, source, 
artifactOverrides);
                     }
                 }
             }
diff --git a/src/main/java/org/apache/sling/feature/builder/FeatureBuilder.java 
b/src/main/java/org/apache/sling/feature/builder/FeatureBuilder.java
index f844443..f079b19 100644
--- a/src/main/java/org/apache/sling/feature/builder/FeatureBuilder.java
+++ b/src/main/java/org/apache/sling/feature/builder/FeatureBuilder.java
@@ -16,16 +16,6 @@
  */
 package org.apache.sling.feature.builder;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
 import org.apache.sling.feature.Artifact;
 import org.apache.sling.feature.ArtifactId;
 import org.apache.sling.feature.Configuration;
@@ -36,6 +26,17 @@
 import org.apache.sling.feature.Include;
 import org.osgi.framework.Version;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 public abstract class FeatureBuilder {
 
     /** Pattern for using variables. */
@@ -195,7 +196,7 @@ public static Feature assemble(
             }
             usedFeatures.add(assembled.getId());
 
-            merge(target, assembled, context, context.getMergeAlgorithm(), 
true);
+            merge(target, assembled, context, context.getArtifactOverrides());
         }
 
         // append feature list in extension
@@ -323,10 +324,14 @@ private static Feature internalAssemble(final 
List<String> processedFeatures,
             // process include instructions
             processInclude(includedFeature, i);
 
-            // and now merge
-            merge(result, includedFeature, context, 
BuilderContext.ArtifactMergeAlgorithm.LATEST, true);
+            // and now merge the included feature into the result. No 
overrides should be needed since the result is empty before
+            merge(result, includedFeature, context, Collections.emptyList());
+
+            // and merge the current feature over the included feature into 
the result
+            merge(result, feature, context, 
Collections.singletonList("*:*:LATEST"));
 
-            merge(result, feature, context, 
BuilderContext.ArtifactMergeAlgorithm.LATEST, false);
+            // set the origin of the artifacts and configurations included and 
part of the resulting feature back to null
+            resetOrigins(result, includedFeature);
         }
 
         result.setAssembled(true);
@@ -339,18 +344,14 @@ private static Feature internalAssemble(final 
List<String> processedFeatures,
     private static void merge(final Feature target,
             final Feature source,
             final BuilderContext context,
-            final BuilderContext.ArtifactMergeAlgorithm mergeAlg, final 
boolean recordOrigin) {
+            final List<String> artifactOverrides) {
         BuilderUtil.mergeVariables(target.getVariables(), 
source.getVariables(), context);
-        BuilderUtil.mergeBundles(target.getBundles(), source.getBundles(), 
recordOrigin ? source : null, mergeAlg);
-        BuilderUtil.mergeConfigurations(target.getConfigurations(), 
source.getConfigurations(),
-                recordOrigin ? source : null);
+        BuilderUtil.mergeBundles(target.getBundles(), source.getBundles(), 
source, artifactOverrides);
+        BuilderUtil.mergeConfigurations(target.getConfigurations(), 
source.getConfigurations(), source);
         BuilderUtil.mergeFrameworkProperties(target.getFrameworkProperties(), 
source.getFrameworkProperties(), context);
         BuilderUtil.mergeRequirements(target.getRequirements(), 
source.getRequirements());
         BuilderUtil.mergeCapabilities(target.getCapabilities(), 
source.getCapabilities());
-        BuilderUtil.mergeExtensions(target,
-                source,
-                mergeAlg,
-                context, recordOrigin);
+        BuilderUtil.mergeExtensions(target, source, context, 
artifactOverrides);
     }
 
     /**
@@ -466,4 +467,34 @@ private static void processInclude(final Feature feature, 
final Include include)
             }
         }
     }
+
+    // Set the origin of elements coming from an included feature to the 
target feature
+    private static void resetOrigins(Feature feature, Feature includedFeature) 
{
+        String currentID = feature.getId().toMvnId();
+        String includedID = includedFeature.getId().toMvnId();
+
+        // As the feature is a prototype, set the origins to the target where 
it's going to
+        for (Artifact a : feature.getBundles()) {
+            Map<String, String> md = a.getMetadata();
+            if 
(currentID.equals(md.get(FeatureConstants.ARTIFACT_ATTR_ORIGINAL_FEATURE))
+                    || 
includedID.equals(md.get(FeatureConstants.ARTIFACT_ATTR_ORIGINAL_FEATURE)))
+                md.remove(FeatureConstants.ARTIFACT_ATTR_ORIGINAL_FEATURE);
+        }
+        for (Configuration c : feature.getConfigurations()) {
+            Dictionary<String, Object> props = c.getProperties();
+            if 
(currentID.equals(props.get(Configuration.PROP_ORIGINAL__FEATURE))
+                    || 
includedID.equals(props.get(Configuration.PROP_ORIGINAL__FEATURE)))
+                props.remove(Configuration.PROP_ORIGINAL__FEATURE);
+        }
+        for (Extension e : feature.getExtensions()) {
+            if (ExtensionType.ARTIFACTS == e.getType()) {
+                for (Artifact a : e.getArtifacts()) {
+                    Map<String, String> md = a.getMetadata();
+                    if 
(currentID.equals(md.get(FeatureConstants.ARTIFACT_ATTR_ORIGINAL_FEATURE))
+                            || 
includedID.equals(md.get(FeatureConstants.ARTIFACT_ATTR_ORIGINAL_FEATURE)))
+                        
md.remove(FeatureConstants.ARTIFACT_ATTR_ORIGINAL_FEATURE);
+                }
+            }
+        }
+    }
 }
diff --git 
a/src/test/java/org/apache/sling/feature/builder/BuilderUtilTest.java 
b/src/test/java/org/apache/sling/feature/builder/BuilderUtilTest.java
index 5fd187d..23f3338 100644
--- a/src/test/java/org/apache/sling/feature/builder/BuilderUtilTest.java
+++ b/src/test/java/org/apache/sling/feature/builder/BuilderUtilTest.java
@@ -22,7 +22,6 @@
 import org.apache.sling.feature.Extension;
 import org.apache.sling.feature.ExtensionType;
 import org.apache.sling.feature.Feature;
-import org.apache.sling.feature.builder.BuilderContext.ArtifactMergeAlgorithm;
 import org.junit.Test;
 import org.mockito.Mockito;
 
@@ -30,6 +29,7 @@
 import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -105,7 +105,8 @@ private int assertContains(final List<Map.Entry<Integer, 
Artifact>> bundles,
 
         final Feature orgFeat = new Feature(new ArtifactId("gid", "aid", 
"123", null, null));
 
-        BuilderUtil.mergeBundles(target, source, orgFeat, 
ArtifactMergeAlgorithm.HIGHEST);
+        List<String> overrides = Arrays.asList("g:a:HIGHEST", "g:b:HIGHEST");
+        BuilderUtil.mergeBundles(target, source, orgFeat, overrides);
 
         final List<Map.Entry<Integer, Artifact>> result = getBundles(target);
         assertEquals(3, result.size());
@@ -133,7 +134,8 @@ private int assertContains(final List<Map.Entry<Integer, 
Artifact>> bundles,
 
         final Feature orgFeat = new Feature(new ArtifactId("gid", "aid", 
"123", null, null));
 
-        BuilderUtil.mergeBundles(target, source, orgFeat, 
ArtifactMergeAlgorithm.LATEST);
+        List<String> overrides = Arrays.asList("g:a:LATEST", "g:b:LATEST");
+        BuilderUtil.mergeBundles(target, source, orgFeat, overrides);
 
         final List<Map.Entry<Integer, Artifact>> result = getBundles(target);
         assertEquals(3, result.size());
@@ -151,7 +153,8 @@ private int assertContains(final List<Map.Entry<Integer, 
Artifact>> bundles,
         source.add(createBundle("g/a/1.1", 2));
 
         final Feature orgFeat = new Feature(new ArtifactId("gid", "aid", 
"123", null, null));
-        BuilderUtil.mergeBundles(target, source, orgFeat, 
ArtifactMergeAlgorithm.LATEST);
+        List<String> overrides = Arrays.asList("g:a:LATEST", "g:b:LATEST");
+        BuilderUtil.mergeBundles(target, source, orgFeat, overrides);
 
         final List<Map.Entry<Integer, Artifact>> result = getBundles(target);
         assertEquals(1, result.size());
@@ -171,7 +174,7 @@ private int assertContains(final List<Map.Entry<Integer, 
Artifact>> bundles,
         source.add(createBundle("g/f/2.5", 3));
 
         final Feature orgFeat = new Feature(new ArtifactId("gid", "aid", 
"123", null, null));
-        BuilderUtil.mergeBundles(target, source, orgFeat, 
ArtifactMergeAlgorithm.LATEST);
+        BuilderUtil.mergeBundles(target, source, orgFeat, new ArrayList<>());
 
         final List<Map.Entry<Integer, Artifact>> result = getBundles(target);
         assertEquals(6, result.size());
@@ -191,11 +194,11 @@ private int assertContains(final List<Map.Entry<Integer, 
Artifact>> bundles,
         source.add(createBundle("g/b/1.0", 1));
 
         final Feature orgFeat = new Feature(new ArtifactId("gid", "aid", 
"123", "c1", "slingfeature"));
-        BuilderUtil.mergeBundles(target, source, orgFeat, 
ArtifactMergeAlgorithm.LATEST);
+        BuilderUtil.mergeBundles(target, source, orgFeat, new ArrayList<>());
 
         final Bundles target2 = new Bundles();
         final Feature orgFeat2 = new Feature(new ArtifactId("g", "a", "1", 
null, null));
-        BuilderUtil.mergeBundles(target2, target, orgFeat2, 
ArtifactMergeAlgorithm.LATEST);
+        BuilderUtil.mergeBundles(target2, target, orgFeat2, new ArrayList<>());
 
         List<Entry<Integer, Artifact>> result = getBundles(target2);
         assertEquals(2, result.size());
@@ -214,7 +217,7 @@ private int assertContains(final List<Map.Entry<Integer, 
Artifact>> bundles,
 
         source.setJSON("[\"source1\",\"source2\"]");
 
-        BuilderUtil.mergeExtensions(target, source, null, 
ArtifactMergeAlgorithm.HIGHEST);
+        BuilderUtil.mergeExtensions(target, source, null, new ArrayList<>());
 
         assertEquals(target.getJSON(), 
"[\"target1\",\"target2\",\"source1\",\"source2\"]");
 
@@ -306,7 +309,7 @@ static void copyJsonObject(JsonObject obj, JsonGenerator 
gen, String ... exclusi
         Feature ft = new Feature(ArtifactId.fromMvnId("g:t:1"));
 
         assertEquals("Precondition", 0, ft.getExtensions().size());
-        BuilderUtil.mergeExtensions(ft, fs, ArtifactMergeAlgorithm.LATEST, 
ctx, true);
+        BuilderUtil.mergeExtensions(ft, fs, ctx, new ArrayList<>());
         assertEquals(1, ft.getExtensions().size());
 
         Extension actual = ft.getExtensions().get(0);
@@ -330,7 +333,7 @@ static void copyJsonObject(JsonObject obj, JsonGenerator 
gen, String ... exclusi
         ft.getExtensions().add(et);
 
         assertEquals("Precondition", 1, ft.getExtensions().size());
-        BuilderUtil.mergeExtensions(ft, fs, ArtifactMergeAlgorithm.LATEST, 
ctx, true);
+        BuilderUtil.mergeExtensions(ft, fs, ctx, new ArrayList<>());
         assertEquals(1, ft.getExtensions().size());
 
         Extension actual = ft.getExtensions().get(0);
@@ -357,7 +360,7 @@ static void copyJsonObject(JsonObject obj, JsonGenerator 
gen, String ... exclusi
         Feature ft = new Feature(ArtifactId.fromMvnId("g:t:1"));
 
         assertEquals("Precondition", 0, ft.getExtensions().size());
-        BuilderUtil.mergeExtensions(ft, fs, ArtifactMergeAlgorithm.LATEST, 
ctx, true);
+        BuilderUtil.mergeExtensions(ft, fs, ctx, new ArrayList<>());
         assertEquals(1, ft.getExtensions().size());
 
         Extension actual = ft.getExtensions().get(0);
@@ -382,7 +385,7 @@ static void copyJsonObject(JsonObject obj, JsonGenerator 
gen, String ... exclusi
         ft.getExtensions().add(et);
 
         assertEquals("Precondition", 1, ft.getExtensions().size());
-        BuilderUtil.mergeExtensions(ft, fs, ArtifactMergeAlgorithm.LATEST, 
ctx, true);
+        BuilderUtil.mergeExtensions(ft, fs, ctx, new ArrayList<>());
         assertEquals(1, ft.getExtensions().size());
 
         Extension actual = ft.getExtensions().get(0);
@@ -393,6 +396,80 @@ static void copyJsonObject(JsonObject obj, JsonGenerator 
gen, String ... exclusi
         assertEquals(er.readObject(), ar.readObject());
     }
 
+    @Test public void testSelectArtifactOverrideAll() {
+        Artifact a1 = new Artifact(ArtifactId.fromMvnId("gid:aid:1"));
+        Artifact a2 = new Artifact(ArtifactId.fromMvnId("gid:aid:2"));
+        List<String> overrides = Arrays.asList("gid:aid2:1", "gid:aid:ALL ");
+        assertEquals(Arrays.asList(a1, a2), 
BuilderUtil.selectArtifactOverride(a1, a2, overrides));
+    }
+
+    @Test public void testSelectArtifactOverrideIdenticalNeedsNoRule() {
+        Artifact a1 = new Artifact(ArtifactId.fromMvnId("gid:aid:1"));
+        Artifact a2 = new Artifact(ArtifactId.fromMvnId("gid:aid:1"));
+        assertEquals(Collections.singletonList(a1), 
BuilderUtil.selectArtifactOverride(a1, a2, Collections.emptyList()));
+    }
+
+    @Test public void testSelectArtifactOverride1() {
+        Artifact a1 = new Artifact(ArtifactId.fromMvnId("gid:aid:1"));
+        Artifact a2 = new Artifact(ArtifactId.fromMvnId("gid:aid:2"));
+        List<String> overrides = Collections.singletonList("gid:aid:1");
+        assertEquals(Collections.singletonList(a1), 
BuilderUtil.selectArtifactOverride(a1, a2, overrides));
+    }
+
+    @Test public void testSelectArtifactOverride2() {
+        Artifact a1 = new Artifact(ArtifactId.fromMvnId("gid:aid:1"));
+        Artifact a2 = new Artifact(ArtifactId.fromMvnId("gid:aid:2"));
+        List<String> overrides = Collections.singletonList("gid:aid:2");
+        assertEquals(Collections.singletonList(a2), 
BuilderUtil.selectArtifactOverride(a1, a2, overrides));
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void testSelectArtifactOverride3() {
+        Artifact a1 = new Artifact(ArtifactId.fromMvnId("gid:aid:1"));
+        Artifact a2 = new Artifact(ArtifactId.fromMvnId("gid:aid:2"));
+        List<String> overrides = Collections.singletonList("gid:aid:3");
+        BuilderUtil.selectArtifactOverride(a1, a2, overrides);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void testSelectArtifactOverrideDifferentGroupID() {
+        Artifact a1 = new Artifact(ArtifactId.fromMvnId("aid:aid:1"));
+        Artifact a2 = new Artifact(ArtifactId.fromMvnId("gid:aid:2"));
+        List<String> overrides = Collections.singletonList("gid:aid:2");
+        BuilderUtil.selectArtifactOverride(a1, a2, overrides);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void testSelectArtifactOverrideDifferentArtifactID() {
+        Artifact a1 = new Artifact(ArtifactId.fromMvnId("gid:gid:1"));
+        Artifact a2 = new Artifact(ArtifactId.fromMvnId("gid:aid:2"));
+        List<String> overrides = Collections.singletonList("gid:aid:2");
+        BuilderUtil.selectArtifactOverride(a1, a2, overrides);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void testSelectArtifactOverrideDifferentNoRule() {
+        Artifact a1 = new Artifact(ArtifactId.fromMvnId("gid:aid:1"));
+        Artifact a2 = new Artifact(ArtifactId.fromMvnId("gid:aid:2"));
+        BuilderUtil.selectArtifactOverride(a1, a2, Collections.emptyList());
+    }
+
+    @Test public void testSelectArtifactOverrideHigest() {
+        Artifact a1 = new Artifact(ArtifactId.fromMvnId("gid:aid:1.1"));
+        Artifact a2 = new Artifact(ArtifactId.fromMvnId("gid:aid:2.0.1"));
+        List<String> overrides = Collections.singletonList("gid:aid:HIGHEST");
+        assertEquals(Collections.singletonList(a2), 
BuilderUtil.selectArtifactOverride(a1, a2, overrides));
+        assertEquals(Collections.singletonList(a2), 
BuilderUtil.selectArtifactOverride(a2, a1, overrides));
+    }
+
+    @Test public void testSelectArtifactOverrideLatest() {
+        Artifact a1 = new Artifact(ArtifactId.fromMvnId("gid:aid:1.1"));
+        Artifact a2 = new Artifact(ArtifactId.fromMvnId("gid:aid:2.0.1"));
+        List<String> overrides = Collections.singletonList("gid:aid:LATEST");
+        assertEquals(Collections.singletonList(a2), 
BuilderUtil.selectArtifactOverride(a1, a2, overrides));
+        assertEquals(Collections.singletonList(a1), 
BuilderUtil.selectArtifactOverride(a2, a1, overrides));
+    }
+
     @SafeVarargs
     public static Artifact createBundle(final String id, final int startOrder, 
Map.Entry<String, String> ... metadata) {
         final Artifact a = new Artifact(ArtifactId.parse(id));
diff --git 
a/src/test/java/org/apache/sling/feature/builder/FeatureBuilderTest.java 
b/src/test/java/org/apache/sling/feature/builder/FeatureBuilderTest.java
index 91c6235..b81a7f2 100644
--- a/src/test/java/org/apache/sling/feature/builder/FeatureBuilderTest.java
+++ b/src/test/java/org/apache/sling/feature/builder/FeatureBuilderTest.java
@@ -16,23 +16,6 @@
  */
 package org.apache.sling.feature.builder;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.util.AbstractMap;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
 import org.apache.felix.utils.resource.CapabilityImpl;
 import org.apache.felix.utils.resource.RequirementImpl;
 import org.apache.sling.feature.Artifact;
@@ -47,6 +30,22 @@
 import org.osgi.resource.Capability;
 import org.osgi.resource.Requirement;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 public class FeatureBuilderTest {
 
     private static final Map<String, Feature> FEATURES = new HashMap<>();
@@ -71,6 +70,25 @@
         FEATURES.put(f1.getId().toMvnId(), f1);
     }
 
+    static {
+        final Feature f2 = new Feature(ArtifactId.parse("g/a/2"));
+
+        f2.getBundles().add(BuilderUtilTest.createBundle("group/testmulti/1", 
4));
+        f2.getBundles().add(BuilderUtilTest.createBundle("group/testmulti/2", 
8));
+        
f2.getBundles().add(BuilderUtilTest.createBundle("group/someart/1.2.3", 4));
+
+        FEATURES.put(f2.getId().toMvnId(), f2);
+    }
+
+    static {
+        final Feature f2 = new Feature(ArtifactId.parse("g/a/3"));
+
+        f2.getBundles().add(BuilderUtilTest.createBundle("group/testmulti/2", 
8));
+        
f2.getBundles().add(BuilderUtilTest.createBundle("group/someart/1.2.3", 4));
+
+        FEATURES.put(f2.getId().toMvnId(), f2);
+    }
+
     private final FeatureProvider provider = new FeatureProvider() {
 
         @Override
@@ -276,16 +294,14 @@ private void equals(final Feature expected, final Feature 
actuals) {
         final Artifact a1 = new 
Artifact(ArtifactId.parse("org.apache.sling/oak-server/1.0.0"));
         a1.getMetadata().put(Artifact.KEY_START_ORDER, "1");
         a1.getMetadata().put("hash", "4632463464363646436");
-        a1.getMetadata().put("org-feature", 
"org.apache.sling:test-feature:1.1");
         base.getBundles().add(a1);
-        Map.Entry<String, String> md = new AbstractMap.SimpleEntry<String, 
String>("org-feature", "org.apache.sling:test-feature:1.1");
-        
base.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/application-bundle/2.0.0",
 1, md));
-        
base.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/another-bundle/2.1.0",
 1, md));
-        
base.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/foo-xyz/1.2.3",
 2, md));
-        
base.getBundles().add(BuilderUtilTest.createBundle("group/testnewversion_low/1",
 5, md));
-        
base.getBundles().add(BuilderUtilTest.createBundle("group/testnewversion_high/5",
 5, md));
-        
base.getBundles().add(BuilderUtilTest.createBundle("group/testnewstartlevel/1", 
10, md));
-        
base.getBundles().add(BuilderUtilTest.createBundle("group/testnewstartlevelandversion/2",
 10, md));
+        
base.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/application-bundle/2.0.0",
 1));
+        
base.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/another-bundle/2.1.0",
 1));
+        
base.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/foo-xyz/1.2.3",
 2));
+        
base.getBundles().add(BuilderUtilTest.createBundle("group/testnewversion_low/1",
 5));
+        
base.getBundles().add(BuilderUtilTest.createBundle("group/testnewversion_high/5",
 5));
+        
base.getBundles().add(BuilderUtilTest.createBundle("group/testnewstartlevel/1", 
10));
+        
base.getBundles().add(BuilderUtilTest.createBundle("group/testnewstartlevelandversion/2",
 10));
 
         final Configuration co1 = new Configuration("my.pid");
         co1.getProperties().put("foo", 5L);
@@ -304,11 +320,9 @@ private void equals(final Feature expected, final Feature 
actuals) {
         result.getVariables().put("varx", "myvalx");
         result.setInclude(null);
         result.getFrameworkProperties().put("bar", "X");
-        Map.Entry<String, String> md2 = new AbstractMap.SimpleEntry<String, 
String>("org-feature", "g:a:1");
-        
result.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/foo-bar/4.5.6",
 3, md2));
+        
result.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/foo-bar/4.5.6",
 3));
         final Configuration co3 = new Configuration("org.apache.sling.foo");
         co3.getProperties().put("prop", "value");
-        co3.getProperties().put(Configuration.PROP_ORIGINAL__FEATURE, 
i1.getId().toMvnId());
         result.getConfigurations().add(co3);
 
         // assemble
@@ -318,6 +332,103 @@ private void equals(final Feature expected, final Feature 
actuals) {
         equals(result, assembled);
     }
 
+    @Test public void testSingleIncludeMultiVersion() {
+        Feature base = new Feature(ArtifactId.fromMvnId("g:tgtart:1"));
+        Include i1 = new Include(ArtifactId.fromMvnId("g:a:3"));
+        base.setInclude(i1);
+        base.getBundles().add(new Artifact(ArtifactId.fromMvnId("g:myart:1")));
+        base.getBundles().add(new 
Artifact(ArtifactId.fromMvnId("group:testmulti:1")));
+        base.getBundles().add(new 
Artifact(ArtifactId.fromMvnId("group:testmulti:3")));
+
+        BuilderContext builderContext = new BuilderContext(provider);
+        Feature assembled = FeatureBuilder.assemble(base, builderContext);
+
+        Feature result = new Feature(ArtifactId.parse("g:tgtart:1"));
+        Artifact b0 = new Artifact(ArtifactId.fromMvnId("g:myart:1"));
+        result.getBundles().add(b0);
+        Artifact b1 = new Artifact(ArtifactId.fromMvnId("group:testmulti:1"));
+        result.getBundles().add(b1);
+        Artifact b2 = new Artifact(ArtifactId.fromMvnId("group:testmulti:3"));
+        result.getBundles().add(b2);
+        Artifact b3 = new 
Artifact(ArtifactId.fromMvnId("group:someart:1.2.3"));
+        b3.setStartOrder(4);
+        result.getBundles().add(b3);
+
+        equals(result, assembled);
+    }
+
+    @Test public void testSingleIncludeMultiVersion2() {
+        Feature base = new Feature(ArtifactId.fromMvnId("g:tgtart:1"));
+        Include i1 = new Include(ArtifactId.fromMvnId("g:a:2"));
+        base.setInclude(i1);
+        base.getBundles().add(new Artifact(ArtifactId.fromMvnId("g:myart:1")));
+
+        BuilderContext builderContext = new BuilderContext(provider);
+        Feature assembled = FeatureBuilder.assemble(base, builderContext);
+
+        Feature result = new Feature(ArtifactId.parse("g:tgtart:1"));
+        Artifact b0 = new Artifact(ArtifactId.fromMvnId("g:myart:1"));
+        result.getBundles().add(b0);
+        Artifact b1 = new Artifact(ArtifactId.fromMvnId("group:testmulti:1"));
+        b1.setStartOrder(4);
+        result.getBundles().add(b1);
+        Artifact b2 = new Artifact(ArtifactId.fromMvnId("group:testmulti:2"));
+        b2.setStartOrder(8);
+        result.getBundles().add(b2);
+        Artifact b3 = new 
Artifact(ArtifactId.fromMvnId("group:someart:1.2.3"));
+        b3.setStartOrder(4);
+        result.getBundles().add(b3);
+
+        equals(result, assembled);
+    }
+
+    @Test public void testSingleIncludeMultiVersion3() {
+        Feature base = new Feature(ArtifactId.fromMvnId("g:tgtart:1"));
+        Include i1 = new Include(ArtifactId.fromMvnId("g:a:2"));
+        base.setInclude(i1);
+        base.getBundles().add(new Artifact(ArtifactId.fromMvnId("g:myart:1")));
+        base.getBundles().add(new 
Artifact(ArtifactId.fromMvnId("group:testmulti:1")));
+
+        BuilderContext builderContext = new BuilderContext(provider);
+        Feature assembled = FeatureBuilder.assemble(base, builderContext);
+
+        Feature result = new Feature(ArtifactId.parse("g:tgtart:1"));
+        Artifact b0 = new Artifact(ArtifactId.fromMvnId("g:myart:1"));
+        result.getBundles().add(b0);
+        Artifact b1 = new Artifact(ArtifactId.fromMvnId("group:testmulti:1"));
+        result.getBundles().add(b1);
+        Artifact b3 = new 
Artifact(ArtifactId.fromMvnId("group:someart:1.2.3"));
+        b3.setStartOrder(4);
+        result.getBundles().add(b3);
+
+        equals(result, assembled);
+    }
+
+    @Test public void testSingleIncludeMultiVersion4() {
+        Feature base = new Feature(ArtifactId.fromMvnId("g:tgtart:1"));
+        Include i1 = new Include(ArtifactId.fromMvnId("g:a:2"));
+        base.setInclude(i1);
+        base.getBundles().add(new Artifact(ArtifactId.fromMvnId("g:myart:1")));
+        base.getBundles().add(new 
Artifact(ArtifactId.fromMvnId("group:testmulti:1")));
+        base.getBundles().add(new 
Artifact(ArtifactId.fromMvnId("group:testmulti:3")));
+
+        BuilderContext builderContext = new BuilderContext(provider);
+        Feature assembled = FeatureBuilder.assemble(base, builderContext);
+
+        Feature result = new Feature(ArtifactId.parse("g:tgtart:1"));
+        Artifact b0 = new Artifact(ArtifactId.fromMvnId("g:myart:1"));
+        result.getBundles().add(b0);
+        Artifact b1 = new Artifact(ArtifactId.fromMvnId("group:testmulti:1"));
+        result.getBundles().add(b1);
+        Artifact b2 = new Artifact(ArtifactId.fromMvnId("group:testmulti:3"));
+        result.getBundles().add(b2);
+        Artifact b3 = new 
Artifact(ArtifactId.fromMvnId("group:someart:1.2.3"));
+        b3.setStartOrder(4);
+        result.getBundles().add(b3);
+
+        equals(result, assembled);
+    }
+
     @Test public void testDeduplicationInclude() throws Exception {
         final ArtifactId idA = ArtifactId.fromMvnId("g:a:1.0.0");
         final ArtifactId idB = ArtifactId.fromMvnId("g:b:1.0.0");
@@ -624,7 +735,7 @@ public Feature provide(ArtifactId id)
             {
                 return null;
             }
-                }).addVariablesOverwrites(override), aFeature, bFeature);
+                }).addVariablesOverrides(override), aFeature, bFeature);
 
         Map<String,String> vars = new HashMap<>();
         vars.putAll(kvMapA);
@@ -645,7 +756,7 @@ public Feature provide(ArtifactId id)
                 {
                     return null;
                 }
-                    }).addVariablesOverwrites(override), aFeature, bFeature);
+                    }).addVariablesOverrides(override), aFeature, bFeature);
             fail("Excepted merge exception");
         } catch (IllegalStateException expected) {}
 
@@ -658,7 +769,7 @@ public Feature provide(ArtifactId id)
             {
                 return null;
             }
-                }).addVariablesOverwrites(override), aFeature, bFeature);
+                }).addVariablesOverrides(override), aFeature, bFeature);
 
         vars = new HashMap<>();
         vars.putAll(kvMapA);
@@ -677,7 +788,7 @@ public Feature provide(ArtifactId id)
             {
                 return null;
             }
-                }).addVariablesOverwrites(override), aFeature, bFeature);
+                }).addVariablesOverrides(override), aFeature, bFeature);
 
         vars.put("var2", null);
         assertTrue(cFeature.getVariables().equals(vars));


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


> Avoid magic when merging features
> ---------------------------------
>
>                 Key: SLING-8104
>                 URL: https://issues.apache.org/jira/browse/SLING-8104
>             Project: Sling
>          Issue Type: Improvement
>          Components: Feature Model
>            Reporter: Carsten Ziegeler
>            Assignee: David Bosschaert
>            Priority: Blocker
>             Fix For: slingfeature-maven-plugin 1.0.0, Feature Model 0.8.0
>
>
> Currently when features are merged a simple algorithm is applied which just 
> picks the highest version based on the artifact version. However this version 
> might not have no meaning at all and might not really reflect what has 
> changed inside the bundle.
> Especially when there is a major version change, this approach seems to be 
> clearly wrong
> But in the end, picking a single version is magic.
> While the problem could probably be solved by using something like a resolver 
> and figure out if just one version is enough or if both versions are needed, 
> without a resolver there is no way to figure this out.
> Therefore we should provide a similar way as we do for variables at the 
> moment: if there is a clash the caller needs to provide context on what to 
> choose.



--
This message was sent by Atlassian JIRA
(v7.6.3#76005)

Reply via email to