Index: trac/env.py
===================================================================
--- trac/env.py	(revision 5999)
+++ trac/env.py	(working copy)
@@ -23,7 +23,7 @@
 import sys
 from urlparse import urlsplit
 
-from trac import db_default
+from trac import db_default, loader
 from trac.config import *
 from trac.core import Component, ComponentManager, implements, Interface, \
                       ExtensionPoint, TracError
@@ -164,18 +164,16 @@
             ('Trac', get_pkginfo(core).get('version', VERSION)),
             ('Python', sys.version),
             ('setuptools', setuptools.__version__),
-            ]
+        ]
         self._href = self._abs_href = None
 
-        from trac.loader import load_components
-        plugins_dir = self.config.get('inherit', 'plugins_dir')
-        load_components(self, plugins_dir and (plugins_dir,))
-
         if create:
             self.create(options)
         else:
             self.verify()
 
+        self._load_components()
+
         if create:
             for setup_participant in self.setup_participants:
                 setup_participant.environment_created()
@@ -225,6 +223,10 @@
         # By default, all components in the trac package are enabled
         return component_name.startswith('trac.')
 
+    def reset(self):
+        ComponentManager.reset(self)
+        self._load_components()
+
     def verify(self):
         """Verify that the provided path points to a valid Trac environment
         directory."""
@@ -429,7 +431,11 @@
         return self._abs_href
     abs_href = property(_get_abs_href, 'The application URL')
 
+    def _load_components(self):
+        plugins_dir = self.shared_plugins_dir
+        loader.load_components(self, plugins_dir and (plugins_dir,) or ())
 
+
 class EnvironmentSetup(Component):
     implements(IEnvironmentSetupParticipant)
 
@@ -521,7 +527,8 @@
             else:
                 # Re-parse the configuration file if it changed since the last
                 # the time it was parsed
-                env.config.parse_if_needed()
+                if env.config.parse_if_needed():
+                    env.reset()
         finally:
             env_cache_lock.release()
     else:
Index: trac/core.py
===================================================================
--- trac/core.py	(revision 5999)
+++ trac/core.py	(working copy)
@@ -1,8 +1,8 @@
 # -*- coding: utf-8 -*-
 #
-# Copyright (C) 2003-2005 Edgewall Software
+# Copyright (C) 2003-2007 Edgewall Software
 # Copyright (C) 2003-2004 Jonas Borgström <jonas@edgewall.com>
-# Copyright (C) 2004-2005 Christopher Lenz <cmlenz@gmx.de>
+# Copyright (C) 2004-2007 Christopher Lenz <cmlenz@gmx.de>
 # All rights reserved.
 #
 # This software is licensed as described in the file COPYING, which
@@ -24,7 +24,7 @@
     """Exception base class for errors in Trac."""
 
     title = 'Trac Error'
-    
+
     def __init__(self, message, title=None, show_traceback=False):
         """If message is a genshi.builder.tag object, everything up to the
         first <p> will be displayed in the red box, and everything after will
@@ -218,3 +218,7 @@
         not be available.
         """
         return True
+
+    def reset(self):
+        self.components = {}
+        self.enabled = {}
Index: trac/admin/web_ui.py
===================================================================
--- trac/admin/web_ui.py	(revision 5999)
+++ trac/admin/web_ui.py	(working copy)
@@ -381,7 +381,7 @@
                 self._do_update(req)
             anchor = ''
             if req.args.has_key('plugin'):
-                anchor = '#no' + req.args.get('plugin')
+                anchor = '#no%d' % (int(req.args.get('plugin')) + 1)
             req.redirect(req.href.admin(cat, page) + anchor)
 
         return self._render_view(req)
@@ -426,6 +426,9 @@
 
         # TODO: Validate that the uploaded file is actually a valid Trac plugin
 
+        # Make the environment reset itself on the next request
+        self.env.config.touch()
+
     def _do_uninstall(self, req):
         """Uninstall a plugin."""
         plugin_filename = req.args.get('plugin_filename')
@@ -437,6 +440,9 @@
         self.log.info('Uninstalling plugin %s', plugin_filename)
         os.remove(plugin_path)
 
+        # Make the environment reset itself on the next request
+        self.env.config.touch()
+
     def _do_update(self, req):
         """Update component enablement."""
         components = req.args.getlist('component')
Index: trac/config.py
===================================================================
--- trac/config.py	(revision 5999)
+++ trac/config.py	(working copy)
@@ -173,14 +173,17 @@
         finally:
             fileobj.close()
 
-    def parse_if_needed(self):
-        # Load global configuration
+    def parse_if_needed(self, check_only=False):
         if not self.filename or not os.path.isfile(self.filename):
-            return
+            return False
+
+        changed = False
         modtime = os.path.getmtime(self.filename)
         if modtime > self._lastmtime:
+            self.parser._sections = {}
             self.parser.read(self.filename)
             self._lastmtime = modtime
+            changed = True
 
         if self.parser.has_option('inherit', 'file'):
             filename = self.parser.get('inherit', 'file')
@@ -189,12 +192,19 @@
                                         filename)
             if not self.parent or self.parent.filename != filename:
                 self.parent = Configuration(filename)
+                changed = True
             else:
-                self.parent.parse_if_needed()
+                changed |= self.parent.parse_if_needed(check_only=check_only)
         elif self.parent:
+            changed = True
             self.parent = None
 
+        return changed
 
+    def touch(self):
+        os.utime(self.filename, None)
+
+
 class Section(object):
     """Proxy for a specific configuration section.
     
@@ -292,7 +302,9 @@
         """
         if self.config.parser.has_option(self.name, name):
             path = self.config.parser.get(self.name, name)
-            if path and not os.path.isabs(path):
+            if not path:
+                return default
+            if not os.path.isabs(path):
                 path = os.path.join(os.path.dirname(self.config.filename),
                                     path)
             return os.path.normcase(os.path.realpath(path))
Index: trac/tests/core.py
===================================================================
--- trac/tests/core.py	(revision 5999)
+++ trac/tests/core.py	(working copy)
@@ -281,7 +281,15 @@
         instance = ComponentA(mgr)
         self.assertEqual(None, mgr[ComponentA])
 
+    def test_reset(self):
+        class ComponentA(Component):
+            pass
+        instance = ComponentA(self.compmgr)
+        assert ComponentA in self.compmgr
+        self.compmgr.reset()
+        assert ComponentA not in self.compmgr
 
+
 def suite():
     return unittest.makeSuite(ComponentTestCase, 'test')
 
Index: trac/tests/env.py
===================================================================
--- trac/tests/env.py	(revision 5999)
+++ trac/tests/env.py	(working copy)
@@ -1,4 +1,5 @@
 from trac import db_default
+from trac.db import sqlite_backend
 from trac.env import Environment
 
 import os.path
Index: trac/log.py
===================================================================
--- trac/log.py	(revision 5999)
+++ trac/log.py	(working copy)
@@ -44,7 +44,7 @@
             format = '%(asctime)s ' + format
     datefmt = ''
     if logtype == 'stderr':
-        datefmt = '%X'        
+        datefmt = '%X'
     level = level.upper()
     if level in ('DEBUG', 'ALL'):
         logger.setLevel(logging.DEBUG)
Index: trac/util/__init__.py
===================================================================
--- trac/util/__init__.py	(revision 5999)
+++ trac/util/__init__.py	(working copy)
@@ -228,6 +228,10 @@
         pkginfo = email.message_from_string(dist.get_metadata('PKG-INFO'))
         for attr in [key for key in attrs if key in pkginfo]:
             info[normalize(attr)] = pkginfo[attr]
+    except IOError, e:
+        err = 'Failed to read PKG-INFO file for %s: %s' % (dist, e)
+        for attr in attrs:
+            info[normalize(attr)] = err
     except email.Errors.MessageError, e:
         err = 'Failed to parse PKG-INFO file for %s: %s' % (dist, e)
         for attr in attrs:

