diff -ru trac-0.8.orig/templates/timeline.cs trac-0.8/templates/timeline.cs
--- trac-0.8.orig/templates/timeline.cs	2004-11-17 23:21:52.000000000 +0100
+++ trac-0.8/templates/timeline.cs	2005-01-18 11:52:26.000000000 +0100
@@ -1,5 +1,6 @@
 <?cs set:html.stylesheet = 'css/timeline.css' ?>
 <?cs include "header.cs"?>
+<?cs include "macros.cs"?>
 
 <div id="ctxtnav" class="nav"></div>
 
@@ -35,6 +36,12 @@
       if:timeline.milestone ?>checked="checked"<?cs /if ?> />
     <label for="milestone">Milestones</label>
    </div><?cs /if ?>
+   <div class="field">
+    <input type="checkbox" id="component" name="component" <?cs
+      if:timeline.component ?>checked="checked"<?cs /if ?> />
+    <label for="component">Component: </label><?cs
+    call:hdf_select(timeline.components, "cfilter", timeline.cfilter) ?>
+   </div>
   </fieldset>
   <div class="buttons">
    <input type="submit" value="Update" />
@@ -60,12 +67,17 @@
 
 <?cs each:item = timeline.items ?>
  <?cs call:day_separator(item.date) ?>
+  <?cs if:item.component ?>
+   <?cs set:component = ' (' + $item.component + ')' ?>
+  <?cs else ?>
+   <?cs set:component = '' ?>
+  <?cs /if ?>
  <?cs if:item.type == #1 ?><!-- Changeset -->
   <?cs call:tlitem(item.href, 'changeset',
-    'Changeset <em>['+$item.idata+']</em> by '+$item.author,$item.node_list+item.message) ?>
+    'Changeset <em>['+$item.idata+']</em>'+$component+' by '+$item.author,$item.node_list+item.message) ?>
  <?cs elif:item.type == #2 ?><!-- New ticket -->
   <?cs call:tlitem(item.href, 'newticket',
-    'Ticket <em>#'+$item.idata+'</em> created by '+$item.author, item.message) ?>
+    'Ticket <em>#'+$item.idata+'</em>'+$component+' created by '+$item.author, item.message) ?>
  <?cs elif:item.type == #3 ?><!-- Closed ticket -->
   <?cs if:item.message ?>
    <?cs set:imessage = ' - ' + $item.message ?>
@@ -73,11 +85,11 @@
    <?cs set:imessage = '' ?>
   <?cs /if ?>
   <?cs call:tlitem(item.href, 'closedticket',
-    'Ticket <em>#'+$item.idata+'</em> resolved by '+$item.author, 
+    'Ticket <em>#'+$item.idata+'</em>'+$component+' resolved by '+$item.author, 
     $item.tdata+$imessage) ?>
  <?cs elif:item.type == #4 ?><!-- Reopened ticket -->
   <?cs call:tlitem(item.href, 'newticket',
-    'Ticket <em>#'+$item.idata+'</em> reopened by '+$item.author, '') ?>
+    'Ticket <em>#'+$item.idata+'</em>'+$component+' reopened by '+$item.author, '') ?>
  <?cs elif:item.type == #5 ?><!-- Wiki change -->
   <?cs call:tlitem(item.href, 'wiki',
     '<em>'+$item.tdata+'</em> edited by '+$item.author, item.message) ?>
diff -ru trac-0.8.orig/templates/timeline_rss.cs trac-0.8/templates/timeline_rss.cs
--- trac-0.8.orig/templates/timeline_rss.cs	2004-10-14 18:16:12.000000000 +0200
+++ trac-0.8/templates/timeline_rss.cs	2005-01-18 11:52:26.000000000 +0100
@@ -33,21 +33,26 @@
         <link><?cs var:$base_url ?><?cs var:$trac.href.timeline ?></link>
       </image>
       <?cs each:item = $timeline.items ?><?cs
+        if:item.component ?>
+          <?cs set:component = ' (' + $item.component + ')' ?><?cs
+	else ?>
+          <?cs set:component = '' ?><?cs
+	/if ?><?cs
         if:item.type == #1 
         ?><!-- Changeset --><?cs call:rss_item('Changeset',
-                             'Changeset ['+$item.idata+'] by '+$item.author, 
+                             'Changeset ['+$item.idata+']'+$component+' by '+$item.author, 
                              $item.href, $item.msg_escwiki) 
         ?><?cs elif:item.type == #2 
         ?><!-- New ticket --> <?cs call:rss_item('Ticket',
-                             'Ticket #'+$item.idata+' created by '+$item.author,
+                             'Ticket #'+$item.idata+$component+' created by '+$item.author,
                              $item.href, $item.msg_escwiki) 
         ?><?cs elif:item.type == #3
         ?><!-- Closed ticket --> <?cs call:rss_item('Ticket',
-                             'Ticket #'+$item.idata+' resolved: '+$item.shortmsg,
+                             'Ticket #'+$item.idata+$component+' resolved: '+$item.shortmsg,
                              $item.href, $item.msg_escwiki) 
         ?><?cs elif:item.type == #4 
         ?><!-- Reopened ticket --><?cs call:rss_item('Ticket',
-                             '#'+$item.idata+' reopened: '+$item.shortmsg,
+                             '#'+$item.idata+$component+' reopened: '+$item.shortmsg,
                              $item.href, $item.msg_escwiki) 
         ?><?cs elif:item.type == #5 
         ?><!-- Wiki change --><?cs call:rss_item('Wiki',
@@ -62,4 +67,4 @@
         <?cs /if ?>
       <?cs /each ?>
     </channel>
-</rss>
\ No newline at end of file
+</rss>
diff -ru trac-0.8.orig/trac/Browser.py trac-0.8/trac/Browser.py
--- trac-0.8.orig/trac/Browser.py	2004-11-16 20:39:00.000000000 +0100
+++ trac-0.8/trac/Browser.py	2005-01-18 11:40:38.000000000 +0100
@@ -22,6 +22,7 @@
 import time
 import string
 import posixpath
+import re
 
 import svn
 
@@ -42,6 +43,8 @@
         # class provided by modpython might give us some strange string-like object
         # that svn doesn't like.
         path = str(path)
+	rfilter = self.env.get_config('trac', 'repository_filter', '')
+	
         try:
             root = svn.fs.revision_root(self.fs_ptr, revision, self.pool)
         except svn.core.SubversionException:
@@ -65,6 +68,9 @@
         for item in entries.keys():
             fullpath = posixpath.join(path, item)
 
+	    if rfilter and not re.search(rfilter, fullpath):
+	    	continue
+	    
             is_dir = svn.fs.is_dir(root, fullpath, self.pool)
             if is_dir:
                 name = item + '/'
diff -ru trac-0.8.orig/trac/Timeline.py trac-0.8/trac/Timeline.py
--- trac-0.8.orig/trac/Timeline.py	2004-11-17 23:21:51.000000000 +0100
+++ trac-0.8/trac/Timeline.py	2005-01-18 12:37:37.000000000 +0100
@@ -22,6 +22,7 @@
 import time
 import string
 import urllib
+import re
 
 import perm
 import util
@@ -35,7 +36,7 @@
     template_rss_name = 'timeline_rss.cs'
 
     def get_info (self, start, stop, maxrows, tickets,
-                  changeset, wiki, milestone):
+                  changeset, wiki, milestone, component, cfilter):
         cursor = self.db.cursor ()
 
         tickets = tickets and self.perm.has_permission(perm.TICKET_VIEW)
@@ -53,41 +54,62 @@
         WIKI = 5
         MILESTONE = 6
 
-        q = []
+	try:
+	    display_components = int(self.env.get_config('timeline', 'display_components', 0))
+	except ValueError, e:
+	    self.env.log.warning("Invalid 'display_components' value, "
+	                         "please edit trac.ini : %s" % e)
+	    display_components = 0
+
+	try:
+	    guess_component = int(self.env.get_config('timeline', 'changeset_guess_component', 0))
+	except ValueError, e:
+	    self.env.log.warning("Invalid 'changeset_guess_component' value, "
+	                         "please edit trac.ini : %s" % e)
+	    guess_component = 0
+
+	rfilter = self.env.get_config('trac', 'repository_filter', '')
+	rcomponent = self.env.get_config('trac', 'repository_component')
+
+	q = []
         if changeset:
             q.append("SELECT time, rev AS idata, '' AS tdata, 1 AS type, "
-                     " message, author "
+                     " message, author, '' AS component "
                      "FROM revision WHERE time>=%s AND time<=%s" %
                      (start, stop))
         if tickets:
             q.append("SELECT time, id AS idata, '' AS tdata, 2 AS type, "
-                     "summary AS message, reporter AS author "
+                     "summary AS message, reporter AS author, component "
                      "FROM ticket WHERE time>=%s AND time<=%s" %
                      (start, stop))
-            q.append("SELECT time, ticket AS idata, '' AS tdata, 4 AS type, "
-                     "'' AS message, author "
-                     "FROM ticket_change WHERE field='status' "
-                     "AND newvalue='reopened' AND time>=%s AND time<=%s" %
+            q.append("SELECT t1.time, t1.ticket AS idata, '' AS tdata, 4 AS type, "
+                     "  '' AS message, t1.author, t2.component AS component "
+		     "FROM ticket_change t1, ticket t2 "
+                     "WHERE t1.field='status' "
+                     "  AND t1.newvalue='reopened' AND t1.ticket = t2.id "
+		     "  AND t1.time>=%s AND t1.time<=%s" %
                      (start, stop))
             q.append("SELECT t1.time AS time, t1.ticket AS idata,"
                      "       t2.newvalue AS tdata, 3 AS type,"
-                     "       t3.newvalue AS message, t1.author AS author"
+                     "       t3.newvalue AS message, t1.author AS author,"
+		     "       t4.component AS component"
                      " FROM ticket_change t1"
                      "   INNER JOIN ticket_change t2 ON t1.ticket = t2.ticket"
                      "     AND t1.time = t2.time"
-                     "   LEFT OUTER JOIN ticket_change t3 ON t1.time = t3.time"
+                     "   INNER JOIN ticket t4 ON t1.ticket = t4.id"
+		     "   LEFT OUTER JOIN ticket_change t3 ON t1.time = t3.time"
                      "     AND t1.ticket = t3.ticket AND t3.field = 'comment'"
                      " WHERE t1.field = 'status' AND t1.newvalue = 'closed'"
                      "   AND t2.field = 'resolution'"
                      "   AND t1.time >= %s AND t1.time <= %s" % (start,stop))
         if wiki:
             q.append("SELECT time, -1 AS idata, name AS tdata, 5 AS type, "
-                     "comment AS message, author "
+                     "comment AS message, author, '' AS component "
                         "FROM wiki WHERE time>=%s AND time<=%s" %
                      (start, stop))
         if milestone:
             q.append("SELECT time, -1 AS idata, '' AS tdata, 6 AS type, "
-                     "name AS message, '' AS author " 
+                     "name AS message, '' AS author, '' AS component " 
                      "FROM milestone WHERE time>=%s AND time<=%s" %
                      (start, stop))
 
@@ -113,11 +135,14 @@
                     'tdata': row['tdata'],
                     'type': int(row['type']),
                     'message': row['message'] or '',
-                    'author': util.escape(row['author'] or 'anonymous')
+                    'author': util.escape(row['author'] or 'anonymous'),
+		    'component': row['component'] or ''
                     }
 
             if item['type'] == CHANGESET:
-                item['href'] = self.env.href.changeset(item['idata'])
+                changeset_component = ''
+		changeset_filtered = 1
+		item['href'] = self.env.href.changeset(item['idata'])
                 msg = item['message']
                 item['shortmsg'] = util.escape(util.shorten_line(msg))
                 item['msg_nowiki'] = util.escape(msg)
@@ -134,18 +159,41 @@
                     self.env.log.warning("Invalid 'changeset_show_files' value, "
                                          "please edit trac.ini : %s" % e)
                     max_node = 0
-                    
-                if max_node != 0:
-                    cursor_node = self.db.cursor ()
+
+		if guess_component or rfilter or max_node != 0:
+		    cursor_node = self.db.cursor ()
+		    if guess_component:
+			cursor_node.execute("SELECT name FROM component "
+			                    "ORDER BY name")
+			components = map(lambda x: x[0], cursor_node.fetchall())
+	    
                     cursor_node.execute("SELECT name, change "
                                         "FROM node_change WHERE rev=%d" % item['idata'])
+		    row_node = cursor_node.fetchone()
+		
+		if rfilter:
+		    if re.search(rfilter, row_node['name']):
+			changeset_filtered = 0
+		else:
+		    changeset_filtered = 0
+
+		if guess_component:
+		    if rcomponent:
+			try:
+			    m = re.search(rcomponent, row_node['name'])
+			    if m: changeset_component = m.groups()[-1]
+			except StandardError, e:
+			    self.env.log.warning("'repository_component' should have at least 1 group on match : %s" % e)
+		    else:
+			changeset_component = row_node['name'].split('/')[0]
+		    if changeset_component not in components:
+			changeset_component = ''
+		    
+                if max_node != 0:
                     node_list = ''
                     node_data = ''
                     node_count = 0;
                     while 1:
-                        row_node = cursor_node.fetchone()
-                        if not row_node:
-                            break
                         if node_count != 0:
                             node_list += ', '
                         if (max_node != -1) and (node_count >= max_node):
@@ -159,8 +207,18 @@
                             node_data = '<span class="diff-rem">' + row_node['name'] + "</span>"
                         node_list += node_data
                         node_count += 1
+			if re.search(rfilter, row_node['name']):
+			    changeset_filtered = 0
+			row_node = cursor_node.fetchone()
+			if not row_node:
+			    break
                     item['node_list'] = node_list + ': '
-
+					
+		if changeset_filtered:
+		    continue
+		
+		item['component'] = changeset_component;
+		
             elif item['type'] == WIKI:
                 item['href'] = self.env.href.wiki(row['tdata'])
                 item['message'] = wiki_to_oneliner(util.shorten_line(item['message']),
@@ -186,8 +244,14 @@
                 item['author.rss'] = ''
             item['message.rss'] = util.escape(item['message'] or '')
 
-            info.append(item)
-        return info
+	    if not component or item['component'] == '' or item['component'] == cfilter:
+		info.append(item)
+
+	    # now, after appending,  remove the component if asked for
+	    if not display_components:
+		item['component'] = ''
+        
+	return info
 
     def render (self):
         self.perm.assert_permission(perm.TIMELINE_VIEW)
@@ -223,6 +287,8 @@
         ticket = self.args.has_key('ticket')
         changeset = self.args.has_key('changeset')
         milestone = self.args.has_key('milestone')
+	component = self.args.has_key('component')
+	
         if not (wiki or ticket or changeset or milestone):
             wiki = ticket = changeset = milestone = 1
 
@@ -234,11 +300,21 @@
             self.req.hdf.setValue('timeline.changeset', 'checked')
         if milestone:
             self.req.hdf.setValue('timeline.milestone', 'checked')
-
+	if component:
+	    self.req.hdf.setValue('timeline.component', 'checked')
+	    
+	if self.args.has_key('cfilter'):
+	    cfilter = self.args['cfilter']
+	else:
+	    cfilter = ''
+	self.req.hdf.setValue('timeline.cfilter', cfilter)
+	
         info = self.get_info (start, stop, maxrows, ticket,
-                              changeset, wiki, milestone)
+                              changeset, wiki, milestone, component, cfilter)
         util.add_dictlist_to_hdf(info, self.req.hdf, 'timeline.items')
         self.req.hdf.setValue('title', 'Timeline')
-
+	util.sql_to_hdf(self.db, 'SELECT name FROM component ORDER BY name',
+                        self.req.hdf, 'timeline.components')
+    
     def display_rss(self):
         self.req.display(self.template_rss_name, 'text/xml')

