Index: modules/book/book.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/book/book.test,v
retrieving revision 1.13
diff -u -p -r1.13 book.test
--- modules/book/book.test	20 Jul 2009 18:51:32 -0000	1.13
+++ modules/book/book.test	7 Aug 2009 15:49:04 -0000
@@ -180,10 +180,10 @@ class BookTestCase extends DrupalWebTest
       $this->drupalPost('node/add/book', $edit, t('Change book (update list of parents)'));
 
       $edit['book[plid]'] = $parent;
-      $this->drupalPost(NULL, $edit, t('Save'));
+      $this->drupalPost(NULL, $edit, t('Publish'));
     }
     else {
-      $this->drupalPost('node/add/book', $edit, t('Save'));
+      $this->drupalPost('node/add/book', $edit, t('Publish'));
     }
 
     // Check to make sure the book node was created.
Index: modules/dblog/dblog.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/dblog/dblog.test,v
retrieving revision 1.24
diff -u -p -r1.24 dblog.test
--- modules/dblog/dblog.test	31 Jul 2009 19:01:01 -0000	1.24
+++ modules/dblog/dblog.test	7 Aug 2009 15:49:10 -0000
@@ -262,7 +262,7 @@ class DBLogTestCase extends DrupalWebTes
     // Create node using form to generate add content event (which is not triggered by drupalCreateNode).
     $edit = $this->getContent($type);
     $title = $edit['title'];
-    $this->drupalPost('node/add/' . $type, $edit, t('Save'));
+    $this->drupalPost('node/add/' . $type, $edit, t('Publish'));
     $this->assertResponse(200);
     // Retrieve node object.
     $node = $this->drupalGetNodeByTitle($title);
Index: modules/field/modules/field_sql_storage/field_sql_storage.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/modules/field_sql_storage/field_sql_storage.module,v
retrieving revision 1.17
diff -u -p -r1.17 field_sql_storage.module
--- modules/field/modules/field_sql_storage/field_sql_storage.module	15 Jul 2009 17:55:18 -0000	1.17
+++ modules/field/modules/field_sql_storage/field_sql_storage.module	7 Aug 2009 15:49:10 -0000
@@ -503,4 +503,4 @@ function field_sql_storage_field_storage
       ->condition('bundle', $bundle_old)
       ->execute();
   }
-}
\ No newline at end of file
+}
Index: modules/forum/forum.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/forum/forum.test,v
retrieving revision 1.27
diff -u -p -r1.27 forum.test
--- modules/forum/forum.test	3 Aug 2009 03:04:33 -0000	1.27
+++ modules/forum/forum.test	7 Aug 2009 15:49:10 -0000
@@ -243,8 +243,7 @@ class ForumTestCase extends DrupalWebTes
     // Instead, the post variables seem to pick up the first non-blank value in the select list.
 
     // Create forum topic.
-//    $this->drupalPost('node/add/forum/' . $forum['tid'], $edit, t('Save'));
-    $this->drupalPost('node/add/forum/', $edit, t('Save'));
+    $this->drupalPost('node/add/forum/', $edit, t('Publish'));
     $type = t('Forum topic');
     if ($container) {
       $this->assertNoRaw(t('@type %title has been created.', array('@type' => $type, '%title' => $title)), t('Forum topic was not created'));
Index: modules/node/node.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.admin.inc,v
retrieving revision 1.60
diff -u -p -r1.60 node.admin.inc
--- modules/node/node.admin.inc	30 Jul 2009 19:24:21 -0000	1.60
+++ modules/node/node.admin.inc	7 Aug 2009 15:49:10 -0000
@@ -75,6 +75,7 @@ function node_filters() {
     'options' => array(
       'status-1' => t('published'),
       'status-0' => t('not published'),
+      'draft-1' => t('pending drafts'),
       'promote-1' => t('promoted'),
       'promote-0' => t('not promoted'),
       'sticky-1' => t('sticky'),
@@ -128,6 +129,9 @@ function node_build_filter_query(SelectQ
       case 'language':
         $query->condition('n.' . $key, $value);
         break;
+      case 'draft':
+        $query->innerJoin('node_revision', 'v', 'n.nid = v.nid AND n.timestamp < v.timestamp');
+        break;
     }
   }
 }
@@ -599,4 +603,4 @@ function node_multiple_delete_confirm_su
 function node_modules_installed($modules) {
   // Clear node type cache for node permissions.
   node_type_clear();
-}
\ No newline at end of file
+}
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.1092
diff -u -p -r1.1092 node.module
--- modules/node/node.module	5 Aug 2009 15:58:34 -0000	1.1092
+++ modules/node/node.module	7 Aug 2009 15:49:12 -0000
@@ -952,10 +952,14 @@ function node_save($node) {
   if (empty($node->created)) {
     $node->created = REQUEST_TIME;
   }
-  // The changed timestamp is always updated for bookkeeping purposes (revisions, searching, ...)
-  $node->changed = REQUEST_TIME;
-
-  $node->timestamp = REQUEST_TIME;
+  // The changed timestamp is always updated for bookkeeping purposes.
+  if (empty($node->timestamp_changed)) {
+    $node->changed = REQUEST_TIME;
+  }
+  // Set the revision timestamp.
+  if (empty($node->timestamp_revision)) {
+    $node->timestamp = REQUEST_TIME;
+  }
   $update_node = TRUE;
 
   // Generate the node table query and the node_revisions table query.
@@ -1180,7 +1184,6 @@ function node_show($node, $message = FAL
   if ($message) {
     drupal_set_title(t('Revision of %title from %date', array('%title' => $node->title, '%date' => format_date($node->revision_timestamp))), PASS_THROUGH);
   }
-
   // Update the history table, stating that this user viewed this node.
   node_tag_new($node->nid);
 
Index: modules/node/node.pages.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.pages.inc,v
retrieving revision 1.73
diff -u -p -r1.73 node.pages.inc
--- modules/node/node.pages.inc	4 Aug 2009 06:44:48 -0000	1.73
+++ modules/node/node.pages.inc	7 Aug 2009 15:49:13 -0000
@@ -232,9 +232,8 @@ function node_form(&$form_state, $node) 
     '#weight' => 95,
   );
   $form['options']['status'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Published'),
-    '#default_value' => $node->status,
+    '#type' => 'value',
+    '#value' => $node->status,
   );
   $form['options']['promote'] = array(
     '#type' => 'checkbox',
@@ -258,12 +257,19 @@ function node_form(&$form_state, $node) 
   // Add the buttons.
   $form['buttons'] = array();
   $form['buttons']['#weight'] = 100;
-  $form['buttons']['submit'] = array(
+  $form['buttons']['publish'] = array(
     '#type' => 'submit',
     '#access' => variable_get('node_preview_' . $node->type, 1) != DRUPAL_REQUIRED || (!form_get_errors() && isset($form_state['node_preview'])),
-    '#value' => t('Save'),
+    '#value' => isset($node->nid) && $node->status ? t('Save') : t('Publish'),
     '#weight' => 5,
-    '#submit' => array('node_form_submit'),
+    '#submit' => array('node_form_publish_submit'),
+  );
+  $form['buttons']['draft'] = array(
+    '#access' => user_access('access revisions') && user_access('publish revisions') || user_access('administer nodes'),
+    '#type' => 'submit',
+    '#value' => t('Save as draft'),
+    '#weight' => 6,
+    '#submit' => array('node_form_draft_submit'),
   );
   $form['buttons']['preview'] = array(
     '#access' => variable_get('node_preview_' . $node->type, 1) != DRUPAL_DISABLED,
@@ -272,6 +278,13 @@ function node_form(&$form_state, $node) 
     '#weight' => 10,
     '#submit' => array('node_form_build_preview'),
   );
+  $form['buttons']['unpublish'] = array(
+    '#access' => !empty($node->status) && user_access('administer nodes'),
+    '#type' => 'submit',
+    '#value' => t('Unpublish'),
+    '#weight' => 11,
+    '#submit' => array('node_form_unpublish_submit'),
+  );
   if (!empty($node->nid) && node_access('delete', $node)) {
     $form['buttons']['delete'] = array(
       '#type' => 'submit',
@@ -395,6 +408,77 @@ function theme_node_preview($node) {
   return $output;
 }
 
+/**
+ * Submit handler for saving as draft.
+ */
+function node_form_draft_submit($form, &$form_state) {
+  $node = node_form_submit_build_node($form, $form_state);
+
+  // Always create a new revision if saving as a draft.
+  $node->revision = TRUE;
+  if (empty($node->nid)) {
+    $node->status = FALSE;
+  }
+  else {
+    // Set $node->changed to the current value so that this draft does not
+    // affect the 'updated' mark on node listings.
+    $original = node_load($nid);
+    $node->timestamp_changed = $original->changed;
+  }
+  node_save($node);
+
+  // If the node is published, and there is already an existing revision
+  // we need to ensure that the data from the current active revision is used
+  // in both the {node} and field API tables.
+  if ($node->status && $node->old_vid) {
+    // Since we have already saved a new revision, load the original again.
+    $current = node_load($node->nid, $node->old_vid);
+
+    // Set taxonomy terms to tids to resolve inconsistencies between loaded
+    // and saved data.
+    if (module_exists('taxonomy')) {
+      $current->taxonomy = array_keys($current->taxonomy);
+    }
+    $current->revision = TRUE;
+
+    // Set both the updated and revision timestamps to those of the revision
+    // we are re-saving to ensure it is listed correctly on the revisions tab.
+    $current->timestamp_updated = $current->timestamp_revision = $original->changed;
+    node_save($current);
+
+    // We now have a duplicate copy of this revision, so delete the old one.
+    // @todo, create node_delete_revision().
+    db_delete('node_revision')
+      ->condition('nid', $original->nid)
+      ->condition('vid', $original->vid)
+      ->execute();
+    module_invoke_all('node_delete_revision', $original);
+    field_attach_delete_revision('node', $original);
+  }
+
+  if ($node->nid) {
+    unset($form_state['rebuild']);
+    $form_state['nid'] = $node->nid;
+    $form_state['redirect'] = 'node/' . $node->nid;
+  }
+}
+
+/**
+ * Submit handler for saving as published.
+ */
+function node_form_publish_submit($form, &$form_state) {
+  $form_state['values']['status'] = 1;
+  node_form_submit($form, $form_state);
+}
+
+/**
+ * Submit handler for saving as unpublished.
+ */
+function node_form_unpublish_submit($form, &$form_state) {
+  $form_state['values']['status'] = 0;
+  node_form_submit($form, $form_state);
+}
+
 function node_form_submit($form, &$form_state) {
   global $user;
 
@@ -507,7 +591,8 @@ function node_revision_overview($node) {
       $row[] = t('!date by !username', array('!date' => l(format_date($revision->timestamp, 'small'), "node/$node->nid/revisions/$revision->vid/view"), '!username' => theme('username', $revision)))
                . (($revision->log != '') ? '<p class="revision-log">' . filter_xss($revision->log) . '</p>' : '');
       if ($revert_permission) {
-        $operations[] = l(t('revert'), "node/$node->nid/revisions/$revision->vid/revert");
+        $title = $node->changed > $revision->timestamp ? t('revert') : t('publish');
+        $operations[] = l($title, "node/$node->nid/revisions/$revision->vid/revert");
       }
       if ($delete_permission) {
         $operations[] = l(t('delete'), "node/$node->nid/revisions/$revision->vid/delete");
@@ -530,7 +615,7 @@ function node_revision_overview($node) {
  */
 function node_revision_revert_confirm($form_state, $node_revision) {
   $form['#node_revision'] = $node_revision;
-  return confirm_form($form, t('Are you sure you want to revert to the revision from %revision-date?', array('%revision-date' => format_date($node_revision->revision_timestamp))), 'node/' . $node_revision->nid . '/revisions', '', t('Revert'), t('Cancel'));
+  return confirm_form($form, t('Are you sure you want to publish the revision from %revision-date?', array('%revision-date' => format_date($node_revision->revision_timestamp))), 'node/' . $node_revision->nid . '/revisions', '', t('Revert'), t('Cancel'));
 }
 
 function node_revision_revert_confirm_submit($form, &$form_state) {
Index: modules/node/node.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.test,v
retrieving revision 1.38
diff -u -p -r1.38 node.test
--- modules/node/node.test	28 Jul 2009 19:18:06 -0000	1.38
+++ modules/node/node.test	7 Aug 2009 15:49:13 -0000
@@ -191,7 +191,7 @@ class PageEditTestCase extends DrupalWeb
     $edit = array();
     $edit['title'] = $this->randomName(8);
     $edit[$body_key] = $this->randomName(16);
-    $this->drupalPost('node/add/page', $edit, t('Save'));
+    $this->drupalPost('node/add/page', $edit, t('Publish'));
 
     // Check that the node exists in the database.
     $node = $this->drupalGetNodeByTitle($edit['title']);
@@ -312,7 +312,7 @@ class PageCreationTestCase extends Drupa
     $edit = array();
     $edit['title'] = $this->randomName(8);
     $edit['body[0][value]'] = $this->randomName(16);
-    $this->drupalPost('node/add/page', $edit, t('Save'));
+    $this->drupalPost('node/add/page', $edit, t('Publish'));
 
     // Check that the page has been created.
     $this->assertRaw(t('!post %title has been created.', array('!post' => 'Page', '%title' => $edit['title'])), t('Page created.'));
@@ -501,7 +501,7 @@ class NodePostSettingsTestCase extends D
     $edit = array();
     $edit['title'] = $this->randomName(8);
     $edit['body[0][value]'] = $this->randomName(16);
-    $this->drupalPost('node/add/page', $edit, t('Save'));
+    $this->drupalPost('node/add/page', $edit, t('Publish'));
 
     // Check that the post information is displayed.
     $node = $this->drupalGetNodeByTitle($edit['title']);
@@ -522,7 +522,7 @@ class NodePostSettingsTestCase extends D
     $edit = array();
     $edit['title'] = $this->randomName(8);
     $edit['body[0][value]'] = $this->randomName(16);
-    $this->drupalPost('node/add/page', $edit, t('Save'));
+    $this->drupalPost('node/add/page', $edit, t('Publish'));
 
     // Check that the post information is displayed.
     $node = $this->drupalGetNodeByTitle($edit['title']);
Index: modules/path/path.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/path/path.test,v
retrieving revision 1.16
diff -u -p -r1.16 path.test
--- modules/path/path.test	14 Jul 2009 10:22:17 -0000	1.16
+++ modules/path/path.test	7 Aug 2009 15:49:14 -0000
@@ -214,7 +214,7 @@ class PathLanguageTestCase extends Drupa
     $edit['title'] = $this->randomName();
     $edit['body[0][value]'] = $this->randomName();
     $edit['path'] = $this->randomName();
-    $this->drupalPost(NULL, $edit, t('Save'));
+    $this->drupalPost(NULL, $edit, t('Publish'));
 
     // Clear the path lookup cache.
     drupal_lookup_path('wipe');
Index: modules/poll/poll.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/poll/poll.test,v
retrieving revision 1.21
diff -u -p -r1.21 poll.test
--- modules/poll/poll.test	20 Jul 2009 18:51:33 -0000	1.21
+++ modules/poll/poll.test	7 Aug 2009 15:49:14 -0000
@@ -44,7 +44,7 @@ class PollTestCase extends DrupalWebTest
       list($edit, $index) = $this->_pollGenerateEdit($title, $choices, $index);
     }
 
-    $this->drupalPost(NULL, $edit, t('Save'));
+    $this->drupalPost(NULL, $edit, t('Publish'));
     $node = $this->drupalGetNodeByTitle($title);
     $this->assertText(t('@type @title has been created.', array('@type' => node_type_get_name('poll'), '@title' => $title)), 'Poll has been created.');
     $this->assertTrue($node->nid, t('Poll has been found in the database.'));
Index: modules/taxonomy/taxonomy.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.test,v
retrieving revision 1.43
diff -u -p -r1.43 taxonomy.test
--- modules/taxonomy/taxonomy.test	4 Aug 2009 06:50:07 -0000	1.43
+++ modules/taxonomy/taxonomy.test	7 Aug 2009 15:49:24 -0000
@@ -455,7 +455,7 @@ class TaxonomyTermTestCase extends Taxon
     $edit['title'] = $this->randomName();
     $edit['body[0][value]'] = $this->randomName();
     $edit['taxonomy[' . $this->vocabulary->vid . ']'] = $term1->tid;
-    $this->drupalPost('node/add/article', $edit, t('Save'));
+    $this->drupalPost('node/add/article', $edit, t('Publish'));
 
     // Check that the term is displayed when the node is viewed.
     $node = $this->drupalGetNodeByTitle($edit['title']);
@@ -496,7 +496,7 @@ class TaxonomyTermTestCase extends Taxon
     // free-tagging field created by the default profile.
     $edit['taxonomy[tags][' . $this->vocabulary->vid . ']'] =  implode(', ', $terms);
     $edit['body[0][value]'] = $this->randomName();
-    $this->drupalPost('node/add/article', $edit, t('Save'));
+    $this->drupalPost('node/add/article', $edit, t('Publish'));
     $this->assertRaw(t('@type %title has been created.', array('@type' => t('Article'), '%title' => $edit['title'])), t('The node was created successfully'));
     foreach ($terms as $term) {
       $this->assertText($term, t('The term was saved and appears on the node page'));
Index: modules/trigger/trigger.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/trigger/trigger.test,v
retrieving revision 1.15
diff -u -p -r1.15 trigger.test
--- modules/trigger/trigger.test	28 Jul 2009 19:18:08 -0000	1.15
+++ modules/trigger/trigger.test	7 Aug 2009 15:49:25 -0000
@@ -39,8 +39,14 @@ class TriggerContentTestCase extends Dru
       $edit = array();
       $edit['title'] = '!SimpleTest test node! ' . $this->randomName(10);
       $edit['body[0][value]'] = '!SimpleTest test body! ' . $this->randomName(32) . ' ' . $this->randomName(32);
-      $edit[$info['property']] = !$info['expected'];
-      $this->drupalPost('node/add/page', $edit, t('Save'));
+      if ($action == 'node_publish_action' || $action == 'node_unpublish_action') {
+        $button = $info['button'];
+      }
+      else {
+        $edit[$info['property']] = !$info['expected'];
+      }
+      $button = isset($button) ? $button : t('Save');
+      $this->drupalPost('node/add/page', $edit, $button);
       // Make sure the text we want appears.
       $this->assertRaw(t('!post %title has been created.', array('!post' => 'Page', '%title' => $edit['title'])), t('Make sure the page has actually been created'));
       // Action should have been fired.
@@ -79,11 +85,13 @@ class TriggerContentTestCase extends Dru
         'property' => 'status',
         'expected' => 1,
         'name' => t('publish post'),
+        'button' => t('Publish'),
       ),
       'node_unpublish_action' => array(
         'property' => 'status',
         'expected' => 0,
         'name' => t('unpublish post'),
+        'button' => t('Unpublish'),
       ),
       'node_make_sticky_action' => array(
         'property' => 'sticky',
