diff --git a/common/dconf-changeset.c b/common/dconf-changeset.c
index 35b02be..823cafe 100644
--- a/common/dconf-changeset.c
+++ b/common/dconf-changeset.c
@@ -770,33 +770,29 @@ dconf_changeset_change (DConfChangeset *changeset,
 }
 
 /**
- * dconf_changeset_diff:
+ * dconf_changeset_filter_changes:
  * @from: a database mode changeset
- * @to: a database mode changeset
+ * @changes: a changeset
  *
- * Compares to database-mode changesets and produces a changeset that
- * describes their differences.
+ * produces a minimal changeset that describes what would happen if
+ * @changes was applied to @to.
  *
- * If there is no difference, %NULL is returned.
+ * If there would be no changes, %NULL is returned
  *
- * Applying the returned changeset to @from using
- * dconf_changeset_change() will result in the two changesets being
- * equal.
+ * Applying the result to @from will yield the same result as applying
+ * @changes to @from
  *
- * Returns: (transfer full): the changes, or %NULL
- *
- * Since: 0.16
+ * Returns: the minimal changes, or %NULL
  */
 DConfChangeset *
-dconf_changeset_diff (DConfChangeset *from,
-                      DConfChangeset *to)
+dconf_changeset_filter_changes (DConfChangeset *from,
+                                DConfChangeset *changes)
 {
-  DConfChangeset *changeset = NULL;
+  DConfChangeset *result = NULL;
   GHashTableIter iter;
   gpointer key, val;
 
   g_return_val_if_fail (from->is_database, NULL);
-  g_return_val_if_fail (to->is_database, NULL);
 
   /* We make no attempt to do dir resets, but we could...
    *
@@ -815,20 +811,69 @@ dconf_changeset_diff (DConfChangeset *from,
    * Note: because 'from' and 'to' are database changesets we don't have
    * to worry about seeing NULL values or dirs.
    */
-  g_hash_table_iter_init (&iter, to->table);
+  g_hash_table_iter_init (&iter, changes->table);
   while (g_hash_table_iter_next (&iter, &key, &val))
     {
       GVariant *from_val = g_hash_table_lookup (from->table, key);
 
       if (from_val == NULL || !g_variant_equal (val, from_val))
         {
-          if (!changeset)
-            changeset = dconf_changeset_new ();
+          if (!result)
+            result = dconf_changeset_new ();
 
-          dconf_changeset_set (changeset, key, val);
+          dconf_changeset_set (result, key, val);
         }
     }
 
+  return result;
+}
+
+
+/**
+ * dconf_changeset_diff:
+ * @from: a database mode changeset
+ * @to: a database mode changeset
+ *
+ * Compares to database-mode changesets and produces a changeset that
+ * describes their differences.
+ *
+ * If there is no difference, %NULL is returned.
+ *
+ * Applying the returned changeset to @from using
+ * dconf_changeset_change() will result in the two changesets being
+ * equal.
+ *
+ * Returns: (transfer full): the changes, or %NULL
+ *
+ * Since: 0.16
+ */
+DConfChangeset *
+dconf_changeset_diff (DConfChangeset *from,
+                      DConfChangeset *to)
+{
+  /* We make no attempt to do dir resets, but we could...
+   *
+   * For now, we just reset each key individually.
+   *
+   * We create our list of changes in two steps:
+   *
+   *   - call dconf_changeset_filter_changes to find values from 'to'
+   *     which are not present in 'from' or hold different values to 'to'
+   *
+   *   - iterate the 'from' changeset and note any keys not present in
+   *     the 'to' changeset, recording resets for them
+   *
+   * This will cover all changes.
+   *
+   * Note: because 'from' and 'to' are database changesets we don't have
+   * to worry about seeing NULL values or dirs.
+   */
+  DConfChangeset *changeset = dconf_changeset_filter_changes (from, to);
+  GHashTableIter iter;
+  gpointer key, val;
+
+  g_return_val_if_fail (to->is_database, NULL);
+
   g_hash_table_iter_init (&iter, from->table);
   while (g_hash_table_iter_next (&iter, &key, &val))
     if (!g_hash_table_lookup (to->table, key))
diff --git a/common/dconf-changeset.h b/common/dconf-changeset.h
index b0ce450..6fe60f2 100644
--- a/common/dconf-changeset.h
+++ b/common/dconf-changeset.h
@@ -65,6 +65,9 @@ DConfChangeset *        dconf_changeset_deserialise                     (GVarian
 void                    dconf_changeset_change                          (DConfChangeset           *changeset,
                                                                          DConfChangeset           *changes);
 
+DConfChangeset *        dconf_changeset_filter_changes                  (DConfChangeset           *from,
+                                                                         DConfChangeset           *changes);
+
 DConfChangeset *        dconf_changeset_diff                            (DConfChangeset           *from,
                                                                          DConfChangeset           *to);
 
diff --git a/service/dconf-writer.c b/service/dconf-writer.c
index f85300c..ed8e3bd 100644
--- a/service/dconf-writer.c
+++ b/service/dconf-writer.c
@@ -129,21 +129,25 @@ dconf_writer_real_change (DConfWriter    *writer,
                           const gchar    *tag)
 {
   g_return_if_fail (writer->priv->uncommited_values != NULL);
+  DConfChangeset *effective_changeset = dconf_changeset_filter_changes (writer->priv->commited_values,
+                                                                        changeset);
 
-  dconf_changeset_change (writer->priv->uncommited_values, changeset);
-
-  if (tag)
+  if (effective_changeset)
     {
-      TaggedChange *change;
+      dconf_changeset_change (writer->priv->uncommited_values, effective_changeset);
+      if (tag)
+        {
+          TaggedChange *change;
 
-      change = g_slice_new (TaggedChange);
-      change->changeset = dconf_changeset_ref (changeset);
-      change->tag = g_strdup (tag);
+          change = g_slice_new (TaggedChange);
+          change->changeset = dconf_changeset_ref (effective_changeset);
+          change->tag = g_strdup (tag);
 
-      g_queue_push_tail (&writer->priv->uncommited_changes, change);
-    }
+          g_queue_push_tail (&writer->priv->uncommited_changes, change);
+        }
 
-  writer->priv->need_write = TRUE;
+      writer->priv->need_write = TRUE;
+    }
 }
 
 static gboolean
diff --git a/tests/changeset.c b/tests/changeset.c
index 5f046df..b6d3c97 100644
--- a/tests/changeset.c
+++ b/tests/changeset.c
@@ -572,6 +572,35 @@ test_diff (void)
     }
 }
 
+static void
+test_filter_changes (void)
+{
+  DConfChangeset *base, *changes;
+  gint i;
+  GVariant *value1 = g_variant_new_string ("value1");
+  GVariant *value2 = g_variant_new_string ("value2");
+
+  /* These tests are mostly negative, since dconf_changeset_filter_changes
+   * is called from dconf_changeset_diff */
+
+  base = dconf_changeset_new_database (NULL);
+  changes = dconf_changeset_new ();
+  /* an empty changeset would not change an empty database */
+  g_assert_null (dconf_changeset_filter_changes (base, changes));
+  dconf_changeset_set (base, "/a", value1);
+  /* an empty changeset would not change a database with values */
+  g_assert_null (dconf_changeset_filter_changes (base, changes));
+  dconf_changeset_set (changes, "/a", value1);
+  /* a changeset would not change a database with the same values */
+  g_assert_null (dconf_changeset_filter_changes (base, changes));
+  dconf_changeset_set (changes, "/a", value2);
+  /* a changeset would change a database with the same keys but different values */
+  g_assert_nonnull (dconf_changeset_filter_changes (base, changes));
+
+  dconf_changeset_unref (base);
+  dconf_changeset_unref (changes);
+}
+
 int
 main (int argc, char **argv)
 {
@@ -584,6 +613,7 @@ main (int argc, char **argv)
   g_test_add_func ("/changeset/serialiser", test_serialiser);
   g_test_add_func ("/changeset/change", test_change);
   g_test_add_func ("/changeset/diff", test_diff);
+  g_test_add_func ("/changeset/filter", test_filter_changes);
 
   return g_test_run ();
 }
