diff --git devel.drush.inc devel.drush.inc
index de63761..e4e1b85 100644
--- devel.drush.inc
+++ devel.drush.inc
@@ -50,6 +50,25 @@ function devel_drush_command() {
     'aliases' => array('token'),
     'core' => array(7), // Remove once 3.0 is released.
   );
+  // The 'release-notes' command.
+  $items['release-notes'] = array(
+    'description' => 'Generate release notes using all commits between two tags.',
+    'arguments' => array(
+      'start tag' => 'Tag or branch to start from.',
+      'end tag' => 'Tag or branch to end on.',
+    ),
+    'options' => array(
+      'git' => 'Path to the git binary, defaults to "git"',
+    ),
+    'examples' => array(
+      'drush release-notes 6.x-1.0 6.x-1.1' => 'Generate release notes from all commits between 6.x-1.0 and 6.x-1.1',
+      'drush release-notes 6.x-1.0 6.x-1.x' => 'Generate release notes from all commits between 6.x-1.0 and 6.x-1.x (a branch)',
+      'drush release-notes 6.x-1.0 6.x-1.1 --git=/usr/local/git/bin/git' => 'Generate release notes from all commits between 6.x-1.0 and 6.x-1.1, using git in /usr/local/git/bin/git.',
+      'drush release-notes 6.x-1.0 origin/6.x-1.x' => 'If you don\'t have the branch locally you might need to use "[remote-name]/[branch-name]".',
+    ),
+    'aliases' => array('rn'),
+  );
+
   return $items;
 }
 
@@ -62,6 +81,8 @@ function devel_drush_help($section) {
       return dt('Disable, Uninstall, and Install a list of projects.');
     case 'drush:devel-download':
       return dt("Downloads the FirePHP library from http://firephp.org/. Places it in the devel module directory. Skips download if library already present. This all happens automatically if you enable devel using drush.");
+    case 'drush:release-notes':
+      return dt('Generate release notes from between two Git tags.');
   }
 }
 
@@ -182,6 +203,94 @@ function drush_devel_token() {
   drush_print_table($rows, TRUE);
 }
 
+/**
+ * Command handler. Generate release notes.
+ */
+function drush_devel_release_notes($start_tag, $end_tag) {
+  $git = 'git';
+  $additional = '.';
+
+  // Locate git binary.
+  if (drush_get_option('git')) {
+    $git = drush_get_option('git');
+    $additional = ' with Git at !git.';
+  }
+
+  // Verify command is being executed from the root of a git repository.
+  chdir(getcwd());
+  if (!is_dir('.git')) {
+    drush_log('This must be run from the root directory of your Git project.');
+  }
+
+  // Test for existence of tags.
+  if (!drush_shell_exec('%s show -s --format=%%H %s^{commit}', $git, $start_tag)) {
+    return drush_set_error('DRUSH_INVALID_TAG', dt('!tag is not a valid Git tag.', array('!tag' => $start_tag)));
+  }
+  $start_tag_output = drush_shell_exec_output();
+
+  if (!drush_shell_exec('%s show -s --format=%%H %s^{commit}', $git, $end_tag)) {
+    return drush_set_error('DRUSH_INVALID_TAG', dt('!tag is not a valid Git tag.', array('!tag' => $end_tag)), 'error');
+  }
+  $end_tag_output = drush_shell_exec_output();
+
+  // Determine changes and print.
+  $changes = _drush_devel_get_changes($start_tag_output[0], $end_tag_output[0], $git);
+  drush_print(_drush_devel_format_changes($changes, $start_tag));
+}
+
+/**
+ * Generate HTML list of changes between two tags or branchs in a repository.
+ *
+ * @param $changes
+ *   Array of log messages.
+ * @param $previous_tag
+ *   Name of start tag.
+ *
+ * @return
+ *   List of changes formatted as an HTML list.
+ */
+function _drush_devel_format_changes($changes, $previous_tag) {
+  $return = "<p>Changes since $previous_tag:</p>\n";
+  $return .= "<ul>\n";
+  foreach ($changes as $line) {
+    $return .= '<li>' . preg_replace('/#(\d+)/', '<a href="/node/$1">#$1</a>', $line) . "</li>\n";
+  }
+  $return .= "</ul>\n";
+
+  return $return;
+}
+
+/**
+ * Get list of changes between two tags or branches in a git repository.
+ *
+ * @param $start_tag
+ *   Tag or branch to start from.
+ * @param $end_tag
+ *   Tag or branch to end on.
+ * @param $git
+ *   Path to git binary.
+ *
+ * @return
+ *   Log messages as an array.
+ */
+function _drush_devel_get_changes($start_tag, $end_tag, $git) {
+  $changes = array();
+
+  // Query git for log messages between two tags.
+  if (!drush_shell_exec("%s log -s --format=%%B %s..%s", $git, $start_tag, $end_tag)) {
+    return drush_set_error('DRUSH_GIT_LOG_ERROR', 'git log returned an error.');
+  }
+  $output = drush_shell_exec_output();
+
+  // Remove empty lines from git-log output.
+  foreach ($output as $key => $value) {
+    if (empty($output[$key])) {
+      unset($output[$key]);
+    }
+  }
+
+  return $output;
+}
 
 /**
  * Print the specified function, including any
