? test.php
? includes/table.inc
Index: commands/core/upgrade.drush.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/drush/commands/core/upgrade.drush.inc,v
retrieving revision 1.25
diff -u -p -r1.25 upgrade.drush.inc
--- commands/core/upgrade.drush.inc	26 Jan 2011 04:32:36 -0000	1.25
+++ commands/core/upgrade.drush.inc	29 Jan 2011 06:55:33 -0000
@@ -20,7 +20,7 @@ function upgrade_drush_command() {
     'drush dependencies' => array('sql', 'pm', 'core'),
     'core' => array(6), // Add 7 once drush4 is released.
     'arguments' => array(
-      'target' => 'The name of a sitealias, which points to the destination site. root, uri, and db-url keys are required. See examples/aliases.drushrc.php for more information about creating a site alias.'),
+      'target' => 'The name of a sitealias, which points to the destination site. root and uri keys are required; db-url is recommended. See examples/aliases.drushrc.php for more information about creating a site alias.'),
     'examples' => array(
       'drush site-upgrade @onward' => 'Upgrade from the current site to the site specified by @onward alias.'
     ),
@@ -31,10 +31,19 @@ function upgrade_drush_command() {
       'db-su-pw' => 'DB password to use when dropping and creating the target database. Optional.',
       'no-cache' => 'Transfer a fresh database from source site. Otherwise, DB dump is re-used for 24 hours.',
       'no-updatedb' => 'Don\'t updatedb on the target site. Can be useful while debugging to run this separately.',
+      'no-modules' => 'Stop after updatedb; do not download and enable new versions of the site\'s modules.',
+      'no-enable' => 'Download and update, but do not enable the new version of the site\'s modules.',
     ),
     'aliases' => array('sup'),
     'topics' => array('docs-aliases'),
   );
+  $items['site-upgrade-modules'] = array(
+    'description' => dt('Download, enable, and run updatedb on all non-core modules after an upgrade.  Called automatically by site-upgrade.'),
+    'hidden' => TRUE,
+    'arguments' => array(
+      'modules' => 'The modules to download and enable.',
+    ),
+  );
   return $items;
 }
 
@@ -44,35 +53,76 @@ function upgrade_drush_command() {
 function upgrade_drush_help($section) {
   switch ($section) {
     case 'drush:site-upgrade':
-      return dt("Execute a major version upgrade for Drupal core and enabled contrib modules. Command will download next version of Drupal and all available contrib modules that have releases (if not already downloaded). It prepares a settings.php for the target site, and copies the prior version's database to the target site. Finally, updatedb is run. The intent is for developers to keep re-running this command until they are satisfied with the resulting site. Run this command from within your source site (D6). Note that this command uses pm-download and sql-sync internally so most options for those commands are valid here too.");
+      return dt("Execute a major version upgrade for Drupal core and enabled contrib modules. Command will download next version of Drupal and all available contrib modules that have releases. It prepares a settings.php for the target site, and copies the prior version's database to the target site. Finally, updatedb is run. The intent is for developers to keep re-running this command until they are satisfied with the resulting site. Run this command from within your source site (D6). Note that this command uses pm-download and sql-sync internally so most options for those commands are valid here too.");
   }
 }
 
+/**
+ * Do some sanity checks to make sure that we are ready to perform an upgrade, and
+ * that the command is being called with reasonable-looking parameters.
+ */
 function drush_upgrade_site_upgrade_validate($target_key = NULL) {
   if (empty($target_key)) {
-    return drush_set_error(dt('Missing argument: target'));
+    return drush_set_error('DRUSH_UPGRADE_NO_TARGET', dt('Missing argument: target'));
   }
 
   if (!$target_alias = drush_sitealias_get_record($target_key)) {
-    return drush_set_error('Site alias not found: @target-key. See example.drushrc.inc.', array('@target-key' => $target_key));
+    return drush_set_error('DRUSH_UPGRADE_NO_TARGET', dt('Site alias not found: @target-key. See example.drushrc.php.', array('@target-key' => $target_key)));
   }
 
   if (!file_exists(dirname($target_alias['root']))) {
-    drush_set_error('Site alias root not found: @root. See example.drushrc.inc.', array('@root' => dirname($target_alias['root'])));
+    drush_set_error('DRUSH_UPGRADE_NO_TARGET', dt('Parent directory for site alias root not found: @root; this folder must exist before running site-upgrade. See example.drushrc.php.', array('@root' => dirname($target_alias['root']))));
+  }
+
+  if (realpath($target_alias['root']) == realpath(DRUPAL_ROOT)) {
+    drush_set_error('DRUSH_UPGRADE_NO_TARGET', dt('Target site alias must have a different Drupal root directory than the source site.  Both are at @root.', array('@root' => $target_alias['root'])));
   }
+  
+  // TODO: we could warn if the current version of Drupal is not
+  // the recommended release (Upgrade.txt recommendation.)
 }
 
 /**
- * A drush command callback.
+ * Main command hook for site-upgrade.
+ *
+ * This runs bootstrapped to the SOURCE site.
  */
 function drush_upgrade_site_upgrade($target_key) {
+  
+  // PREPARE:  Find the target version and determine the non-core projects and enabled modules installed
+  
   $source_version = drush_drupal_major_version();
   $target_version = $source_version + 1;
   $target_alias = drush_sitealias_get_record($target_key);
+  if (empty($target_alias)) {
+    return drush_set_error('DRUSH_UPGRADE_NO_TARGET', dt("Could not find target site for upgrade: !target", array("!target" => $target_key)));
+  }
+
   $destination_core = $target_alias['root'];
 
-  // Fetch target core and place as per target alias root.
-  if (!file_exists($destination_core)) {
+  // Get a list of enabled non-core extensions
+  $result = drush_invoke_process_args('pm-list', array(), array('status'=>'enabled','no-core'=>TRUE, '#integrate' => FALSE));
+  $non_core_extensions = array_keys($result['object']);
+
+  // CONFIRM:  Ask the user before overwriting an exsiting site
+  
+  // Check to see what we should do if the target Drupal folder already exists
+  $selection = 'replace';
+  if (file_exists($destination_core) && !drush_get_option('no-updatedb')) {
+    $options = array(
+      'replace' => dt("Delete the existing site and start over"),
+      'reuse' => dt("Re-use the existing site, skipping the Drupal download and updatedb steps"),
+    );
+    $selection = drush_choice($options, dt("Drupal site already exists at !root.  Would you like to:", array('!root' => $destination_core)));
+    if (!$selection) {
+      return drush_user_abort();
+    }
+  }
+  
+  // STEP 1:  Download the next major version of Drupal
+  
+  if ($selection == 'replace') {
+    // Fetch target core and place as per target alias root.
     drush_set_option('destination', dirname($destination_core));
     drush_set_option('drupal-project-rename', basename($destination_core));
 
@@ -82,93 +132,212 @@ function drush_upgrade_site_upgrade($tar
     // TODO: get releases other than dev snapshot.
     drush_pm_download('drupal-'. $target_version . '.x');
     if (drush_get_error()) return -1; // Early exit if we see an error.
-  }
 
-  // Get enabled projects and their paths.
-  // TODO: D5 compatibility.
-  _update_cache_clear();
-  module_load_include('inc', 'update', 'update.compare');
-  $projects = update_get_projects();
-  // We already downloaded Drupal project.
-  unset($projects['drupal']);
-  $projects = _pm_get_project_path($projects, 'includes');
-
-  // Fetch and place each project into target.
-  // TODO: use non dev snapshot releases.
-  // TODO: Fix pm-download so this cancel hack is not needed.
-  drush_set_option('bootstrap_cancel', TRUE);
-  foreach ($projects as $key => $project) {
-    if (empty($project['path'])) {
-      $project['path'] = 'sites/all/modules';
-    }
-    $destination_module = $destination_core . '/' . $project['path'];
-    if (!file_exists($destination_module)) {
-      drush_set_option('destination', dirname($destination_module));
-      drush_pm_download($key . '-'. $target_version . '.x');
-    }
-  }
-  if (drush_get_error()) return -1; // Early exit if we see an error.
-
-  // Create sites subdirectory in target if needed.
-  $settings_source = conf_path() . '/settings.php';
-  $settings_destination = $destination_core . '/' . $settings_source;
-  $settings_destination_folder = dirname($settings_destination);
-  if (!file_exists($settings_destination_folder)) {
-    if (!drush_op('mkdir', $settings_destination_folder) && !drush_get_context('DRUSH_SIMULATE')) {
-      drush_set_error(dt('Failed to create directory @settings_destination', array('@settings_destination' => $settings_destination_folder)));
-      return;
+    // Check and see if there is a Drupal site at the target
+    if (!file_exists($destination_core . '/includes/bootstrap.inc')) {
+      return drush_set_error('DRUSH_UPGRADE_NO_DRUPAL', dt('Drupal could not be downloaded to the target directory, @root.  Move existing content out of the way first.', array('@root' => $target_alias['root'])));
     }
-  }
 
-  // Copy settings.php to target.
-  if (!file_exists($settings_destination)) {
-    if (!drush_op('copy', $settings_source, $settings_destination) && !drush_get_context('DRUSH_SIMULATE')) {
-      drush_set_error(dt('Failed to copy @source to  @dest', array('@source' => $settings_source, 'dest' => $settings_destination)));
-      return;
+    // Create sites subdirectory in target if needed.
+    $settings_source = conf_path() . '/settings.php';
+    $settings_destination = $destination_core . '/' . $settings_source;
+    $settings_destination_folder = dirname($settings_destination);
+    if (!file_exists($settings_destination_folder)) {
+      if (!drush_op('mkdir', $settings_destination_folder) && !drush_get_context('DRUSH_SIMULATE')) {
+        return drush_set_error(dt('Failed to create directory @settings_destination', array('@settings_destination' => $settings_destination_folder)));
+      }
+    }
+
+    // Copy settings.php to target.
+    if (!file_exists($settings_destination)) {
+      if (!drush_op('copy', $settings_source, $settings_destination) && !drush_get_context('DRUSH_SIMULATE')) {
+        return drush_set_error(dt('Failed to copy @source to  @dest', array('@source' => $settings_source, 'dest' => $settings_destination)));
+      }
+    }
+
+    // Append new $db_url with new DB name in target's settings.php.
+    drush_upgrade_fix_db_url($target_alias, $settings_destination);
+
+    // Copy source database to target database. The source DB is not changed.
+    // Always set 'common' at minimum. Sites that want other can create other key in drushrc.php.
+    if (!drush_get_option('structure-tables-key')) {
+      drush_set_option('structure-tables-key', 'common');
+    }
+    // Always blow away the target database so we start fresh.
+    drush_set_option('create-db', TRUE);
+    drush_include(DRUSH_BASE_PATH . '/commands/sql', 'sync.sql');
+    drush_invoke('sql-sync', '@self', $target_key);
+    if (drush_get_error()) return -1; // Early exit if we see an error.
+  
+    if (!empty($non_core_extensions)) {
+      // Make an alias record that uses the FILES from @self and the DATABASE from $target.
+      // Since we just did an sql-sync from @self to @target, we can use this hybrid specification
+      // to do manipulations on the target database before runing updatedb.  In brief, we are going
+      // to disable all non-core modules to prevent problems with updatedb.
+      $modify_site = array (
+        'root' => DRUPAL_ROOT,
+        'uri' => $target_alias['databases']['default']['default']['database'],
+      );
+      $modify_site_conf_path = dirname(conf_path()) . '/' . $modify_site['uri'];
+      $modify_site_settings = $modify_site_conf_path . '/settings.php';
+      if ((drush_mkdir($modify_site_conf_path) === FALSE) || drush_op('copy', $settings_destination, $modify_site_settings) !== TRUE) {
+        return drush_set_error('DRUSH_UPGRADE_COULD_NOT_DISABLE', dt("Could not create a temporary multisite "));
+      }
+      
+      // set theme back to garland per Upgrade.txt
+      $result = drush_invoke_sitealias_args($modify_site, 'variable-set', array('theme_default', 'garland'), array('always-set' => TRUE, '#integrate' => TRUE));
+      $result = drush_invoke_sitealias_args($modify_site, 'variable-set', array('admin_theme', 'garland'), array('always-set' => TRUE, '#integrate' => TRUE));
+      
+      // disable all non-core modules per Upgrade.txt
+      drush_log(dt("Disable non-core extensions !list", array('!list' => implode(",", $non_core_extensions))), 'ok');
+      $result = drush_invoke_sitealias_args($modify_site, 'pm-disable', $non_core_extensions, array('#integrate' => TRUE));
+    }
+    
+    // STEP 2:  Call updatedb for Drupal core
+    
+    if (drush_get_context('DRUSH_SIMULATE') || drush_get_option('no-updatedb')) {
+      // Updatedb does not support simulate so we don't call it.
+      drush_log(dt('Skipping updatedb for Drupal core on !target', array('!target' => $target_key)), 'ok');
     }
+    else {
+      // Run update.php in a subshell. It is run on @target site whereas this request was on @self.
+      drush_log(dt('About to perform updatedb for Drupal core on !target', array('!target' => $target_key)), 'ok');
+      $result = drush_do_site_command($target_alias, 'updatedb', array(), array('yes' => TRUE, '#interactive' => TRUE), TRUE);
+      // TODO: accurate error testing. This fails when it should not: if (drush_get_error()) return -1; // Exit if we see an error.
+      drush_log(dt('updatedb complete'), 'ok');
+    }
+  }
+  
+  // STEP 3: Download and re-enable the non-core modules
+  
+  if (!empty($non_core_extensions) && !drush_get_option('no-modules') && !drush_get_option('no-updatedb')) {
+    // Redispatch to site-upgrade-modules command, so that we will be
+    // bootstrapped to the target site.
+    $result = drush_invoke_sitealias_args($target_alias, 'site-upgrade-modules', $non_core_extensions, array('yes' => TRUE, '#interactive' => TRUE));
   }
+}
+
+/**
+ * Upgrade all of the non-core modules of the site being upgraded.
+ *
+ * This runs bootstrapped to the TARGET site, after the new version
+ * of Drupal has been downloaded, and after updatedb has been run
+ * for Drupal core.
+ */
+function drush_upgrade_site_upgrade_modules() {
+  $non_core_extensions = func_get_args();
 
-  // Append new $db_url with new DB name in target's settings.php.
-  drush_upgrade_fix_db_url($target_alias, $settings_destination);
-
-  // Copy source database to target database. The source DB is not changed.
-  // Always set 'common' at minimum. Sites that want other can create other key in drushrc.php.
-  if (!drush_get_option('structure-tables-key')) {
-    drush_set_option('structure-tables-key', 'common');
-  }
-  // Always blow away the target database so we start fresh.
-  drush_set_option('create-db', TRUE);
-  drush_include(DRUSH_BASE_PATH . '/commands/sql', 'sync.sql');
-  drush_invoke('sql_sync', '@self', $target_key);
-  if (drush_get_error()) return -1; // Early exit if we see an error.
-
-  if (drush_get_context('DRUSH_SIMULATE') || drush_get_option('no-updatedb')) {
-    // Updatedb does not support simulate so we don't call it.
-    drush_log(dt('About to perform updatedb on !target', array('!target' => $target_key)), 'ok');
+  drush_log(dt('Download modules: !modules', array('!modules' => implode(' ', $non_core_extensions))), 'ok');
+  drush_set_option('destination', NULL);
+  call_user_func_array('drush_pm_download', $non_core_extensions);
+  
+  // Order the modules that were downloaded based upon their dependencies.
+  // Enable modules with no dependencies before the modules that depend on them.
+  $extension_info = drush_get_extensions();
+  $ordered_extensions = array();
+  drush_upgrade_order_extensions($non_core_extensions, $ordered_extensions, $extension_info);
+  
+  $module_dir = DRUPAL_ROOT . '/sites/all/modules';
+  $module_holding_dir = DRUPAL_ROOT . '/sites/all/module-holding';
+  if (!is_dir($module_holding_dir)) {
+    drush_mkdir($module_holding_dir);
+  }
+  
+  // Move all of the modules to be enabled out of the way, so that
+  // we can process them one at a time.
+  foreach ($ordered_extensions as $extension => $project) {
+    if (is_dir($module_dir . '/' . $project)) {
+      drush_move_dir($module_dir . '/' . $project, $module_holding_dir . '/' . $project, TRUE);
+    }
   }
-  else {
-    // Run update.php in a subshell. It is run on @target site whereas this request was on @self.
-    drush_do_site_command($target_alias, 'updatedb', array(), array(), TRUE);
+  
+  // Now move the modules back one at a time, calling updatedb after each
+  // module is moved in.  Note that drush will perform the update action
+  // on all modules that have been downloaded to the modules directory,
+  // whether or not they have been enabled.  Also, we have multiple
+  // extensions per project; we'll only call updatedb once per module,
+  // though.
+  foreach ($ordered_extensions as $extension => $project) {
+    if (is_dir($module_holding_dir . '/' . $project)) {
+      drush_log(dt('Call updatedb for !project', array('!project' => $project)), 'ok');
+      drush_move_dir($module_holding_dir . '/' . $project, $module_dir . '/' . $project);
+      $result = drush_invoke_process_args('updatedb', array(), array('yes' => TRUE, '#interactive' => TRUE));
+    }
   }
+  
+  // Finally, enable the modules that site-upgrade previously disabled.
+  // We will set the option --resolve-dependencies to pick up new modules
+  // that may now be required; for example, views-7.x picked up a dependency
+  // on ctools that views-6.x did not have.
+  drush_set_option('resolve-dependencies', TRUE);
+  drush_invoke_args('pm-enable', array_keys($ordered_extensions));
 }
 
-// Replace db_url with DB name from target. updatedb will later append a DBTNG compatible version.
-function drush_upgrade_fix_db_url($target_alias, $settings_destination) {
+/**
+ * Order extensions so that those with no dependencies will come
+ * first in the output list, and those with dependencies will
+ * come after those extensions that they depend on.
+ *
+ * @param $extensions_to_order
+ *   A list of extensions; in the case of site-upgrade, these
+ *   will be all of the non-core modules.
+ * @param &$ordered_extensions
+ *   The extensions provided in $extensions_to_order will be
+ *   appended to this array.  The parameter should initially
+ *   start off empy.
+ * @param $extension_info
+ *   Information about all extensions, enabled or not, available
+ *   in the current Drupal site.  @see drush_get_extensions()
+ */
+function drush_upgrade_order_extensions($extensions_to_order, &$ordered_extensions, $extension_info) {
+  if (!empty($extensions_to_order)) {
+    foreach ($extensions_to_order as $extension) {
+      if (!array_key_exists($extension, $ordered_extensions)) {
+        if (array_key_exists($extension, $extension_info)) {
+          drush_upgrade_order_extensions($extension_info[$extension]->info['dependencies'], $ordered_extensions, $extension_info);
+          $ordered_extensions[$extension] = $extension_info[$extension]->info['project'];
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Replace db_url with DB name from target. updatedb will later append a DBTNG compatible version.
+ */
+function drush_upgrade_fix_db_url(&$target_alias, $settings_destination) {
   $old_url = $GLOBALS['db_url'];
   if (is_array($old_url)) {
     $old_url = $old_url['default'];
   }
+  $old_databases = $GLOBALS['databases'];
+  if (empty($old_databases)) {
+    $old_databases = drush_sitealias_convert_db_from_db_url($old_url);
+  }
+  
   $target_alias_databases = sitealias_get_databases_from_record($target_alias);
-  $new_url = substr($old_url, 0, strrpos(trim($old_url), '/')) . '/'. $target_alias_databases['default']['default']['database'];
+  $database_name = $target_alias_databases['default']['default']['database'];
+  if (empty($database_name)) {
+    $database_name = str_replace("@", "", $target_alias['name']) . "db";
+    drush_log(dt("No database name specified; defaulting to !dbname", array("!dbname" => $database_name)), 'notice');
+  }
 
   $append = "\n# Added by drush site-upgrade.";
   if (drush_drupal_major_version() <= 6) {
+    $new_url = substr($old_url, 0, strrpos(trim($old_url), '/')) . '/'. $database_name;
     $append .= "\n" . '$db_url = \'' . $new_url . '\';';
+    $databases = drush_sitealias_convert_db_from_db_url($new_url);
   }
   else {
     $databases = $GLOBALS['databases'];
     $databases['default']['default']['database'] = $target_alias_databases['default']['default']['database'];
     $append .= "\n" . '$databases = ' . var_export($databases, TRUE) . ';';
   }
+  // Caching the database record in the alias record allows sql-sync to work
+  // before updatedb is called. updatedb is what converts from a db_url to a 
+  // DBTNG array; this conversion is required by sql-sync.
+  drush_sitealias_cache_db_settings($target_alias, $databases);
+  
+  // Also append the new configuration options to the end of settings.php
   drush_op('file_put_contents', $settings_destination, $append, FILE_APPEND);
 }
Index: commands/core/drupal/environment_6.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/drush/commands/core/drupal/environment_6.inc,v
retrieving revision 1.11
diff -u -p -r1.11 environment_6.inc
--- commands/core/drupal/environment_6.inc	10 Sep 2010 19:58:06 -0000	1.11
+++ commands/core/drupal/environment_6.inc	29 Jan 2011 06:55:33 -0000
@@ -49,9 +49,8 @@ function drush_check_module_dependencies
           'message' => dt('Module !module cannot be enabled because it depends on the following modules which could not be found: !unmet_dependencies', array('!module' => $module, '!unmet_dependencies' => implode(',', $unmet_dependencies)))
       );
     }
-    else {
-      $status[$key]['dependencies'] = $dependencies;
-    }
+    $status[$key]['unmet-dependencies'] = $unmet_dependencies;
+    $status[$key]['dependencies'] = $dependencies;
   }
 
   return $status;
Index: commands/core/drupal/environment_7.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/drush/commands/core/drupal/environment_7.inc,v
retrieving revision 1.16
diff -u -p -r1.16 environment_7.inc
--- commands/core/drupal/environment_7.inc	15 Oct 2010 09:04:37 -0000	1.16
+++ commands/core/drupal/environment_7.inc	29 Jan 2011 06:55:33 -0000
@@ -52,6 +52,7 @@ function drush_check_module_dependencies
         }
       }
     }
+    $status[$key]['unmet-dependencies'] = $unmet_dependencies;
     $status[$key]['dependencies'] = array_keys($dependencies);
   }
 
Index: commands/pm/pm.drush.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/drush/commands/pm/pm.drush.inc,v
retrieving revision 1.208
diff -u -p -r1.208 pm.drush.inc
--- commands/pm/pm.drush.inc	25 Jan 2011 14:29:48 -0000	1.208
+++ commands/pm/pm.drush.inc	29 Jan 2011 06:55:35 -0000
@@ -465,21 +465,25 @@ function drush_pm_list() {
   uasort($extension_info, '_drush_pm_sort_extensions');
 
   $major_version = drush_drupal_major_version();
-  foreach ($extension_info as $extension) {
+  foreach ($extension_info as $key => $extension) {
     if (!in_array($extension->type, $type_filter)) {
+      unset($extension_info[$key]);
       continue;
     }
     $status = drush_get_extension_status($extension);
     if (!in_array($status, $status_filter)) {
+      unset($extension_info[$key]);
       continue;
     }
     if (($major_version >= 6) and (isset($extension->info['hidden']))) {
+      unset($extension_info[$key]);
       continue;
     }
     
     // filter out core if --no-core specified
     if (drush_get_option('no-core', FALSE)) {
       if ($extension->info['version'] == VERSION) {
+        unset($extension_info[$key]);
         continue;
       }
     }
@@ -487,6 +491,7 @@ function drush_pm_list() {
     // filter out non-core if --core specified
     if (drush_get_option('core', FALSE)) {
       if ($extension->info['version'] != VERSION) {
+        unset($extension_info[$key]);
         continue;
       }
     }
@@ -494,6 +499,7 @@ function drush_pm_list() {
     // filter by package
     if (!empty($package_filter)) {
       if (!in_array(strtolower($extension->info['package']), $package_filter)) {
+        unset($extension_info[$key]);
         continue;
       }
     }
@@ -529,6 +535,25 @@ function drush_pm_list() {
     // Newline-delimited list for use by other scripts. Set the --pipe option.
     drush_print_pipe($pipe);
   }
+  // Set the result for backend invoke
+  drush_backend_set_result($extension_info);
+}
+
+function drush_pm_find_project_from_extension($extension) {
+  $result = NULL;
+  
+  $extension_cache = drush_pm_get_extension_cache();
+  
+  if (!empty($extension_cache) && array_key_exists($extension, $extension_cache)) {
+    $result = $extension_cache[$extension];
+  }
+  // We could do a quick check to see if the extension name == the project name.
+  // True for some subset of the dependencies.
+  elseif ($extension == 'ctools') { // just ctools for now
+    $result = $extension;
+  }
+  
+  return $result;
 }
 
 /**
@@ -537,36 +562,71 @@ function drush_pm_list() {
 function drush_pm_enable() {
   $args = _convert_csv_to_array(func_get_args());
 
-  $extension_info = drush_get_extensions();
-
-  // Classify $args in themes, modules or unknown.
-  $modules = array();
-  $themes = array();
-  drush_pm_classify_extensions($args, $modules, $themes, $extension_info);
-  $extensions = array_merge($modules, $themes);
-  $unknown = array_diff($args, $extensions);
+  $recheck = TRUE;
+  while ($recheck) {
+    $recheck = FALSE;
+    $extension_info = drush_get_extensions();
 
-  // Discard and set an error for each unknown extension.
-  foreach ($unknown as $name) {
-    drush_log(dt('!extension was not found and will not be enabled.', array('!extension' => $name)), 'warning');
-  }
+    // Classify $args in themes, modules or unknown.
+    $modules = array();
+    $themes = array();
+    drush_pm_classify_extensions($args, $modules, $themes, $extension_info);
+    $extensions = array_merge($modules, $themes);
+    $unknown = array_diff($args, $extensions);
+
+    // Discard and set an error for each unknown extension.
+    foreach ($unknown as $name) {
+      drush_log(dt('!extension was not found and will not be enabled.', array('!extension' => $name)), 'warning');
+    }
+
+    // Discard already enabled extensions.
+    foreach ($extensions as $name) {
+      if ($extension_info[$name]->status) {
+        if ($extension_info[$name]->type == 'module') {
+          unset($modules[$name]);
+        }
+        else {
+          unset($themes[$name]);
+        }
+        drush_log(dt('!extension is already enabled.', array('!extension' => $name)), 'ok');
+      }
+    }
 
-  // Discard already enabled extensions.
-  foreach ($extensions as $name) {
-    if ($extension_info[$name]->status) {
-      if ($extension_info[$name]->type == 'module') {
-        unset($modules[$name]);
+    if (!empty($modules)) {
+      // Check module dependencies.
+      $dependencies = drush_check_module_dependencies($modules, $extension_info);
+      $still_unmet = array();
+      $resolved_projects = array();
+      foreach ($dependencies as $key => $info) {
+        if (!empty($info['unmet-dependencies'])) {
+          foreach ($info['unmet-dependencies'] as $unmet) {
+            if (TRUE) {
+              $project = drush_pm_find_project_from_extension($unmet);
+              if (!empty($project)) {
+                $resolved_projects[$project][] = $unmet;
+              }
+              else {
+                $stil_unmet[$unmet] = $unmet;
+              }
+            }
+          }
+        }
       }
-      else {
-        unset($themes[$name]);
+      if (!empty($resolved_projects)) {
+        $msgs = array();
+        foreach ($resolved_projects as $project => $unmet) {
+          $msgs[] = dt("!project for !unmet", array('!project' => $project, '!unmet' => implode(',', $unmet)));
+        }
+        if (drush_get_option('resolve-dependencies') || drush_confirm(dt("Would you like to download the following projects to resolve unmet dependencies: !list", array('!list' => implode(';', $msgs))))) {
+          $result = drush_invoke_process_args('pm-download', array_keys($resolved_projects), array('y' => TRUE));
+          // After downloading, refresh our info
+          $recheck = TRUE;
+        }
       }
-      drush_log(dt('!extension is already enabled.', array('!extension' => $name)), 'ok');
     }
   }
-
+  
   if (!empty($modules)) {
-    // Check module dependencies.
-    $dependencies = drush_check_module_dependencies($modules, $extension_info);
     $all_dependencies = array();
     $dependencies_ok = TRUE;
     foreach ($dependencies as $key => $info) {
@@ -2315,6 +2375,32 @@ function drush_pm_update_lock(&$projects
   return $locked_result;
 }
 
+function drush_pm_get_extension_cache() {
+  $extension_cache = array();
+  $cache_file = "/tmp/cache.inc";
+  
+  if (file_exists($cache_file)) {
+    include "/tmp/cache.inc";
+  }
+  return $extension_cache;
+}
+
+function drush_pm_put_extension_cache($extension_cache) {
+  $output = var_export($extension_cache, TRUE);
+  file_put_contents("/tmp/cache.inc", '<?php $extension_cache = ' . $output . ';');
+}
+
+function drush_pm_cache_project_extensions($project, $found) {
+  $extension_cache = drush_pm_get_extension_cache();
+  foreach($found as $extension) {
+    // Simple cache does not handle conflicts
+    // We could keep an array of projects, and count
+    // how many times each one has been seen...
+    $extension_cache[$extension] = $project['name'];
+  }
+  drush_pm_put_extension_cache($extension_cache);
+}
+
 /**
  * Print out all extensions (modules/themes/profiles) found in specified project.
  *
@@ -2350,7 +2436,7 @@ function drush_pm_extensions_in_project(
       foreach (drush_scan_directory($project['project_install_location'], "/.*\.profile$/", $nomask) as $filename => $info) {
         $found['profile'][] = $info->name;
       }
-    }
+    }    
     // Log results.
     $msg = "Project !project contains:\n";
     $args = array('!project' => $project['name']);
@@ -2389,6 +2475,8 @@ function drush_pm_extensions_in_project(
       drush_print(dt($msg, array('!project' => $project['name'], '!count' => count($found), '!type' => $project['project_type'], '!found' => implode(', ', $found))));
     }
     drush_print_pipe($found);
+    // Cache results.
+    drush_pm_cache_project_extensions($project, $found);
   }
 }
 
Index: includes/backend.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/drush/includes/backend.inc,v
retrieving revision 1.39
diff -u -p -r1.39 backend.inc
--- includes/backend.inc	27 Jan 2011 04:13:39 -0000	1.39
+++ includes/backend.inc	29 Jan 2011 06:55:36 -0000
@@ -192,11 +192,12 @@ function drush_backend_parse_output($str
 function _drush_backend_integrate($data) {
   if (is_array($data['log'])) {
     foreach($data['log'] as $log) {
+      $message = is_array($log['message']) ? implode("\n", $log['message']) : $log['message'];
       if (!is_null($log['error'])) {
-        drush_set_error($log['error'], $log['message']);
+        drush_set_error($log['error'], $message);
       }
       else {
-        drush_log($log['message'], $log['type'], $log['error']);
+        drush_log($message, $log['type']);
       }
     }
   }
@@ -316,7 +317,7 @@ function drush_backend_invoke($command, 
  */
 function drush_backend_invoke_args($command, $args, $data = array(), $method = 'GET', $integrate = TRUE, $drush_path = NULL, $hostname = NULL, $username = NULL, $ssh_options = NULL) {
   $cmd = _drush_backend_generate_command($command, $args, $data, $method, $drush_path, $hostname, $username, $ssh_options);
-  return _drush_backend_invoke($cmd, $data, $integrate);
+  return _drush_backend_invoke($cmd, $data, array_key_exists('#integrate', $data) ? $data['#integrate'] : $integrate);
 }
 
 /**
@@ -362,7 +363,7 @@ function drush_backend_invoke_args($comm
  */
 function drush_backend_invoke_sitealias($site_record, $command, $args, $data = array(), $method = 'GET', $integrate = TRUE) {
   $cmd = _drush_backend_generate_command_sitealias($site_record, $command, $args, $data, $method);
-  return _drush_backend_invoke($cmd, $data, $integrate);
+  return _drush_backend_invoke($cmd, $data, array_key_exists('#integrate', $data) ? $data['#integrate'] : $integrate);
 }
 
 /**
@@ -388,19 +389,25 @@ function drush_backend_invoke_sitealias(
  */
 function _drush_backend_invoke($cmd, $data = null, $integrate = TRUE) {
   drush_log(dt('Running: !cmd', array('!cmd' => $cmd)), 'command');
-  $proc = _drush_proc_open($cmd, $data);
-
-  if (($proc['code'] == DRUSH_APPLICATION_ERROR) && $integrate) {
-    drush_set_error('DRUSH_APPLICATION_ERROR', dt("The external command could not be executed due to an application error."));
+  if (array_key_exists('#interactive', $data)) {
+    drush_log(dt("executing !cmd", array('!cmd', $cmd)));
+    drush_op_system($cmd);
   }
+  else {
+    $proc = _drush_proc_open($cmd, $data);
 
-  if ($proc['output']) {
-    $values = drush_backend_parse_output($proc['output'], $integrate);
-    if (is_array($values)) {
-      return $values;
+    if (($proc['code'] == DRUSH_APPLICATION_ERROR) && $integrate) {
+      drush_set_error('DRUSH_APPLICATION_ERROR', dt("The external command could not be executed due to an application error."));
     }
-    else {
-      return drush_set_error('DRUSH_FRAMEWORK_ERROR', dt("The command could not be executed successfully (returned: !return, code: %code)", array("!return" => $proc['output'], "%code" =>  $proc['code'])));
+
+    if ($proc['output']) {
+      $values = drush_backend_parse_output($proc['output'], $integrate);
+      if (is_array($values)) {
+        return $values;
+      }
+      else {
+        return drush_set_error('DRUSH_FRAMEWORK_ERROR', dt("The command could not be executed successfully (returned: !return, code: %code)", array("!return" => $proc['output'], "%code" =>  $proc['code'])));
+      }
     }
   }
   return FALSE;
@@ -520,15 +527,19 @@ function _drush_backend_generate_command
   foreach ($args as $arg) {
     $command .= ' ' . escapeshellarg($arg);
   }
+  $interactive = ' ' . (empty($data['#interactive']) ? '' : ' > `tty`') . ' 2>&1';
   // @TODO: Implement proper multi platform / multi server support.
-  $cmd = escapeshellcmd($drush_path) . " " . $option_str . " " . $command . " --backend";
+  $cmd = escapeshellcmd($drush_path) . " " . $option_str . " " . $command . (empty($data['#interactive']) ? " --backend" : "");
 
   if (!is_null($hostname)) {
     $username = (!is_null($username)) ? $username : get_current_user();
     $ssh_options = (!is_null($ssh_options)) ? $ssh_options : drush_get_option('ssh-options', "-o PasswordAuthentication=no");
-    $cmd = "ssh " . $ssh_options . " " . escapeshellarg($username) . "@" . escapeshellarg($hostname) . " " . escapeshellarg($cmd);
+    $cmd = "ssh " . $ssh_options . " " . escapeshellarg($username) . "@" . escapeshellarg($hostname) . " " . escapeshellarg($cmd . ' 2>&1') . $interactive;
   }
-
+  else {
+    $cmd .= $interactive;
+  }
+  
   return $cmd;
 }
 
@@ -576,7 +587,7 @@ function _drush_backend_argument_string(
       if (is_numeric($key)) {
         $args[$key] = $value;
       }
-      else {
+      elseif (substr($key,0,1) != '#') {
         $options[$key] = $value;
       }
     }
Index: includes/command.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/drush/includes/command.inc,v
retrieving revision 1.107
diff -u -p -r1.107 command.inc
--- includes/command.inc	26 Jan 2011 04:14:23 -0000	1.107
+++ includes/command.inc	29 Jan 2011 06:55:37 -0000
@@ -55,7 +55,17 @@ function drush_invoke($command) {
   drush_command_include($command);
   $args = func_get_args();
   array_shift($args);
+  
+  return drush_invoke_args($command, $args);
+}
 
+/**
+ * As drush_invoke, but args are passed in as an array
+ * rather than as individual function parameters.
+ *
+ * @see drush_invoke()
+ */
+function drush_invoke_args($command, $args) {
   // Generate the base name for the hook by converting all
   // dashes in the command name to underscores.
   $hook = str_replace("-", "_", $command);
Index: includes/sitealias.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/drush/includes/sitealias.inc,v
retrieving revision 1.76
diff -u -p -r1.76 sitealias.inc
--- includes/sitealias.inc	25 Jan 2011 21:50:09 -0000	1.76
+++ includes/sitealias.inc	29 Jan 2011 06:55:39 -0000
@@ -638,29 +638,36 @@ function drush_sitealias_add_db_settings
   // then we'll need to look one up
   if (!isset($alias_record['db-url']) && !isset($alias_record['databases']) && !isset($alias_record['site-list'])) {
     $values = drush_invoke_sitealias_args($alias_record, "sql-conf", array(), array('all' => TRUE));
-    if (isset($values['object'])) {
-      $alias_record['databases'] = $values['object'];
+    if (isset($values) && ($values['error_status'] == 0)) {
       $altered_record = TRUE;
       // If there are any special settings in the '@self' record returned by drush_invoke_sitealias_args,
       // then add those into our altered record as well
       if (array_key_exists('self', $values)) {
         $alias_record = array_merge($values['self'], $alias_record);
       }
-      // If the name is set, then re-cache the record after we fetch the databases
-      if (array_key_exists('name', $alias_record)) {
-        $all_site_aliases =& drush_get_context('site-aliases');
-        $all_site_aliases[$alias_record['name']] = $alias_record;
-	// Check and see if this record is a copy of 'self'
-	if (($alias_record['name'] != 'self') && array_key_exists('@self', $all_site_aliases) && ($all_site_aliases['@self']['name'] == $alias_record['name'])) {
-          $all_site_aliases['@self'] = $alias_record;
-	}
-      }
+      drush_sitealias_cache_db_settings($alias_record, $values['object']);
     }
   }
 
   return $altered_record;
 }
 
+function drush_sitealias_cache_db_settings(&$alias_record, $databases) {
+  if (!empty($databases)) {
+    $alias_record['databases'] = $databases;
+  }
+
+  // If the name is set, then re-cache the record after we fetch the databases
+  if (array_key_exists('name', $alias_record)) {
+    $all_site_aliases =& drush_get_context('site-aliases');
+    $all_site_aliases[$alias_record['name']] = $alias_record;
+    // Check and see if this record is a copy of 'self'
+    if (($alias_record['name'] != 'self') && array_key_exists('@self', $all_site_aliases) && ($all_site_aliases['@self']['name'] == $alias_record['name'])) {
+      $all_site_aliases['@self'] = $alias_record;
+    }
+  }
+}
+
 /**
  * Check to see if we have already bootstrapped to a site.
  */
@@ -1048,11 +1055,11 @@ function _drush_find_local_sites_at_root
     else {
       $bootstrap_files = drush_scan_directory($base_path, '/' . basename(DRUSH_DRUPAL_BOOTSTRAP) . '/' , array('.', '..', 'CVS'), 0, drush_get_option('search-depth', $search_depth) + 1, 'filename', 1);
       foreach ($bootstrap_files as $one_bootstrap => $info) {
-	$includes_dir = dirname($one_bootstrap);
-	if (basename($includes_dir) == basename(dirname(DRUSH_DRUPAL_BOOTSTRAP))) {
+        $includes_dir = dirname($one_bootstrap);
+        if (basename($includes_dir) == basename(dirname(DRUSH_DRUPAL_BOOTSTRAP))) {
           $drupal_root = dirname($includes_dir);
           $site_list = array_merge(_drush_find_local_sites_in_sites_folder($drupal_root), $site_list);
-	}
+        }
       }
     }
   }
