Index: commands/pm/pm.drush.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/drush/commands/pm/pm.drush.inc,v
retrieving revision 1.73
diff -u -p -u -p -r1.73 pm.drush.inc
--- commands/pm/pm.drush.inc	11 Jan 2010 05:22:02 -0000	1.73
+++ commands/pm/pm.drush.inc	13 Jan 2010 02:17:22 -0000
@@ -806,6 +806,7 @@ function pm_drush_engine_package_handler
 function pm_drush_engine_version_control() {
   return array(
     'svn' => array(
+      'signature' => 'svn info %s',
       'options' => array(
         '--version-control=svn' => 'Quickly add/remove/commit your project changes to Subversion.',
         '  --svnsync' => 'Automatically add new files to the SVN repository and remove deleted files. Caution.',
@@ -814,13 +815,21 @@ function pm_drush_engine_version_control
         '  --svnstatusparams' => "Add options to the 'svn status' command",
         '  --svnaddparams' => 'Add options to the `svn add` command',
         '  --svnremoveparams' => 'Add options to the `svn remove` command',
+        '  --svnrevertparams' => 'Add options to the `svn revert` command',
         '  --svncommitparams' => 'Add options to the `svn commit` command',
       ),
       'examples' => array(
         'drush [command] cck --svncommitparams=\"--username joe\"' =>  'Commit changes as the user \'joe\' (Quotes are required).'
       ),
     ),
+    'backup' => array(
+      'options' => array(
+        '--version-control=backup' => 'Backup all project files before updates.',
+        '  --backup-dir' => 'Backup destination directory. Defaults to a "/backup" subdirectory inside your Drupal root.',
+      ),
+    ),
     'bzr' => array(
+      'signature' => 'bzr root %s',
       'options' => array(
         '--version-control=bzr' => 'Quickly add/remove/commit your project changes to Bazaar.',
         '  --bzrsync' => 'Automatically add new files to the BZR repository and remove deleted files. Caution.',
@@ -832,6 +841,52 @@ function pm_drush_engine_version_control
 }
 
 /**
+ * Interface for version control systems.
+ * We use a simple object layer because we concevably need more than one 
+ * loaded at a time. 
+ */
+interface drush_pm_version_control {
+  function pre_update(&$release);
+  function rollback($release);
+  function post_update($release);
+  function post_install($release);
+}
+
+/**
+ * As simple factory function that tests for version control systems, in a user
+ * specified order, and return the one that appears to be appropriate for a
+ * specific directory.
+ */
+function drush_pm_include_version_control($directory = '.') {
+  $version_controls = explode(',', drush_get_option('version-control', 'svn,backup'));
+  $version_control_engines = drush_get_engines('version_control');
+
+  // Find the first valid engine in the list, checking signatures if needed.
+  $engine = FALSE;
+  while (!$engine && count($version_controls)) {
+    $version_control = array_shift($version_controls);
+    if (isset($version_control_engines[$version_control])) {
+      if ($version_control_engines[$version_control]['signature']) {
+        if (drush_shell_exec($version_control_engines[$version_control]['signature'], $directory)) {
+          $engine = $version_control;
+        }
+      }
+      else {
+        $engine = $version_control;
+      }
+    }
+  }
+  if (!$engine) {
+    return drush_set_error('DRUSH_PM_NO_VERSION_CONTROL', dt('No valid version control or backup engine found (the --version-control option was set to "!version-control").', array('!version-control' => $version_control)));
+  }
+  if (!drush_include_engine('version_control', $engine)) {
+    return FALSE;
+  }
+  $engine = 'drush_pm_version_control_' . $engine;
+  return new $engine();
+}
+
+/**
  * Command callback. Download drupal core.
  */
 function drush_pm_download() {
@@ -839,7 +894,6 @@ function drush_pm_download() {
   drush_bootstrap_max();
 
   drush_include_engine('package_handler', drush_get_option('package-handler', 'wget'));
-  drush_include_engine('version_control', drush_get_option('version-control', 'svn'));
 
   if (!$full_projects = func_get_args()) {
     $full_projects = array('drupal');
@@ -901,11 +955,15 @@ function drush_pm_download() {
           }
 
           if ($destination = pm_dl_destination($release['type'])) {
+            if (!$version_control = drush_pm_include_version_control($destination)) {
+              return FALSE;
+            }
             if (package_handler_install_project($project, $release, $destination)) {
+              $release['full_project_path'] = $destination .  $project;
               drush_log(dt("Project !project (!version) downloaded to !dest.",
                              array('!project' => $project, '!version' => $release['version'], '!dest' => $destination)), 'success');
-              drush_command_invoke_all('drush_pm_post_pm_install', $project, $release, $destination);
-              version_control_post_install($project, $release, $destination);
+              drush_command_invoke_all('drush_pm_post_install', $project, $release, $destination);
+              $version_control->post_install($release);
             }
           }
         }
Index: commands/pm/updatecode.pm.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/drush/commands/pm/updatecode.pm.inc,v
retrieving revision 1.1
diff -u -p -u -p -r1.1 updatecode.pm.inc
--- commands/pm/updatecode.pm.inc	8 Jan 2010 20:13:27 -0000	1.1
+++ commands/pm/updatecode.pm.inc	13 Jan 2010 02:17:22 -0000
@@ -84,7 +84,7 @@ function drush_pm_updatecode() {
   }
 
   // Offer to update to the identified releases
-  pm_update_packages($updateable);
+  return pm_update_packages($updateable);
 }
 
 /**
@@ -97,7 +97,7 @@ function drush_pm_updatecode() {
  */
 function pm_update_packages($releases) {
   drush_include_engine('package_handler', drush_get_option('package-handler', 'wget'));
-  drush_include_engine('version_control', drush_get_option('version-control', 'svn'));
+  $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
 
   drush_print(dt('Code updates will be made to the following projects:'));
   foreach($releases as $release) {
@@ -112,69 +112,56 @@ function pm_update_packages($releases) {
     drush_die('Aborting.');
   }
 
-  // Save the date to be used in the backup directory's path name.
-  $date = date('YmdHis');
-
   // Now we start the actual updating.
+  $updated = array();
   foreach($releases as $release) {
-    drush_log(dt('Starting to update !project code ...', array('!project' => $release['title'])));
-
-    $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
-
-    $source = $drupal_root .'/' . $release['path'];
+    drush_log(dt('Starting to update !project code at !dir...', array('!project' => $release['title'], '!dir' => $release['path'])));
     if (empty($release['path'])) {
-      drush_set_error('DRUSH_PM_UPDATING_NO_PROJECT_PATH', dt('Module !project path is not available, perhaps the module is enabled but has been deleted from disk.', array('!project' => $release['name'])));
-      continue;
+      return drush_set_error('DRUSH_PM_UPDATING_NO_PROJECT_PATH', dt('The !project project path is not available, perhaps the !type is enabled but has been deleted from disk.', array('!project' => $release['name'], '!type' => $release['project_type'])));
     }
-    if (!is_dir($source)) {
-      drush_set_error('DRUSH_PM_UPDATING_PATH_NOT_FOUND', dt('Module !project directory could not be found at !source, perhaps the module is enabled but has been deleted from disk.', array('!project' => $release['name'], '!source' => $source)));
-      continue;
+    $release['full_project_path'] = $drupal_root . '/' . $release['path'];
+    // Check that the directory exists, and is where we expect it to be.
+    if (stripos($release['path'], $release['project_type']) === FALSE || !is_dir($release['full_project_path'])) {
+      return drush_set_error('DRUSH_PM_UPDATING_PATH_NOT_FOUND', dt('The !project directory could not be found within the !types directory at !full_project_path, perhaps the project is enabled but has been deleted from disk.', array('!project' => $release['name'], '!type' => $release['project_type'], '!full_project_path' => $release['full_project_path'])));
     }
 
-    $skip_backup = version_control_is_versioned($source);
-    if (!$skip_backup) {
-      $backup_dir = drush_get_option('backup-dir', $drupal_root  . '/backup');
-      $backup_dir = rtrim($backup_dir, '/');
-      @drush_op('mkdir', $backup_dir, 0777);
-      $backup_dir .= '/modules';
-      @drush_op('mkdir', $backup_dir, 0777);
-      $backup_dir .= "/$date";
-      @drush_op('mkdir', $backup_dir, 0777);
-      $backup_target = $backup_dir . '/'. $release['name'];
-      if (!drush_op('rename', $source, $backup_target)) {
-        drush_die(dt('Failed to backup project directory !source to !backup_target', array('!source' => $source, '!backup_target' => $backup_target)));
-      }
+    // Check we have a version control system, and it clears it's pre-flight.
+    if (!$version_control = drush_pm_include_version_control($release['full_project_path'])) {
+      return FALSE;
     }
+    if (!$version_control->pre_update($release)) {
+      return FALSE;
+    }
+    // Add the project to a context so we can roll back if needed.
+    $updated[] = $release;
+    drush_set_context('DRUSH_PM_UPDATED', $updated);
 
-    // Install the new version.
-    // $basepath is the dir where the current module is installed. It's one dir up from the
-    // place of the project's info files.
-    $basepath = explode('/', $release['path']);
-    // move a directory up, so we can copy updated dir to parent
-    array_pop($basepath);
-    $project_parent_path = $drupal_root. '/' . implode('/', $basepath). '/';
-    if (!package_handler_update_project($release['name'], $release['releases'][$release['candidate_version']], $project_parent_path)) {
-      if (!$skip_backup) {
-        drush_set_error('DRUSH_PM_UPDATING_FAILED_BACKUP_RESTORE', dt('Updating project !project failed. Restoring previously installed version.', array('!project' => $release['name'])));
-        drush_op('rename', $backup_target, $source);
-      }
-      else {
-        drush_set_error('DRUSH_PM_UPDATING_FAILED_NO_BACKUP', dt('Updating project !project failed. Please revert to the previously installed version.', array('!project' => $release['name'])));
-      }
+    if (!package_handler_update_project($release['name'], $release['releases'][$release['candidate_version']], $release['full_project_path'])) {
+      return drush_set_error('DRUSH_PM_UPDATING_FAILED', dt('Updating project !project failed. Attempting to roll back to previously installed version.', array('!project' => $release['name'])));
     }
     else {
       drush_print(dt('Project !project was updated successfully. Installed version is now !version.', array('!project' => $release['name'], '!version' => $release['candidate_version'])));
-      drush_command_invoke_all('pm_post_update', $release['name'], $release['releases'][$release['candidate_version']], $project_parent_path);
-      version_control_post_update($release['name'], $release['releases'][$release['candidate_version']], $project_parent_path);
+      drush_command_invoke_all('pm_post_update', $release['name'], $release['releases'][$release['candidate_version']]);
+      $version_control->post_update($release);
     }
   }
-  if ($backup_dir) {
-    drush_log(dt("Backups were saved into the directory !backup_dir.", array('!backup_dir' => $backup_dir)), 'ok');
-  }
   // Clear the cache, since some modules could have moved around.
   drush_drupal_cache_clear_all();
 }
 
+function drush_pm_updatecode_rollback() {
+  $releases = array_reverse(drush_get_context('DRUSH_PM_UPDATED', array()));
+  foreach($releases as $release) {
+    drush_log(dt('Rolling back update of !project code ...', array('!project' => $release['title'])));
+
+    // Check we have a version control system, and it clears it's pre-flight.
+    if (!$version_control = drush_pm_include_version_control($release['path'])) {
+      return FALSE;
+    }
+    $version_control->rollback($release);
+  }
+}
+
 function pm_project_filter(&$releases, &$rows) {
   $updateable = array();
   foreach ($releases as $key => $release) {
Index: commands/pm/package_handler/cvs.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/drush/commands/pm/package_handler/cvs.inc,v
retrieving revision 1.6
diff -u -p -u -p -r1.6 cvs.inc
--- commands/pm/package_handler/cvs.inc	6 Dec 2009 12:53:38 -0000	1.6
+++ commands/pm/package_handler/cvs.inc	13 Jan 2010 02:17:22 -0000
@@ -13,22 +13,20 @@
  * @param $path The path to install the module to.
  */
 function package_handler_install_project($project, $info, $path = '.') {
-  drush_op('chdir', $path);
-
   drush_log("Downloading project $project ...");
 
-  $cvsparams = drush_get_option('cvsparams');
+  $destination = rtrim($path, '/') . '/' . $project;
 
   // Check it out.
-  drush_pm_cvs_checkout($project, $info, $cvsparams);
+  drush_pm_cvs($project, $info, $destination);
 
   if (!drush_get_context('DRUSH_SIMULATE')) {
-    if (is_dir($path . $project)) {
+    if (is_dir($destination)) {
       drush_log("Checking out " . $project . " was successful.");
       return TRUE;
     }
     else {
-      drush_set_error('DRUSH_PM_CVS_CHECKOUT_PROBLEMS', dt("Unable to check out !filename to !path from cvs.drupal.org", array('!filename' => $filename, '!path' => $path)));
+      drush_set_error('DRUSH_PM_CVS_CHECKOUT_PROBLEMS', dt("Unable to check out !project to !destination from cvs.drupal.org", array('!filename' => $project, '!destination' => $destination)));
       return FALSE;
     }
   }
@@ -39,39 +37,16 @@ function package_handler_install_project
  *
  * @param $project The short name of the drupal.org project
  * @param $info The details (fetched from drupal.org via xml-rpc)
- * @param $path The path to install the module to.
+ * @param $path The path of the module to update.
  */
 function package_handler_update_project($project, $info, $path = '.') {
-  drush_op('chdir', $path);
-
   drush_log("Updating project $project ...");
 
-  $cvsmethod = drush_get_option('cvsmethod');
-  if (empty($cvsmethod) && file_exists($project. '/.svn')) {
-    // If we have .svn files the default is to update in place
-    // this will try and merge any changes, which could break things
-    // but we assume anyone using SVN is competent enough to deal with this!
-    $cvsmethod = 'update';
-  }
 
-  $cvsparams = drush_get_option('cvsparams');
+  // Check out a fresh copy, or update an existing one.
+  drush_pm_cvs($project, $info, $path);
 
-  drush_log($cvsmethod);
-  drush_log(strpos(strtolower($cvsmethod), 'up'));
-  if (substr($cvsmethod, 0, 2) == 'up') {
-    // Update the working copy.
-    drush_op('chdir', $project);
-    if (!drush_shell_exec('cvs update -dP '. $cvsparams . ' -r '. $info['tag'])) {
-      drush_die("Unable to update $project from cvs.drupal.org.");
-    }
-    drush_op('chdir', '..');
-  }
-  else {
-    // Check it out.
-    drush_pm_cvs_checkout($project, $info, $cvsparams);
-  }
-
-  if (is_dir($path . $project) && !drush_get_context('DRUSH_SIMULATE')) {
+  if (is_dir($path) && !drush_get_context('DRUSH_SIMULATE')) {
     drush_log("Updating out " . $project . " was successful.");
     return TRUE;
   }
@@ -80,7 +55,13 @@ function package_handler_update_project(
   }
 }
 
-function drush_pm_cvs_checkout($project, $info, $cvsparams) {
+/**
+ * General CVS helper function
+ * @param $project The short name of the drupal.org project
+ * @param $info The details (fetched from drupal.org via xml-rpc)
+ * @param $path The path of the module to work on.
+ */
+function drush_pm_cvs($project, $info, $path) {
   // By default we assume a module
   $repos = 'drupal-contrib';
   $cvsdir = 'contributions/modules/';
@@ -103,10 +84,36 @@ function drush_pm_cvs_checkout($project,
       $repos = 'drupal';
   }
 
-  $cvscredentials = drush_get_option('cvscredentials', 'anonymous:anonymous');
+  $cvsmethod = drush_get_option('cvsmethod', '');
+  if (empty($cvsmethod) && file_exists($path . '/.svn')) {
+    // If we have .svn files the default is to update in place
+    // this will try and merge any changes, which could break things
+    // but we assume anyone using SVN is competent enough to deal with this!
+    // TODO: Make this work with BZR.
+    $cvsmethod = 'update';
+  }
+  else {
+    $cvsmethod = 'checkout';
+  }
+  
+  // CVS does not accept absolute paths, so generate a relative path.
+  $path = '.' . ltrim(str_replace(getcwd(), '', $path), '.');
+  
+  $cvsparts = array();
+  $cvsparts[] = '-z6';
+  $cvsparts[] = drush_get_option('cvsparams', '');
+  $cvsparts[] = '-d:pserver:' . drush_get_option('cvscredentials', 'anonymous:anonymous') . '@cvs.drupal.org:/cvs/' . $repos;
+  $cvsparts[] = $cvsmethod;
+  $cvsparts[] = '-r '. $info['tag'];
+  if ($cvsmethod == 'checkout') {
+    $cvsparts[] = '-d '. $path;
+    $cvsparts[] = $cvsdir . $project;
+  }
+  else {
+    $cvsparts[] = $path;
+  }
 
-  // TRUE means failure in the twilight zone.
-  if (drush_shell_exec('cvs -z6 '. $cvsparams ." -d:pserver:$cvscredentials@cvs.drupal.org:/cvs/" . $repos . ' checkout -d '. $project .' -r '. $info['tag'] .' '. $cvsdir . $project)) {
+  if (!drush_shell_exec('cvs ' . implode(' ', $cvsparts))) {
     drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', "Unable to checkout $project from cvs.drupal.org.");
   }
 }
Index: commands/pm/package_handler/wget.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/drush/commands/pm/package_handler/wget.inc,v
retrieving revision 1.8
diff -u -p -u -p -r1.8 wget.inc
--- commands/pm/package_handler/wget.inc	11 Jan 2010 05:22:02 -0000	1.8
+++ commands/pm/package_handler/wget.inc	13 Jan 2010 02:17:22 -0000
@@ -18,7 +18,6 @@ function package_handler_install_project
   // Get the filename...
   $filename = explode('/', $info['download_link']);
   $filename = array_pop($filename);
-  $fullpath = $path . $filename;
 
   // Set our directory to the download location.
   $olddir = getcwd();
@@ -71,8 +70,8 @@ function package_handler_install_project
 
   // 'drupal' project and profiles get untarred as drupal-6-10/foo instead of drupal/foo so must be excluded from test below.
   // Translations get untarred into the Drupal root, so there is no easy way to check.
-  if (!is_dir($path . $project) && !drush_get_context('DRUSH_SIMULATE') && $project != 'drupal' && !in_array($info['type'], array('translation', 'profile'))) {
-    return drush_set_error('DRUSH_PM_FILE_UNTAR_ERROR', "Downloaded file $filename couldn't be untarred correctly");
+  if (!is_dir($path . '/' . $project) && !drush_get_context('DRUSH_SIMULATE') && $project != 'drupal' && !in_array($info['type'], array('translation', 'profile'))) {
+    return drush_set_error('DRUSH_PM_FILE_UNTAR_ERROR', "Downloaded file $filename couldn't be untarred to $path/$project correctly");
   }
   else {
     return TRUE;
@@ -83,5 +82,9 @@ function package_handler_install_project
  * This is an alias of the install function, since they are identical
  */
 function package_handler_update_project($project, $info, $path = '.') {
-  return package_handler_install_project($project, $info, $path);
+  // Updates are passed the current project directory
+  // so we move up a directory so that the untar lands in the right place.
+  $path = explode('/', $path);
+  array_pop($path);
+  return package_handler_install_project($project, $info, implode('/', $path));
 }
Index: commands/pm/version_control/bzr.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/drush/commands/pm/version_control/bzr.inc,v
retrieving revision 1.1
diff -u -p -u -p -r1.1 bzr.inc
--- commands/pm/version_control/bzr.inc	8 Jan 2010 21:05:01 -0000	1.1
+++ commands/pm/version_control/bzr.inc	13 Jan 2010 02:17:22 -0000
@@ -5,124 +5,123 @@
  * @file Drush pm BZR extension
  */
 
-/**
- * Helper function: get the root of a repository path.
- *
- * @param $source
- *   The source path.
- * @return
- *   An absolute path to the repository root.
- */
-function bzr_get_repository_root($source) {
-  if (drush_shell_exec('bzr root %s', $source)) {
-    $output = drush_shell_exec_output();
-    return reset($output);
+class drush_pm_version_control_bzr implements drush_pm_version_control {
+  
+  /**
+   * Helper function: get the root of a repository path.
+   *
+   * @param $path
+   *   The path to check.
+   * @return
+   *   An absolute path to the repository root.
+   */
+  public function bzr_get_repository_root($path) {
+    if (drush_shell_exec('bzr root %s', $path)) {
+      $output = drush_shell_exec_output();
+      return reset($output);
+    }
   }
-}
 
-/**
- * Implementation of version_control_is_versioned().
- */
-function version_control_is_versioned($source) {
-  if (bzr_get_repository_root($source)) {
-    // Skip backup since we need all .bzr  directories. Assuming admin can use bzr revert if new project is bad.
-    drush_log(dt('Backup skipped because .bzr directory was detected. Use Bzr to revert if needed.'));
-    return TRUE;
+  /**
+   * Implementation of pre_update().
+   */
+  public function pre_update(&$release) {
+    // TODO: Add similar checks to SVN?
   }
-  return FALSE;
-}
 
-/**
- * Implementation of version_control_post_update().
- */
-function version_control_post_update($project, $info, $path = '.') {
-  if (version_control_is_versioned($path) && version_control_sync($project, $info, $path)) {
-    // Only attempt commit on a sucessful sync
-    version_control_commit($project, $info, $path);
+  /**
+   * Implementation of post_update().
+   */
+  public function post_update($release) {
+    if (is_versioned($release['full_project_path']) && sync($release)) {
+      // Only attempt commit on a sucessful sync
+      commit($release);
+    }
   }
-}
-
-/**
- * Implementation of version_control_post_install().
- */
-function version_control_post_install($project, $info, $path = '.') {
-  if (version_control_is_versioned($path) && version_control_sync($project, $info, $path)) {
-    // Only attempt commit on a sucessful sync
-    version_control_commit($project, $info, $path);
+  
+  /**
+   * Implementation of post_install().
+   */
+  public function post_install($release) {
+    if (is_versioned($release['full_project_path']) && sync($release)) {
+      // Only attempt commit on a sucessful sync
+      commit($release);
+    }
   }
-}
-
-/**
- * Automatically add any unversioned files to Bzr and remove any files
- * that have been deleted on the file system
- */
-function version_control_sync($project, $info, $path = '.') {
-  if (drush_get_option('bzrsync')) {
-    $errors = '';
-
-    // All paths returned by bzr status are relative to the repository root.
-    $root = bzr_get_repository_root($source);
-
-    if (drush_shell_exec('bzr status -S %s', $path . $project)) {
-      $output = drush_shell_exec_output();
-
-      // Parse the output of bzr status to get the list of added and removed
-      // files.
-      $new_files = array();
-      $removed_files = array();
-      foreach ($output as $line) {
-        if (preg_match('/^\? *(.*)/', $line, $matches)) {
-          $new_files[] = $root . '/' . $matches[1];
+  
+  /**
+   * Automatically add any unversioned files to Bzr and remove any files
+   * that have been deleted on the file system
+   */
+  private function sync($release) {
+    if (drush_get_option('bzrsync')) {
+      $errors = '';
+  
+      // All paths returned by bzr status are relative to the repository root.
+      $root = bzr_get_repository_root($release['full_project_path']);
+  
+      if (drush_shell_exec('bzr status -S %s', $release['full_project_path'])) {
+        $output = drush_shell_exec_output();
+  
+        // Parse the output of bzr status to get the list of added and removed
+        // files.
+        $new_files = array();
+        $removed_files = array();
+        foreach ($output as $line) {
+          if (preg_match('/^\? *(.*)/', $line, $matches)) {
+            $new_files[] = $root . '/' . $matches[1];
+          }
+          if (preg_match('/^\ D *(.*)/', $line, $matches)) {
+            $removed_files[] = $root . '/' . $matches[1];
+          }
         }
-        if (preg_match('/^\ D *(.*)/', $line, $matches)) {
-          $removed_files[] = $root . '/' . $matches[1];
+  
+        // Proceed adding new files.
+        foreach (array_chunk($new_files, 100) as $chunk) {
+          $chunk = array_map('escapeshellarg', $chunk);
+          if (!drush_shell_exec('bzr add ' . implode(' ', $chunk))) {
+            $errors .= implode("\n", drush_shell_exec_output());
+          }
         }
-      }
-
-      // Proceed adding new files.
-      foreach (array_chunk($new_files, 100) as $chunk) {
-        $chunk = array_map('escapeshellarg', $chunk);
-        if (!drush_shell_exec('bzr add ' . implode(' ', $chunk))) {
-          $errors .= implode("\n", drush_shell_exec_output());
+  
+        // Proceed removing old files.
+        foreach (array_chunk($removed_files, 100) as $chunk) {
+          $chunk = array_map('escapeshellarg', $chunk);
+          if (!drush_shell_exec('bzr remove ' . implode(' ', $chunk))) {
+            $errors .= implode("\n", drush_shell_exec_output());
+          }
         }
-      }
-
-      // Proceed removing old files.
-      foreach (array_chunk($removed_files, 100) as $chunk) {
-        $chunk = array_map('escapeshellarg', $chunk);
-        if (!drush_shell_exec('bzr remove ' . implode(' ', $chunk))) {
-          $errors .= implode("\n", drush_shell_exec_output());
+  
+        if (!empty($errors)) {
+          return drush_set_error('DRUSH_PM_BZR_SYNC_PROBLEMS', dt("Problems were encountered adding or removing files to/from Bazaar.\nThe specific errors are below:\n!errors", array('!errors' => $errors)));
         }
       }
-
-      if (!empty($errors)) {
-        return drush_set_error('DRUSH_PM_BZR_SYNC_PROBLEMS', dt("Problems were encountered adding or removing files to/from Bazaar.\nThe specific errors are below:\n!errors", array('!errors' => $errors)));
+      else {
+        return drush_set_error('DRUSH_PM_BZR_NOT_FOUND', dt("Drush was unable to get the bzr status. Check that you have Bazaar \ninstalled and that the site is a Bazaar working copy.\nThe specific errors are below:\n!errors", array('!errors' => implode("\n", drush_shell_exec_output()))));
       }
+      return TRUE;
     }
-    else {
-      return drush_set_error('DRUSH_PM_BZR_NOT_FOUND', dt("Drush was unable to get the bzr status. Check that you have Bazaar \ninstalled and that the site is a Bazaar working copy.\nThe specific errors are below:\n!errors", array('!errors' => implode("\n", drush_shell_exec_output()))));
-    }
-    return TRUE;
   }
-}
-
-/**
- * Automatically commit changes to the repository
- */
-function version_control_commit($project, $info, $path = '.') {
-  if (drush_get_option('bzrcommit')) {
-    $message = drush_get_option('bzrmessage');
-    if (empty($message)) {
-      $message = dt("Drush automatic commit: \n") . implode(' ', $_SERVER['argv']);
-    }
-    if (drush_shell_exec('bzr commit -m %s %s', $message, $path . $project)) {
-      drush_log(dt('Project committed to Bazaar successfully'), 'ok');
+  
+  /**
+   * Automatically commit changes to the repository
+   */
+  private function commit($release) {
+    if (drush_get_option('bzrcommit')) {
+      $message = drush_get_option('bzrmessage');
+      if (empty($message)) {
+        $message = dt("Drush automatic commit: \n") . implode(' ', $_SERVER['argv']);
+      }
+      if (drush_shell_exec('bzr commit -m %s %s', $message, $release['full_project_path'])) {
+        drush_log(dt('Project committed to Bazaar successfully'), 'ok');
+      }
+      else {
+        drush_set_error('DRUSH_PM_BZR_COMMIT_PROBLEMS', dt("Problems were encountered committing your changes to Bazaar.\nThe specific errors are below:\n!errors", array('!errors' => implode("\n", drush_shell_exec_output()))));
+      }
     }
     else {
-      drush_set_error('DRUSH_PM_BZR_COMMIT_PROBLEMS', dt("Problems were encountered committing your changes to Bazaar.\nThe specific errors are below:\n!errors", array('!errors' => implode("\n", drush_shell_exec_output()))));
+      drush_print(dt("You should consider committing the new code to your Bazaar repository.\nIf this version becomes undesireable, use Bazaar to roll back."));
     }
   }
-  else {
-    drush_print(dt("You should consider committing the new code to your Bazaar repository.\nIf this version becomes undesireable, use Bazaar to roll back."));
-  }
-}
+
+}
\ No newline at end of file
Index: commands/pm/version_control/svn.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/drush/commands/pm/version_control/svn.inc,v
retrieving revision 1.6
diff -u -p -u -p -r1.6 svn.inc
--- commands/pm/version_control/svn.inc	18 Aug 2009 16:37:46 -0000	1.6
+++ commands/pm/version_control/svn.inc	13 Jan 2010 02:17:22 -0000
@@ -5,87 +5,120 @@
  * @file Drush pm SVN extension
  */
 
-/**
- * Implementation of version_control_is_versioned().
- */
-function version_control_is_versioned($source) {
-  if (file_exists($source. '/.svn')) {
-    // Skip backup since we need all .svn  directories. Assuming admin can use svn revert if new project is bad.
-    drush_log(dt('Backup skipped because .svn directory was detected. Use Subversion to revert if needed.'));
+class drush_pm_version_control_svn implements drush_pm_version_control {
+
+  /**
+   * Implementation of pre_update().
+   */
+  public function pre_update(&$release) {
+    // Check the project directory looks clean
+    if (drush_shell_exec('svn status '. drush_get_option('svnstatusparams') .' '. $release['full_project_path'])) {
+      $output = drush_shell_exec_output();
+      if (!empty($output)) {
+        return drush_set_error('DRUSH_PM_SVN_LOCAL_CHANGES', dt("The SVN working copy at !path appears to have uncommmitted changes (see below). Please commit or revert these changes before continuing:\n!output", array('!path' => $release['full_project_path'], '!output' => implode("\n", $output))));      
+      }
+    }
+    else {
+      return drush_set_error('DRUSH_PM_SVN_NOT_FOUND', dt("Drush was unable to get the svn status on !path. Check that you have Subversion \ninstalled and that this directory is a subversion working copy.\nThe specific errors are below:\n!errors", array('!path' => $release['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output()))));
+    }
+    // Check for incoming updates
+    if (drush_shell_exec('svn status -u '. drush_get_option('svnstatusparams') .' '. $release['full_project_path'])) {
+      $output = preg_grep('/^Status against revision:\s*[0-9]*$/', drush_shell_exec_output(), PREG_GREP_INVERT);
+      if (!empty($output)) {
+        return drush_set_error('DRUSH_PM_SVN_REMOTE_CHANGES', dt("The SVN working copy at !path appears to be out of date with the repository (see below). Please run 'svn update' to pull down changes before continuing:\n!output", array('!path' => $release['full_project_path'], '!output' => implode("\n", $output))));      
+      }
+    }
+    else {
+      return drush_set_error('DRUSH_PM_SVN_NOT_FOUND', dt("Drush was unable to get the svn remote status on !path. Check that you have Subversion \ninstalled, that this directory is a subversion working copy and that you have connectivity to the repository.\nThe specific errors are below:\n!errors", array('!path' => $release['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output()))));
+    }
     return TRUE;
   }
-  return FALSE;
-}
-
-/**
- * Implementation of version_control_post_update().
- */
-function version_control_post_update($project, $info, $path = '.') {
-  if (version_control_is_versioned($path) && version_control_sync($project, $info, $path)) {
-    // Only attempt commit on a sucessful sync
-    version_control_commit($project, $info, $path);
+  
+  /**
+   * Implementation of rollback().
+   */
+  public function rollback($release) {
+    if (drush_shell_exec('svn revert '. drush_get_option('svnrevertparams') .' '. $release['full_project_path'])) {
+      $output = drush_shell_exec_output();
+      if (!empty($output)) {
+        return drush_set_error('DRUSH_PM_SVN_LOCAL_CHANGES', dt("The SVN working copy at !path appears to have uncommmitted changes (see below). Please commit or revert these changes before continuing:\n!output", array('!path' => $release['full_project_path'], '!output' => implode("\n", $output))));      
+      }
+    }
+    else {
+      return drush_set_error('DRUSH_PM_SVN_NOT_FOUND', dt("Drush was unable to get the svn status on !path. Check that you have Subversion \ninstalled and that this directory is a subversion working copy.\nThe specific errors are below:\n!errors", array('!path' => $release['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output()))));
+    }
   }
-}
-
-/**
- * Implementation of version_control_post_install().
- */
-function version_control_post_install($project, $info, $path = '.') {
-  if (version_control_is_versioned($path) && version_control_sync($project, $info, $path)) {
-    // Only attempt commit on a sucessful sync
-    version_control_commit($project, $info, $path);
+  
+  /**
+   * Implementation of post_update().
+   */
+  public function post_update($release) {
+    if ($this->sync($release)) {
+      // Only attempt commit on a sucessful sync
+      $this->commit($release);
+    }
   }
-}
-
-/**
- * Automatically add any unversioned files to Subversion and remove any files
- * that have been deleted on the file system
- */
-function version_control_sync($project, $info, $path = '.') {
-  if (drush_get_option('svnsync')) {
-    $errors = '';
-    if (drush_shell_exec('svn status '. drush_get_option('svnstatusparams') .' '. $path.$project)) {
-      $output = drush_shell_exec_output();
-      foreach ($output as $line) {
-        if (preg_match('/^\? *(.*)/', $line, $matches)) {
-          if (!drush_shell_exec('svn add '. drush_get_option('svnaddparams') .' '. $matches[1])) {
-            $errors .= implode("\n", drush_shell_exec_output());
+  
+  /**
+   * Implementation of post_install().
+   */
+  public function post_install($release) {
+    if ($this->sync($release)) {
+      // Only attempt commit on a sucessful sync
+      $this->commit($release);
+    }
+  }
+  
+  /**
+   * Automatically add any unversioned files to Subversion and remove any files
+   * that have been deleted on the file system
+   */
+  private function sync($release) {
+    if (drush_get_option('svnsync')) {
+      $errors = '';
+      if (drush_shell_exec('svn status '. drush_get_option('svnstatusparams') .' '. $release['full_project_path'])) {
+        $output = drush_shell_exec_output();
+        foreach ($output as $line) {
+          if (preg_match('/^\? *(.*)/', $line, $matches)) {
+            if (!drush_shell_exec('svn add '. drush_get_option('svnaddparams') .' '. $matches[1])) {
+              $errors .= implode("\n", drush_shell_exec_output());
+            }
           }
-        }
-        if (preg_match('/^\! *(.*)/', $line, $matches)) {
-          if (!drush_shell_exec('svn remove '. drush_get_option('svnremoveparams') .' '. $matches[1])) {
-            $errors .= implode("\n", drush_shell_exec_output());
+          if (preg_match('/^\! *(.*)/', $line, $matches)) {
+            if (!drush_shell_exec('svn remove '. drush_get_option('svnremoveparams') .' '. $matches[1])) {
+              $errors .= implode("\n", drush_shell_exec_output());
+            }
           }
         }
+        if (!empty($errors)) {
+          return drush_set_error('DRUSH_PM_SVN_SYNC_PROBLEMS', dt("Problems were encountered adding or removing files to/from this SVN working copy.\nThe specific errors are below:\n!errors", array('!errors' => $errors)));
+        }
       }
-      if (!empty($errors)) {
-        return drush_set_error('DRUSH_PM_SVN_SYNC_PROBLEMS', dt("Problems were encountered adding or removing files to/from subversion.\nThe specific errors are below:\n!errors", array('!errors' => $errors)));
+      else {
+        return drush_set_error('DRUSH_PM_SVN_NOT_FOUND', dt("Drush was unable to get the svn status on !path. Check that you have Subversion \ninstalled and that this directory is a subversion working copy.\nThe specific errors are below:\n!errors", array('!path' => $release['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output()))));
       }
+      return TRUE;
     }
-    else {
-      return drush_set_error('DRUSH_PM_SVN_NOT_FOUND', dt("Drush was unable to get the svn status. Check that you have Subversion \ninstalled and that the site is a subversion working copy.\nThe specific errors are below:\n!errors", array('!errors' => implode("\n", drush_shell_exec_output()))));
-    }
-    return TRUE;
   }
-}
-
-/**
- * Automatically commit changes to the repository
- */
-function version_control_commit($project, $info, $path = '.') {
-  if (drush_get_option('svncommit')) {
-    $message = drush_get_option('svnmessage');
-    if (empty($message)) {
-      $message = dt("Drush automatic commit: \n") . implode(' ', $_SERVER['argv']);
-    }
-    if (drush_shell_exec('svn commit '. drush_get_option('svncommitparams') .' -m "'. $message .'" '. $path.$project)) {
-      drush_log(dt('Project committed to Subversion successfully'), 'ok');
+  
+  /**
+   * Automatically commit changes to the repository
+   */
+  private function commit($release) {
+    if (drush_get_option('svncommit')) {
+      $message = drush_get_option('svnmessage');
+      if (empty($message)) {
+        $message = dt("Drush automatic commit: \n") . implode(' ', $_SERVER['argv']);
+      }
+      if (drush_shell_exec('svn commit '. drush_get_option('svncommitparams') .' -m "'. $message .'" '. $release['full_project_path'])) {
+        drush_log(dt('Project committed to Subversion successfully'), 'ok');
+      }
+      else {
+        drush_set_error('DRUSH_PM_SVN_COMMIT_PROBLEMS', dt("Problems were encountered committing your changes to Subversion.\nThe specific errors are below:\n!errors", array('!errors' => implode("\n", drush_shell_exec_output()))));
+      }
     }
     else {
-      drush_set_error('DRUSH_PM_SVN_COMMIT_PROBLEMS', dt("Problems were encountered committing your changes to Subversion.\nThe specific errors are below:\n!errors", array('!errors' => implode("\n", drush_shell_exec_output()))));
+      drush_print(dt("You should consider committing the new code to your Subversion repository.\nIf this version becomes undesireable, use Subversion to roll back."));
     }
   }
-  else {
-    drush_print(dt("You should consider committing the new code to your Subversion repository.\nIf this version becomes undesireable, use Subversion to roll back."));
-  }
-}
+}
\ No newline at end of file
Index: commands/pm/version_control/backup.inc
===================================================================
RCS file: commands/pm/version_control/backup.inc
diff -N commands/pm/version_control/backup.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ commands/pm/version_control/backup.inc	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,60 @@
+<?php
+// $Id$
+
+/**
+ * @file Drush pm directory copy backup extension
+ */
+
+class drush_pm_version_control_backup implements drush_pm_version_control {
+
+  /**
+   * Implementation of pre_update().
+   */
+  public function pre_update(&$release) {
+    $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
+  
+    // Save the date to be used in the backup directory's path name.
+    $date = date('YmdHis');
+    
+    $backup_dir = drush_get_option('backup-dir', $drupal_root  . '/backup');
+    $backup_dir = rtrim($backup_dir, '/');
+    @drush_op('mkdir', $backup_dir, 0777);
+    $backup_dir .= '/modules';
+    @drush_op('mkdir', $backup_dir, 0777);
+    $backup_dir .= "/$date";
+    @drush_op('mkdir', $backup_dir, 0777);
+    $backup_target = $backup_dir . '/'. $release['name'];
+    // Save for rollback or notifications.
+    $release['backup_target'] = $backup_target;
+    if (!drush_op('rename', $release['full_project_path'], $backup_target)) {
+      return drush_set_error('DRUSH_PM_BACKUP_FAILED', dt('Failed to backup project directory !project to !backup_target', array('!project' => $release['full_project_path'], '!backup_target' => $backup_target)));
+    }
+    return TRUE;
+  }
+  
+  /**
+   * Implementation of rollback().
+   */
+  public function rollback($release) {
+    if (drush_op('rename', $release['backup_target'], $release['full_project_path']) && is_dir($release['full_project_path'])) {
+      return drush_log(dt("Backups were restored successfully."), 'ok');
+    }
+    return drush_set_error('DRUSH_PM_BACKUP_ROLLBACK_FAILED', dt('Could not restore backup and rollback from failed upgrade. You will need to resolve manually.'));
+  }
+  
+  /**
+   * Implementation of hook_drush_pm_post_update().
+   */
+  public function post_update($release) {
+    if ($release['backup_target']) {
+      drush_log(dt("Backups were saved into the directory !backup_target.", array('!backup_target' => $release['backup_target'])), 'ok');
+    }
+  }
+  
+  /**
+   * Implementation of hook_drush_pm_post_install().
+   */
+  public function post_install($release) {
+   // NOOP
+  }
+}
\ No newline at end of file
