diff --git a/pift.admin.inc b/pift.admin.inc
index ca73d8f..f254b66 100644
--- a/pift.admin.inc
+++ b/pift.admin.inc
@@ -79,6 +79,25 @@ function pift_admin_settings_form() {
     '#required' => TRUE,
   );
 
+  $project_types = array();
+  foreach (project_get_project_types() as $key => $project_type) {
+    $project_types[$key] = $project_type->name;
+  }
+  $form['results'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Results Display'),
+    '#weight' => 0,
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+  );
+  $form['results']['pift_results_display'] = array(
+    '#type' => 'checkboxes',
+    '#title' => t('Project types'),
+    '#description' => t('Project types for which the "Testing Status" tab should be displayed on the project page.'),
+    '#options' => $project_types,
+    '#default_value' => variable_get('pift_results_display', array()),
+  );
+
   $form['criteria'] = array(
     '#type' => 'fieldset',
     '#title' => t('Criteria'),
diff --git a/pift.module b/pift.module
index 9a1d4f1..688d560 100644
--- a/pift.module
+++ b/pift.module
@@ -91,11 +91,44 @@ function pift_menu() {
     'file' => 'pift.pages.inc',
     'type' => MENU_CALLBACK,
   );
+  $items['node/%node/testing-status'] = array(
+    'title' => t('Automated Testing'),
+    'access callback' => 'pift_results_visibility',
+    'access arguments' => array(1),
+    'page callback' => 'pift_results_project_tab',
+    'page arguments' => array(1),
+    'file' => 'pift.results.inc',
+    'weight' => '5',
+    'type' => MENU_LOCAL_TASK,
+  );
 
   return $items;
 }
 
 /**
+ * Access callback to determine whether the Automated Testing tab is visible.
+ */
+function pift_results_visibility($node) {
+  // Check if we have a module node which has a Git repository
+  // Do not display the tab if the project does not have any release nodes
+  // TODO: Once PIFT/PIFR has been refactored to use label_ids instead of
+  // release nids, the last condition can be changed to enable reporting
+  // of tests for any tag/branch within a project.
+
+  // Determine if node represents a valid project type for results display
+  $valid_type = array_intersect(variable_get('pift_results_display', array()), array_keys($node->taxonomy));
+  if ($node->type == 'project_project'
+    && !empty($valid_type)
+    && !empty($node->versioncontrol_project['repo']->vcs)
+    && $node->versioncontrol_project['repo']->vcs == 'git'
+    && user_is_logged_in()
+    && pift_get_releases($node, TRUE) != array()) {
+      return TRUE;
+  }
+  return FALSE;
+}
+
+/**
  * Implementation of hook_perm().
  */
 function pift_perm() {
@@ -204,6 +237,64 @@ function pift_versioncontrol_git_refs_updated($repository, $refs) {
 }
 
 /**
+ * Helper fuction to build a list of releases for this project's repository.
+ *
+ * @param $node The project node object
+ * @param $quiet Silence error messages
+ * @return array List of available labels with corresponding release nodes
+ */
+function pift_get_releases($node, $quiet = FALSE) {
+  $branches = array();
+  if ($node->type == 'project_project') {
+    $result = db_query("Select b.name from {versioncontrol_release_labels} a
+       join {versioncontrol_labels} b on a.label_id = b.label_id
+       where a.project_nid = %s", $node->nid);
+    while ($data = db_fetch_object($result)) {
+      // Filter out any branches < 6 (not accepted by PIFT)
+      // Move '.x' releases to the top
+      if (in_array(substr($data->name, 0, 1), array('6', '7', '8'))) {
+        if (substr($data->name, strlen($data->name) - 2, 2) == '.x') {
+          $topbranches[$data->name] = $data->name;
+        }
+        else {
+          $branches[$data->name] = $data->name;
+        }
+      }
+    }
+    // Sort to get the highest branch on top
+    uasort($branches, 'version_compare');
+    uasort($topbranches, 'version_compare');
+    $branches = array_merge($branches, $topbranches);
+    $branches = pift_array_reverse($branches);
+
+    if (empty($branches) && !$quiet) {
+      drupal_set_message(t('No releases found for the given project.'), 'error', FALSE);
+    }
+  }
+  return $branches;
+}
+
+/**
+ * Alternative function for the php standard array_reverse()
+ *
+ * As array_reverse() would damage numerical array keys, from
+ * http://drupal.org/node/1074220.  Code copied from project_git_instructions
+ *
+ * Borrowed from http://php.net/manual/en/function.array-reverse.php#102492
+ */
+function pift_array_reverse($array) {
+  $array_key = array_keys($array);
+  $array_value = array_values($array);
+
+  $array_return = array();
+  for ($i = 1, $size_of_array = sizeof($array_key); $i <= $size_of_array; $i++) {
+    $array_return[$array_key[$size_of_array-$i]] = $array_value[$size_of_array-$i];
+  }
+
+  return $array_return;
+}
+
+/**
  * Implementation of hook_form_alter(). Must use generic form_alter() due to
  * comment_upload implementation.
  */
@@ -226,7 +317,19 @@ function pift_form_project_issue_project_edit_form_alter(&$form, $form_state) {
  * Implementation of hook_nodeapi().
  */
 function pift_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
-  if ($node->type != 'project_issue' || empty($node->files)) {
+  if ($node->type == 'project_project') {
+    // Used to maintain 'nice' url aliases for testing status pages
+    $src = 'node/' . $node->nid . '/testing-status';
+    if ($op == 'delete') {
+      db_query("DELETE FROM {url_alias} WHERE src = '%s'", $src);
+    }
+    elseif (($op == 'update' || $op == 'insert') && !empty($node->project['uri'])) {
+      $dst = 'project/' . drupal_urlencode($node->project['uri']) . '/testing-status';
+      db_query("DELETE FROM {url_alias} WHERE src = '%s'", $src);
+      db_query("INSERT INTO {url_alias} (src, dst) VALUES ('%s', '%s')", $src, $dst);
+    }
+  }
+  elseif ($node->type != 'project_issue' || empty($node->files)) {
     return;
   }
 
diff --git a/pift.pages.inc b/pift.pages.inc
index 99a1781..7d1acc0 100644
--- a/pift.pages.inc
+++ b/pift.pages.inc
@@ -94,19 +94,36 @@ function pift_pages_retest_confirm_form(&$form_state, $test_id) {
 
   if ($test) {
     $form = array();
-    $form['test'] = array(
-      '#type' => 'value',
-      '#value' => array(
-        'test_id' => $test_id,
-        'fid' => $test['fid'],
-        'nid' => ($test['fid'] ? $test['nid'] : $test['rid']),
-        'cid' => ($test['cid'] ? $test['cid'] : 0),
-        'status' => $test['status'],
-        'title' => ($test['fid'] ? $test['filename'] : project_release_get_version((object) $test))
-      ),
-    );
-    $form['#redirect'] = 'node/' . $form['test']['#value']['nid'];
-
+    // Key off type to determine if this is a file retest or branch retest
+    if ($test['type'] == PIFT_TYPE_FILE) {
+      // File test
+      $form['test'] = array(
+        '#type' => 'value',
+        '#value' => array(
+          'test_id' => $test_id,
+          'fid' => $test['fid'],
+          'nid' => ($test['fid'] ? $test['nid'] : $test['rid']),
+          'cid' => ($test['cid'] ? $test['cid'] : 0),
+          'status' => $test['status'],
+          'title' => ($test['fid'] ? $test['filename'] : project_release_get_version((object) $test)),
+          'type' => PIFT_TYPE_FILE,
+        ),
+      );
+      $form['#redirect'] = 'node/' . $form['test']['#value']['nid'];
+    }
+    elseif ($test['type'] == PIFT_TYPE_RELEASE) {
+      // Branch test
+      $form['test'] = array(
+        '#type' => 'value',
+        '#value' => array(
+          'test_id' => $test_id,
+          'rid' => $test['id'],
+          'status' => $test['status'],
+          'title' => "Test " . check_plain($test['test_id']),
+          'type' => PIFT_TYPE_RELEASE,
+        ),
+      );
+    }
     return confirm_form(
       $form,
       t('Are you sure you want to request that %title be re-tested?', array('%title' => $form['test']['#value']['title'])),
@@ -130,22 +147,24 @@ function pift_pages_retest_confirm_form_submit($form, &$form_state) {
   $test = $form_state['values']['test'];
 
   if ($test['status'] > PIFT_STATUS_SENT) {
-    // Base changes to be made in followup comment.
-    $changes = array(
-      'nid' => $test['nid'],
-      'uid' => $user->uid,
-      'comment' => theme('pift_auto_followup', 'retest', $test['nid'], $test['cid'], $test['title']),
-    );
+    if ($test['type'] == PIFT_TYPE_FILE) {
+      // Base changes to be made in followup comment.
+      $changes = array(
+        'nid' => $test['nid'],
+        'uid' => $user->uid,
+        'comment' => theme('pift_auto_followup', 'retest', $test['nid'], $test['cid'], $test['title']),
+      );
 
-    // If node issue status is not already a valid status then set to default
-    // retest status, otherwise leave status alone.
-    $node = node_load($test['nid']);
-    if (!in_array($node->project_issue['sid'], variable_get('pift_status', array()))) {
-      $changes['sid'] = PIFT_FOLLOWUP_RETEST;
-    }
+      // If node issue status is not already a valid status then set to default
+      // retest status, otherwise leave status alone.
+      $node = node_load($test['nid']);
+      if (!in_array($node->project_issue['sid'], variable_get('pift_status', array()))) {
+        $changes['sid'] = PIFT_FOLLOWUP_RETEST;
+      }
 
-    // Add followup to issue.
-    project_issue_add_auto_followup($changes);
+      // Add followup to issue.
+      project_issue_add_auto_followup($changes);
+    }
 
     // Update test record to reflect change.
     db_query('UPDATE {pift_test}
