Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.1264
diff -u -p -r1.1264 common.inc
--- includes/common.inc	22 Nov 2010 05:22:43 -0000	1.1264
+++ includes/common.inc	29 Nov 2010 04:29:44 -0000
@@ -7308,14 +7308,36 @@ function entity_create_stub_entity($enti
  *
  * @todo Remove $conditions in Drupal 8.
  */
-function entity_load($entity_type, $ids = FALSE, $conditions = array(), $reset = FALSE) {
+function entity_load($entity_type, $ids = array(), $conditions = array(), $reset = FALSE) {
   if ($reset) {
-    entity_get_controller($entity_type)->resetCache();
+    entity_get_controller($entity_type)->resetCache($ids);
   }
   return entity_get_controller($entity_type)->load($ids, $conditions);
 }
 
 /**
+ * Loads the unchanged, i.e. not modified, entity from the database.
+ *
+ * Unlike entity_load() this function ensures the entity is directly loaded from
+ * the database, thus bypassing any static cache. In particular, this function
+ * is useful to determine changes by comparing the entity being saved to the
+ * stored entity.
+ *
+ * @param $entity_type
+ *   The entity type to load, e.g. node or user.
+ * @param $id
+ *   The id of the entity to load.
+ *
+ * @return
+ *   The unchanged entity, or FALSE if the entity cannot be loaded.
+ */
+function entity_load_unchanged($entity_type, $id) {
+  entity_get_controller($entity_type)->resetCache(array($id));
+  $result = entity_get_controller($entity_type)->load(array($id));
+  return reset($result);
+}
+
+/**
  * Get the entity controller class for an entity type.
  */
 function entity_get_controller($entity_type) {
Index: includes/entity.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/entity.inc,v
retrieving revision 1.18
diff -u -p -r1.18 entity.inc
--- includes/entity.inc	14 Nov 2010 22:07:57 -0000	1.18
+++ includes/entity.inc	29 Nov 2010 04:31:14 -0000
@@ -24,8 +24,12 @@ interface DrupalEntityControllerInterfac
 
   /**
    * Resets the internal, static entity cache.
+   *
+   * @param $id
+   *   (optional) If specified, the cache is only reset for the entity with
+   *   the given id.
    */
-  public function resetCache();
+  public function resetCache(array $ids = NULL);
 
   /**
    * Loads one or more entities.
@@ -38,7 +42,7 @@ interface DrupalEntityControllerInterfac
    * @return
    *   An array of entity objects indexed by their ids.
    */
-  public function load($ids = array(), $conditions = array());
+  public function load(array $ids = array(), array $conditions = array());
 }
 
 /**
@@ -139,14 +143,21 @@ class DrupalDefaultEntityController impl
   /**
    * Implements DrupalEntityControllerInterface::resetCache().
    */
-  public function resetCache() {
-    $this->entityCache = array();
+  public function resetCache(array $ids = NULL) {
+    if (isset($ids)) {
+      foreach ($ids as $id) {
+        unset($this->entityCache[$id]);
+      }
+    }
+    else {
+      $this->entityCache = array();
+    }
   }
 
   /**
    * Implements DrupalEntityControllerInterface::load().
    */
-  public function load($ids = array(), $conditions = array()) {
+  public function load(array $ids = array(), array $conditions = array()) {
     $entities = array();
 
     // Revisions are not statically cached, and require a different query to
Index: modules/comment/comment.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/comment/comment.module,v
retrieving revision 1.920
diff -u -p -r1.920 comment.module
--- modules/comment/comment.module	23 Nov 2010 06:02:06 -0000	1.920
+++ modules/comment/comment.module	29 Nov 2010 04:33:07 -0000
@@ -1442,6 +1442,11 @@ function comment_save($comment) {
       $comment->node_type = 'comment_node_' . $node->type;
     }
 
+    // Load the stored entity, if any.
+    if (!empty($comment->cid)) {
+      $comment->original = entity_load_unchanged('comment', $comment->cid);
+    }
+
     field_attach_presave('comment', $comment);
 
     // Allow modules to alter the comment before saving.
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.1326
diff -u -p -r1.1326 node.module
--- modules/node/node.module	27 Nov 2010 20:25:44 -0000	1.1326
+++ modules/node/node.module	29 Nov 2010 04:35:45 -0000
@@ -1032,6 +1032,11 @@ function node_save($node) {
   $transaction = db_transaction();
 
   try {
+    // Load the stored entity, if any.
+    if (!empty($node->nid)) {
+      $node->original = entity_load_unchanged('node', $node->nid);
+    }
+
     field_attach_presave('node', $node);
     global $user;
 
Index: modules/node/node.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.test,v
retrieving revision 1.102
diff -u -p -r1.102 node.test
--- modules/node/node.test	24 Nov 2010 16:25:39 -0000	1.102
+++ modules/node/node.test	29 Nov 2010 04:24:58 -0000
@@ -1041,14 +1041,14 @@ class NodeSaveTestCase extends DrupalWeb
     $changed = $node->changed;
 
     node_save($node);
-    $node = $this->drupalGetNodeByTitle($edit['title']);
+    $node = $this->drupalGetNodeByTitle($edit['title'], TRUE);
     $this->assertEqual($node->created, $created, t('Updating a node preserves "created" timestamp.'));
 
     // Programmatically set the timestamps using hook_node_presave.
     $node->title = 'testing_node_presave';
 
     node_save($node);
-    $node = $this->drupalGetNodeByTitle('testing_node_presave');
+    $node = $this->drupalGetNodeByTitle('testing_node_presave', TRUE);
     $this->assertEqual($node->created, 280299600, t('Saving a node uses "created" timestamp set in presave hook.'));
     $this->assertEqual($node->changed, 979534800, t('Saving a node uses "changed" timestamp set in presave hook.'));
 
@@ -1071,10 +1071,35 @@ class NodeSaveTestCase extends DrupalWeb
     $node->changed = 280299600;
 
     node_save($node);
-    $node = $this->drupalGetNodeByTitle($edit['title']);
+    $node = $this->drupalGetNodeByTitle($edit['title'], TRUE);
     $this->assertEqual($node->created, 979534800, t('Updating a node uses user-set "created" timestamp.'));
     $this->assertNotEqual($node->changed, 280299600, t('Updating a node doesn\'t use user-set "changed" timestamp.'));
   }
+
+  /**
+   * Tests determing changes in hook_node_presave().
+   */
+  function testDeterminingChanges() {
+    // Initial creation.
+    $node = (object) array(
+      'uid' => $this->web_user->uid,
+      'type' => 'article',
+      'title' => 'test_changes',
+    );
+    node_save($node);
+
+    // Update the node without applying changes.
+    node_save($node);
+    $this->assertEqual($node->title, 'test_changes', 'No changes have been determined.');
+
+    // Apply changes.
+    $node->title = 'updated';
+    node_save($node);
+
+    // The hook implementation node_test_node_presave() has to determine the
+    // changes and set the title to 'changed'.
+    $this->assertEqual($node->title, 'changed', 'Changes have been determined.');
+  }
 }
 
 /**
Index: modules/node/tests/node_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/tests/node_test.module,v
retrieving revision 1.13
diff -u -p -r1.13 node_test.module
--- modules/node/tests/node_test.module	24 Nov 2010 16:25:39 -0000	1.13
+++ modules/node/tests/node_test.module	29 Nov 2010 04:24:58 -0000
@@ -131,4 +131,10 @@ function node_test_node_presave($node) {
     // Drupal 1.0 release.
     $node->changed = 979534800;
   }
+  // Determine changes.
+  if (!empty($node->nid)) {
+    if ($node->original->title == 'test_changes' && $node->original->title != $node->title) {
+      $node->title = 'changed';
+    }
+  }
 }
Index: modules/simpletest/drupal_web_test_case.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_web_test_case.php,v
retrieving revision 1.251
diff -u -p -r1.251 drupal_web_test_case.php
--- modules/simpletest/drupal_web_test_case.php	28 Nov 2010 07:32:39 -0000	1.251
+++ modules/simpletest/drupal_web_test_case.php	29 Nov 2010 04:24:58 -0000
@@ -801,12 +801,14 @@ class DrupalWebTestCase extends DrupalTe
    *
    * @param title
    *   A node title, usually generated by $this->randomName().
+   * @param $reset
+   *   (optional) Whether to reset the internal node_load() cache.
    *
    * @return
    *   A node object matching $title.
    */
-  function drupalGetNodeByTitle($title) {
-    $nodes = node_load_multiple(array(), array('title' => $title));
+  function drupalGetNodeByTitle($title, $reset = FALSE) {
+    $nodes = node_load_multiple(array(), array('title' => $title), $reset);
     // Load the first node returned from the database.
     $returned_node = reset($nodes);
     return $returned_node;
Index: modules/taxonomy/taxonomy.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.module,v
retrieving revision 1.621
diff -u -p -r1.621 taxonomy.module
--- modules/taxonomy/taxonomy.module	27 Nov 2010 20:25:44 -0000	1.621
+++ modules/taxonomy/taxonomy.module	29 Nov 2010 04:39:13 -0000
@@ -388,9 +388,12 @@ function taxonomy_vocabulary_save($vocab
   if (!empty($vocabulary->name)) {
     $vocabulary->name = trim($vocabulary->name);
   }
-  // For existing vocabularies, make sure we can detect machine name changes.
-  if (!empty($vocabulary->vid) && !isset($vocabulary->old_machine_name)) {
-    $vocabulary->old_machine_name = db_query("SELECT machine_name FROM {taxonomy_vocabulary} WHERE vid = :vid", array(':vid' => $vocabulary->vid))->fetchField();
+  // Load the stored entity, if any.
+  if (!empty($vocabulary->vid)) {
+    $vocabulary->original = entity_load_unchanged('vocabulary', $vocabulary->vid);
+
+    // Make sure machine name changes are detected.
+    $vocabulary->old_machine_name = $vocabulary->original->machine_name;
   }
 
   if (!isset($vocabulary->module)) {
@@ -540,6 +543,11 @@ function taxonomy_term_save($term) {
     $term->vocabulary_machine_name = $vocabulary->machine_name;
   }
 
+  // Load the stored entity, if any.
+  if (!empty($term->tid)) {
+    $term->original = entity_load_unchanged('taxonomy_term', $term->tid);
+  }
+
   field_attach_presave('taxonomy_term', $term);
   module_invoke_all('taxonomy_term_presave', $term);
 
Index: modules/user/user.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.module,v
retrieving revision 1.1221
diff -u -p -r1.1221 user.module
--- modules/user/user.module	26 Nov 2010 10:14:10 -0000	1.1221
+++ modules/user/user.module	29 Nov 2010 04:40:22 -0000
@@ -405,6 +405,11 @@ function user_save($account, $edit = arr
       unset($edit['pass']);
     }
 
+    // Load the stored entity, if any.
+    if (!empty($account->uid)) {
+      $account->original = entity_load_unchanged('user', $account->uid);
+    }
+
     // Presave field allowing changing of $edit.
     $edit = (object) $edit;
     field_attach_presave('user', $edit);
