? includes/table.inc
Index: drush.php
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/drush/drush.php,v
retrieving revision 1.79
diff -u -p -r1.79 drush.php
--- drush.php	5 Jan 2010 23:09:51 -0000	1.79
+++ drush.php	15 Jan 2010 00:42:50 -0000
@@ -128,62 +128,6 @@ function drush_main() {
 }
 
 /**
- * Process commands that are executed on a remote drush instance.
- *
- * @return
- *   TRUE if the command was handled remotely.
- */
-function drush_remote_command() {
-  // The command will be executed remotely if the --remote-host flag
-  // is set; note that if a site alias is provided on the command line,
-  // and the site alias references a remote server, then the --remote-host
-  // option will be set when the site alias is processed.
-  // @see _drush_process_site_alias
-  $remote_host = drush_get_option('remote-host');
-  if (isset($remote_host)) {
-
-    $args = drush_get_arguments();
-    $command = array_shift($args);
-    $data = drush_get_context('options');
-
-    // Most command options are forwarded on to the remote
-    // server; however, we will clear certain flags such as
-    // -v, -d and -i from the 'options' context (defaults, and
-    // options passed in on the command line).
-    foreach (array('v', 'd', 'i') as $key) {
-      unset($data[$key]);
-    }
-
-    // After we clear out the flags we do not want from the
-    // 'options' context, we will add in the 'root' and 'uri'
-    // options from the 'alias' context.
-    foreach (array('root', 'r', 'uri', 'l') as $key) {
-      $value = drush_get_option($key, null, 'alias');
-      if (isset($value)) {
-        $data[$key] = $value;
-      }
-    }
-
-    // Finally, we call backend invoke with the
-    // specified remote host, remote user and drush path.
-    $drush_path = drush_get_option('drush-script');
-    if (!isset($drush_path)) {
-      $drush_folder = drush_get_option('drush');
-      if (isset($drush)) {
-        $drush_path = $drush_folder . '/drush';
-      }
-    }
-    $remote_user = drush_get_option('remote-user');
-    drush_log(dt('Begin drush_backend_invoke'));
-    $values = drush_backend_invoke_args($command, $args, $data, 'GET', TRUE, $drush_path, $remote_host, $remote_user);
-    drush_log(dt('drush_backend_invoke is complete'));
-
-    return TRUE;
-  }
-  return FALSE;
-}
-
-/**
  * Shutdown function for use while Drupal is bootstrapping and to return any
  * registered errors.
  *
Index: commands/core/rsync.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/drush/commands/core/rsync.inc,v
retrieving revision 1.6
diff -u -p -r1.6 rsync.inc
--- commands/core/rsync.inc	1 Jan 2010 07:53:11 -0000	1.6
+++ commands/core/rsync.inc	15 Jan 2010 00:42:51 -0000
@@ -19,9 +19,13 @@
  */
 function drush_core_rsync($source, $destination) {
   $sync_all_paths = drush_get_option('all-paths');
-  $source_settings = drush_sitealias_get_record($source);
-  $destination_settings = drush_sitealias_get_record($destination);
-  if ((isset($sync_all_paths)) && ($result !== FALSE)) {
+  
+  // TODO:  this implementation of --all-paths does not work well 
+  // with multiple sync targets; needs rewriting
+  if ((isset($sync_all_paths)) && (strpos($source, ':') === FALSE) && (strpos($destination, ':') === FALSE)) {
+    $source_settings = drush_sitealias_get_record($source);
+    $destination_settings = drush_sitealias_get_record($destination);
+    
     // If the source and destination paths are both aliases that contain
     // no path component (that is, 'alias', not 'alias:/path'), then we will
     // iterate over all of the path aliases of both, and sync all that are
@@ -62,18 +66,26 @@ function drush_core_rsync($source, $dest
  **/
 function _drush_core_sync($source, $destination, $parametric_options = array()) {
   // evaluate file paths
-  $source = _drush_core_evaluate_path($source);
-  $destination = _drush_core_evaluate_path($destination);
-
-  // Prompt for confirmation. This is destructive.
-  if (!drush_get_context('DRUSH_SIMULATE')) {
-    drush_print(dt("You will destroy data from !target and replace with data from !source", array('!source' => $source, '!target' => $destination)));
-    if (!drush_confirm(dt('Do you really want to continue?'))) {
-      return drush_set_error('CORE_SYNC_ABORT', 'Aborting.');
+  $source_settings = _drush_core_evaluate_path($source);
+  $destination_settings = _drush_core_evaluate_path($destination);
+  $source_path = $source_settings['rsync-path'];
+  $destination_path = $destination_settings['rsync-path'];
+  
+  // Check to see if this is an rsync multiple command (multiple sources and multiple destinations)
+  $is_multiple = drush_do_multiple_command('rsync', $source_settings, $destination_settings, TRUE);
+
+  if ($is_multiple === FALSE) {
+    // Prompt for confirmation. This is destructive.
+    if (!drush_get_context('DRUSH_SIMULATE')) {
+      drush_print(dt("You will destroy data from !target and replace with data from !source", array('!source' => $source_path, '!target' => $destination_path)));
+      if (!drush_confirm(dt('Do you really want to continue?'))) {
+        // was: return drush_set_error('CORE_SYNC_ABORT', 'Aborting.');
+        drush_die('Aborting.');
+      }
     }
-  }
 
-  return drush_core_call_rsync($source, $destination, $parametric_options);
+    drush_core_call_rsync($source_path, $destination_path, $parametric_options);
+  }
 }
 
 function drush_core_call_rsync($source, $destination, $parametric_options = array()) {
@@ -221,13 +233,15 @@ function drush_core_call_rsync($source, 
  * @param remote_user
  *   The remote user, as specified in the remote alias or on the command line.
  * @return
- *   The evaluated path
+ *   The site record for the machine specified in the path, if any,
+ *   with the path to pass to rsync (including the machine specifier)
+ *   in the 'rsync-path' item.
  */
 function _drush_core_evaluate_path($path, $remote_user = '') {
   $path_aliases = array();
   // Parse site aliases if there is a colon in the path
   $colon_pos = strpos($path, ':');
-  if ($colon_pos != FALSE) {
+  if ($colon_pos !== FALSE) {
     $alias = substr($path, 0, $colon_pos);
     $path = substr($path, $colon_pos + 1);
     $site_alias_settings = drush_sitealias_get_record($alias);
@@ -304,14 +318,26 @@ function _drush_core_evaluate_path($path
   // Fill in path aliases in the path.
   $path = str_replace(array_keys($path_aliases), array_values($path_aliases), $path);
 
-  // If the path is empty or if it does not exist, then
-  // make it relative to the Drupal root.
+  // The path component is just the path part of the full
+  // machine:path specification (including the colon).
+  $path_component = (!empty($path) ? ':' . $path : '');
+  
+  // Next make the rsync path, which includes the machine
+  // and path components together.
+  // First make empty paths or relative paths start from the drupal root.
   if (empty($path) || ($path[0] != '/')) {
       $path = $path_aliases['!root'] . $path;
   }
+
+  // If there is a $machine component, to the path, then
+  // add it to the beginning
   if (!empty($machine)) {
-    $separator = ':';
+    $path = $machine . ':' . $path;
   }
-  return "$machine$separator$path";
+  
+  $site_alias_settings['rsync-path'] = $path;
+  $site_alias_settings['path-component'] = $path_component;
+  
+  return $site_alias_settings;
 }
 
Index: commands/core/sitealias.drush.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/drush/commands/core/sitealias.drush.inc,v
retrieving revision 1.9
diff -u -p -r1.9 sitealias.drush.inc
--- commands/core/sitealias.drush.inc	7 Jan 2010 14:15:16 -0000	1.9
+++ commands/core/sitealias.drush.inc	15 Jan 2010 00:42:51 -0000
@@ -90,18 +90,7 @@ function _drush_sitealias_user_specified
 
   // Iterate over the arguments and convert them to alias records
   if (!empty($specifications)) {
-    foreach ($specifications as $site) {
-      $alias_record = drush_sitealias_get_record($site, $db_settings_needed);
-      if (!empty($alias_record)) {
-        // If the alias record has a better name for itself than the
-	// one that was used to search for it, then it will put it
-	// into the 'name' element.
-        if (array_key_exists('name', $alias_record)) {
-          $site = $alias_record['name'];
-	}
-        $site_list[drush_sitealias_uri_to_site_dir($site)] = $alias_record;
-      }
-    }
+    $site_list = drush_sitealias_resolve_sitespecs($specifications, $db_settings_needed);
   }
   // If the user provided no args, then we will return everything.
   else {
@@ -126,6 +115,7 @@ function drush_sitealias_print() {
       _drush_sitealias_print_record($alias_record, $site);
     }
     elseif (isset($long_output)) {
+      _drush_sitealias_check_sitelist_fields($alias_record);
       $site_spec = drush_sitealias_alias_record_to_spec($alias_record, $with_db);
       drush_print($site_spec);
     }
@@ -136,41 +126,6 @@ function drush_sitealias_print() {
 }
 
 /**
- * Convert from an alias record to a site specification
- *
- * @param alias_record
- *   The full alias record to convert
- *
- * @param with_db
- *   True if the site specification should include a ?db-url term
- *
- * @return string
- *   The site specification
- */
-function drush_sitealias_alias_record_to_spec($alias_record, $with_db = false) {
-    $result = '';
-    if (array_key_exists('uri', $alias_record)) {
-      $result = '#' . str_replace('http://', '', $alias_record['uri']);
-    }
-    if (array_key_exists('path-aliases', $alias_record)) {
-      $path_aliases = $alias_record['path-aliases'];
-      if (array_key_exists('!root', $path_aliases)) {
-        $result = $path_aliases['!root'] . $result;
-      }
-    }
-    if (array_key_exists('remote-host', $alias_record)) {
-      $result = $alias_record['remote-host'] . $result;
-    }
-
-    // add the database info to the specification if desired
-    if ($with_db) {
-      $result = $result . '?db-url=' . urlencode($alias_record['db-url']);
-    }
-
-    return $result;
-}
-
-/**
  * Given a site alias name, print out a php-syntax
  * representation of it.
  *
@@ -215,14 +170,14 @@ function _drush_sitealias_print_record($
   $alias_name = drush_get_option('alias-name');
   if (!isset($alias_name)) {
     $alias_name = $site_alias;
-    if (empty($alias_name)) {
+    if (empty($alias_name) || is_numeric($alias_name)) {
       $alias_name = drush_sitealias_uri_to_site_dir($alias_record['uri']);
     }
   }
 
   // We don't want the name to go into the output
   unset($alias_record['name']);
-
+    
   // We only want to output the 'root' item; don't output the '!root' path alias
   if (array_key_exists('path-aliases', $alias_record) && array_key_exists('!root', $alias_record['path-aliases'])) {
     unset($alias_record['path-aliases']['!root']);
Index: commands/sql/sync.sql.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/drush/commands/sql/sync.sql.inc,v
retrieving revision 1.11
diff -u -p -r1.11 sync.sql.inc
--- commands/sql/sync.sql.inc	13 Jan 2010 22:06:21 -0000	1.11
+++ commands/sql/sync.sql.inc	15 Jan 2010 00:42:51 -0000
@@ -1,5 +1,5 @@
 <?php
-// $Id: sync.sql.inc,v 1.11 2010/01/13 22:06:21 weitzman Exp $
+// $Id: sync.sql.inc,v 1.8 2009/12/28 23:18:37 greg1anderson Exp $
 
 require_once DRUSH_BASE_PATH . '/commands/core/rsync.inc';
 
@@ -39,255 +39,318 @@ function _drush_sql_sync($source, $desti
   $source_settings = drush_sitealias_get_record($source, TRUE);
   $destination_settings = drush_sitealias_get_record($destination, TRUE);
 
-  // Evaluate the source and destination specifications into options.
-  // The options from the 'source-*' and 'target-*' aliases are set
-  // in a drush context that has a lower priority than the command-line
-  // options; this allows command-line options to override the default
-  // values specified in a site-alias.
-  drush_sitealias_set_alias_context($source_settings, 'source-');
-  drush_sitealias_set_alias_context($destination_settings, 'target-');
-
-  // Get the options for the source and target databases
-  $source_db_url = _drush_sql_get_spec_from_options('source-');
-  $source_remote_host = drush_get_option('source-remote-host');
-  $source_remote_port = drush_get_option('source-remote-port');
-  $source_rsync_options = array();
-
-  $target_db_url = _drush_sql_get_spec_from_options('target-');
-  $target_remote_host = drush_get_option('target-remote-host');
-  $target_remote_port = drush_get_option('target-remote-port');
-  $target_rsync_options = array();
-
-  // Set up the result file and the remote file.
-  // If the result file is not set, then create a temporary file.
-  // If the remote file is not set, use the same name for the remote
-  // and local files and hope for the best.
-  $source_dump = drush_get_option('source-dump');
-  $target_dump = drush_get_option('target-dump');
-  $use_temp_files = drush_get_option('temp');
-  $source_is_tmp = FALSE;
-  $target_is_tmp = FALSE;
-  $local_is_tmp = FALSE;
-  if (!isset($source_remote_host) && !isset($target_remote_host)) {
-    if (isset($source_dump)) {
-      $target_dump = $source_dump;
-    }
-    else {
-      if (!isset($target_dump)) {
-        $target_dump = tempnam(sys_get_temp_dir(), $target_db_url['database'] . '.sql.');
-        $target_is_tmp = TRUE;
+  // Check to see if this is an sql-sync multiple command (multiple sources and multiple destinations)
+  $is_multiple = drush_do_multiple_command('sql-sync', $source_settings, $destination_settings);
+
+  if ($is_multiple === FALSE) {
+    // Evaluate the source and destination specifications into options.
+    // The options from the 'source-*' and 'target-*' aliases are set
+    // in a drush context that has a lower priority than the command-line
+    // options; this allows command-line options to override the default
+    // values specified in a site-alias.
+    drush_sitealias_set_alias_context($source_settings, 'source-');
+    drush_sitealias_set_alias_context($destination_settings, 'target-');
+
+    // Get the options for the source and target databases
+    $source_db_url = _drush_sql_get_spec_from_options('source-');
+    $source_remote_host = drush_get_option('source-remote-host');
+    $source_remote_port = drush_get_option('source-remote-port');
+    $source_rsync_options = array();
+
+    $target_db_url = _drush_sql_get_spec_from_options('target-');
+    $target_remote_host = drush_get_option('target-remote-host');
+    $target_remote_port = drush_get_option('target-remote-port');
+    $target_rsync_options = array();
+
+    // Set up the result file and the remote file.
+    // If the result file is not set, then create a temporary file.
+    // If the remote file is not set, use the same name for the remote
+    // and local files and hope for the best.
+    $source_dump = drush_get_option('source-dump');
+    $target_dump = drush_get_option('target-dump');
+    $use_temp_files = drush_get_option('temp');
+    $source_is_tmp = FALSE;
+    $target_is_tmp = FALSE;
+    $local_is_tmp = FALSE;
+    if (!isset($source_remote_host) && !isset($target_remote_host)) {
+      if (isset($source_dump)) {
+	$target_dump = $source_dump;
+      }
+      else {
+	if (!isset($target_dump)) {
+          $target_dump = tempnam(sys_get_temp_dir(), $target_db_url['database'] . '.sql.');
+          $target_is_tmp = TRUE;
+	}
+	$source_dump = $target_dump;
       }
-      $source_dump = $target_dump;
     }
-  }
-  if (!isset($target_dump)) {
-    $target_dump = tempnam(sys_get_temp_dir(), $target_db_url['database'] . '.sql.');
-    $target_is_tmp = TRUE;
-  }
-  if (!isset($source_dump)) {
-    $source_dump = tempnam(sys_get_temp_dir(), $source_db_url['database'] . '.sql.');
-    $source_is_tmp = TRUE;
-    $source_rsync_options['remove-source-files'] = TRUE;
-  }
-
-  if (isset($source_remote_host) && isset($target_remote_host)) {
-    $local_file = tempnam(sys_get_temp_dir(), $source_db_url['database'] . ($source_db_url['database'] == $target_db_url['database'] ? '' : '-to-' . $target_db_url['database']) . '.sql.');
-    $local_is_tmp = TRUE;
-  }
-  elseif (!isset($source_remote_host)) {
-    $local_file = $source_dump;
-  }
-  elseif (!isset($target_remote_host)) {
-    $local_file = $target_dump;
-  }
+    if (!isset($target_dump)) {
+      $target_dump = tempnam(sys_get_temp_dir(), $target_db_url['database'] . '.sql.');
+      $target_is_tmp = TRUE;
+    }
+    if (!isset($source_dump)) {
+      $source_dump = tempnam(sys_get_temp_dir(), $source_db_url['database'] . '.sql.');
+      $source_is_tmp = TRUE;
+      $source_rsync_options['remove-source-files'] = TRUE;
+    }
+
+    if (isset($source_remote_host) && isset($target_remote_host)) {
+      $local_file = tempnam(sys_get_temp_dir(), $source_db_url['database'] . ($source_db_url['database'] == $target_db_url['database'] ? '' : '-to-' . $target_db_url['database']) . '.sql.');
+      $local_is_tmp = TRUE;
+    }
+    elseif (!isset($source_remote_host)) {
+      $local_file = $source_dump;
+    }
+    elseif (!isset($target_remote_host)) {
+      $local_file = $target_dump;
+    }
 
-  // Check to see if we are using a temporary file in a situation
-  // where the user did not specify "--temp".
-  if (($source_is_tmp || $target_is_tmp) && (!isset($use_temp_files)) && (isset($source_remote_host)  || isset($target_remote_host))) {
-    drush_print(dt('WARNING:  Using temporary files to store and transfer sql-dump. It is recommended that you specify --source-dump and --target-dump options on the command line, or set \'!dump\' in the path-aliases section of your site alias records. This facilitates fast file transfer via rsync.'));
-  }
 
-  // Prompt for confirmation. This is destructive.
-  if (!drush_get_context('DRUSH_SIMULATE') && $show_warning) {
-    $txt_source = (isset($source_remote_host) ? $source_remote_host . '/' : '') . $source_db_url['database'];
-    $txt_destination = (isset($target_remote_host) ? $target_remote_host . '/' : '') . $target_db_url['database'];
-    drush_print(dt("You will destroy data from !target and replace with data from !source.", array('!source' => $txt_source, '!target' => $txt_destination)));
-    drush_print();
-    // TODO: actually make the backup if desired.
-    drush_print(dt("You might want to make a backup first, using sql_dump command.\n"));
-    if (!drush_confirm(dt('Do you really want to continue?'))) {
-      if ($source_is_tmp) {
-	unlink($source_dump);
+    // Prompt for confirmation. This is destructive.
+    if (!drush_get_context('DRUSH_SIMULATE') && $show_warning) {
+
+      // If there are multiple destinations, then
+      // prompt once here and suppress the warning message
+      // and the normal confirmation below.
+      if (array_key_exists('site-list', $destination_settings)) {
+
+	drush_print(dt('You are about to use the experimental MULTIPLE-TARGET drush sql-sync feature.'));
+	drush_print(dt('This code is new; please be careful (make backups), and use at your own risk!'));
+
+	drush_print(dt('You are about to sync the database from !source, overwriting all of the following targets:', array('!source' => $source)));
+	foreach ($destination_settings['site-list'] as $one_destination) {
+          drush_print(dt('  !target', array('!target' => $one_destination)));
+	}
+
+	// Temporary: ask what the user wants to do
+	// Once this code works, we'll replace this with a
+	// simple 'drush_confirm', so -y will work.
+	// You must manually confirm every time until this
+	// code is tested.  Help out at issue #628996
+	// if this is annoying to you.
+
+	$options['s'] = 'Run simulation first (add -s flag)';
+	$options['g'] = 'Go!';
+	$choice = drush_choice($options, 'Enter a number to choose which action to take.');
+
+	if ($choice === FALSE) {
+           // TODO:  When this is just drush_confirm, merge in with the code below.
+           drush_die('Aborting.');
+	}
+	if ($choice != 'g') {
+          drush_set_option('s', TRUE, 'options');
+          drush_set_context('DRUSH_SIMULATE', TRUE);
+	}
       }
-      if ($target_is_tmp) {
-	unlink($target_dump);
-      }
-      if ($local_is_tmp) {
-	unlink($local_file);
+      else {
+
+
+	// Check to see if we are using a temporary file in a situation
+	// where the user did not specify "--temp".
+	if (($source_is_tmp || $target_is_tmp) && (!isset($use_temp_files)) && (isset($source_remote_host)  || isset($target_remote_host))) {
+          drush_print(dt('WARNING:  Using temporary files to store and transfer sql-dump.  It is recommended that you specify --source-dump and --target-dump options on the command line, or set \'!dump\' in the path-aliases section of your site alias records. This facilitates fast file transfer via rsync.'));
+	}
+
+	$txt_source = (isset($source_remote_host) ? $source_remote_host . '/' : '') . $source_db_url['database'];
+	$txt_destination = (isset($target_remote_host) ? $target_remote_host . '/' : '') . $target_db_url['database'];
+	drush_print(dt("You will destroy data from !target and replace with data from !source.", array('!source' => $txt_source, '!target' => $txt_destination)));
+	drush_print();
+	// TODO: actually make the backup if desired.
+	drush_print(dt("You might want to make a backup first, using sql_dump command.\n"));
+	if (!drush_confirm(dt('Do you really want to continue?'))) {
+          if ($source_is_tmp) {
+	    unlink($source_dump);
+          }
+          if ($target_is_tmp) {
+	    unlink($target_dump);
+          }
+          if ($local_is_tmp) {
+	    unlink($local_file);
+          }
+          drush_die('Aborting.');
+	}
       }
-      drush_die('Aborting.');
     }
-  }
 
-  // If source is remote, then use ssh to dump the database and then rsync to local machine
-  // If source is local, call drush_sql_dump to dump the database to local machine
-  // In either case, the '--no-dump' option will cause the sql-dump step to be skipped, and
-  // we will import from the existing local file (first using rsync to fetch it if it does not exist)
-  //
-  // No dump affects both local and remote sql-dumps; it prevents drush sql-sync
-  // from calling sql-dump when the local cache file is newer than the cache threshhold
-  // No sync affects the remote sql-dump;it will prevent drush sql-sync from
-  // rsyncing the local sql-dump file with the remote sql-dump file.
-  $no_sync = drush_get_option(array('no-sync', 'source-no-sync'));
-  $no_dump = drush_get_option(array('no-dump', 'source-no-dump'));
-  $no_cache = drush_get_option(array('no-cache', 'source-no-cache'));
-  if (!isset($no_cache)) {
-    $cache = drush_get_option(array('cache', 'source-cache'));
-    if (!isset($cache)) {
-      $cache = 24; // Default cache is 24 hours if nothing else is specified.
+    // If source is remote, then use ssh to dump the database and then rsync to local machine
+    // If source is local, call drush_sql_dump to dump the database to local machine
+    // In either case, the '--no-dump' option will cause the sql-dump step to be skipped, and
+    // we will import from the existing local file (first using rsync to fetch it if it does not exist)
+    //
+    // No dump affects both local and remote sql-dumps; it prevents drush sql-sync
+    // from calling sql-dump when the local cache file is newer than the cache threshhold
+    // No sync affects the remote sql-dump; it will prevent drush sql-sync from
+    // rsyncing the local sql-dump file with the remote sql-dump file.
+    $no_sync = drush_get_option(array('no-sync', 'source-no-sync'));
+    $no_dump = drush_get_option(array('no-dump', 'source-no-dump'));
+    $no_cache = drush_get_option(array('no-cache', 'source-no-cache'));
+    if (!isset($no_cache)) {
+      $cache = drush_get_option(array('cache', 'source-cache'));
+      if (!isset($cache)) {
+	$cache = 24; // Default cache is 24 hours if nothing else is specified.
+      }
     }
-  }
-  // If the 'cache' option is set, then we will set the no-dump option iff the
-  // target file exists and its modification date is less than "cache" hours.
-  if (isset($cache)) {
-    if (file_exists($local_file) && (filesize($local_file) > 0)) {
-      if ((filemtime($local_file) - time()) < ($cache * 60 * 60)) {
-        drush_log(dt('Modification time of local dump file is less than !cache hours old.', array('!cache' => $cache)));
-        $no_dump = TRUE;
-        $no_sync = TRUE;
+    // If the 'cache' option is set, then we will set the no-dump option iff the
+    // target file exists and its modification date is less than "cache" hours.
+    if (isset($cache)) {
+      if (file_exists($local_file) && (filesize($local_file) > 0)) {
+	if ((filemtime($local_file) - time()) < ($cache * 60 * 60)) {
+          drush_log(dt('Modification time of local dump file is less than !cache hours old.', array('!cache' => $cache)));
+          $no_dump = TRUE;
+          $no_sync = TRUE;
+	}
+	else {
+          drush_log(dt('Local sql cache file exists but is greater than !cache hours old.', array('!cache' => $cache)));
+	}
       }
       else {
-        drush_log(dt('Local sql cache file exists but is greater than !cache hours old.', array('!cache' => $cache)));
+	drush_log('Local sql cache file does not exist.');
       }
     }
-    else {
-      drush_log('Local sql cache file does not exist.');
+    if (isset($source_remote_host)) {
+      if (isset($source_remote_port)) {
+	$source_db_url['port'] = $source_remote_port;
+      }
+      $source_remote_user = drush_get_option('source-remote-user');
+      if (isset($source_remote_user)) {
+	$source_at ='@';
+	$source_remote_pass = drush_get_option('source-remote-pass') ? ':' . drush_get_option('source-remote-pass') : '';
+      }
+
+      if (!isset($no_dump)) {
+	$source_intermediate = $source_dump;
+	$mv_intermediate = '';
+	// If we are doing a remote dump and the source is not a temporary file,
+	// then first dump to a temporary file and move it to the specified file after
+	// the dump is complete.  This will reduce contention during simultaneous dumps
+	// from different users sharing the same dump file.
+	if (!isset($source_is_tmp)) {
+          $source_intermediate = $source_dump . '-' . date("U");
+          $mv_intermediate = '; mv -f ' . $source_intermediate . ' ' . $source_dump;
+	}
+	drush_set_option('result-file', $source_intermediate);
+	$dump_exec = drush_sql_dump($source_db_url) . $mv_intermediate;
+	if (isset($cache) && !isset($source_is_tmp)) {
+          // Inject some bash commands to remotely test the modification date of the target file
+          // if the cache option is set.
+          $dump_exec = 'if [ ! -s ' . $source_dump . '] || [ $((`date "+%s"`-`stat --format="%Y" ' . $source_dump . '`)) -gt ' . ($cache * 60 *  60) . ' ] ; then ' . $dump_exec . '; fi';
+	}
+	$dump_exec = 'ssh ' . $source_remote_user . $source_at . $source_remote_host . ' ' . escapeshellarg($dump_exec);
+      }
     }
-  }
-  if (isset($source_remote_host)) {
-    if (isset($source_remote_port)) {
-      $source_db_url['port'] = $source_remote_port;
-    }
-    $source_remote_user = drush_get_option('source-remote-user');
-    if (isset($source_remote_user)) {
-      $source_at ='@';
-      $source_remote_pass = drush_get_option('source-remote-pass') ? ':' . drush_get_option('source-remote-pass') : '';
-    }
-
-    if (!isset($no_dump)) {
-      $source_intermediate = $source_dump;
-      $mv_intermediate = '';
-      // If we are doing a remote dump and the source is not a temporary file,
-      // then first dump to a temporary file and move it to the specified file after
-      // the dump is complete.  This will reduce contention during simultaneous dumps
-      // from different users sharing the same dump file.
-      if (!isset($source_is_tmp)) {
-        $source_intermediate = $source_dump . '-' . date("U");
-        $mv_intermediate = '; mv -f ' . $source_intermediate . ' ' . $source_dump;
-      }
-      drush_set_option('result-file', $source_intermediate);
-      $dump_exec = drush_sql_dump($source_db_url) . $mv_intermediate;
-      if (isset($cache) && !isset($source_is_tmp)) {
-        // Inject some bash commands to remotely test the modification date of the target file
-        // if the cache option is set.
-        $dump_exec = 'if [ ! -s ' . $source_dump . '] || [ $((`date "+%s"`-`stat --format="%Y" ' . $source_dump . '`)) -gt ' . ($cache * 60 *  60) . ' ] ; then ' . $dump_exec . '; fi';
+    else {
+      if (!isset($no_dump)) {
+	drush_set_option('result-file', $local_file);
+	$dump_exec = drush_sql_dump($source_db_url);
       }
-      $dump_exec = 'ssh ' . $source_remote_user . $source_at . $source_remote_host . ' ' . escapeshellarg($dump_exec);
+      $no_sync = TRUE;
     }
-  }
-  else {
-    if (!isset($no_dump)) {
-      drush_set_option('result-file', $local_file);
-      $dump_exec = drush_sql_dump($source_db_url);
+
+    // Call sql-dump, either on the local machine or remotely via ssh, as appropriate.
+    if (!empty($dump_exec)) {
+      drush_op('system', $dump_exec);
+      // TODO: IF FAILURE THEN ABORT
+    }
+
+    // If the sql-dump was remote, then rsync the file over to the local machine.
+    if (!isset($no_sync)) {
+      // If the source file is a temporary file, then we will have rsync
+      // delete it for us (remove-source-files option set above).
+      drush_core_call_rsync($source_remote_user . $source_at . $source_remote_host . ':' . $source_dump, $local_file, $source_rsync_options);
+    }
+
+    // We will handle lists of destination sites differently from
+    // single source-to-destination syncs.
+    if (array_key_exists('site-list', $destination_settings)) {
+      // Insure that we will not dump the source sql database
+      // repeatedly, but will instead re-use it each time through
+      // the redispatch loop.
+      drush_set_option('no-dump', TRUE);
+      drush_set_option('no-sync', TRUE);
+      drush_set_option('source-dump', $source_dump);
+      // Call sql-sync for each destination to push the $source_dump
+      // to each target in turn.
+      foreach ($destination_settings['site-list'] as $one_destination) {
+	drush_do_command_redispatch('sql-sync', array($source, $one_destination));
+      }
     }
-    $no_sync = TRUE;
-  }
+    else {
+      // Prior to database import, we will generate a "create database" command
+      // if the '--create-db' option was specified.  Note that typically the
+      // web server user will not have permissions to create a database; to specify
+      // a different user to use with the create db command, the '--db-su' option
+      // may be used.
+      // Under postgres, "alter role username with createdb;" will give create database
+      // permissions to the specified user if said user was not created with this right.
+      $pre_import_commands = '';
+      $create_db = drush_get_option('create-db');
+      if (isset($create_db)) {
+	$create_db_target = $target_db_url;
+	$create_db_target['database'] = '';
+	$db_superuser = drush_get_option(array('db-su', 'target-db-su'));
+	if (isset($db_superuser)) {
+          $create_db_target['username'] = $db_superuser;
+	}
+	$db_su_connect = _drush_sql_connect($create_db_target);
+	switch (_drush_sql_get_scheme($target_db_url)) {
+          case 'mysql':
+            $pre_import_commands = 'echo "DROP DATABASE IF EXISTS ' . $target_db_url['database'] . '; CREATE DATABASE ' . $target_db_url['database'] . '; GRANT ALL PRIVILEGES ON ' . $target_db_url['database'] . '.* TO \'' . $target_db_url['username'] . '\'@\'localhost\' IDENTIFIED BY \'' . $target_db_url['password'] . '\';" | mysql --password=\'' . $target_db_url['password'] . '\'; ';
+            break;
+          case 'pgsql':
+            $pre_import_commands = 'echo "drop database if exists ' . $target_db_url['database'] . '; create database ' . $target_db_url['database'] . ';" | ' . $db_su_connect . '; ';
+            break;
+	}
+      }
 
-  // Call sql-dump,either on the local machine or remotely via ssh, as appropriate.
-  if (!empty($dump_exec)) {
-    drush_op('system', $dump_exec);
-    // TODO: IF FAILURE THEN ABORT
-  }
+      // Generate the import command
+      $import_command = _drush_sql_connect($target_db_url);
+      switch (_drush_sql_get_scheme($target_db_url)) {
+	case 'mysql':
+          $import_command .= ' ' . (drush_get_context('DRUSH_DEBUG') ? ' -v' : '--silent');
+          break;
+	case 'pgsql':
+          $import_command .= ' ' . (drush_get_context('DRUSH_DEBUG') ? ' ' : '-q');
+          break;
+      }
 
-  // If the sql-dump was remote, then rsync the file over to the local machine.
-  if (!isset($no_sync)) {
-    // If the source file is a temporary file, then we will have rsync
-    // delete it for us (remove-source-files option set above).
-    drush_core_call_rsync($source_remote_user . $source_at . $source_remote_host . ':' . $source_dump, $local_file, $source_rsync_options);
-  }
+      // If destination is remote, then use rsync to push the database, then use ssh to import the database
+      // If destination is local, then just import the database locally
+      if (isset($target_remote_host)) {
+	if (isset($target_remote_port)) {
+          $target_db_url['port'] = $target_remote_port;
+	}
+	$target_remote_user = drush_get_option('target-remote-user');
+	if (isset($target_remote_user)) {
+          $target_at ='@';
+          $target_remote_pass = drush_get_option('target-remote-pass') ? ':' . drush_get_option('target-remote-pass') : '';
+	}
+
+	drush_core_call_rsync($local_file, $target_remote_user . $target_at . $target_remote_host . ':' . $target_dump);
+
+	$connect_exec = $pre_import_commands . $import_command . ' < ' . $target_dump;
+	$import_exec = 'ssh ' . $target_remote_user . $target_at . $target_remote_host . ' ' . escapeshellarg($connect_exec);
+	// delete the remote target file if it is a temporary file
+	if ($target_is_tmp) {
+          $import_exec .= '; rm -f ' . escapeshellarg($target_dump);
+	}
+      }
+      else {
+	$import_exec = $pre_import_commands . $import_command . ' < ' . $local_file;
+      }
 
-  // Prior to database import, we will generate a "create database" command
-  // if the '--create-db' option was specified.  Note that typically the
-  // web server user will not have permissions to create a database; to specify
-  // a different user to use with the create db command, the '--db-su' option
-  // may be used.
-  // Under postgres, "alter role username with createdb;" will give create database
-  // permissions to the specified user if said user was not created with this right.
-  $pre_import_commands = '';
-  $create_db = drush_get_option('create-db');
-  if (isset($create_db)) {
-    $create_db_target = $target_db_url;
-    $create_db_target['database'] = '';
-    $db_superuser = drush_get_option(array('db-su', 'target-db-su'));
-    if (isset($db_superuser)) {
-      $create_db_target['username'] = $db_superuser;
-    }
-    $db_su_connect = _drush_sql_connect($create_db_target);
-    switch (_drush_sql_get_scheme($target_db_url)) {
-      case 'mysql':
-        $pre_import_commands = 'echo "DROP DATABASE IF EXISTS ' . $target_db_url['database'] . '; CREATE DATABASE ' . $target_db_url['database'] . '; GRANT ALL PRIVILEGES ON ' . $target_db_url['database'] . '.* TO \'' . $target_db_url['username'] . '\'@\'localhost\' IDENTIFIED BY \'' . $target_db_url['password'] . '\';" | mysql --password=\'' . $target_db_url['password'] . '\'; ';
-        break;
-      case 'pgsql':
-        $pre_import_commands = 'echo "drop database if exists ' . $target_db_url['database'] . '; create database ' . $target_db_url['database'] . ';" | ' . $db_su_connect . '; ';
-        break;
+      drush_op('system', $import_exec);
     }
-  }
 
-  // Generate the import command
-  $import_command = _drush_sql_connect($target_db_url);
-  switch (_drush_sql_get_scheme($target_db_url)) {
-    case 'mysql':
-      $import_command .= ' ' . (drush_get_context('DRUSH_DEBUG') ? ' -v' : '--silent');
-      break;
-    case 'pgsql':
-      $import_command .= ' ' . (drush_get_context('DRUSH_DEBUG') ? ' ' : '-q');
-      break;
-  }
-
-  // If destination is remote, then use rsync to push the database, then use ssh to import the database
-  // If destination is local, then just import the database locally
-  if (isset($target_remote_host)) {
-    if (isset($target_remote_port)) {
-      $target_db_url['port'] = $target_remote_port;
-    }
-    $target_remote_user = drush_get_option('target-remote-user');
-    if (isset($target_remote_user)) {
-      $target_at ='@';
-      $target_remote_pass = drush_get_option('target-remote-pass') ? ':' . drush_get_option('target-remote-pass') : '';
-    }
-
-    drush_core_call_rsync($local_file, $target_remote_user . $target_at . $target_remote_host . ':' . $target_dump);
-
-    $connect_exec = $pre_import_commands . $import_command . ' < ' . $target_dump;
-    $import_exec = 'ssh ' . $target_remote_user . $target_at . $target_remote_host . ' ' . escapeshellarg($connect_exec);
-    // delete the remote target file if it is a temporary file
+    // delete local temporary files
+    // (these are created even with -s or multiple targets)
+    if ($source_is_tmp) {
+      unlink($source_dump);
+    }
     if ($target_is_tmp) {
-      $import_exec .= '; rm -f ' . escapeshellarg($target_dump);
+      unlink($target_dump);
+    }
+    if ($local_is_tmp) {
+      unlink($local_file);
     }
-  }
-  else {
-    $import_exec = $pre_import_commands . $import_command . ' < ' . $local_file;
-  }
-
-  drush_op('system', $import_exec);
-  // delete local temporary files
-  if ($source_is_tmp) {
-    unlink($source_dump);
-  }
-  if ($target_is_tmp) {
-    unlink($target_dump);
-  }
-  if ($local_is_tmp) {
-    unlink($local_file);
   }
 }
Index: includes/drush.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/drush/includes/drush.inc,v
retrieving revision 1.76
diff -u -p -r1.76 drush.inc
--- includes/drush.inc	11 Jan 2010 01:22:46 -0000	1.76
+++ includes/drush.inc	15 Jan 2010 00:42:52 -0000
@@ -730,6 +730,229 @@ function drush_table_column_autowidth($r
 }
 
 /**
+ * @defgroup dispatching Command dispatching functions.
+ * @{
+ *
+ * These functions manage parameter and option manipulation
+ * for calls to drush backend invoke.
+ */
+
+/**
+ * Process commands that are executed on a remote drush instance.
+ *
+ * @return
+ *   TRUE if the command was handled remotely.
+ */
+function drush_remote_command() {
+  // The command will be executed remotely if the --remote-host flag
+  // is set; note that if a site alias is provided on the command line,
+  // and the site alias references a remote server, then the --remote-host
+  // option will be set when the site alias is processed.
+  // @see _drush_process_site_alias
+  $remote_host = drush_get_option('remote-host');
+  if (isset($remote_host)) {
+
+    $args = drush_get_arguments();
+    $command = array_shift($args);
+    $remote_user = drush_get_option('remote-user');
+
+    drush_do_command_redispatch($command, $args, $remote_host, $remote_user);
+    return TRUE;
+  }
+  // If the --site-list flag is set, then we will execute the specified
+  // command once for every site listed in the site list.
+  $site_list = drush_get_option('site-list');
+  if (isset($site_list)) {
+    if (!is_array($site_list)) {
+      $site_list = explode(',', $site_list);
+    }
+    $args = drush_get_arguments();
+    
+    if (!drush_get_context('DRUSH_SIMULATE')) {
+      drush_print(dt('You are about to use the experimental MULTIPLE-TARGET drush command feature.'));
+      drush_print(dt('This code is new; please be careful (make backups), and use at your own risk!'));
+      
+      drush_print(dt("You are about to execute '!command' on all of the following targets:", array('!command' => implode(" ", $args))));
+      foreach ($site_list as $one_destination) {
+        drush_print(dt('  !target', array('!target' => $one_destination)));
+      }
+      
+      // Temporary: ask what the user wants to do
+      // Once this code works, we'll replace this with a
+      // simple 'drush_confirm', so -y will work.
+      // You must manually confirm every time until this
+      // code is tested.  Help out at issue #628996
+      // if this is annoying to you.
+      
+      $options['s'] = 'Run simulation first (add -s flag)  WARNING: ignored by some commands!';
+      $options['g'] = 'Go!';
+      $choice = drush_choice($options, 'Enter a number to choose which action to take.');
+      
+      if ($choice === FALSE) {
+         drush_die('Aborting.');
+      }
+      if ($choice != 'g') {
+        drush_set_option('s', TRUE, 'options');
+        drush_set_context('DRUSH_SIMULATE', TRUE);
+      }
+    }
+    $command = array_shift($args);
+
+    foreach ($site_list as $site_spec) {
+      $site_record = _drush_sitealias_get_record($site_spec);
+      if (!empty($site_record)) {
+        // Pass only the root and uri parameters from the site alias;
+        // put them in the 'options' context where they are sure to
+        // be picked up by the command redispatch helper.
+        drush_set_option('root', $site_record['root'], 'options');
+        drush_set_option('uri', $site_record['uri'], 'options');
+        drush_unset_option('r');
+        drush_unset_option('l');
+        drush_do_command_redispatch($command, $args, $site_record['remote-host'], $site_record['remote-user']);
+      }
+    }
+    return TRUE;
+  }
+  return FALSE;
+}
+
+/**
+ * Used by functions that operate on lists of sites, moving
+ * information from the source to the destination.  Currenlty
+ * this includes 'drush rsync' and 'drush sql sync'.
+ */
+function drush_do_multiple_command($command, $source_record, $destination_record, $allow_single_source = FALSE) {
+  $is_multiple_command = FALSE;
+  
+  if ((($allow_single_source == TRUE) || array_key_exists('site-list', $source_record)) && array_key_exists('site-list', $destination_record)) {
+    $is_multiple_command = TRUE;
+    $source_path = array_key_exists('path-component', $source_record) ? $source_record['path-component'] : '';
+    $destination_path = array_key_exists('path-component', $destination_record) ? $destination_record['path-component'] : '';
+    
+    $target_list = drush_sitealias_resolve_sitelist($destination_record);
+    if (array_key_exists('site-list', $source_record)) {
+      $source_list = drush_sitealias_resolve_sitelist($source_record);
+
+      if (drush_sitealias_check_lists_alignment($source_list, $target_list) === FALSE) {
+        if (array_key_exists('unordered-list', $source_record) || array_key_exists('unordered-list', $destination_record)) {
+          drush_sitelist_align_lists($source_list, $target_list, $aligned_source, $aligned_target);
+          $source_list = $aligned_source;
+          $target_list = $aligned_target;
+        }
+      }
+    }
+    else {
+      $source_list = array_fill(0, count($target_list), $source_record);
+    }
+    
+    drush_print($source_path);
+    drush_print($destination_path);
+    
+    if (!drush_get_context('DRUSH_SIMULATE')) {
+      drush_print(dt('You are about to use the experimental MULTIPLE-SYNCHRONIZED-TARGET drush !command feature.', array('!command' => $command)));
+      drush_print(dt('This code is new; please be careful (make backups), and use at your own risk!'));
+
+      drush_print(dt('You are about to rsync between all of the following targets:'));
+      $i = 0;
+      foreach ($source_list as $one_source) {
+        $one_target = $target_list[$i];
+        ++$i;
+        drush_print(dt('  !source will overwrite !target', array('!source' => drush_sitealias_alias_record_to_spec($one_source) . $source_path, '!target' => drush_sitealias_alias_record_to_spec($one_target) . $destination_path)));
+      }
+
+      // Temporary: ask what the user wants to do
+      // Once this code works, we'll replace this with a
+      // simple 'drush_confirm', so -y will work.
+      // You must manually confirm every time until this
+      // code is tested.  Help out at issue #628996
+      // if this is annoying to you.
+
+      $options['s'] = 'Run simulation first (add -s flag)';
+      $options['g'] = 'Go!';
+      $choice = drush_choice($options, 'Enter a number to choose which action to take.');
+
+      if ($choice === FALSE) {
+         drush_die('Aborting.');
+      }
+      if ($choice != 'g') {
+        drush_set_option('s', TRUE, 'options');
+        drush_set_context('DRUSH_SIMULATE', TRUE);
+      }
+    }
+    
+    $data = drush_redispatch_get_options();
+    $i = 0;
+    foreach ($source_list as $one_source) {
+      $one_target = $target_list[$i];
+      ++$i;
+      
+      $source_spec = drush_sitealias_alias_record_to_spec($one_source);
+      $target_spec = drush_sitealias_alias_record_to_spec($one_target);
+      
+      drush_log(dt('Begin do_multiple !command via backend invoke', array('!command' => $command)));
+      $values = drush_backend_invoke_args($command, array($source_spec . $source_path, $target_spec . $destination_path), $data, 'GET', TRUE);
+      drush_log(dt('Backend invoke is complete'));
+    }
+  }
+  
+  return $is_multiple_command;
+}
+
+/**
+ * Redispatch the specified command using the same
+ * options that were passed to this invocation of drush.
+ */
+function drush_do_command_redispatch($command, $args, $remote_host = NULL, $remote_user = NULL) {
+  $data = drush_redispatch_get_options();
+
+  // If the path to drush was supplied, then pass it to backend invoke.
+  // (Perhaps this should be added to the default handling of backend invoke.)
+  $drush_path = drush_get_option('drush-script');
+  if (!isset($drush_path)) {
+    $drush_folder = drush_get_option('drush');
+    if (isset($drush)) {
+      $drush_path = $drush_folder . '/drush';
+    }
+  }
+  // Call through to backend invoke.
+  drush_log(dt('Begin redispatch via backend invoke'));
+  $values = drush_backend_invoke_args($command, $args, $data, 'GET', TRUE, $drush_path, $remote_host, $remote_user);
+  drush_log(dt('Backend invoke is complete'));
+}
+
+/**
+ * Get the options for this command.
+ *
+ * This function returns an array that contains all of the options
+ * that are appropriate for forwarding along to backend invoke.
+ * Pass the result from this function to backend invoke in the $data
+ * parameter when doing a redispatch.
+ */
+function drush_redispatch_get_options() {
+  // Start off by taking everything from the command line ('options' context)
+  $options = drush_get_context('options');
+  // If we can parse the current command, then examine all contexts
+  // in order for any option that is directly related to the current command
+  $command = drush_parse_command();
+  if (is_array($command)) {
+    foreach ($command['options'] as $key => $value) {
+      // Strip leading --
+      $key = ltrim($key, '-');
+      $value = drush_get_option($key);
+      if (isset($value)) {
+        $options[$key] = $value;
+      }
+    }
+  }
+  return $options;
+}
+
+
+/**
+ * @} End of "defgroup dispatching".
+ */
+
+/**
  * @defgroup logging Logging information to be provided as output.
  * @{
  *
Index: includes/sitealias.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/drush/includes/sitealias.inc,v
retrieving revision 1.11
diff -u -p -r1.11 sitealias.inc
--- includes/sitealias.inc	7 Jan 2010 14:15:16 -0000	1.11
+++ includes/sitealias.inc	15 Jan 2010 00:42:53 -0000
@@ -61,6 +61,28 @@ function drush_sitealias_check_arg() {
  *   An alias record.
  */
 function drush_sitealias_get_record($alias, $db_settings_needed = false) {
+  // Check to see if the alias contains commas.  If it does, then
+  // we will go ahead and make a site list record
+  $alias_record = array();
+  if (strpos($alias, ',') !== false) {
+    // TODO:  If the site list contains any site lists, or site
+    // search paths, then we should expand those and merge them
+    // into this list longhand.
+    $alias_record['site-list'] = explode(',', $alias);
+    if ($db_settings_needed) {
+      $alias_record['db-settings-needed'] = TRUE;
+    }
+  }
+  else {
+    $alias_record = _drush_sitealias_get_record($alias, $db_settings_needed);
+  }
+  if (!empty($alias_record) && !array_key_exists('name', $alias_record)) {
+    $alias_record['name'] = $alias;
+  }
+  return $alias_record;
+}
+
+function _drush_sitealias_get_record($alias, $db_settings_needed = false) {
   // Sometimes getting an alias record involves loading settings.php or calling
   // backend invoke.  We'll cache all alias records fetched by this routine to
   // insure that we never have to do anything like that twice.
@@ -70,29 +92,6 @@ function drush_sitealias_get_record($ali
     $all_site_aliases = drush_get_option('site-aliases', array());
     if (array_key_exists($alias, $all_site_aliases)) {
       $alias_record = $all_site_aliases[$alias];
-      // If the alias record does not have a defined 'databases' entry,
-      // then we'll need to look one up
-      if ($db_settings_needed && !isset($alias_record['db-url']) && !isset($alias_record['databases'])) {
-        // Check to see if we've cached the databases record from a previous invocation of this function
-        $alias_record['databases'] = drush_get_option('databases-' . $alias, NULL, 'sitealias-cache');
-        if (!isset($alias_record['databases'])) {
-          // If the alias record is remote, then we'll use backend_invoke to fetch the
-          // database settings from the remote machine
-          if (array_key_exists('remote-host', $alias_record)) {
-            $data = array('root' => $alias_record['path-aliases']['!root'], 'uri' => $alias_record['uri'], 'all' => TRUE);
-            $args = array();
-            $values = drush_backend_invoke_args("sql-conf", $args, $data, 'GET', FALSE, $alias_record['path-aliases']['!drush-script'], $alias_record['remote-host'], $alias_record['remote-user']);
-            $alias_record['databases'] = $values['object'];
-          }
-          // If the alias record is for a local machine, then look up and
-          // use the database settings from the appropriate settings.php file.
-          else {
-            $alias_record = array_merge(_drush_sitealias_build_record_from_settings($alias), $alias_record);
-          }
-          // Cache our result so we don't need to do IPC for successive calls to this function
-          drush_set_option('databases-' . $alias, $alias_record['databases'], 'sitealias-cache');
-        }
-      }
     }
     // If the parameter is not an alias, then it is some form of
     // site specification (or it is nothing at all)
@@ -108,25 +107,35 @@ function drush_sitealias_get_record($ali
         else {
           $parsed = parse_url($alias);
         }
-        // Copy various parts of the parsed URL into the appropriate records of the alias record
-        foreach (array('user' => 'remote-user', 'pass' => 'remote-pass', 'host' => 'remote-host', 'fragment' => 'uri', 'path' => '!root') as $url_key => $option_key) {
-          if (array_key_exists($url_key, $parsed)) {
-            _drush_sitealias_set_record_element($alias_record, $option_key, $parsed[$url_key]);
-          }
-        }
-        // If the site specification has a query, also set the query items
-        // in the alias record.  This allows passing db_url as part of the
-        // site specification, for example.
-        foreach (explode('&', $parsed['query']) as $query_arg) {
-          $query_components = explode('=', $query_arg);
-          _drush_sitealias_set_record_element($alias_record, urldecode($query_components[0]), urldecode($query_components[1]));
+        // Special checking:  /path/to/root#all means all of the
+        // sites at the given drupal root.  Convert to a 'site-search-path'
+        // record, which will be converted to a 'site-list' later.
+        if (array_key_exists('fragment', $parsed) && /*!array_key_exists('host', $parsed) &&*/ ($parsed['fragment'] == 'all')) {
+          $alias_record['site-search-path'] = $parsed['path'];
         }
-
-        // Case 3.): If the URL contains a 'host' portion but no fragment, then set the uri to the host
-        // Note: We presume that 'server' is the best default for case 3; without this code, the default would
-        // be whatever is set in $options['l'] on the target machine's drushrc.php settings file.
-        if (array_key_exists('host', $parsed) && !array_key_exists('fragment', $parsed)) {
-          $alias_record['uri'] = $parsed['host'];
+        else {
+          // Copy various parts of the parsed URL into the appropriate records of the alias record
+          foreach (array('user' => 'remote-user', 'pass' => 'remote-pass', 'host' => 'remote-host', 'fragment' => 'uri', 'path' => '!root') as $url_key => $option_key) {
+            if (array_key_exists($url_key, $parsed)) {
+              _drush_sitealias_set_record_element($alias_record, $option_key, $parsed[$url_key]);
+            }
+          }
+          // If the site specification has a query, also set the query items
+          // in the alias record.  This allows passing db_url as part of the
+          // site specification, for example.
+          if (array_key_exists('query', $parsed)) {
+            foreach (explode('&', $parsed['query']) as $query_arg) {
+              $query_components = explode('=', $query_arg);
+              _drush_sitealias_set_record_element($alias_record, urldecode($query_components[0]), urldecode($query_components[1]));
+            }
+          }
+          
+          // Case 3.): If the URL contains a 'host' portion but no fragment, then set the uri to the host
+          // Note: We presume that 'server' is the best default for case 3; without this code, the default would
+          // be whatever is set in $options['l'] on the target machine's drushrc.php settings file.
+          if (array_key_exists('host', $parsed) && !array_key_exists('fragment', $parsed)) {
+            $alias_record['uri'] = $parsed['host'];
+          }
         }
       }
       else {
@@ -135,19 +144,179 @@ function drush_sitealias_get_record($ali
         // then use it as a local site specification.
         $alias_record = _drush_sitealias_find_record_for_local_site($alias, $db_settings_needed);
       }
+
     }
+
+    // Process site list related fields
+    _drush_sitealias_check_sitelist_fields($alias_record);
+
+    // If the alias record does not have a defined 'databases' entry,
+    // then we'll need to look one up
+    if ($db_settings_needed && !isset($alias_record['db-url']) && !isset($alias_record['databases']) && !isset($alias_record['site-list'])) {
+      // If the alias record is remote, then we'll use backend_invoke to fetch the
+      // database settings from the remote machine
+      if (array_key_exists('remote-host', $alias_record)) {
+        $data = array('root' => $alias_record['root'], 'uri' => $alias_record['uri'], 'all' => TRUE);
+        $args = array();
+        $values = drush_backend_invoke_args("sql-conf", $args, $data, 'GET', FALSE, $alias_record['path-aliases']['!drush-script'], $alias_record['remote-host'], $alias_record['remote-user']);
+        $alias_record['databases'] = $values['object'];
+      }
+      // If the alias record is for a local machine, then look up and
+      // use the database settings from the appropriate settings.php file.
+      else {
+        $alias_record = array_merge(_drush_sitealias_build_record_from_settings($alias_record['uri'], $alias_record['root']), $alias_record);
+      }
+    }
+
+    // Add the static defaults
     _drush_sitealias_add_static_defaults($alias_record);
+
     // Fail fast if database settings are not available and the caller
-    // said that they are required
-    if ($db_settings_needed && !isset($alias_record['databases'])) {
+    // said that they are required (but give site lists a pass)
+    if ($db_settings_needed && !isset($alias_record['databases']) && !isset($alias_record['site-list'])) {
       drush_print("Error: could not get database spec when it was required for " . $alias);
       exit(1);
     }
+    
+    // Cache the result
     drush_set_option('sitealias-' . $alias, $alias_record, 'sitealias-cache');
   }
   return $alias_record;
 }
 
+function drush_sitealias_resolve_sitelist($alias_record) {
+  $result_list = array();
+  if (isset($alias_record)) {
+    if (array_key_exists('site-list', $alias_record)) {
+      $db_settings_needed = array_key_exists('db-settings-needed', $alias_record);
+      foreach ($alias_record['site-list'] as $sitespec) {
+        $one_result = drush_sitealias_get_record($sitespec, $db_settings_needed);
+	if ($db_settings_needed) {
+          $one_result['db-settings-needed'] = TRUE;
+        }
+	$result_list = array_merge($result_list, drush_sitealias_resolve_sitelist($one_result));
+      }
+    }
+    else {
+      // Q: drush_sitealias_uri_to_site_dir?
+      // $result_list[$alias_record['name']] = $alias_record;
+      $result_list[] = $alias_record;
+    }
+  }
+  
+  return $result_list;
+}
+
+function drush_sitealias_check_lists_alignment($source, $target) {
+  // Check to see if the uri is the same in the source and target
+  // lists for all items in the array.  This is a strong requirement
+  // in D6; in D7, it is still highly convenient for the uri to
+  // be the same, because the site folder name == the uri, and if
+  // the uris match, then it is easier to rsync between remote machines.
+  $is_aligned = TRUE;
+  
+  $i = 0;
+  foreach ($source as $one_source) {
+    if ((!isset($target[$i])) || (!_drush_sitelist_check_site_records($one_source, $target[$i]))) {
+      $is_aligned = FALSE;
+      break;
+    }
+    ++$i;
+  }
+  
+  return $is_aligned;
+}
+
+function drush_sitelist_align_lists(&$source, &$target, &$source_result, &$target_result) {
+  $source_result = array();
+  $target_result = array();
+  
+  foreach ($source as $key => $one_source) {
+    $one_target = _drush_sitelist_find_in_list($one_source, $target);
+    if ($one_target !== FALSE) {
+      $source_result[] = $one_source;
+      $target_result[] = $one_target;
+      unset($source[$key]);
+    }
+  }
+  
+  $source = $source_result;
+  $target = $target_result;
+}
+
+function _drush_sitelist_find_in_list($one_source, &$target) {
+  $result = FALSE;
+  
+  foreach ($target as $key => $one_target) {
+    if(_drush_sitelist_check_site_records($one_source, $one_target)) {
+      $result = $one_target;
+      unset($target[$key]);
+    }
+  }
+  
+  return $result;
+}
+
+function _drush_sitelist_check_site_records($source, $target) {
+  if ((array_key_exists('uri', $source)) && (array_key_exists('uri', $target)) && ($source['uri'] == $target['uri'])) {
+    return TRUE;
+  }
+  return FALSE;
+}
+
+function drush_sitealias_resolve_sitespecs($site_specifications, $db_settings_needed = false) {
+  $result_list = array();
+  if (!empty($site_specifications)) {
+    foreach ($site_specifications as $site) {
+      $alias_record = drush_sitealias_get_record($site, $db_settings_needed);
+      $result_list = array_merge($result_list, drush_sitealias_resolve_sitelist($alias_record));
+    }
+  }
+  return $result_list;
+}
+
+function _drush_sitealias_check_sitelist_fields(&$alias_record) {
+  // If there is a 'from-list' entry, then build a derived
+  // list based on the site list with the given name.
+  if (array_key_exists('from-list', $alias_record)) {
+    // danger of infinite loops... move to transient defaults?
+    $from_record = drush_sitealias_get_record($alias_record['from-list']);
+    $from_list = drush_sitealias_resolve_sitelist($from_record);
+    $derived_list = array();
+    foreach ($from_list as $one_record) {
+      $derived_record = _drush_sitealias_derive_record($one_record, $alias_record);
+      $derived_list[] = drush_sitealias_alias_record_to_spec($derived_record);
+    }
+    
+    $alias_record = array();
+    if (!empty($derived_list)) {
+      $alias_record['site-list'] = $derived_list;
+    }
+  }
+  // If there is a 'site-search-path' entry, then build
+  // a 'site-list' entry from all of the sites that can be
+  // found in the search path.
+  if (array_key_exists('site-search-path', $alias_record)) {
+    // TODO:  Is there any point in merging the sites from
+    // the search path with any sites already listed in the
+    // 'site-list' entry?  For now we'll just overwrite.
+    $search_path = $alias_record['site-search-path'];
+    if (!is_array($search_path)) {
+      $search_path = explode(',', $search_path);
+    }
+    $found_sites = _drush_sitealias_find_local_sites($search_path);
+    $alias_record['site-list'] = $found_sites;
+    // The 'unordered-list' flag indicates that the order of the items in the site list is not stable.
+    $alias_record['unordered-list'] = '1';
+    // DEBUG: var_export($alias_record, FALSE);
+  }
+  if (array_key_exists('site-list', $alias_record)) {
+    if (!is_array($alias_record['site-list'])) {
+      $alias_record['site_list'] = explode(',', $alias_record['site-list']);
+    }
+  }
+}
+
 /**
  * Add "static" default values to the given alias record.  The
  * difference between a static default and a transient default is
@@ -177,6 +346,156 @@ function _drush_sitealias_add_static_def
   }
 }
 
+function _drush_sitealias_derive_record($from_record, $modifying_record) {
+  $result = $from_record;
+  
+  // If there is a 'remote-user' in the modifying record, copy it.
+  if (array_key_exists('remote-user', $modifying_record)) {
+    $result['remote-user'] = $from_record['remote_user'];
+  }
+  // If there is a 'remote-host', then:
+  //   If it is empty, clear the remote host in the result record
+  //   If it ends in '.', then prepend it to the remote host in the result record
+  //   Otherwise, copy it to the result record
+  if (array_key_exists('remote-host', $modifying_record)) {
+    $remote_host_modifier = $modifying_record['remote-host'];
+    if(empty($remote_host_modifier)) {
+      unset($result['remote-host']);
+      unset($result['remote-user']);
+    }
+    elseif ($remote_host_modifier[strlen($remote_host_modifier)-1] == '.') {
+      $result['remote-host'] = $remote_host_modifier . $result['remote-host'];
+    }
+    else {
+      $result['remote-host'] = $remote_host_modifier;
+    }
+  }
+  // If there is a 'root', then:
+  //   If it begins with '/', copy it to the result record
+  //   Otherwise, append it to the result record 
+  if (array_key_exists('root', $modifying_record)) {
+    $root_modifier = $modifying_record['root'];
+    if($root_modifier[0] == '/') {
+      $result['root'] = $root_modifier;
+    }
+    else {
+      $result['root'] = $result['root'] . '/' . $root_modifier;
+    }
+  }
+  // Poor man's realpath: take out the /../ with preg_replace.
+  // (realpath fails if the files in the path do not exist)
+  while(strpos($result['root'], '/../') !== FALSE) {
+    $result['root'] = preg_replace('/\w+\/\.\.\//', '', $result['root']);
+  }
+  
+  // TODO:  Should we allow the uri to be transformed?
+  // I think that if the uri does not match, then you should
+  // always build the list by hand, and not rely on '_drush_sitealias_derive_record'.
+  
+  return $result;
+}
+
+/**
+ * Convert from an alias record to a site specification
+ *
+ * @param alias_record
+ *   The full alias record to convert
+ *
+ * @param with_db
+ *   True if the site specification should include a ?db-url term
+ *
+ * @return string
+ *   The site specification
+ */
+function drush_sitealias_alias_record_to_spec($alias_record, $with_db = false) {
+    $result = '';
+    
+    // TODO:  we should handle 'site-list' records too.
+    if (array_key_exists('site-list', $alias_record)) {
+      // TODO:  we should actually expand the site list and recompose it
+      $result = implode(',', $alias_record['site-list']);
+    }
+    else {
+      // There should always be a uri
+      if (array_key_exists('uri', $alias_record)) {
+        $result = '#' . str_replace('http://', '', $alias_record['uri']);
+      }
+      // There should always be a root
+      if (array_key_exists('root', $alias_record)) {
+        $result = $alias_record['root'] . $result;
+      }
+      if (array_key_exists('remote-host', $alias_record)) {
+        $result = $alias_record['remote-host'] . $result;
+        if (array_key_exists('remote-user', $alias_record)) {
+          $result = $alias_record['remote-user'] . '@' . $result;
+        }
+      }
+
+      // add the database info to the specification if desired
+      if ($with_db) {
+        $result = $result . '?db-url=' . urlencode($alias_record['db-url']);
+      }
+    }
+    
+    return $result;
+}
+
+/**
+ * Search for drupal installations in the search path.
+ *
+ * @param search_path
+ *   An array of drupal root folders
+ *
+ * @return
+ *   An array of site specifications (/path/to/root#sitename.com)
+ */
+function _drush_sitealias_find_local_sites($search_path) {
+  $result = array();
+  foreach ($search_path as $a_drupal_root) {
+    $result = array_merge($result, _drush_find_local_sites_at_root($a_drupal_root));
+  }
+  return $result;  
+}
+
+/**
+ * Return a list of all of the local sites at the specified drupal root.
+ */
+function _drush_find_local_sites_at_root($a_drupal_root = '') {
+  $site_list = array();
+  $base_path = (empty($a_drupal_root) ? drush_get_context('DRUSH_DRUPAL_ROOT') : $a_drupal_root );
+  if (drush_valid_drupal_root($base_path)) {
+    $base_path .= '/sites';
+  }
+  
+  // TODO:  build a cache keyed off of $base_path (realpath($base_path)?),
+  // so that it is guarenteed that the lists returned will definitely be
+  // exactly the same should this routine be called twice with the same path.
+  
+  $files = drush_scan_directory($base_path, '/settings\.php/', array('.', '..', 'CVS', 'all'));
+  foreach ($files as $filename => $info) {
+    if ($info->basename == 'settings.php') {
+      // First we'll resolve the realpath of the settings.php file,
+      // so that we get the correct drupal root when symlinks are in use.
+      $real_sitedir = dirname(realpath($filename));
+      $real_root = drush_locate_root($filename);
+      if ($real_root !== FALSE) {
+        $a_drupal_site = $real_root . '#' . basename($real_sitedir);
+      }
+      // If the symlink points to some folder outside of any drupal
+      // root, then we'll use the
+      else {
+        $uri = drush_sitealias_site_dir_from_filename($filename);
+        $a_drupal_site = $a_drupal_root . '#' . $uri;
+      }
+      // Add the site if it isn't already in the array
+      if (!in_array($a_drupal_site, $site_list)) {
+        $site_list[] = $a_drupal_site;
+      }
+    }
+  }
+  return $site_list;
+}
+
 /**
  * Add "transient" default values to the given alias record.  The
  * difference between a static default and a transient default is
@@ -196,6 +515,7 @@ function _drush_sitealias_add_static_def
  */
 function _drush_sitealias_add_transient_defaults(&$alias_record) {
   if (isset($alias_record['path-aliases'])) {
+    // Add the path to the drush folder to the path aliases as !drush
     if (!array_key_exists('!drush', $alias_record['path-aliases'])) {
       if (array_key_exists('!drush-script', $alias_record['path-aliases'])) {
         $alias_record['path-aliases']['!drush'] = dirname($alias_record['path-aliases']['!drush-script']);
@@ -204,6 +524,10 @@ function _drush_sitealias_add_transient_
         $alias_record['path-aliases']['!drush'] = dirname($GLOBALS['argv'][0]);
       }
     }
+    // Add the path to the site folder to the path aliases as !site
+    if (!array_key_exists('!site', $alias_record['path-aliases']) && array_key_exists('uri', $alias_record)) {
+      $alias_record['path-aliases']['!site'] = 'sites/' . $alias_record['uri'] . '/';
+    }
   }
 }
 
@@ -225,6 +549,8 @@ function _drush_sitealias_find_record_fo
 
   // Clip off the leading '#' if it is there
   if (substr($alias,0,1) == '#') {
+    // TODO: if there is a leading #, then search for
+    // matching sites using the site search path
     $alias = substr($alias,1);
   }
 
@@ -259,7 +585,7 @@ function _drush_sitealias_find_record_fo
   // If the alias was looked up via the name "current", then
   // plug in the actual alias name that was used to find it.
   if (!empty($alias_record) && $use_name_from_alias) {
-    $alias_record['name'] = $alias;
+    $alias_record['name'] = $drupal_root . '#' . $alias;
   }
 
   return $alias_record;
@@ -462,11 +788,14 @@ function _drush_sitealias_set_context_by
  *   'destination-databases'.
  */
 function drush_sitealias_set_alias_context($site_alias_settings, $prefix) {
+  // backend invoke needs 'root' and 'uri' on the command line, so we will
+  // handle these two specially
+  $special = empty($prefix) ? array('root', 'uri') : array();
   // Transfer all non-array options from the site alias to the drush options
   // in the 'alias' context.
   foreach ($site_alias_settings as $key => $value) {
-    if (!is_array($value) || ($key == "databases")) {
-      drush_set_option($prefix . $key, $value, 'alias');
+    if (!is_array($value) || ($key == "databases") || ($key == "site-list")) {
+      drush_set_option($prefix . $key, $value, in_array($key, $special) ? 'options' : 'alias');
     }
   }
   // Transfer selected path aliases to the drush options.
