Index: includes/menu.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/menu.inc,v
retrieving revision 1.341
diff -u -p -r1.341 menu.inc
--- includes/menu.inc	24 Aug 2009 01:49:41 -0000	1.341
+++ includes/menu.inc	9 Sep 2009 15:29:59 -0000
@@ -1225,28 +1225,29 @@ function menu_tree_data(array $links, ar
  * the next menu link.
  */
 function _menu_tree_data(&$links, $parents, $depth) {
-  $done = FALSE;
   $tree = array();
-  while (!$done && $item = array_pop($links)) {
+  while ($item = array_pop($links)) {
     // We need to determine if we're on the path to root so we can later build
     // the correct active trail and breadcrumb.
     $item['in_active_trail'] = in_array($item['mlid'], $parents);
-    // Look ahead to the next link, but leave it on the array so it's available
-    // to other recursive function calls if we return or build a sub-tree.
-    $next = end($links);
     // Add the current link to the tree.
     $tree[$item['mlid']] = array(
       'link' => $item,
       'below' => array(),
     );
+    // Look ahead to the next link, but leave it on the array so it's available
+    // to other recursive function calls if we return or build a sub-tree.
+    $next = end($links);
     // Check whether the next link is the first in a new sub-tree.
     if ($next && $next['depth'] > $depth) {
       // Recursively call _menu_tree_data to build the sub-tree.
       $tree[$item['mlid']]['below'] = _menu_tree_data($links, $parents, $next['depth']);
+      // Fetch next link after filling the sub-tree.
+      $next = end($links);
     }
-    else {
-      // Determine if we should exit the loop and return.
-      $done = (!$next || $next['depth'] < $depth);
+    // Determine if we should exit the loop and return.
+    if (!$next || $next['depth'] < $depth) {
+      break;
     }
   }
   return $tree;
@@ -2036,8 +2037,9 @@ function _menu_navigation_links_rebuild(
           $item['plid'] = $existing_item['plid'];
         }
         else {
-          // If it moved, put it at the top level in the new menu.
-          $item['plid'] = 0;
+          // It moved to a new menu, so let menu_link_save() try to find a
+          // valid parent based on path.
+          unset($item['plid']);
         }
         $item['has_children'] = $existing_item['has_children'];
         $item['updated'] = $existing_item['updated'];
@@ -2164,7 +2166,6 @@ function _menu_delete_item($item, $force
  *   saved.
  */
 function menu_link_save(&$item) {
-
   drupal_alter('menu_link', $item);
 
   // This is the easiest way to handle the unique internal path '<front>',
@@ -2190,14 +2191,11 @@ function menu_link_save(&$item) {
     }
   }
 
-  if (isset($item['plid'])) {
-    if ($item['plid']) {
-      $parent = db_query("SELECT * FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $item['plid']))->fetchAssoc();
-    }
-    else {
-      // Don't bother with the query - mlid can never equal zero..
-      $parent = FALSE;
-    }
+  // The re-parenting process always needs to be invoked, since there is no
+  // guarantee that {menu_links} does not contain stale data caused by a
+  // re-parenting process that went wrong in a previous rebuild.
+  if (!empty($item['plid'])) {
+    $parent = db_query("SELECT * FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $item['plid']))->fetchAssoc();
   }
   else {
     $query = db_select('menu_links');
@@ -2206,10 +2204,9 @@ function menu_link_save(&$item) {
     if ($item['module'] == 'system') {
       $query->condition('module', 'system');
     }
-    else {
-      // If not derived from a router item, we respect the specified menu name.
-      $query->condition('menu_name', $item['menu_name']);
-    }
+    // Always respect the link's menu name. Inheritance for router items is
+    // ensured in _menu_router_build().
+    $query->condition('menu_name', $item['menu_name']);
 
     // Find the parent - it must be unique.
     $parent_path = $item['link_path'];
@@ -2224,6 +2221,7 @@ function menu_link_save(&$item) {
       }
     } while ($parent === FALSE && $parent_path);
   }
+  // If a parent link was found, derive its menu.
   if ($parent !== FALSE) {
     $item['menu_name'] = $parent['menu_name'];
   }
@@ -2675,6 +2673,15 @@ function _menu_router_build($callbacks) 
 
         $parent = $menu[$parent_path];
 
+        // Try to inherit the menu name from parent router items.
+        if (!isset($item['menu_name'])) {
+          if (!isset($parent['menu_name'])) {
+            $parent['menu_name'] = db_query("SELECT menu_name FROM {menu_links} WHERE router_path = :router_path", array(':router_path' => $parent_path))->fetchColumn();
+          }
+          if (!empty($parent['menu_name'])) {
+            $item['menu_name'] = $parent['menu_name'];
+          }
+        }
         if (!isset($item['tab_parent'])) {
           // Parent stores the parent of the path.
           $item['tab_parent'] = $parent_path;
Index: modules/simpletest/tests/menu.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/menu.test,v
retrieving revision 1.13
diff -u -p -r1.13 menu.test
--- modules/simpletest/tests/menu.test	14 Jul 2009 20:53:16 -0000	1.13
+++ modules/simpletest/tests/menu.test	9 Sep 2009 15:31:15 -0000
@@ -164,3 +164,59 @@ class MenuRebuildTestCase extends Drupal
   }
 
 }
+
+/**
+ * Menu tree data related tests.
+ */
+class MenuTreeDataTestCase extends DrupalUnitTestCase {
+  /**
+   * Dummy link structure acceptable for menu_tree_data().
+   */
+  var $links = array(
+    1 => array('mlid' => 1, 'depth' => 1),
+    2 => array('mlid' => 2, 'depth' => 1),
+    3 => array('mlid' => 3, 'depth' => 2),
+    4 => array('mlid' => 4, 'depth' => 3),
+    5 => array('mlid' => 5, 'depth' => 1),
+  );
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Menu tree generation',
+      'description' => 'Tests recursive menu tree generation functions.',
+      'group' => 'Menu',
+    );
+  }
+
+  /**
+   * Validate the generation of a proper menu tree hierarchy.
+   */
+  function testMenuTreeData() {
+    $tree = menu_tree_data($this->links);
+
+    // Validate that parent items #1, #2, and #5 exist on the root level.
+    $this->assertSameLink($this->links[1], $tree[1]['link'], t('Parent item #1 exists.'));
+    $this->assertSameLink($this->links[2], $tree[2]['link'], t('Parent item #2 exists.'));
+    $this->assertSameLink($this->links[5], $tree[5]['link'], t('Parent item #5 exists.'));
+
+    // Validate that child item #4 exists at the correct location in the hierarchy.
+    $this->assertSameLink($this->links[4], $tree[2]['below'][3]['below'][4]['link'], t('Child item #4 exists in the hierarchy.'));
+  }
+
+  /**
+   * Check that two menu links are the same by comparing the mlid.
+   *
+   * @param $link1
+   *   A menu link item.
+   * @param $link2
+   *   A menu link item.
+   * @param $message
+   *   The message to display along with the assertion.
+   * @return
+   *   TRUE if the assertion succeeded, FALSE otherwise.
+   */
+  protected function assertSameLink($link1, $link2, $message = '') {
+    return $this->assert($link1['mlid'] == $link2['mlid'], $message ? $message : t('First link is identical to second link'));
+  }
+}
+
