=== modified file 'CHANGELOG.txt'
--- CHANGELOG.txt	2009-07-28 12:13:46 +0000
+++ CHANGELOG.txt	2009-07-28 15:08:47 +0000
@@ -145,6 +145,8 @@ Drupal 7.0, xxxx-xx-xx (development vers
     * Upgraded the jQuery Forms library to 2.21.
     * Added jQuery UI 1.7.2, which allows improvements to Drupal's user
       experience.
+- Better module version support.
+    * Modules now can specify which version of another module they depend on.
 
 Drupal 6.0, 2008-02-13
 ----------------------

=== modified file 'includes/graph.inc'
--- includes/graph.inc	2009-04-11 17:58:17 +0000
+++ includes/graph.inc	2009-07-28 15:07:52 +0000
@@ -14,7 +14,7 @@
  *   A three dimensional associated array, with the first keys being the names
  *   of the vertices, these can be strings or numbers. The second key is
  *   'edges' and the third one are again vertices, each such key representing
- *   an edge. Values of array elements do not matter.
+ *   an edge. Values of array elements are copied over.
  *
  *   Example:
  *   @code
@@ -108,7 +108,7 @@ function _drupal_depth_first_search(&$gr
   if (isset($graph[$start]['edges'])) {
     foreach ($graph[$start]['edges'] as $end => $v) {
       // Mark that $start can reach $end.
-      $graph[$start]['paths'][$end] = TRUE;
+      $graph[$start]['paths'][$end] = $v;
 
       if (isset($graph[$end]['component']) && $component != $graph[$end]['component']) {
         // This vertex already has a component, use that from now on and
@@ -136,7 +136,7 @@ function _drupal_depth_first_search(&$gr
   // paths.
   foreach ($graph[$start]['paths'] as $end => $v) {
     if (isset($graph[$end])) {
-      $graph[$end]['reverse_paths'][$start] = TRUE;
+      $graph[$end]['reverse_paths'][$start] = $v;
     }
   }
 

=== modified file 'includes/module.inc'
--- includes/module.inc	2009-07-23 21:14:09 +0000
+++ includes/module.inc	2009-07-28 15:07:52 +0000
@@ -95,11 +95,52 @@ function module_list($refresh = FALSE, $
 function _module_build_dependencies($files) {
   require_once DRUPAL_ROOT . '/includes/graph.inc';
   $roots = $files;
+  // We use named subpatterns and support every op that version_compare
+  // supports. Also, op is optional and defaults to equals.
+  $p_op = '(?P<operation>!=|==|=|<|<=|>|>=|<>)?';
+  // Core version is always optional: 7.x-2.x and 2.x is treated the same.
+  $p_core = '(?:' . preg_quote(DRUPAL_CORE_COMPATIBILITY) . '-)?';
+  $p_major = '(?P<major>\d+)';
+  // By setting the minor version to x, branches can be matched.
+  $p_minor = '(?P<minor>\d+|x)';
   foreach ($files as $filename => $file) {
     $graph[$file->name]['edges'] = array();
     if (isset($file->info['dependencies']) && is_array($file->info['dependencies'])) {
       foreach ($file->info['dependencies'] as $dependency_name) {
-        $graph[$file->name]['edges'][$dependency_name] = 1;
+        $value = array();
+        $parts = explode('(', $dependency_name, 2);
+        $dependency_name = trim($parts[0]);
+        if (isset($parts[1])) {
+          $value['original_version'] = ' (' . $parts[1];
+          foreach (explode(',', $parts[1]) as $version) {
+            if (preg_match("/^\s*$p_op\s*$p_core$p_major\.$p_minor/", $version, $matches)) {
+              $op = !empty($matches['operation']) ? $matches['operation'] : '=';
+              if ($matches['minor'] == 'x') {
+                // If a module is newer than 2.x then it's at least 3.0. 
+                $matches['minor'] = 0;
+                if ($op == '>') {
+                  $matches['major']++;
+                  $op = '>=';
+                }
+                // If a module is older or equivalent than 2.x then it is older
+                // than 3.0.
+                if ($op == '<=') {
+                  $matches['major']++;
+                  $op = '<';
+                }
+                // Equivalence is checked by preg.
+                if ($op == '=' || $op == '==') {
+                  $value['versions'][] = array('preg' => '/^' . $matches['major'] . '\./');
+                  $op = '';
+                }
+              }
+              if ($op) {
+                $value['versions'][] = array('op' => $op, 'version' => $matches['major'] . '.' . $matches['minor']);
+              }
+            }
+          }
+        }
+        $graph[$file->name]['edges'][$dependency_name] = $value;
         unset($roots[$dependency_name]);
       }
     }
@@ -138,13 +179,13 @@ function module_load_install($module) {
 
 /**
  * Load a module include file.
- * 
+ *
  * Examples:
  * @code
  *   // Load node.admin.inc from the node module.
  *   module_load_include('inc', 'node', 'node.admin');
  *   // Load content_types.inc from the node module.
- *   module_load_include('inc', 'node', 'content_types'); 
+ *   module_load_include('inc', 'node', 'content_types');
  * @endcode
  *
  * Do not use this function to load an install file. Use module_load_install()
@@ -155,7 +196,7 @@ function module_load_install($module) {
  * @param $module
  *   The module to which the include file belongs.
  * @param $name
- *   Optionally, specify the base file name (without the $type extension). 
+ *   Optionally, specify the base file name (without the $type extension).
  *   If not set, $module is used.
  */
 function module_load_include($type, $module, $name = NULL) {
@@ -194,7 +235,7 @@ function module_load_all_includes($type,
  */
 function module_enable($module_list, $disable_modules_installed_hook = FALSE) {
   $invoke_modules = array();
-  
+
   // Try to install the enabled modules and collect which were installed.
   // $module_list is not changed and already installed modules are ignored.
   $modules_installed = array_filter($module_list, '_drupal_install_module');

=== modified file 'modules/simpletest/tests/system_test.module'
--- modules/simpletest/tests/system_test.module	2009-06-06 15:43:05 +0000
+++ modules/simpletest/tests/system_test.module	2009-07-28 15:07:52 +0000
@@ -165,3 +165,21 @@ function system_test_exit() {
   watchdog('system_test', 'hook_exit');
 }
 
+
+function system_test_system_info_alter(&$info, $file) {
+  // We need a static otherwise the last test will fail to alter common_test.
+  static $test;
+  if (($dependencies = variable_get('dependencies', array())) || $test) {
+    if ($file->name == 'module_test') {
+      $info['hidden'] = FALSE;
+      $info['dependencies'][] = array_shift($dependencies);
+      variable_set('dependencies', $dependencies);
+      $test = TRUE;
+    }
+    if ($file->name == 'common_test') {
+      $info['hidden'] = FALSE;
+      $info['version'] = '7.x-2.4';
+    }
+  }
+}
+

=== modified file 'modules/system/system.admin.inc'
--- modules/system/system.admin.inc	2009-07-20 18:51:31 +0000
+++ modules/system/system.admin.inc	2009-07-28 15:07:52 +0000
@@ -598,13 +598,32 @@ function system_modules($form_state = ar
     foreach ($module->requires as $requires => $v) {
       if (!isset($files[$requires])) {
         $extra['requires'][$requires] = t('@module (<span class="admin-missing">missing</span>)', array('@module' => drupal_ucfirst($requires)));
-          $extra['disabled'] = TRUE;
+        $extra['disabled'] = TRUE;
+      }
+      else {
+        $requires_name = $files[$requires]->info['name'];
+        if ($v) {
+          $requires_name .= $v['original_version'];
+          $current_version = str_replace(DRUPAL_CORE_COMPATIBILITY . '-', '', $files[$requires]->info['version']);
+          foreach ($v['versions'] as $required_version) {
+            if ((isset($required_version['op']) && !version_compare($current_version, $required_version['version'], $required_version['op'])) ||
+                (isset($required_version['preg']) && !preg_match($required_version['preg'], $current_version))) {
+              $extra['requires'][$requires] = t('@module (<span class="admin-missing">incompatible with</span> version @version)', array(
+                '@module' => $requires_name,
+                '@version' => $files[$requires]->info['version'],
+              ));
+              $extra['disabled'] = TRUE;
+            }
+          }
         }
-      elseif (!$files[$requires]->status) {
-        $extra['requires'][$requires] = t('@module (<span class="admin-disabled">disabled</span>)', array('@module' => $files[$requires]->info['name']));
+        if (!isset($extra['requires'][$requires])) {
+          if ($files[$requires]->status) {
+            $extra['requires'][$requires] = t('@module (<span class="admin-enabled">enabled</span>)', array('@module' => $requires_name));
+          }
+          else {
+            $extra['requires'][$requires] = t('@module (<span class="admin-disabled">disabled</span>)', array('@module' => $requires_name));
+          }
         }
-        else {
-        $extra['requires'][$requires] = t('@module (<span class="admin-enabled">enabled</span>)', array('@module' => $files[$requires]->info['name']));
       }
     }
     // Generate link for module's help page, if there is one.

=== modified file 'modules/system/system.test'
--- modules/system/system.test	2009-07-23 20:58:25 +0000
+++ modules/system/system.test	2009-07-28 15:07:52 +0000
@@ -198,6 +198,61 @@ class ModuleDependencyTestCase extends M
 }
 
 /**
+ * Test module dependency on specific versions.
+ */
+class ModuleVersionTestCase extends ModuleTestCase {
+  public static function getInfo() {
+    return array(
+      'name' => 'Module versions',
+      'description' => 'Check module version dependencies.',
+      'group' => 'Module',
+    );
+  }
+  
+  function setup() {
+    parent::setUp('module_test');
+  }
+
+  /**
+   * Attempt to enable translation module without locale enabled.
+   */
+  function testModuleVersions() {
+    $dependencies = array(
+      // Alternating between being compatible and incompatible with 7.x-2.4.
+      // The first is always a compatible.
+      'common_test',
+      // Branch incompatibility.
+      'common_test (1.x)',
+      // Branch compatibility.
+      'common_test (2.x)',
+      // Another branch incompatibility.
+      'common_test (>2.x)',
+      // Another branch compatibility.
+      'common_test (<=2.x)',
+      // Another branch incompatibility.
+      'common_test (<2.x)',
+      // Another branch compatibility.
+      'common_test (>=2.x)',
+      // Nonsense, misses a dash. Incompatible with everything.
+      'common_test (=7.x2.x, >=2.4)',
+      // Core version is optional. Compatible.
+      'common_test (=7.x-2.x, >=2.4)',
+      // Test !=, explicitly incompatible.
+      'common_test (=2.x, !=2.4)',
+      // Three operations. Compatible.
+      'common_test (=2.x, !=2.3, <2.5)',
+    );
+    variable_set('dependencies', $dependencies);
+    $n = count($dependencies);
+    for ($i = 0; $i < $n; $i++) {
+      $this->drupalGet('admin/structure/modules');
+      $checkbox = $this->xpath('//input[@id="edit-modules-Testing-module-test-enable"]');
+      $this->assertEqual(!empty($checkbox[0]['disabled']), $i % 2, $dependencies[$i]);
+    }
+  }
+}
+
+/**
  * Test required modules functionality.
  */
 class ModuleRequiredTestCase extends ModuleTestCase {

