diff --git includes/VersioncontrolOperation.php includes/VersioncontrolOperation.php
index 678b2d0..272cac7 100644
--- includes/VersioncontrolOperation.php
+++ includes/VersioncontrolOperation.php
@@ -116,8 +116,8 @@ abstract class VersioncontrolOperation extends VersioncontrolEntity {
   public $itemRevisions = array();
 
   protected $defaultCrudOptions = array(
-    'update' => array('nested' => TRUE),
-    'insert' => array('nested' => TRUE),
+    'update' => array('nested' => TRUE, 'map users' => FALSE),
+    'insert' => array('nested' => TRUE, 'map users' => FALSE),
     'delete' => array('nested' => TRUE),
   );
 
@@ -133,6 +133,37 @@ abstract class VersioncontrolOperation extends VersioncontrolEntity {
     return $this->backend->loadEntities('item', $ids, $conditions, $options);
   }
 
+  public function mapUsers() {
+    $this->mapAuthor();
+    $this->mapCommitter();
+  }
+
+  /**
+   * Perform the mapping between Drupal users and this commit's author.
+   */
+  public function mapAuthor() {
+    if ($mapper = $this->repository->getAuthorMapper()) {
+      $uid = $mapper->mapAuthor($this);
+      $this->author_uid = empty($uid) ? 0 : $uid;
+    }
+    else {
+      $this->author_uid = 0;
+    }
+  }
+
+  /**
+   * Perform the mapping between Drupal users and this commit's committer.
+   */
+  public function mapCommitter() {
+    if ($mapper = $this->repository->getCommitterMapper()) {
+      $uid = $mapper->mapCommitter($this);
+      $this->committer_uid = empty($uid) ? 0 : $uid;
+    }
+    else {
+      $this->committer_uid = 0;
+    }
+  }
+
   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.
@@ -142,6 +173,10 @@ abstract class VersioncontrolOperation extends VersioncontrolEntity {
     // Append default options.
     $options += $this->defaultCrudOptions['insert'];
 
+    if ($options['map users']) {
+      $this->mapUsers();
+    }
+
     // make sure repo id is set for drupal_write_record()
     if (empty($this->repo_id)) {
       $this->repo_id = $this->repository->repo_id;
@@ -177,6 +212,10 @@ abstract class VersioncontrolOperation extends VersioncontrolEntity {
     // Append default options.
     $options += $this->defaultCrudOptions['update'];
 
+    if ($options['map users']) {
+      $this->mapUsers();
+    }
+
     // make sure repo id is set for drupal_write_record()
     if (empty($this->repo_id)) {
       $this->repo_id = $this->repository->repo_id;
diff --git includes/VersioncontrolRepository.php includes/VersioncontrolRepository.php
index 829532e..29b9202 100644
--- includes/VersioncontrolRepository.php
+++ includes/VersioncontrolRepository.php
@@ -93,6 +93,24 @@ abstract class VersioncontrolRepository implements VersioncontrolEntityInterface
 
   protected $built = FALSE;
 
+  /**
+   * An array describing the plugins that will be used for this repository.
+   *
+   * The current plugin types(array keys) are:
+   * - author_mapper
+   * - committer_mapper
+   *
+   * @var array
+   */
+  public $plugins = array();
+
+  /**
+   * An array of plugin instances (instanciated plugin objects).
+   *
+   * @var array
+   */
+  protected $pluginInstances = array();
+
   protected $defaultCrudOptions = array(
     'update' => array('nested' => TRUE),
     'insert' => array('nested' => TRUE),
@@ -131,6 +149,9 @@ abstract class VersioncontrolRepository implements VersioncontrolEntityInterface
     if (!empty($this->data) && is_string($this->data)) {
       $this->data = unserialize($this->data);
     }
+    if (!empty($this->plugins) && is_string($this->plugins)) {
+      $this->plugins = unserialize($this->plugins);
+    }
     $this->built = TRUE;
   }
 
@@ -446,6 +467,61 @@ abstract class VersioncontrolRepository implements VersioncontrolEntityInterface
     return NULL;
   }
 
+  /**
+   * Get an instantiated plugin object based on a requested plugin slot, and the
+   * plugin this repository object has assigned to that slot.
+   *
+   * Internal function - other methods should provide a nicer public-facing
+   * interface. This method exists primarily to reduce code duplication involved
+   * in ensuring error handling and sound loading of the plugin.
+   */
+  protected function getPluginClass($plugin_slot, $plugin_type, $class_type) {
+    ctools_include('plugins');
+
+    if (empty($this->plugins[$plugin_slot])) {
+      throw new Exception("Attempted to get plugin in slot '$plugin_slot', but no plugin has been assigned to that slot on this repository.", E_STRICT);
+      return FALSE;
+    }
+    $plugin_name = $this->plugins[$plugin_slot];
+
+    $plugin = ctools_get_plugins('versioncontrol', $plugin_type, $plugin_name);
+    if (!is_array($plugin)) {
+      throw new Exception("Attempted to get a plugin of type '$plugin_type' named '$plugin_name', but no such plugin could be found.", E_WARNING);
+      return FALSE;
+    }
+
+    $class_name = ctools_plugin_get_class($plugin, $class_type);
+    if (!class_exists($class_name)) {
+      throw new Exception("Plugin '$plugin_name' of type '$plugin_type' does not contain a valid class name in handler slot '$class_type'", E_WARNING);
+      return FALSE;
+    }
+
+    return new $class_name();
+  }
+
+  public function getAuthorMapper() {
+    if (!isset($this->pluginInstances['author_mapper'])) {
+      // if no plugin is set, simply
+      $this->pluginInstances['author_mapper'] = $this->getPluginClass('author_mapper', 'user_mapping_method', 'mapper');
+    }
+    return $this->pluginInstances['author_mapper'];
+  }
+
+  public function getCommitterMapper() {
+    if (!isset($this->pluginInstances['committer_mapper'])) {
+      // If nothing is set for the committer mapper plugin, reuse the author one
+      if (empty($this->plugins['committer_mapper'])) {
+        $this->pluginInstances['committer_mapper'] = $this->getAuthorMapper();
+      }
+      else {
+        $this->pluginInstances['committer_mapper'] = $this->getPluginClass('committer_mapper', 'user_mapping_method', 'mapper');
+      }
+    }
+
+    return $this->pluginInstances['committer_mapper'];
+  }
+
+
   //ArrayAccess interface implementation FIXME soooooooo deprecated
   public function offsetExists($offset) {
     return isset($this->$offset);
diff --git includes/interfaces.inc includes/interfaces.inc
index 8ea1302..d8dc55d 100644
--- includes/interfaces.inc
+++ includes/interfaces.inc
@@ -218,3 +218,27 @@ interface VersioncontrolRepositoryImportExport {
   public function exportAccounts($accounts);
 
 }
+
+interface VersioncontrolUserMapperInterface {
+  /**
+   * Map the author of the passed VersioncontrolOperation object to a Drupal
+   * uid, or FALSE if no uid mapping could be made.
+   *
+   * @param VersioncontrolOperation $commit
+   *   The commit to be mapped.
+   * @return mixed
+   *   Either a uid (int), or FALSE if the mapping failed.
+   */
+  public function mapAuthor(VersioncontrolOperation $commit);
+
+  /**
+   * Map the committer of the passed VersioncontrolOperation object to a Drupal
+   * uid, or FALSE if no uid mapping could be made.
+   *
+   * @param VersioncontrolOperation $commit
+   *   The commit to be mapped.
+   * @return mixed
+   *   Either a uid (int), or FALSE if the mapping failed.
+   */
+  public function mapCommitter(VersioncontrolOperation $commit);
+}
\ No newline at end of file
diff --git includes/plugins/user_mapping_methods/VersioncontrolUserMapperSimpleMail.class.php includes/plugins/user_mapping_methods/VersioncontrolUserMapperSimpleMail.class.php
new file mode 100644
index 0000000..0c1de5c
--- /dev/null
+++ includes/plugins/user_mapping_methods/VersioncontrolUserMapperSimpleMail.class.php
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * Plugin to do very simple mapping of email to Drupal users.
+ *
+ * Assumes $commit->author and $commit->committer are valid email addresses;
+ * does no checking to ensure they're good.
+ */
+class VersioncontrolUserMapperSimpleMail implements VersioncontrolUserMapperInterface {
+  public function mapAuthor(VersioncontrolOperation $commit) {
+    return $this->map($commit->author);
+  }
+
+  public function mapCommitter(VersioncontrolOperation $commit) {
+    return $this->map($commit->committer);
+  }
+
+  public function map($email) {
+    $uid = db_result(db_query("SELECT uid FROM {users} WHERE mail = '%s'"));
+    return empty($uid) ? FALSE : $uid;
+  }
+}
\ No newline at end of file
diff --git includes/plugins/user_mapping_methods/simple_mail.inc includes/plugins/user_mapping_methods/simple_mail.inc
new file mode 100644
index 0000000..edbd2e0
--- /dev/null
+++ includes/plugins/user_mapping_methods/simple_mail.inc
@@ -0,0 +1,10 @@
+<?php
+// $Id$
+
+$plugin = array(
+  'title' => t('Map using Drupal user email'),
+  'mapper' => array(
+    'class' => 'VersioncontrolUserMapperSimpleMail',
+    'file' => 'VersioncontrolUserMapperSimpleMail.class.php',
+  ),
+);
diff --git tests/VersioncontrolTestCase.test tests/VersioncontrolTestCase.test
index f82e06f..b04f8a5 100644
--- tests/VersioncontrolTestCase.test
+++ tests/VersioncontrolTestCase.test
@@ -47,7 +47,7 @@ abstract class VersioncontrolTestCase extends DrupalWebTestCase {
     $magic_modules = $this->determineBackends();
     // load crucial required modules in addition to requested ones.
     $arg_modules = func_get_args();
-    $modules = array_merge(array('autoload', 'dbtng', 'views', 'versioncontrol'), $arg_modules, $magic_modules);
+    $modules = array_merge(array('autoload', 'dbtng', 'ctools', 'views', 'versioncontrol'), $arg_modules, $magic_modules);
     call_user_func_array(array('VersioncontrolTestCase', 'parent::setUp'), $modules);
 
     if (!($this->useBackends & (self::BACKENDS_NONE))) {
diff --git versioncontrol.admin.inc versioncontrol.admin.inc
index cf8d20c..cc0ce38 100644
--- versioncontrol.admin.inc
+++ versioncontrol.admin.inc
@@ -620,6 +620,35 @@ function versioncontrol_admin_repository_edit(&$form_state, $repository, $vcs =
     '#maxlength' => 255,
   );
 
+  $text = t('Versioncontrol can map commit activity in this repository to user accounts. These options allow you to select the logic that should be used to perform the mapping between Drupal users and raw VCS data.');
+  $text .= '<br/><br/>';
+  $text .= t('Versioncontrol internally allows for both "authors" and "committers" to be tracked on each commit, so you can choose distinct mapping logic for each.');
+
+  $form['user_mapping'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('User mapping'),
+    '#description' => $text,
+    '#collapsible' => TRUE,
+    '#collapsed' => FALSE,
+    '#weight' => 5,
+  );
+
+  $form['user_mapping']['author_mapper'] = array(
+    '#type' => 'radios',
+    '#title' => t('Author mapping'),
+    '#description' => t('The mapping logic to be used for "author" data. All VCSes have some sort of author field.'),
+    '#default_value' => $repository_exists ? $repository->plugins['author_mapper'] : 0,
+    '#options' => array(0 => t('None (no mapping)')) + versioncontrol_user_mapping_methods_get_names(),
+  );
+
+  $form['user_mapping']['committer_mapper'] = array(
+    '#type' => 'radios',
+    '#title' => t('Committer mapping'),
+    '#description' => t('The mapping logic to be used for "committer" data. Only some VCSes distinguish between author and committer. It is usually best to have this use the same plugin as the author mapper, especially if using "None."'),
+    '#default_value' => $repository_exists ? $repository->plugins['committer_mapper'] : 0,
+    '#options' => array(0 => t('Use author mapping plugin')) + versioncontrol_user_mapping_methods_get_names(),
+  );
+
   if ($repository_exists && isset($repository->data['versioncontrol']['registration_message'])) {
     $current_registration_message = $repository->data['versioncontrol']['registration_message'];
   }
@@ -633,7 +662,7 @@ function versioncontrol_admin_repository_edit(&$form_state, $repository, $vcs =
     '#title' => t('Account registration'),
     '#collapsible' => TRUE,
     '#collapsed' => TRUE,
-    '#weight' => 1,
+    '#weight' => 10,
   );
   $form['account_registration']['authorization_method'] = array(
     '#type' => 'select',
@@ -668,7 +697,7 @@ function versioncontrol_admin_repository_edit(&$form_state, $repository, $vcs =
       '#title' => t('Commit restrictions'),
       '#collapsible' => TRUE,
      '#collapsed' => TRUE,
-      '#weight' => 6,
+      '#weight' => 10,
     );
     $form['commit_restrictions']['allow_unauthorized_access'] = array(
       '#type' => 'checkbox',
@@ -691,7 +720,7 @@ function versioncontrol_admin_repository_edit(&$form_state, $repository, $vcs =
     '#description' =>  t('These URLs will be used to add links to item and commit displays such as the commit log.'),
     '#collapsible' => TRUE,
     '#collapsed' => TRUE,
-    '#weight' => 10,
+    '#weight' => 15,
   );
   $form['repository_urls']['commit_view'] = array(
     '#type' => 'textfield',
@@ -786,6 +815,10 @@ function versioncontrol_admin_repository_edit_submit($form, &$form_state) {
     $repository->name                 = $form_state['values']['repo_name'];
     $repository->root                 = $form_state['values']['root'];
     $repository->authorization_method = $form_state['values']['authorization_method'];
+    $repository->plugins              = array(
+      'author_mapper' => $form_state['values']['author_mapper'],
+      'committer_mapper' => $form_state['values']['committer_mapper'],
+    );
   }
   else {
     $backends = versioncontrol_get_backends();
@@ -795,8 +828,12 @@ function versioncontrol_admin_repository_edit_submit($form, &$form_state) {
       'root' => $form_state['values']['root'],
       'authorization_method' => $form_state['values']['authorization_method'],
       'backend' => $backends[$form['#vcs']],
+      'plugins' => array(
+        'author_mapper' => $form_state['values']['author_mapper'],
+        'committer_mapper' => $form_state['values']['committer_mapper'],
+      ),
     );
-     $repository = $backends[$form['#vcs']]->buildEntity('repo', $repository);
+    $repository = $backends[$form['#vcs']]->buildEntity('repo', $repository);
   }
 
   // We also provide a 'data' array where other modules can put their own
diff --git versioncontrol.info versioncontrol.info
index 37c0660..cb18a39 100644
--- versioncontrol.info
+++ versioncontrol.info
@@ -3,6 +3,7 @@ name = "Version Control API"
 description = "An interface to version control systems whose functionality is provided by pluggable back-end modules."
 dependencies[] = autoload
 dependencies[] = dbtng
+dependencies[] = ctools
 dependencies[] = views
 files[] = includes/VersioncontrolAccount.php
 files[] = includes/VersioncontrolBackend.php
diff --git versioncontrol.install versioncontrol.install
index b6bca10..d27e085 100644
--- versioncontrol.install
+++ versioncontrol.install
@@ -357,6 +357,13 @@ function versioncontrol_schema() {
         'not null' => TRUE,
         'serialize' => TRUE,
       ),
+      'plugins' => array(
+        'description' => t('The list of ctools plugins associated with this repository.'),
+        'type' => 'text',
+        'size' => 'medium',
+        'not null' => TRUE,
+        'serialize' => TRUE,
+      ),
     ),
     'unique keys' => array(
       'name' => array('name'),
@@ -804,7 +811,8 @@ function versioncontrol_update_6306() {
   return $ret;
 }
 
-/* Allow independent uid mappings for both author and committer data.
+/**
+ * Allow independent uid mappings for both author and committer data.
  *
  * @return array
  */
@@ -834,3 +842,20 @@ function versioncontrol_update_6307() {
   db_query('UPDATE {versioncontrol_operations} SET committer_uid = author_uid WHERE (committer = author OR committer = "")');
   return $ret;
 }
+
+/**
+ * Add 'plugins' column to {versioncontrol_repositories}.
+ */
+function versioncontrol_update_6308() {
+  $ret = array();
+  $plugins = array(
+    'description' => 'The list of ctools plugins associated with this repository.',
+    'type' => 'text',
+    'size' => 'medium',
+    'not null' => TRUE,
+    'serialize' => TRUE,
+  );
+  db_add_field($ret, 'versioncontrol_repositories', 'plugins', $plugins);
+  // FIXME add array('user mapping' => 'some_plugin') as default
+  return $ret;
+}
diff --git versioncontrol.module versioncontrol.module
index f54703a..4a5623d 100644
--- versioncontrol.module
+++ versioncontrol.module
@@ -1105,3 +1105,27 @@ function _versioncontrol_get_string_presets() {
 
   return $presets;
 }
+
+/**
+ * Implementation of ctools hook_ctools_plugin_directory().
+ */
+function versioncontrol_ctools_plugin_directory($module, $plugin) {
+  if ($module == 'versioncontrol') {
+    return "includes/plugins/$plugin";
+  }
+}
+
+/**
+ * Load the names of all 'user_mapping_methods' for use at forms.
+ */
+function versioncontrol_user_mapping_methods_get_names() {
+  ctools_include('plugins');
+
+  $names = array();
+  foreach (ctools_get_plugins('versioncontrol', 'user_mapping_methods') as $name => $plugin) {
+    $names[$name] = $plugin['title'];
+  }
+
+  asort($names);
+  return $names;
+}
diff --git versioncontrol_account_status/versioncontrol_account_status.test versioncontrol_account_status/versioncontrol_account_status.test
index 8a0cbe3..86298ba 100644
--- versioncontrol_account_status/versioncontrol_account_status.test
+++ versioncontrol_account_status/versioncontrol_account_status.test
@@ -61,8 +61,7 @@ class VersioncontrolAccountStatusTestCase extends VersioncontrolTestCase {
     $this->drupalPost('admin/project/versioncontrol-repositories/add-test', $repo_edit, t('Save repository'));
 
     // get the created repo
-    $found_repositories = $this->testBackend->loadEntities('repo', array(), array('name' => array($repo_edit['repo_name'])));
-    $repo = array_shift($found_repositories);
+    $repo = $this->testBackend->loadEntity('repo', array(), array('name' => array($repo_edit['repo_name'])));
     $this->assertTrue(!is_null($repo), t('Repository found in database.'));
 
     // visit the repository edit form again, and make sure those texts still exist in the same text fields
