diff --git includes/VersioncontrolBranch.php includes/VersioncontrolBranch.php
index 0f2d3d0..1eecf53 100644
--- includes/VersioncontrolBranch.php
+++ includes/VersioncontrolBranch.php
@@ -9,6 +9,8 @@
  * Represents a repository branch.
  */
 class VersioncontrolBranch extends VersioncontrolEntity {
+  protected $_id = 'label_id';
+
   /**
    * The tag identifier (a simple integer), used for unique identification of
    * this tag in the database.
@@ -59,55 +61,41 @@ class VersioncontrolBranch extends VersioncontrolEntity {
     return $this->backend->loadCommits($ids, $conditions, $options);
   }
 
-  /**
-   * Insert a tag entry into the {versioncontrol_labels} table, or retrieve the
-   * same one that's already there.
-   *
-   * The object is enhanced with the newly added property 'label_id' specifying
-   * the database identifier for that label. There may be labels with a similar
-   * 'name' but different 'type' properties, those are considered to be
-   * different and will both go into the database side by side.
-   *
-   * @deprecated FIXME remove this approach, it leads to inefficient single-loading.
-   */
-  public function ensure() {
-    if (!empty($this->label_id)) { // already in the database
-      return;
-    }
-    $result = db_result(db_query("SELECT label_id FROM {versioncontrol_labels} WHERE repo_id = %d AND name = '%s' AND type = %d",
-      $this->repository->repo_id, $this->name, $this->type));
-    if ($result) {
-      $this->label_id = $result;
-    }
-    else {
-      // The item doesn't yet exist in the database, so create it.
-      $this->insert();
+  public function update($options = array()) {
+    if (empty($this->label_id)) {
+      // This is supposed to be an existing branch, but has no label_id.
+      throw new Exception('Attempted to update a Versioncontrol branch which has not yet been inserted in the database.', E_ERROR);
     }
+
+    drupal_write_record('versioncontrol_labels', $this, 'label_id');
+
+    // Let the backend take action.
+    $this->backendUpdate();
+
+    // Everything's done, invoke the hook.
+    module_invoke_all('versioncontrol_entity_branch_update', $this);
+    return $this;
   }
 
-  /**
-   * Insert label to db
-   */
-  public function insert() {
-    if (isset($this->label_id)) {
-      // The label already exists in the database, update the record.
-      drupal_write_record('versioncontrol_labels', $this, 'label_id');
-    }
-    else {
-      // The label does not yet exist, create it.
-      // drupal_write_record() also assigns the new id to $this->label_id.
-      drupal_write_record('versioncontrol_labels', $this);
+  public function insert($options = array()) {
+    if (!empty($this->label_id)) {
+      // This is supposed to be a new branch, but has a label_id already.
+      throw new Exception('Attempted to insert a Versioncontrol branch which is already present in the database.', E_ERROR);
     }
-    unset($this->repo_id);
+
+    drupal_write_record('versioncontrol_labels', $this);
+
+    $this->backendInsert();
+
+    // Everything's done, invoke the hook.
+    module_invoke_all('versioncontrol_entity_branch_insert', $this);
+    return $this;
   }
-  public function save() {}
-  public function update() {}
-  public function buildSave(&$query) {}
 
   /**
    * Delete this branch from the database, along with all related data.
    */
-  public function delete() {
+  public function delete($options = array()) {
     $commits_as_op = $this->loadCommits();
     foreach($commits_as_op as $commit_op) {
       $new_labels = array();
@@ -118,8 +106,20 @@ class VersioncontrolBranch extends VersioncontrolEntity {
       }
       $commit_op->updateLabels($new_labels);
     }
+
     db_delete('versioncontrol_labels')
-      // ->condition('repo_id', $this->repository->repo_id)
-      ->condition('label_id', $this->label_id);
+      ->condition('label_id', $this->label_id)
+      ->execute();
+
+    $this->backendDelete();
+
+    module_invoke_all('versioncontrol_entity_branch_delete', $this);
+  }
+
+  public function deleteRelatedCommits() {
+    $commits = $this->loadCommits();
+    foreach ($commits as $commit) {
+      $commit->delete();
+    }
   }
 }
diff --git includes/VersioncontrolItem.php includes/VersioncontrolItem.php
index 838ff5f..b319ed5 100644
--- includes/VersioncontrolItem.php
+++ includes/VersioncontrolItem.php
@@ -14,6 +14,8 @@
  * them, are recorded in the database.
  */
 abstract class VersioncontrolItem extends VersioncontrolEntity {
+  protected $_id = 'item_revision_id';
+
   /**
    * DB identifier.
    *
@@ -758,51 +760,46 @@ abstract class VersioncontrolItem extends VersioncontrolEntity {
       ($line_changes ? $this->line_changes['removed'] : 0));
   }
 
-  /**
-   * Insert an item entry into the {versioncontrol_item_revisions} table,
-   * or retrieve the same one that's already there on the object.
-   */
-  public function ensure() {
-    $result = db_query(
-      "SELECT item_revision_id, type
-       FROM {versioncontrol_item_revisions}
-       WHERE repo_id = %d AND path = '%s' AND revision = '%s'",
-       $this->repository->repo_id, $this->path, $this->revision
-    );
-    while ($item_revision = db_fetch_object($result)) {
-      // Replace / fill in properties that were not in the WHERE condition.
-      $this->item_revision_id = $item_revision->item_revision_id;
-
-      if ($this->type == $item_revision->type) {
-        return; // no changes needed - otherwise, replace the existing item.
-      }
+  public function update($options = array()) {
+    if (empty($this->item_revision_id)) {
+      // This is supposed to be an existing item, but has no item_revision_id.
+      throw new Exception('Attempted to update a Versioncontrol item which has not yet been inserted in the database.', E_ERROR);
     }
-    // The item doesn't yet exist in the database, so create it.
-    $this->insert();
-  }
 
-  /**
-   * Insert an item revision entry into the {versioncontrol_items_revisions}
-   * table.
-   */
-  public function insert() {
-    $this->repo_id = $this->repository->repo_id; // for drupal_write_record() only
+    drupal_write_record('versioncontrol_item_revisions', $this, 'item_revision_id');
 
-    if (isset($this->item_revision_id)) {
-      // The item already exists in the database, update the record.
-      drupal_write_record('versioncontrol_item_revisions', $this, 'item_revision_id');
-    }
-    else {
-      // The label does not yet exist, create it.
-      // drupal_write_record() also adds the 'item_revision_id' to the $item array.
-      drupal_write_record('versioncontrol_item_revisions', $this);
+    // Let the backend take action.
+    $this->backendUpdate();
+
+    // Everything's done, invoke the hook.
+    module_invoke_all('versioncontrol_entity_item_update', $this);
+    return $this;
+  }
+
+  public function insert($options = array()) {
+    if (!empty($this->item_revision_id)) {
+      // This is supposed to be a new item, but has an item_revision_id already.
+      throw new Exception('Attempted to insert a Versioncontrol item which is already present in the database.', E_ERROR);
     }
-    unset($this->repo_id);
+
+    drupal_write_record('versioncontrol_item_revisions', $this);
+
+    $this->backendInsert();
+
+    // Everything's done, invoke the hook.
+    module_invoke_all('versioncontrol_entity_item_insert', $this);
+    return $this;
   }
 
-  public function save() {}
-  public function update() {}
-  public function buildSave(&$query) {}
+  public function delete($options = array()) {
+    db_delete('verisoncontrol_item_revisions')
+      ->condition('item_revision_id', $this->item_revision_id)
+      ->execute();
+
+    $this->backendDelete();
+
+    module_invoke_all('versioncontrol_entity_item_delete', $this);
+  }
 
   /**
    * Get the user-visible version of an item's revision identifier, as
diff --git includes/VersioncontrolOperation.php includes/VersioncontrolOperation.php
index 1b84564..a5035ea 100644
--- includes/VersioncontrolOperation.php
+++ includes/VersioncontrolOperation.php
@@ -9,6 +9,7 @@
  * Stuff that happened in a repository at a specific time
  */
 abstract class VersioncontrolOperation extends VersioncontrolEntity {
+  protected $_id = 'vc_op_id';
   /**
    * db identifier
    *
@@ -197,160 +198,37 @@ abstract class VersioncontrolOperation extends VersioncontrolEntity {
     return $items;
   }
 
-  /**
-   * Replace the set of affected labels of the actual object with the one in
-   * @p $labels. If any of the given labels does not yet exist in the
-   * database, a database entry (including new 'label_id' array element) will
-   * be written as well.
-   */
-  public function updateLabels($labels) {
-    module_invoke_all('versioncontrol_operation_labels',
-      'update', $this, $labels
-    );
-    $this->setLabels($labels);
-  }
-
-  public function save() {
-    return isset($this->repo_id) ? $this->update() : $this->save();
-  }
-
-  public function buildSave(&$query) {
-
-  }
-
-  /**
-   * Insert a commit, branch or tag operation into the database, and call the
-   * necessary module hooks. Only call this function after the operation has been
-   * successfully executed.
-   *
-   * @param $item_revisions
-   *   A structured array containing the exact details of happened to each
-   *   item in this operation. The structure of this array is the same as
-   *   the return value of VersioncontrolOperation::getItems() - that is,
-   *   elements for 'type', 'path' and 'revision' - but doesn't include the
-   *   'item_revision_id' element, that one will be filled in by this function.
-   *
-   *   For commit operations, you also have to fill in the 'action' and
-   *   'source_items' elements (and optionally 'replaced_item') that are also
-   *   described in the VersioncontrolOperation::getItems() API documentation.
-   *   The 'line_changes' element, as in VersioncontrolOperation::getItems(),
-   *   is optional to provide.
-   *
-   *   This parameter is passed by reference as the insert operation will
-   *   check the validity of a few item properties and will also assign an
-   *   'item_revision_id' property to each of the given items. So when this
-   *   function returns with a result other than NULL, the @p $item_revisions
-   *   array will also be up to snuff for further processing.
-   *
-   * @return
-   *   The finalized operation array, with all of the 'vc_op_id', 'repository'
-   *   and 'uid' properties filled in, and 'repo_id' removed if it existed before.
-   *   Labels are now equipped with an additional 'label_id' property.
-   *   (For more info on these labels, see the API documentation for
-   *   versioncontrol_get_operations() and VersioncontrolOperation::getItems().)
-   *   In case of an error, NULL is returned instead of the operation array.
-   */
-  public final function insert(&$item_revisions) {
-    $this->fill(TRUE);
-
-    if (!isset($this->repository)) {
-      return NULL;
+  public function insert($options = array()) {
+    if (!empty($this->vc_op_id)) {
+      // This is supposed to be a new commit, but has a vc_op_id already.
+      throw new Exception('Attempted to insert a Versioncontrol commit which is already present in the database.', E_ERROR);
     }
 
-    // Ok, everything's there, insert the operation into the database.
-    $this->repo_id = $this->repository->repo_id; // for drupal_write_record()
-    //FIXME $this->uid = 0;
     drupal_write_record('versioncontrol_operations', $this);
-    unset($this->repo_id);
-    // drupal_write_record() has now added the 'vc_op_id' to the $operation array.
-
-    // Insert labels that are attached to the operation.
-    $this->setLabels($this->labels);
-
-    $vcs = $this->repository->vcs;
 
-    // So much for the operation itself, now the more verbose part: items.
-    ksort($item_revisions); // similar paths should be next to each other
+    $this->backendInsert();
 
-    foreach ($item_revisions as $path => $item) {
-      $item->sanitize();
-      $item->vc_op_id = $this->vc_op_id;
-      $item->ensure();
-      $item['selected_label'] = new stdClass();
-      $item['selected_label']->get_from = 'operation';
-      $item['selected_label']->successor_item = &$this;
-
-      // If we've got source items (which is the case for commit operations),
-      // add them to the item revisions and source revisions tables as well.
-      foreach ($item->source_items as $key => $source_item) {
-        $source_item->ensure();
-        $item->insertSourceRevision($source_item, $item->action);
-
-        $source_item->selected_label = new stdClass();
-        $source_item->selected_label->get_from = 'other_item';
-        $source_item->selected_label->other_item = &$item;
-        $source_item->selected_label->other_item_tags = array('successor_item');
-
-        $item->source_items[$key] = $source_item;
-      }
-      // Plus a special case for the "added" action, as it needs an entry in the
-      // source items table but contains no items in the 'source_items' property.
-      if ($item->action == VERSIONCONTROL_ACTION_ADDED) {
-        $item->insertSourceRevision(0, $item['action']);
-      }
+    // Everything's done, invoke the hook.
+    module_invoke_all('versioncontrol_entity_commit_insert', $this);
+    return $this;
+  }
 
-      // If we've got a replaced item (might happen for copy/move commits),
-      // add it to the item revisions and source revisions table as well.
-      if (isset($item->replaced_item)) {
-        $item->replaced_item->ensure();
-        $item->insertSourceRevision($item->replaced_item,
-          VERSIONCONTROL_ACTION_REPLACED);
-        $item->replaced_item->selected_label = new stdClass();
-        $item->replaced_item->selected_label->get_from = 'other_item';
-        $item->replaced_item->selected_label->other_item = &$item;
-        $item->replaced_item->selected_label->other_item_tags = array('successor_item');
-      }
-      $item_revisions[$path] = $item;
+  public function update($options = array()) {
+    if (empty($this->vc_op_id)) {
+      // This is supposed to be an existing branch, but has no vc_op_id.
+      throw new Exception('Attempted to update a Versioncontrol commit which has not yet been inserted in the database.', E_ERROR);
     }
 
-    // Notify the backend first.
-    $this->_insert($item_revisions);
-
-    // Everything's done, let the world know about it!
-    module_invoke_all('versioncontrol_operation',
-      'insert', $this, $item_revisions
-    );
-
-    // This one too, as there is also an update function & hook for it.
-    // Pretend that the labels didn't exist beforehand.
-    $labels = $this->labels;
-    $this->labels = array();
-    module_invoke_all('versioncontrol_operation_labels',
-      'insert', $this, $labels
-    );
-    $this->labels = $labels;
-
-    // Rules integration, because we like to enable people to be flexible.
-    // FIXME change callback
-    if (module_exists('rules')) {
-      rules_invoke_event('versioncontrol_operation_insert', array(
-        'operation' => $this,
-        'items' => $item_revisions,
-      ));
-    }
+    drupal_write_record('versioncontrol_operations', $this, 'vc_op_id');
 
-    //FIXME avoid return, it's on the object
-    return $this;
-  }
+    // Let the backend take action.
+    $this->backendUpdate();
 
-  /**
-   * Let child backend operation classes add information if necessary.
-   */
-  protected function _insert($item_revisions) {
+    // Everything's done, invoke the hook.
+    module_invoke_all('versioncontrol_entity_commit_update', $this);
+    return $this;
   }
 
-  public function update() {}
-
   /**
    * Delete a commit, a branch operation or a tag operation from the database,
    * and call the necessary hooks.
@@ -359,64 +237,14 @@ abstract class VersioncontrolOperation extends VersioncontrolEntity {
    *   The commit, branch operation or tag operation array containing
    *   the operation that should be deleted.
    */
-  public final function delete() {
-    $item_revisions = $this->getItems();
-
-    // As versioncontrol_update_operation_labels() provides an update hook for
-    // operation labels, we should also have a delete hook for completeness.
-    module_invoke_all('versioncontrol_operation_labels',
-      'delete', $this, array());
-    // Announce deletion of the operation before anything has happened.
-    // Calls hook_versioncontrol_commit(), hook_versioncontrol_branch_operation()
-    // or hook_versioncontrol_tag_operation().
-    module_invoke_all('versioncontrol_operation',
-      'delete', $this, $item_revisions);
-
-    // Provide an opportunity for the backend to delete its own stuff.
-    $this->_delete($item_revisions);
-
-    db_query('DELETE FROM {versioncontrol_operation_labels}
-    WHERE vc_op_id = %d', $this->vc_op_id);
-    db_query('DELETE FROM {versioncontrol_item_revisions}
-    WHERE vc_op_id = %d', $this->vc_op_id);
-    db_query('DELETE FROM {versioncontrol_operations}
-    WHERE vc_op_id = %d', $this->vc_op_id);
-  }
-
-  /**
-   * Let child backend repo classes add information that _is not_ in
-   * VersioncontrolRepository::data without modifying general flow if
-   * necessary.
-   */
-  protected function _delete($item_revisions) {
-  }
-
-  /**
-   * Fill in various operation members into the object(commit, branch op or tag
-   * op), in case those values are not given.
-   *
-   * @param $operation
-   *   The plain operation array that might lack have some properties yet.
-   * @param $include_unauthorized
-   *   If FALSE, the 'uid' property will receive a value of 0 for known
-   *   but unauthorized users. If TRUE, all known users are mapped to their uid.
-   */
-  private function fill($include_unauthorized = FALSE) {
-    // If not already there, retrieve the Drupal user id of the committer.
-    if (!isset($this->author)) {
-      $account = $this->repository->load('account', array(), array('username' => $this->author));
+  public function delete($options = array()) {
+    db_delete('versioncontrol_operations')
+      ->condition('vc_op_id', $this->vc_op_id)
+      ->execute();
 
-      // If no uid could be retrieved, blame the commit on user 0 (anonymous).
-      $this->author = isset($this->author) ? $this->author : 0;
-    }
+    $this->backendDelete($options);
 
-    // For insertions (which have 'date' set, as opposed to write access checks),
-    // fill in the log message if it's unset. We don't want to do this for
-    // write access checks because empty messages are denied access,
-    // which requires distinguishing between unset and empty.
-    if (isset($this->date) && !isset($this->message)) {
-      $this->message = '';
-    }
+    module_invoke_all('versioncontrol_entity_commit_delete', $this);
   }
 
   /**
@@ -442,26 +270,6 @@ abstract class VersioncontrolOperation extends VersioncontrolEntity {
   }
 
   /**
-   * Write @p $labels to the database as set of affected labels of the
-   * actual operation object. Label ids are not required to exist yet.
-   * After this the set of labels, all of them with 'label_id' filled in.
-   *
-   * @return
-   */
-  private function setLabels($labels) {
-    db_query("DELETE FROM {versioncontrol_operation_labels}
-    WHERE vc_op_id = %d", $this->vc_op_id);
-
-    foreach ($labels as &$label) {
-      $label->ensure();
-      db_query("INSERT INTO {versioncontrol_operation_labels}
-      (vc_op_id, label_id, action) VALUES (%d, %d, %d)",
-        $this->vc_op_id, $label->label_id, $label->action);
-    }
-    $this->labels = $labels;
-  }
-
-  /**
    * Determine if a commit, branch or tag operation may be executed or not.
    * Call this function inside a pre-commit hook.
    *
diff --git includes/VersioncontrolRepository.php includes/VersioncontrolRepository.php
index 7335a06..04687de 100644
--- includes/VersioncontrolRepository.php
+++ includes/VersioncontrolRepository.php
@@ -9,13 +9,8 @@
  * Contain fundamental information about the repository.
  */
 abstract class VersioncontrolRepository implements VersioncontrolEntityInterface {
-  /**
-   * Override the parent declaration that has the $repository property; no
-   * need to be self-referential.
-   *
-   * @var void
-   */
-  protected $repository = NULL;
+  protected $_id = 'repo_id';
+
   /**
    * db identifier
    *
@@ -220,37 +215,33 @@ abstract class VersioncontrolRepository implements VersioncontrolEntityInterface
     return TRUE;
   }
 
-  public function save() {
-    return isset($this->repo_id) ? $this->update() : $this->insert();
-  }
-
-  public function buildSave(&$query) {
-
+  public function save($options = array()) {
+    return isset($this->repo_id) ? $this->update($options) : $this->insert($options);
   }
 
   /**
-   * Update a repository in the database, and call the necessary hooks.
+   * Update a repository in the database, and invoke the necessary hooks.
+   *
    * The 'repo_id' and 'vcs' properties of the repository object must stay
    * the same as the ones given on repository creation,
    * whereas all other values may change.
-   * TODO refactor to use a custom-built db_insert().
    */
-  public final function update() {
+  public function update($options = array()) {
+    if (empty($this->repo_id)) {
+      // This is supposed to be an existing repository, but has no repo_id.
+      throw new Exception('Attempted to update a Versioncontrol repository which has not yet been inserted in the database.', E_ERROR);
+    }
+
     drupal_write_record('versioncontrol_repositories', $this, 'repo_id');
 
-    $this->_update();
+    $this->backendUpdate();
 
     // Everything's done, let the world know about it!
-    module_invoke_all('versioncontrol_repository', 'update', $this);
-
-    watchdog('special',
-      'Version Control API: updated repository @repository',
-      array('@repository' => $this->name),
-      WATCHDOG_NOTICE, l('view', 'admin/project/versioncontrol-repositories')
-    );
+    module_invoke_all('versioncontrol_repository_entity_update', $this);
+    return $this;
   }
 
-  protected function _update() {}
+  protected function backendUpdate($options = array()) {}
 
   /**
    * Insert a repository into the database, and call the necessary hooks.
@@ -258,24 +249,19 @@ abstract class VersioncontrolRepository implements VersioncontrolEntityInterface
    * @return
    *   The finalized repository array, including the 'repo_id' element.
    */
-  public final function insert() {
-    if (isset($this->repo_id)) {
-      // This is a new repository, it's not supposed to have a repo_id yet.
-      unset($this->repo_id);
+  public function insert($options = array()) {
+    if (!empty($this->repo_id)) {
+      // This is supposed to be a new repository, but has a repo_id already.
+      throw new Exception('Attempted to insert a Versioncontrol repository which is already present in the database.', E_ERROR);
     }
+
+    // drupal_write_record() will fill the $repo_id property on $this.
     drupal_write_record('versioncontrol_repositories', $this);
-    // drupal_write_record() has now added the 'repo_id' to the $repository array.
 
-    $this->_insert();
+    $this->backendInsert();
 
     // Everything's done, let the world know about it!
-    module_invoke_all('versioncontrol_repository', 'insert', $this);
-
-    watchdog('special',
-      'Version Control API: added repository @repository',
-      array('@repository' => $this->name),
-      WATCHDOG_NOTICE, l('view', 'admin/project/versioncontrol-repositories')
-    );
+    module_invoke_all('versioncontrol_repository_entity_insert', $this);
     return $this;
   }
 
@@ -284,17 +270,16 @@ abstract class VersioncontrolRepository implements VersioncontrolEntityInterface
    * VersioncontrolRepository::data without modifying general flow if
    * necessary.
    */
-  protected function _insert() {}
+  protected function backendInsert($options = array()) {}
 
   /**
    * Delete a repository from the database, and call the necessary hooks.
    * Together with the repository, all associated commits and accounts are
    * deleted as well.
    */
-  public final function delete() {
+  public final function delete($options = array()) {
     // Delete operations.
-    $branches = $this->loadBranches();
-    foreach ($branches as $branch) {
+    foreach ($this->loadBranches() as $branch) {
       $branch->delete();
     }
     foreach ($this->loadTags() as $tag) {
@@ -304,70 +289,21 @@ abstract class VersioncontrolRepository implements VersioncontrolEntityInterface
       $commit->delete();
     }
 
-    // $operations = VersioncontrolOperationCache::getInstance()->getOperations(array('repo_ids' => array($this->repo_id)));
-    // foreach ($operations as $operation) {
-    //  $operation->delete();
-    // }
-    // unset($operations); // conserve memory, this might get quite large
-
-    // Delete labels.
-    db_query('DELETE FROM {versioncontrol_labels}
-              WHERE repo_id = %d', $this->repo_id);
-
-    // Delete item revisions and related source item entries.
-    $result = db_query('SELECT item_revision_id
-                        FROM {versioncontrol_item_revisions}
-                        WHERE repo_id = %d', $this->repo_id);
-    $item_ids = array();
-    $placeholders = array();
-
-    while ($item_revision = db_fetch_object($result)) {
-      $item_ids[] = $item_revision->item_revision_id;
-      $placeholders[] = '%d';
-    }
-    if (!empty($item_ids)) {
-      $placeholders = '('. implode(',', $placeholders) .')';
-
-      db_query('DELETE FROM {versioncontrol_source_items}
-                WHERE item_revision_id IN '. $placeholders, $item_ids);
-      db_query('DELETE FROM {versioncontrol_source_items}
-                WHERE source_item_revision_id IN '. $placeholders, $item_ids);
-      db_query('DELETE FROM {versioncontrol_item_revisions}
-                WHERE repo_id = %d', $this->repo_id);
-    }
-    unset($item_ids); // conserve memory, this might get quite large
-    unset($placeholders); // ...likewise
-
-    // Delete accounts.
-    $accounts = $this->loadAccounts();
-    foreach ($accounts as $uid => $usernames_by_repository) {
-      foreach ($usernames_by_repository as $repo_id => $account) {
-        $account->delete();
-      }
+    // FIXME accounts are changing significantly, this will need to, too
+    foreach ($this->loadAccounts() as $account) {
+      $account->delete();
     }
 
-    // Announce deletion of the repository before anything has happened.
-    module_invoke_all('versioncontrol_repository', 'delete', $this);
+    db_delete('versioncontrol_repositories')
+      ->condition('repo_id', $this->repo_id)
+      ->execute();
 
-    $this->_delete();
+    $this->backendDelete();
 
-    // Phew, everything's cleaned up. Finally, delete the repository.
-    db_query('DELETE FROM {versioncontrol_repositories} WHERE repo_id = %d',
-      $this->repo_id);
-
-    watchdog('special',
-      'Version Control API: deleted repository @repository',
-      array('@repository' => $this->name),
-      WATCHDOG_NOTICE, l('view', 'admin/project/versioncontrol-repositories')
-    );
+    module_invoke_all('versioncontrol_entity_repository_delete', $this);
   }
 
-  /**
-   * Let child backend repo classes delete information that _is not_ in
-   * VersioncontrolRepository::data without modifying general flow if
-   * necessary.
-   */
-  protected function _delete() {}
+  protected function backendDelete($options = array()) {}
 
   /**
    * Export a repository's authenticated accounts to the version control system's
diff --git includes/VersioncontrolTag.php includes/VersioncontrolTag.php
index 358a2bf..9f7a74f 100644
--- includes/VersioncontrolTag.php
+++ includes/VersioncontrolTag.php
@@ -9,6 +9,8 @@
  * Represents a tag of code (not changing state)
  */
 class VersioncontrolTag extends VersioncontrolEntity {
+  protected $_id = 'label_id';
+
   /**
    * The tag identifier (a simple integer), used for unique identification of
    * this tag in the database.
@@ -47,48 +49,44 @@ class VersioncontrolTag extends VersioncontrolEntity {
    */
   public $repo_id;
 
-  /**
-   * Insert a tag entry into the {versioncontrol_labels} table, or retrieve the
-   * same one that's already there.
-   *
-   * The object is enhanced with the newly added property 'label_id' specifying
-   * the database identifier for that label. There may be labels with a similar
-   * 'name' but different 'type' properties, those are considered to be
-   * different and will both go into the database side by side.
-   *
-   * @deprecated FIXME remove this approach, it leads to inefficient single-loading.
-   */
-  public function ensure() {
-    if (!empty($this->label_id)) { // already in the database
-      return;
-    }
-    $result = db_result(db_query("SELECT label_id FROM {versioncontrol_labels} WHERE repo_id = %d AND name = '%s' AND type = %d",
-      $this->repository->repo_id, $this->name, $this->type));
-    if ($result) {
-      $this->label_id = $result;
-    }
-    else {
-      // The item doesn't yet exist in the database, so create it.
-      $this->insert();
+  public function update($options = array()) {
+    if (empty($this->label_id)) {
+      // This is supposed to be an existing tag, but has no label_id.
+      throw new Exception('Attempted to update a Versioncontrol branch which has not yet been inserted in the database.', E_ERROR);
     }
+
+    drupal_write_record('versioncontrol_labels', $this, 'label_id');
+
+    // Let the backend take action.
+    $this->backendUpdate();
+
+    // Everything's done, invoke the hook.
+    module_invoke_all('versioncontrol_entity_tag_update', $this);
+    return $this;
   }
 
-  /**
-   * Insert label to db
-   */
-  public function insert() {
-    if (isset($this->label_id)) {
-      // The label already exists in the database, update the record.
-      drupal_write_record('versioncontrol_labels', $this, 'label_id');
-    }
-    else {
-      // The label does not yet exist, create it.
-      // drupal_write_record() also assigns the new id to $this->label_id.
-      drupal_write_record('versioncontrol_labels', $this);
+  public function insert($options = array()) {
+    if (!empty($this->label_id)) {
+      // This is supposed to be a new tag, but has a label_id already.
+      throw new Exception('Attempted to insert a Versioncontrol branch which is already present in the database.', E_ERROR);
     }
-    unset($this->repo_id);
+
+    drupal_write_record('versioncontrol_labels', $this);
+
+    $this->backendInsert();
+
+    // Everything's done, invoke the hook.
+    module_invoke_all('versioncontrol_entity_tag_insert', $this);
+    return $this;
+  }
+
+  public function delete($options = array()) {
+    db_delete('versioncontrol_labels')
+      ->condition('label_id', $this->label_id)
+      ->execute();
+
+    $this->backendDelete();
+
+    module_invoke_all('versioncontrol_entity_tag_delete', $this);
   }
-  public function save() {}
-  public function update() {}
-  public function buildSave(&$query) {}
 }
diff --git includes/controllers.inc includes/controllers.inc
index c8db1f1..0377443 100644
--- includes/controllers.inc
+++ includes/controllers.inc
@@ -572,8 +572,25 @@ class VersioncontrolItemController extends VersioncontrolEntityController {
 interface VersioncontrolEntityInterface extends ArrayAccess {
   public function build($args = array());
   public function save();
-  // public function insert();
-  // public function update();
+
+  /**
+   * Insert a new entity into the database, then invoke relevant hooks, if any.
+   */
+  public function insert($options = array());
+  protected function backendInsert($options = array());
+
+  /**
+   * Update an existing entity's record in the database, then invoke relevant
+   * hooks, if any.
+   */
+  public function update($options = array());
+  protected function backendUpdate($options = array());
+
+  /**
+   * Delete an entity from the database, along with any of its dependent data.
+   */
+  public function delete($options = array());
+  protected function backendDelete($options = array());
 }
 
 /**
@@ -598,6 +615,14 @@ abstract class VersioncontrolEntity implements VersioncontrolEntityInterface {
   protected $repository;
 
   /**
+   * Internal property that holds the name of the property which contains the
+   * entity's primary key.
+   *
+   * @var string
+   */
+  protected $_id = NULL;
+
+  /**
    * An instance of the Backend factory used to create this object, passed in
    * to the constructor. If this entity needs to spawn more entities, then it
    * should reuse this backend object to do so.
@@ -616,6 +641,10 @@ abstract class VersioncontrolEntity implements VersioncontrolEntityInterface {
     }
   }
 
+  public function save($options = array()) {
+    return isset($this->{$this->_id}) ? $this->insert($options = array()) : $this->update($options = array());
+  }
+
   /**
    * Pseudo-constructor method; call this method with an associative array or
    * stdClass object containing properties to be assigned to this object.
@@ -638,15 +667,25 @@ abstract class VersioncontrolEntity implements VersioncontrolEntityInterface {
   }
 
   /**
-   * Attach this entity's data to a query that will insert it into the database.
+   * Default, empty implementation of backendInsert().
    *
-   * This method is separated out to avoid code duplication between insert &
-   * update queries, as well as making it possible to build up large queries
-   * rather than issuing lots of small, single insert queries.
+   * Overridden by backends as needed to decorate the new entity insertion
+   * process.
+   */
+  protected function backendInsert($options = array());
+
+  /**
+   * Default, empty implementation of backendUpdate().
    *
-   * @param Query $query
+   * Overridden by backends as needed to decorate the existing entity update
+   * process.
+   */
+  protected function backendUpdate($options = array()) {}
+
+  /**
+   * Default, empty implementation of backendDelete().
    */
-  abstract public function buildSave(&$query);
+  protected function backendDelete($options = array()) {}
 
   //ArrayAccess interface implementation FIXME soooooooo deprecated
   public function offsetExists($offset) {
