diff -rupN content.module content.module
--- content.module	2009-01-10 01:10:06.000000000 +0100
+++ content.module	2009-01-12 12:08:19.000000000 +0100
@@ -492,13 +492,13 @@ function theme_content_multiple_values($
     $items = array();
     foreach (element_children($element) as $key) {
       if ($key !== $element['#field_name'] .'_add_more') {
-        $items[] = &$element[$key];
+        $items[$element[$key]['#delta']] = &$element[$key];
       }
     }
-    usort($items, '_content_sort_items_value_helper');
+    uasort($items, '_content_sort_items_value_helper');
 
     // Add the items as table rows.
-    foreach ($items as $key => $item) {
+    foreach ($items as $delta => $item) {
       $item['_weight']['#attributes']['class'] = $order_class;
       $delta_element = drupal_render($item['_weight']);
       $cells = array(
@@ -710,22 +710,22 @@ function content_field($op, &$node, $fie
       }
 
       // Filter out empty values.
-      $items = content_set_empty($field, $items);
+      $items = content_set_empty($field, $items, FALSE);
 
       break;
 
     case 'view':
       $addition = array();
 
-      // Previewed nodes bypass the 'presave' op, so we need to some massaging.
-      if ($node->build_mode == NODE_BUILD_PREVIEW && content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) {
+      // Previewed nodes bypass the 'presave' op, so we need to do some massaging.
+      if ($node->build_mode == NODE_BUILD_PREVIEW) {
         if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) {
           // Reorder items to account for drag-n-drop reordering.
           $items = _content_sort_items($field, $items);
         }
 
         // Filter out empty values.
-        $items = content_set_empty($field, $items);
+        $items = content_set_empty($field, $items, FALSE);
       }
 
       // NODE_BUILD_NORMAL is 0, and ('whatever' == 0) is TRUE, so we need a ===.
@@ -763,10 +763,10 @@ function content_field($op, &$node, $fie
         );
 
         // Fill-in items.
-        foreach ($items as $delta => $item) {
+        foreach (array_keys($items) as $weight => $delta) {
           $element['items'][$delta] = array(
-            '#item' => $item,
-            '#weight' => $delta,
+            '#item' => $items[$delta],
+            '#weight' => $weight,
           );
         }
 
@@ -882,26 +882,54 @@ function content_field($op, &$node, $fie
  *
  * @param array $field
  * @param array $items
+ * @param boolean $keep_empty_items
  * @return array
  *   returns filtered and adjusted item array
  */
-function content_set_empty($field, $items) {
-  // Filter out empty values.
+function content_set_empty($field, $items, $keep_empty_items = TRUE) {
+  // Prepare an empty item.
+  $empty = array();
+  foreach (array_keys($field['columns']) as $column) {
+    $empty[$column] = NULL;
+  }
+
+  // Keep track of empty values.
   $filtered = array();
   $function = $field['module'] .'_content_is_empty';
   foreach ((array) $items as $delta => $item) {
+    // Get the item if it isn't empty.
     if (!$function($item, $field)) {
-      $filtered[] = $item;
+      $filtered[$delta] = $item;
+    }
+    // When the item is empty, keep it only if requested to.
+    else if ($keep_empty_items || (isset($item['_keep_empty']) && $item['_keep_empty'])) {
+      $filtered[$delta] = $empty;
     }
   }
 
   // Make sure we store the right number of 'empty' values.
-  $empty = array();
-  foreach (array_keys($field['columns']) as $column) {
-    $empty[$column] = NULL;
+  if ($field['multiple'] == 1) {
+    // Remove last element if empty for multi-valued fields.
+    $last_item = end($filtered);
+    if ($function($last_item, $field)) {
+      unset($filtered[key($filtered)]);
+    }
+  }
+  else {
+    $pad = $field['multiple'] > 1 ? (int)$field['multiple'] : 1;
+    $count = count($filtered);
+    if ($count < $pad) {
+      $delta = $count > 0 ? max(array_keys($filtered)) + 1 : 0;
+      for ($i = $count; $i < $pad; $i++) {
+        $filtered[$delta++] = $empty;
+      }
+    }
+  }
+
+  // Rebuild deltas for fields with multiple values.
+  if ($field['multiple'] >= 1) {
+    $filtered = array_values($filtered);
   }
-  $pad = $field['multiple'] > 1 ? $field['multiple'] : 1;
-  $filtered = array_pad($filtered, $pad, $empty);
 
   return $filtered;
 }
@@ -914,7 +942,7 @@ function _content_sort_items($field, $it
   if ($field['multiple'] >= 1 && isset($items[0]['_weight'])) {
     usort($items, '_content_sort_items_helper');
     foreach ($items as $delta => $item) {
-      if (is_array($items[$delta])) {
+      if (is_array($item) && isset($item['_weight'])) {
         unset($items[$delta]['_weight']);
       }
     }
@@ -995,7 +1023,19 @@ function content_storage($op, $node) {
             if (!isset($additions[$field_name])) {
               $additions[$field_name] = array();
             }
-            $additions[$field_name][] = $item;
+
+            // When the node is loaded we need to preserve any possible empty
+            // item to ensure deltas are in sync for fields in multigroup in
+            // case the node is loaded and saved programatically.
+            $item['_keep_empty'] = 1;
+
+            // Preserve deltas when loading items from database.
+            if (isset($row['delta'])) {
+              $additions[$field_name][$row['delta']] = $item;
+            }
+            else {
+              $additions[$field_name][] = $item;
+            }
           }
         }
       }
@@ -2415,8 +2455,7 @@ function content_extra_field_weight($typ
  * Do we want to eliminate them from the results?
  */
 function content_max_delta($field_name, $type_name = NULL) {
-  $fields = content_fields();
-  $field = $fields[$field_name];
+  $field = content_fields($field_name);
 
   // Non-multiple value fields don't use the delta column,
   // but could exist in multiple databases. If any value
diff -rupN includes/content.node_form.inc includes/content.node_form.inc
--- includes/content.node_form.inc	2008-12-27 23:22:56.000000000 +0100
+++ includes/content.node_form.inc	2008-12-30 16:55:08.000000000 +0100
@@ -151,21 +151,31 @@ function content_multiple_value_form(&$f
 
   switch ($field['multiple']) {
     case 0:
+      $deltas = array(0);
       $max = 0;
       break;
-    case 1:
-      $filled_items = content_set_empty($field, $items);
-      $current_item_count = isset($form_state['item_count'][$field_name])
-                            ? $form_state['item_count'][$field_name]
-                            : count($items);
-      // We always want at least one empty icon for the user to fill in.
-      $max = ($current_item_count > count($filled_items))
-              ? $current_item_count - 1
-              : $current_item_count;
 
+    case 1:
+      $deltas = array_keys(content_set_empty($field, $items));
+      $current_item_count = (isset($form_state['item_count'][$field_name]) ? $form_state['item_count'][$field_name] : count($deltas));
+      if ($current_item_count > 0) {
+        // We always want at least one empty item for the user to fill in.
+        $current_item_count++;
+      }
+      else {
+        // Default number of empty items when none is present.
+        $current_item_count = 2;
+      }
+      $max = (!empty($deltas) ? max($deltas) : -1);
+      while (count($deltas) < $current_item_count) {
+        $max++;
+        $deltas[] = $max;
+      }
       break;
+
     default:
       $max = $field['multiple'] - 1;
+      $deltas = array_keys(array_fill(0, $field['multiple'], 0));
       break;
   }
 
@@ -180,7 +190,7 @@ function content_multiple_value_form(&$f
   );
   $function = $field['widget']['module'] .'_widget';
 
-  for ($delta = 0; $delta <= $max; $delta++) {
+  foreach ($deltas as $delta) {
     if ($element = $function($form, $form_state, $field, $items, $delta)) {
       $defaults = array(
         '#title' => ($field['multiple'] >= 1) ? '' : $title,
@@ -206,6 +216,18 @@ function content_multiple_value_form(&$f
         );
       }
 
+      // Add a hidden element to allow the multigroup module tell us if content
+      // module needs to keep track of empty fields. Disabled by default.
+      // See content_set_empty().
+      if ($field['multiple'] == 1) {
+        // We name the element '_keep_empty' to avoid clashing with column names
+        // defined by field modules.
+        $element['_keep_empty'] = array(
+          '#type' => 'hidden',
+          '#value' => isset($items[$delta]['_keep_empty']) ? $items[$delta]['_keep_empty'] : 0,
+        );
+      }
+
       $form_element[$delta] = array_merge($element, $defaults);
     }
   }
@@ -260,7 +282,7 @@ function content_add_more_submit($form, 
 
   // Make the changes we want to the form state.
   if ($form_state['values'][$field_name][$field_name .'_add_more']) {
-    $form_state['item_count'][$field_name] = count($form_state['values'][$field_name]);
+    $form_state['item_count'][$field_name] = count($form_state['values'][$field_name]) - 1;
   }
 }
 
@@ -318,7 +340,8 @@ function content_add_more_js($type_name_
   $_POST[$field_name] = _content_sort_items($field, $_POST[$field_name]);
 
   // Build our new form element for the whole field, asking for one more element.
-  $form_state['item_count'] = array($field_name => count($_POST[$field_name]) + 1);
+  $delta = max(array_keys($_POST[$field_name])) + 1;
+  $form_state['item_count'] = array($field_name => count($_POST[$field_name]));
   $form_element = content_field_form($form, $form_state, $field);
   // Let other modules alter it.
   drupal_alter('form', $form_element, array(), 'content_add_more_js');
@@ -337,7 +360,6 @@ function content_add_more_js($type_name_
 
   // Build the new form against the incoming $_POST values so that we can
   // render the new element.
-  $delta = max(array_keys($_POST[$field_name])) + 1;
   $_POST[$field_name][$delta]['_weight'] = $delta;
   $form_state = array('submitted' => FALSE);
   $form += array(
