diff --git includes/VersioncontrolBranch.php includes/VersioncontrolBranch.php
index 0f2d3d0..895e547 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,50 +61,36 @@ 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() {
+    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);
+    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.
@@ -118,8 +106,19 @@ 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);
+
+    $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 ec491fa..a42b720 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.
    *
@@ -751,51 +753,45 @@ 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() {
+    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();
+
+    drupal_write_record('versioncontrol_item_revisions', $this, 'item_revision_id');
+
+    // Let the backend take action.
+    $this->backendUpdate();
+
+    // Everything's done, invoke the hook.
+    module_invoke_all('versioncontrol_entity_item_update', $this);
+    return $this;
   }
 
-  /**
-   * 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
-
-    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);
+    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() {
+    db_delete('verisoncontrol_item_revisions')
+      ->condition('item_revision_id', $this->item_revision_id);
+
+    $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/VersioncontrolRepository.php includes/VersioncontrolRepository.php
index 99bc542..2b8e67e 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
    *
@@ -214,37 +209,29 @@ abstract class VersioncontrolRepository implements VersioncontrolEntityInterface
     return TRUE;
   }
 
-  public function save() {
-    return isset($this->repo_id) ? $this->update() : $this->insert();
-  }
-
-  public function buildSave(&$query) {
-
-  }
-
   /**
-   * 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() {
+    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() {}
 
   /**
    * Insert a repository into the database, and call the necessary hooks.
@@ -252,24 +239,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() {
+    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;
   }
 
@@ -278,7 +260,7 @@ abstract class VersioncontrolRepository implements VersioncontrolEntityInterface
    * VersioncontrolRepository::data without modifying general flow if
    * necessary.
    */
-  protected function _insert() {}
+  protected function backendInsert() {}
 
   /**
    * Delete a repository from the database, and call the necessary hooks.
@@ -287,8 +269,7 @@ abstract class VersioncontrolRepository implements VersioncontrolEntityInterface
    */
   public final function delete() {
     // Delete operations.
-    $branches = $this->loadBranches();
-    foreach ($branches as $branch) {
+    foreach ($this->loadBranches() as $branch) {
       $branch->delete();
     }
     foreach ($this->loadTags() as $tag) {
@@ -298,70 +279,17 @@ 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);
+    $this->backendDelete();
 
-    $this->_delete();
-
-    // 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() {}
 
   /**
    * Export a repository's authenticated accounts to the version control system's
diff --git includes/VersioncontrolTag.php includes/VersioncontrolTag.php
index 358a2bf..ff253cf 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,43 @@ 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() {
+    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);
+    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() {
+    db_delete('versioncontrol_labels')
+      ->condition('label_id', $this->label_id);
+
+    $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 1331f9f..dd66173 100644
--- includes/controllers.inc
+++ includes/controllers.inc
@@ -571,8 +571,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();
+  protected function backendInsert();
+
+  /**
+   * Update an existing entity's record in the database, then invoke relevant
+   * hooks, if any.
+   */
+  public function update();
+  protected function backendUpdate();
+
+  /**
+   * Delete an entity from the database, along with any of its dependent data.
+   */
+  public function delete();
+  protected function backendDelete();
 }
 
 /**
@@ -597,6 +614,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.
@@ -615,6 +640,10 @@ abstract class VersioncontrolEntity implements VersioncontrolEntityInterface {
     }
   }
 
+  public function save() {
+    return isset($this->{$this->_id}) ? $this->insert() : $this->update();
+  }
+
   /**
    * Pseudo-constructor method; call this method with an associative array or
    * stdClass object containing properties to be assigned to this object.
@@ -637,15 +666,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();
+
+  /**
+   * Default, empty implementation of backendUpdate().
    *
-   * @param Query $query
+   * Overridden by backends as needed to decorate the existing entity update
+   * process.
+   */
+  protected function backendUpdate() {}
+
+  /**
+   * Default, empty implementation of backendDelete().
    */
-  abstract public function buildSave(&$query);
+  protected function backendDelete() {}
 
   //ArrayAccess interface implementation FIXME soooooooo deprecated
   public function offsetExists($offset) {
