diff -ruN -x .svn trac-0.9b2/htdocs/css/browser.css intertrac-branch/htdocs/css/browser.css
--- trac-0.9b2/htdocs/css/browser.css	2005-09-25 16:47:54.000000000 +0200
+++ intertrac-branch/htdocs/css/browser.css	2005-09-11 14:35:38.000000000 +0200
@@ -45,6 +45,29 @@
 #dirlist td.name a, #dirlist td.rev a { border-bottom: none; display: block }
 #dirlist td.change * { font-size: 9px }
 
+/* Log */
+tr.diff input { 
+ padding: 0 1em 0 1em;
+ margin: 0; 
+}
+
+div.buttons {
+ clear: left;
+}
+
+#anydiff {
+ margin: 0 0 1em;
+ float: left;
+}
+#anydiff form, #anydiff div, #anydiff h2 {
+ display: inline;
+}
+#anydiff input { 
+ vertical-align: baseline;
+ margin: 0 -0.5em 0 1em;
+}
+
+
 /* Styles for the revision log table
    (extends the styles for "table.listing") */
 #chglist { margin-top: 0 }
diff -ruN -x .svn trac-0.9b2/htdocs/css/changeset.css intertrac-branch/htdocs/css/changeset.css
--- trac-0.9b2/htdocs/css/changeset.css	2005-09-25 16:47:54.000000000 +0200
+++ intertrac-branch/htdocs/css/changeset.css	2005-08-18 12:29:18.000000000 +0200
@@ -26,3 +26,19 @@
 
 .diff ul.props { font-size: 90%; list-style: disc; margin: .5em 0 0; padding: 0 .5em 1em 2em }
 .diff ul.props li { margin: 0; padding: 0 }
+
+
+#title dl {
+ display: inline;
+ font-size: 110%
+}
+#title dt { 
+  font-size: 110%;
+  font-weight: bold;
+  display: inline; 
+  margin-left: 3em;
+}
+#title dd { 
+  display: inline;
+  margin-left: 0.4em;
+}
diff -ruN -x .svn trac-0.9b2/templates/anydiff.cs intertrac-branch/templates/anydiff.cs
--- trac-0.9b2/templates/anydiff.cs	1970-01-01 01:00:00.000000000 +0100
+++ intertrac-branch/templates/anydiff.cs	2005-08-31 17:47:32.000000000 +0200
@@ -0,0 +1,47 @@
+<?cs include "header.cs"?>
+
+<div id="ctxtnav" class="nav">
+ <h2>Navigation</h2><?cs
+ with:links = chrome.links ?>
+  <ul>
+  </ul><?cs
+ /with ?>
+</div>
+
+<div id="content" class="changeset">
+ <div id="title">
+    <h1>Select Base and Target for Diff:</h1>
+ </div>
+
+ <div id="anydiff">
+  <form action="<?cs var:anydiff.diff_href ?>" method="post">
+   <table>
+    <tr>
+     <th><label for="old_path">From:</label></th>
+     <td>
+      <input type="text" id="old_path" name="old_path" value="<?cs
+         var:anydiff.old_path ?>" size="44" />
+      <label for="old_rev">at Revision:</label>
+      <input type="text" id="old_rev" name="old" value="<?cs
+         var:anydiff.old_rev ?>" size="4" />
+     </td>
+    </tr>
+    <tr>
+     <th><label for="new_path">To:</label></th>
+     <td>
+      <input type="text" id="new_path" name="path" value="<?cs
+         var:anydiff.new_path ?>" size="44" />
+      <label for="new_rev">at Revision:</label>
+      <input type="text" id="new_rev" name="new" value="<?cs
+         var:anydiff.new_rev ?>" size="4" />
+     </td>
+    </tr>
+   </table>
+   <div class="buttons">
+      <input type="submit" value="View changes" />
+   </div>
+  </form>
+ </div>
+</div>
+
+<?cs include "footer.cs"?>
diff -ruN -x .svn trac-0.9b2/templates/browser.cs intertrac-branch/templates/browser.cs
--- trac-0.9b2/templates/browser.cs	2005-09-25 16:48:01.000000000 +0200
+++ intertrac-branch/templates/browser.cs	2005-09-23 09:51:58.000000000 +0200
@@ -3,19 +3,25 @@
 
 <div id="ctxtnav" class="nav">
  <ul>
-  <li class="last"><a href="<?cs var:browser.log_href ?>">Revision Log</a></li>
+  <li class="first"><a href="<?cs var:browser.restr_changeset_href ?>">
+   Last Change</a></li>
+  <li class="last"><a href="<?cs var:browser.log_href ?>">
+   Revision Log</a></li>
  </ul>
 </div>
 
+
 <div id="content" class="browser">
  <h1><?cs call:browser_path_links(browser.path, browser) ?></h1>
 
  <div id="jumprev">
-  <form action="" method="get"><div>
-   <label for="rev">View revision:</label>
-   <input type="text" id="rev" name="rev" value="<?cs
-     var:browser.revision ?>" size="4" />
-  </div></form>
+  <form action="" method="get">
+   <div>
+    <label for="rev">View revision:</label>
+    <input type="text" id="rev" name="rev" value="<?cs
+       var:browser.revision ?>" size="4" />
+   </div>
+  </form>
  </div>
 
  <?cs if:browser.is_dir ?>
@@ -114,5 +120,19 @@
   ?>/TracBrowser">TracBrowser</a> for help on using the browser.
  </div>
 
+  <div id="anydiff"><?cs
+   if len(browser.path) > #1 ?>
+    <form action="<?cs var:browser.anydiff_href ?>" method="get">
+     <input type="hidden" name="new_path" value="<?cs var:browser.path ?>" />
+     <input type="hidden" name="old_path" value="<?cs var:browser.path ?>" />
+     <input type="hidden" name="new_rev" value="<?cs var:browser.revision ?>" />
+     <input type="hidden" name="old_rev" value="<?cs var:browser.revision ?>" />
+     <div class="buttons">
+      <input type="submit" value="View changes..." title="Prepare an Arbitrary Diff" />
+     </div>
+    </form><?cs
+   /if ?>
+  </div>
+
 </div>
 <?cs include:"footer.cs"?>
diff -ruN -x .svn trac-0.9b2/templates/changeset.cs intertrac-branch/templates/changeset.cs
--- trac-0.9b2/templates/changeset.cs	2005-09-25 16:48:01.000000000 +0200
+++ intertrac-branch/templates/changeset.cs	1970-01-01 01:00:00.000000000 +0100
@@ -1,206 +0,0 @@
-<?cs include "header.cs"?>
-<?cs include "macros.cs"?>
-
-<div id="ctxtnav" class="nav">
- <h2>Changeset Navigation</h2><?cs
- with:links = chrome.links ?>
-  <ul><?cs
-   if:len(links.prev) ?>
-    <li class="first<?cs if:!len(links.next) ?> last<?cs /if ?>">
-     <a class="prev" href="<?cs var:links.prev.0.href ?>" title="<?cs
-       var:links.prev.0.title ?>">Previous Changeset</a>
-    </li><?cs
-   /if ?><?cs
-   if:len(links.next) ?>
-    <li class="<?cs if:len(links.prev) ?>first <?cs /if ?>last">
-     <a class="next" href="<?cs var:links.next.0.href ?>" title="<?cs
-       var:links.next.0.title ?>">Next Changeset</a>
-    </li><?cs
-   /if ?>
-  </ul><?cs
- /with ?>
-</div>
-
-<div id="content" class="changeset">
-<h1>Changeset <?cs var:changeset.revision ?></h1>
-
-<?cs each:change = changeset.changes ?><?cs
- if:len(change.diff) ?><?cs
-  set:has_diffs = 1 ?><?cs
- /if ?><?cs
-/each ?><?cs if:has_diffs || diff.options.ignoreblanklines 
-  || diff.options.ignorecase || diff.options.ignorewhitespace ?>
-<form method="post" id="prefs" action="">
- <div>
-  <label for="style">View differences</label>
-  <select id="style" name="style">
-   <option value="inline"<?cs
-     if:diff.style == 'inline' ?> selected="selected"<?cs
-     /if ?>>inline</option>
-   <option value="sidebyside"<?cs
-     if:diff.style == 'sidebyside' ?> selected="selected"<?cs
-     /if ?>>side by side</option>
-  </select>
-  <div class="field">
-   Show <input type="text" name="contextlines" id="contextlines" size="2"
-     maxlength="2" value="<?cs var:diff.options.contextlines ?>" />
-   <label for="contextlines">lines around each change</label>
-  </div>
-  <fieldset id="ignore">
-   <legend>Ignore:</legend>
-   <div class="field">
-    <input type="checkbox" id="blanklines" name="ignoreblanklines"<?cs
-      if:diff.options.ignoreblanklines ?> checked="checked"<?cs /if ?> />
-    <label for="blanklines">Blank lines</label>
-   </div>
-   <div class="field">
-    <input type="checkbox" id="case" name="ignorecase"<?cs
-      if:diff.options.ignorecase ?> checked="checked"<?cs /if ?> />
-    <label for="case">Case changes</label>
-   </div>
-   <div class="field">
-    <input type="checkbox" id="whitespace" name="ignorewhitespace"<?cs
-      if:diff.options.ignorewhitespace ?> checked="checked"<?cs /if ?> />
-    <label for="whitespace">White space changes</label>
-   </div>
-  </fieldset>
-  <div class="buttons">
-   <input type="submit" name="update" value="Update" />
-  </div>
- </div>
-</form><?cs /if ?>
-
-<?cs def:node_change(item,cl,kind) ?><?cs 
-  set:ndiffs = len(item.diff) ?><?cs
-  set:nprops = len(item.props) ?>
-  <div class="<?cs var:cl ?>"></div><?cs 
-  if:cl == "rem" ?>
-   <a title="Show what was removed (rev. <?cs var:item.rev.old ?>)" href="<?cs
-     var:item.browser_href.old ?>"><?cs var:item.path.old ?></a><?cs
-  else ?>
-   <a title="Show entry in browser" href="<?cs
-     var:item.browser_href.new ?>"><?cs var:item.path.new ?></a><?cs
-  /if ?>
-  <span class="comment">(<?cs var:kind ?>)</span><?cs
-  if:item.path.old && item.change == 'copy' || item.change == 'move' ?>
-   <small><em>(<?cs var:kind ?> from <a href="<?cs
-    var:item.browser_href.old ?>" title="Show original file (rev. <?cs
-    var:item.rev.old ?>)"><?cs var:item.path.old ?></a>)</em></small><?cs
-  /if ?><?cs
-  if:$ndiffs + $nprops > #0 ?>
-    (<a href="#file<?cs var:name(item) ?>" title="Show differences"><?cs
-      if:$ndiffs > #0 ?><?cs var:ndiffs ?>&nbsp;diff<?cs if:$ndiffs > #1 ?>s<?cs /if ?><?cs 
-      /if ?><?cs
-      if:$ndiffs && $nprops ?>, <?cs /if ?><?cs 
-      if:$nprops > #0 ?><?cs var:nprops ?>&nbsp;prop<?cs if:$nprops > #1 ?>s<?cs /if ?><?cs
-      /if ?></a>)<?cs
-  elif:cl == "mod" ?>
-    (<a href="<?cs var:item.browser_href.old ?>"
-        title="Show previous version in browser">previous</a>)<?cs
-  /if ?>
-<?cs /def ?>
-
-<dl id="overview">
- <dt class="time">Timestamp:</dt>
- <dd class="time"><?cs var:changeset.time ?></dd>
- <dt class="author">Author:</dt>
- <dd class="author"><?cs var:changeset.author ?></dd>
- <dt class="message">Message:</dt>
- <dd class="message" id="searchable"><?cs var:changeset.message ?></dd>
- <dt class="files">Files:</dt>
- <dd class="files">
-  <ul><?cs each:item = changeset.changes ?>
-   <li><?cs
-    if:item.change == 'add' ?><?cs
-     call:node_change(item, 'add', 'added') ?><?cs
-    elif:item.change == 'delete' ?><?cs
-     call:node_change(item, 'rem', 'deleted') ?><?cs
-    elif:item.change == 'copy' ?><?cs
-     call:node_change(item, 'cp', 'copied') ?><?cs
-    elif:item.change == 'move' ?><?cs
-     call:node_change(item, 'mv', 'moved') ?><?cs
-    elif:item.change == 'edit' ?><?cs
-     call:node_change(item, 'mod', 'modified') ?><?cs
-    /if ?>
-   </li>
-  <?cs /each ?></ul>
- </dd>
-</dl>
-
-<div class="diff">
- <div id="legend">
-  <h3>Legend:</h3>
-  <dl>
-   <dt class="unmod"></dt><dd>Unmodified</dd>
-   <dt class="add"></dt><dd>Added</dd>
-   <dt class="rem"></dt><dd>Removed</dd>
-   <dt class="mod"></dt><dd>Modified</dd>
-   <dt class="cp"></dt><dd>Copied</dd>
-   <dt class="mv"></dt><dd>Moved</dd>
-  </dl>
- </div>
- <ul class="entries"><?cs
- each:item = changeset.changes ?><?cs
-  if:len(item.diff) || len(item.props) ?><li class="entry" id="file<?cs
-   var:name(item) ?>"><h2><a href="<?cs
-   var:item.browser_href.new ?>" title="Show new revision <?cs
-   var:item.rev.new ?> of this file in browser"><?cs
-   var:item.path.new ?></a></h2><?cs
-   if:len(item.props) ?><ul class="props"><?cs
-    each:prop = item.props ?><li>Property <strong><?cs
-     var:name(prop) ?></strong> <?cs
-     if:prop.old && prop.new ?>changed from <?cs
-     elif:!prop.old ?>set<?cs
-     else ?>deleted<?cs
-     /if ?><?cs
-     if:prop.old && prop.new ?><em><tt><?cs var:prop.old ?></tt></em><?cs /if ?><?cs
-     if:prop.new ?> to <em><tt><?cs var:prop.new ?></tt></em><?cs /if ?></li><?cs
-    /each ?></ul><?cs
-   /if ?><?cs
-   if:len(item.diff) ?><table class="<?cs
-    var:diff.style ?>" summary="Differences" cellspacing="0"><?cs
-    if:diff.style == 'sidebyside' ?>
-     <colgroup class="l"><col class="lineno" /><col class="content" /></colgroup>
-     <colgroup class="r"><col class="lineno" /><col class="content" /></colgroup>
-     <thead><tr>
-      <th colspan="2"><a href="<?cs
-       var:item.browser_href.old ?>" title="Show old rev. <?cs
-       var:item.rev.old ?> of <?cs var:item.path.old ?>">Revision <?cs
-       var:item.rev.old ?></a></th>
-      <th colspan="2"><a href="<?cs
-       var:item.browser_href.new ?>" title="Show new rev. <?cs
-       var:item.rev.new ?> of <?cs var:item.path.new ?>">Revision <?cs
-       var:item.rev.new ?></a></th>
-      </tr>
-     </thead><?cs
-     each:change = item.diff ?><tbody><?cs
-      call:diff_display(change, diff.style) ?></tbody><?cs
-      if:name(change) < len(item.diff) - 1 ?><tbody class="skipped"><tr>
-       <th>&hellip;</th><td>&nbsp;</td><th>&hellip;</th><td>&nbsp;</td>
-      </tr></tbody><?cs /if ?><?cs
-     /each ?><?cs
-    else ?>
-     <colgroup><col class="lineno" /><col class="lineno" /><col class="content" /></colgroup>
-     <thead><tr>
-      <th title="Revision <?cs var:item.rev.old ?>"><a href="<?cs
-       var:item.browser_href.old ?>" title="Show old version of <?cs
-       var:item.path.old ?>">r<?cs var:item.rev.old ?></a></th>
-      <th title="Revision <?cs var:item.rev.new ?>"><a href="<?cs
-       var:item.browser_href.new ?>" title="Show new version of <?cs
-       var:item.path.new ?>">r<?cs var:item.rev.new ?></a></th>
-      <th>&nbsp;</th></tr>
-     </thead><?cs
-     each:change = item.diff ?><?cs
-      call:diff_display(change, diff.style) ?><?cs
-      if:name(change) < len(item.diff) - 1 ?><tbody class="skipped"><tr>
-       <th>&hellip;</th><th>&hellip;</th><td>&nbsp;</td>
-      </tr></tbody><?cs /if ?><?cs
-     /each ?><?cs
-    /if ?></table><?cs
-   /if ?></li><?cs
-  /if ?><?cs
- /each ?></ul>
-</div>
-
-</div>
-<?cs include "footer.cs"?>
diff -ruN -x .svn trac-0.9b2/templates/diff.cs intertrac-branch/templates/diff.cs
--- trac-0.9b2/templates/diff.cs	1970-01-01 01:00:00.000000000 +0100
+++ intertrac-branch/templates/diff.cs	2005-09-23 09:51:58.000000000 +0200
@@ -0,0 +1,258 @@
+<?cs include "header.cs"?>
+<?cs include "macros.cs"?>
+
+<div id="ctxtnav" class="nav">
+ <h2>Navigation</h2><?cs
+ with:links = chrome.links ?>
+  <ul><?cs
+   if:diff.chgset ?><?cs
+    if:len(links.prev) ?>
+     <li class="first<?cs if:!len(links.next) ?> last<?cs /if ?>">
+      <a class="prev" href="<?cs var:links.prev.0.href ?>" title="<?cs
+        var:links.prev.0.title ?>">Previous <?cs 
+         if:diff.restricted ?>Change<?cs else ?>Changeset<?cs /if ?></a>
+     </li><?cs
+    /if ?><?cs
+    if:len(links.next) ?>
+     <li class="<?cs if:len(links.prev) ?>first <?cs /if ?>last">
+      <a class="next" href="<?cs var:links.next.0.href ?>" title="<?cs
+        var:links.next.0.title ?>">Next <?cs 
+         if:diff.restricted ?>Change<?cs else ?>Changeset<?cs /if ?></a>
+     </li><?cs
+    /if ?><?cs
+   else ?>
+    <li class="first"><a href="<?cs var:diff.reverse_href ?>">Reverse Diff</a></li><?cs
+   /if ?>
+  </ul><?cs
+ /with ?>
+</div>
+
+<div id="content" class="changeset">
+ <div id="title"><?cs
+  if:diff.chgset ?><?cs
+   if:diff.restricted ?>
+    <h1>Changeset <a title="Show full changeset" href="<?cs var:diff.href.new_rev ?>">
+      <?cs var:diff.new_rev ?></a> 
+     for <a title="Show entry in browser" href="<?cs var:diff.href.new_path ?>">
+      <?cs var:diff.new_path ?></a> 
+    </h1><?cs
+   else ?>
+    <h1>Changeset <?cs var:diff.new_rev ?></h1><?cs
+   /if ?><?cs
+  else ?><?cs
+    if:diff.restricted ?>
+    <h1>Changes in <a title="Show entry in browser" href="<?cs var:diff.href.new_path ?>">
+      <?cs var:diff.new_path ?></a>
+     from revision <a title="Show full changeset" href="<?cs var:diff.href.old_rev ?>">
+      <?cs var:diff.old_rev ?></a>
+     to <a title="Show full changeset" href="<?cs var:diff.href.new_rev ?>">
+      <?cs var:diff.new_rev ?></a>
+    </h1><?cs
+   else ?>
+    <h1>Changes from <a title="Show entry in browser" href="<?cs var:diff.href.old_path ?>">
+      <?cs var:diff.old_path ?></a> 
+     at revision <a title="Show full changeset" href="<?cs var:diff.href.old_rev ?>">
+      <?cs var:diff.old_rev ?></a>
+     to <a title="Show entry in browser" href="<?cs var:diff.href.new_path ?>">
+     <?cs var:diff.new_path ?></a> 
+     at revision <a title="Show full changeset" href="<?cs var:diff.href.new_rev ?>">
+     <?cs var:diff.new_rev ?></a>
+    </h1><?cs
+   /if ?><?cs
+  /if ?>
+ </div>
+
+<?cs each:change = diff.changes ?><?cs
+ if:len(change.diff) ?><?cs
+  set:has_diffs = 1 ?><?cs
+ /if ?><?cs
+/each ?><?cs if:has_diffs || diff.options.ignoreblanklines 
+  || diff.options.ignorecase || diff.options.ignorewhitespace ?>
+<form method="post" id="prefs" action="">
+ <div><?cs 
+  if:!diff.chgset ?>
+   <input type="hidden" name="old_path" value="<?cs var:diff.old_path ?>" />
+   <input type="hidden" name="path" value="<?cs var:diff.new_path ?>" />
+   <input type="hidden" name="old" value="<?cs var:diff.old_rev ?>" />
+   <input type="hidden" name="new" value="<?cs var:diff.new_rev ?>" /><?cs
+  /if ?>
+  <label for="style">View differences</label>
+  <select id="style" name="style">
+   <option value="inline"<?cs
+     if:diff.style == 'inline' ?> selected="selected"<?cs
+     /if ?>>inline</option>
+   <option value="sidebyside"<?cs
+     if:diff.style == 'sidebyside' ?> selected="selected"<?cs
+     /if ?>>side by side</option>
+  </select>
+  <div class="field">
+   Show <input type="text" name="contextlines" id="contextlines" size="2"
+     maxlength="2" value="<?cs var:diff.options.contextlines ?>" />
+   <label for="contextlines">lines around each change</label>
+  </div>
+  <fieldset id="ignore">
+   <legend>Ignore:</legend>
+   <div class="field">
+    <input type="checkbox" id="blanklines" name="ignoreblanklines"<?cs
+      if:diff.options.ignoreblanklines ?> checked="checked"<?cs /if ?> />
+    <label for="blanklines">Blank lines</label>
+   </div>
+   <div class="field">
+    <input type="checkbox" id="case" name="ignorecase"<?cs
+      if:diff.options.ignorecase ?> checked="checked"<?cs /if ?> />
+    <label for="case">Case changes</label>
+   </div>
+   <div class="field">
+    <input type="checkbox" id="whitespace" name="ignorewhitespace"<?cs
+      if:diff.options.ignorewhitespace ?> checked="checked"<?cs /if ?> />
+    <label for="whitespace">White space changes</label>
+   </div>
+  </fieldset>
+  <div class="buttons">
+   <input type="submit" name="update" value="Update" />
+  </div>
+ </div>
+</form><?cs /if ?>
+
+<?cs def:node_change(item,cl,kind) ?><?cs 
+  set:ndiffs = len(item.diff) ?><?cs
+  set:nprops = len(item.props) ?>
+  <div class="<?cs var:cl ?>"></div><?cs 
+  if:cl == "rem" ?>
+   <a title="Show what was removed (rev. <?cs var:item.rev.old ?>)" href="<?cs
+     var:item.browser_href.old ?>"><?cs var:item.path.old ?></a><?cs
+  else ?>
+   <a title="Show entry in browser" href="<?cs
+     var:item.browser_href.new ?>"><?cs var:item.path.new ?></a><?cs
+  /if ?>
+  <span class="comment">(<?cs var:kind ?>)</span><?cs
+  if:item.path.old && item.change == 'copy' || item.change == 'move' ?>
+   <small><em>(<?cs var:kind ?> from <a href="<?cs
+    var:item.browser_href.old ?>" title="Show original file (rev. <?cs
+    var:item.rev.old ?>)"><?cs var:item.path.old ?></a>)</em></small><?cs
+  /if ?><?cs
+  if:$ndiffs + $nprops > #0 ?>
+    (<a href="#file<?cs var:name(item) ?>" title="Show differences"><?cs
+      if:$ndiffs > #0 ?><?cs var:ndiffs ?>&nbsp;diff<?cs if:$ndiffs > #1 ?>s<?cs /if ?><?cs 
+      /if ?><?cs
+      if:$ndiffs && $nprops ?>, <?cs /if ?><?cs 
+      if:$nprops > #0 ?><?cs var:nprops ?>&nbsp;prop<?cs if:$nprops > #1 ?>s<?cs /if ?><?cs
+      /if ?></a>)<?cs
+  elif:cl == "mod" ?>
+    (<a href="<?cs var:item.browser_href.old ?>"
+        title="Show previous version in browser">previous</a>)<?cs
+  /if ?>
+<?cs /def ?>
+
+<dl id="overview"><?cs
+ if:diff.chgset ?>
+ <dt class="time">Timestamp:</dt>
+ <dd class="time"><?cs var:changeset.time ?></dd>
+ <dt class="author">Author:</dt>
+ <dd class="author"><?cs var:changeset.author ?></dd>
+ <dt class="message">Message:</dt>
+ <dd class="message" id="searchable"><?cs var:changeset.message ?></dd><?cs
+ /if ?>
+ <dt class="files"><?cs 
+  if:len(diff.changes) > #0 ?>
+   Files:<?cs
+  else ?>
+   (None)<?cs
+  /if ?>
+ </dt>
+ <dd class="files">
+  <ul><?cs each:item = diff.changes ?>
+   <li><?cs
+    if:item.change == 'add' ?><?cs
+     call:node_change(item, 'add', 'added') ?><?cs
+    elif:item.change == 'delete' ?><?cs
+     call:node_change(item, 'rem', 'deleted') ?><?cs
+    elif:item.change == 'copy' ?><?cs
+     call:node_change(item, 'cp', 'copied') ?><?cs
+    elif:item.change == 'move' ?><?cs
+     call:node_change(item, 'mv', 'moved') ?><?cs
+    elif:item.change == 'edit' ?><?cs
+     call:node_change(item, 'mod', 'modified') ?><?cs
+    /if ?>
+   </li>
+  <?cs /each ?></ul>
+ </dd>
+</dl>
+
+<div class="diff">
+ <div id="legend">
+  <h3>Legend:</h3>
+  <dl>
+   <dt class="unmod"></dt><dd>Unmodified</dd>
+   <dt class="add"></dt><dd>Added</dd>
+   <dt class="rem"></dt><dd>Removed</dd>
+   <dt class="mod"></dt><dd>Modified</dd>
+   <dt class="cp"></dt><dd>Copied</dd>
+   <dt class="mv"></dt><dd>Moved</dd>
+  </dl>
+ </div>
+ <ul class="entries"><?cs
+ each:item = diff.changes ?><?cs
+  if:len(item.diff) || len(item.props) ?><li class="entry" id="file<?cs
+   var:name(item) ?>"><h2><a href="<?cs
+   var:item.browser_href.new ?>" title="Show new revision <?cs
+   var:item.rev.new ?> of this file in browser"><?cs
+   var:item.path.new ?></a></h2><?cs
+   if:len(item.props) ?><ul class="props"><?cs
+    each:prop = item.props ?><li>Property <strong><?cs
+     var:name(prop) ?></strong> <?cs
+     if:prop.old && prop.new ?>changed from <?cs
+     elif:!prop.old ?>set<?cs
+     else ?>deleted<?cs
+     /if ?><?cs
+     if:prop.old && prop.new ?><em><tt><?cs var:prop.old ?></tt></em><?cs /if ?><?cs
+     if:prop.new ?> to <em><tt><?cs var:prop.new ?></tt></em><?cs /if ?></li><?cs
+    /each ?></ul><?cs
+   /if ?><?cs
+   if:len(item.diff) ?><table class="<?cs
+    var:diff.style ?>" summary="Differences" cellspacing="0"><?cs
+    if:diff.style == 'sidebyside' ?>
+     <colgroup class="l"><col class="lineno" /><col class="content" /></colgroup>
+     <colgroup class="r"><col class="lineno" /><col class="content" /></colgroup>
+     <thead><tr>
+      <th colspan="2"><a href="<?cs
+       var:item.browser_href.old ?>" title="Show old rev. <?cs
+       var:item.rev.old ?> of <?cs var:item.path.old ?>">Revision <?cs
+       var:item.rev.old ?></a></th>
+      <th colspan="2"><a href="<?cs
+       var:item.browser_href.new ?>" title="Show new rev. <?cs
+       var:item.rev.new ?> of <?cs var:item.path.new ?>">Revision <?cs
+       var:item.rev.new ?></a></th>
+      </tr>
+     </thead><?cs
+     each:change = item.diff ?><tbody><?cs
+      call:diff_display(change, diff.style) ?></tbody><?cs
+      if:name(change) < len(item.diff) - 1 ?><tbody class="skipped"><tr>
+       <th>&hellip;</th><td>&nbsp;</td><th>&hellip;</th><td>&nbsp;</td>
+      </tr></tbody><?cs /if ?><?cs
+     /each ?><?cs
+    else ?>
+     <colgroup><col class="lineno" /><col class="lineno" /><col class="content" /></colgroup>
+     <thead><tr>
+      <th title="Revision <?cs var:item.rev.old ?>"><a href="<?cs
+       var:item.browser_href.old ?>" title="Show old version of <?cs
+       var:item.path.old ?>">r<?cs var:item.rev.old ?></a></th>
+      <th title="Revision <?cs var:item.rev.new ?>"><a href="<?cs
+       var:item.browser_href.new ?>" title="Show new version of <?cs
+       var:item.path.new ?>">r<?cs var:item.rev.new ?></a></th>
+      <th>&nbsp;</th></tr>
+     </thead><?cs
+     each:change = item.diff ?><?cs
+      call:diff_display(change, diff.style) ?><?cs
+      if:name(change) < len(item.diff) - 1 ?><tbody class="skipped"><tr>
+       <th>&hellip;</th><th>&hellip;</th><td>&nbsp;</td>
+      </tr></tbody><?cs /if ?><?cs
+     /each ?><?cs
+    /if ?></table><?cs
+   /if ?></li><?cs
+  /if ?><?cs
+ /each ?></ul>
+</div>
+
+</div>
+<?cs include "footer.cs"?>
diff -ruN -x .svn trac-0.9b2/templates/log.cs intertrac-branch/templates/log.cs
--- trac-0.9b2/templates/log.cs	2005-09-25 16:48:01.000000000 +0200
+++ intertrac-branch/templates/log.cs	2005-09-23 09:51:58.000000000 +0200
@@ -3,8 +3,9 @@
 
 <div id="ctxtnav" class="nav">
  <ul>
-  <li class="last"><a href="<?cs
-    var:log.browser_href ?>">View Latest Revision</a></li><?cs
+  <li class="last">
+   <a href="<?cs var:log.browser_href ?>">View Latest Revision</a>
+  </li><?cs
   if:len(chrome.links.prev) ?>
    <li class="first<?cs if:!len(chrome.links.next) ?> last<?cs /if ?>">
     &larr; <a href="<?cs var:chrome.links.prev.0.href ?>" title="<?cs
@@ -61,6 +62,7 @@
           title="Warning: by updating, you will clear the page history" />
   </div>
  </form>
+
  <div class="diff">
   <div id="legend">
    <h3>Legend:</h3>
@@ -74,9 +76,16 @@
    </dl>
   </div>
  </div>
+
+ <form action="<?cs var:log.href ?>" method="post">
+  <div class="buttons"><input type="submit" value="View changes" 
+       title="Diff from Old Revision to New Revision (select them below)" />
+ </div>
  <table id="chglist" class="listing">
   <thead>
    <tr>
+    <th>Old</th>
+    <th>New</th>
     <th class="change"></th>
     <th class="data">Date</th>
     <th class="rev">Rev</th>
@@ -87,10 +96,11 @@
   </thead>
   <tbody><?cs
    set:indent = #1 ?><?cs
+   set:idx = #0 ?><?cs
    each:item = log.items ?><?cs
     if:item.copyfrom_path ?>
      <tr class="<?cs if:name(item) % #2 ?>even<?cs else ?>odd<?cs /if ?>">
-      <td class="copyfrom_path" colspan="6" style="padding-left: <?cs var:indent ?>em">
+      <td class="copyfrom_path" colspan="8" style="padding-left: <?cs var:indent ?>em">
        copied from <a href="<?cs var:item.browser_href ?>"?><?cs var:item.copyfrom_path ?></a>:
       </td>
      </tr><?cs
@@ -99,6 +109,12 @@
       set:indent = #1 ?><?cs
     /if ?>
     <tr class="<?cs if:name(item) % #2 ?>even<?cs else ?>odd<?cs /if ?>">
+     <td><input type="radio" name="old" 
+                value="<?cs var:item.path ?>#<?cs var:item.rev ?>" <?cs
+          if:idx == #1 ?> checked="checked" <?cs /if ?> /></td>
+     <td><input type="radio" name="new" 
+                value="<?cs var:item.path ?>#<?cs var:item.rev ?>" <?cs
+          if:idx == #0 ?> checked="checked" <?cs /if ?> /></td>
      <td class="change" style="padding-left:<?cs var:indent ?>em">
       <a title="View log starting at this revision" href="<?cs var:item.log_href ?>">
        <span class="<?cs var:item.change ?>"></span>
@@ -117,9 +133,14 @@
      <td class="author"><?cs var:log.changes[item.rev].author ?></td>
      <td class="summary"><?cs var:log.changes[item.rev].message ?></td>
     </tr><?cs
+    set:idx = idx + 1 ?><?cs
    /each ?>
   </tbody>
- </table><?cs
+ </table>
+ <div class="buttons"><input type="submit" value="View changes" 
+      title="Diff from Old Revision to New Revision (select them above)" />
+ </div>
+ </form><?cs
  if:len(links.prev) || len(links.next) ?><div id="paging" class="nav"><ul><?cs
   if:len(links.prev) ?><li class="first<?cs
    if:!len(links.next) ?> last<?cs /if ?>">&larr; <a href="<?cs
diff -ruN -x .svn trac-0.9b2/templates/wiki.cs intertrac-branch/templates/wiki.cs
--- trac-0.9b2/templates/wiki.cs	2005-09-25 16:48:01.000000000 +0200
+++ intertrac-branch/templates/wiki.cs	2005-09-23 09:51:58.000000000 +0200
@@ -154,6 +154,9 @@
     var:wiki.page_name ?></a></h1>
   <?cs if:len(wiki.history) ?><form method="get" action="">
    <input type="hidden" name="action" value="diff" />
+   <div class="buttons">
+    <input type="submit" value="View changes" />
+   </div>
    <table id="wikihist" class="listing" summary="Change history">
     <thead><tr>
      <th class="diff"></th>
diff -ruN -x .svn trac-0.9b2/trac/db_default.py intertrac-branch/trac/db_default.py
--- trac-0.9b2/trac/db_default.py	2005-09-25 16:48:00.000000000 +0200
+++ intertrac-branch/trac/db_default.py	2005-09-23 09:51:58.000000000 +0200
@@ -432,6 +432,9 @@
   ('timeline', 'default_daysback', '30'),
   ('browser', 'hide_properties', 'svk:merge'),
   ('wiki', 'ignore_missing_pages', 'false'),
+  ('disabled_components', 'trac.wiki.api.StandardWikiPageNames', 'no'),
+  ('disabled_components', 'trac.wiki.api.FlexibleWikiPageNames', 'yes'),
+  ('disabled_components', 'trac.wiki.api.SubWikiPageNames', 'yes'),
 )
 
 default_components = ('trac.About', 'trac.attachment', 
diff -ruN -x .svn trac-0.9b2/trac/env.py intertrac-branch/trac/env.py
--- trac-0.9b2/trac/env.py	2005-09-25 16:48:00.000000000 +0200
+++ intertrac-branch/trac/env.py	2005-09-23 09:51:58.000000000 +0200
@@ -66,6 +66,7 @@
         ComponentManager.__init__(self)
 
         self.path = path
+        self.siblings = {}
         self.__cnx_pool = None
         if create:
             self.create(db_str)
diff -ruN -x .svn trac-0.9b2/trac/__init__.py intertrac-branch/trac/__init__.py
--- trac-0.9b2/trac/__init__.py	2005-09-25 16:48:00.000000000 +0200
+++ intertrac-branch/trac/__init__.py	2005-09-28 14:57:27.000000000 +0200
@@ -10,7 +10,7 @@
 """
 __docformat__ = 'epytext en'
 
-__version__ = '0.9b2'
+__version__ = '0.9b2-intertrac'
 __url__ = 'http://trac.edgewall.com/'
 __copyright__ = '(C) 2003-2005 Edgewall Software'
 __license__ = 'BSD'
diff -ruN -x .svn trac-0.9b2/trac/siteconfig.py intertrac-branch/trac/siteconfig.py
--- trac-0.9b2/trac/siteconfig.py	1970-01-01 01:00:00.000000000 +0100
+++ intertrac-branch/trac/siteconfig.py	2005-09-26 13:41:30.000000000 +0200
@@ -0,0 +1,9 @@
+
+# PLEASE DO NOT EDIT THIS FILE!
+# This file was autogenerated when installing Trac 0.9b1-intertrac.
+#
+__default_templates_dir__ = '/opt/trac/stable-bct-trac/share/trac/templates'
+__default_htdocs_dir__ = '/opt/trac/stable-bct-trac/share/trac/htdocs'
+__default_wiki_dir__ = '/opt/trac/stable-bct-trac/share/trac/wiki-default'
+__default_macros_dir__ = '/opt/trac/stable-bct-trac/share/trac/wiki-macros'
+
diff -ruN -x .svn trac-0.9b2/trac/ticket/api.py intertrac-branch/trac/ticket/api.py
--- trac-0.9b2/trac/ticket/api.py	2005-09-25 16:47:57.000000000 +0200
+++ intertrac-branch/trac/ticket/api.py	2005-09-23 09:51:55.000000000 +0200
@@ -19,7 +19,7 @@
 from trac import util
 from trac.core import *
 from trac.perm import IPermissionRequestor
-from trac.wiki import IWikiSyntaxProvider
+from trac.wiki import IWikiSyntaxProvider, INTERTRAC_SCHEME
 from trac.Search import ISearchSource, query_to_sql, shorten_result
 
 
@@ -149,10 +149,14 @@
                 ('ticket', self._format_link)]
 
     def get_wiki_syntax(self):
-        yield (r"!?#\d+",
-               lambda x, y, z: self._format_link(x, 'ticket', y[1:], y))
+        yield (r"!?#(?P<it_ticket>%s)?\d+" % INTERTRAC_SCHEME,
+               lambda x, y, z: self._format_link(x, 'ticket', y[1:], y, z))
 
-    def _format_link(self, formatter, ns, target, label):
+    def _format_link(self, formatter, ns, target, label, fullmatch=None):
+        intertrac = formatter.shorthand_intertrac_helper(ns, target, label,
+                                                         fullmatch)
+        if intertrac:
+            return intertrac
         cursor = formatter.db.cursor()
         cursor.execute("SELECT summary,status FROM ticket WHERE id=%s",
                        (target,))
diff -ruN -x .svn trac-0.9b2/trac/ticket/report.py intertrac-branch/trac/ticket/report.py
--- trac-0.9b2/trac/ticket/report.py	2005-09-25 16:47:57.000000000 +0200
+++ intertrac-branch/trac/ticket/report.py	2005-09-23 09:51:55.000000000 +0200
@@ -24,7 +24,7 @@
 from trac.perm import IPermissionRequestor
 from trac.web import IRequestHandler
 from trac.web.chrome import add_link, add_stylesheet, INavigationContributor
-from trac.wiki import wiki_to_html, IWikiSyntaxProvider
+from trac.wiki import wiki_to_html, IWikiSyntaxProvider, INTERTRAC_SCHEME
 
 
 dynvars_re = re.compile('\$([A-Z]+)')
@@ -510,8 +510,13 @@
         yield ('report', self._format_link)
 
     def get_wiki_syntax(self):
-        yield (r"!?\{\d+\}", lambda x, y, z: self._format_link(x, 'report', y[1:-1], y))
+        yield (r"!?\{(?P<it_report>%s\s*)?\d+\}" % INTERTRAC_SCHEME,
+               lambda x, y, z: self._format_link(x, 'report', y[1:-1], y, z))
 
-    def _format_link(self, formatter, ns, target, label):
+    def _format_link(self, formatter, ns, target, label, fullmatch=None):
+        intertrac = formatter.shorthand_intertrac_helper(ns, target, label,
+                                                         fullmatch)
+        if intertrac:
+            return intertrac
         return '<a class="report" href="%s">%s</a>' % (formatter.href.report(target), label)
 
diff -ruN -x .svn trac-0.9b2/trac/versioncontrol/api.py intertrac-branch/trac/versioncontrol/api.py
--- trac-0.9b2/trac/versioncontrol/api.py	2005-09-25 16:47:58.000000000 +0200
+++ intertrac-branch/trac/versioncontrol/api.py	2005-09-23 09:51:56.000000000 +0200
@@ -39,6 +39,12 @@
         """
         raise NotImplementedError
 
+    def has_node(self, path, rev):
+        """
+        Tell if there's a node at the specified (path,rev) combination.
+        """
+        raise NotImplementedError
+    
     def get_node(self, path, rev=None):
         """
         Retrieve a Node (directory or file) from the repository at the
@@ -111,7 +117,17 @@
         'None' is a valid revision value and represents the youngest revision.
         """
         return NotImplementedError
-        
+
+    def get_deltas(self, old_path, old_rev, new_path, new_rev, ignore_ancestry=1):
+        """
+        Generator that yields change tuples (old_node, new_node, kind, change)
+        for each node change between the two arbitrary (path,rev) pairs.
+
+        The old_node is assumed to be None when the change is an ADD,
+        the new_node is assumed to be None when the change is a DELETE.
+        """
+        raise NotImplementedError
+
 
 class Node(object):
     """
@@ -149,9 +165,22 @@
         node (if the underlying version control system supports that), which
         will be indicated by the first element of the tuple (i.e. the path)
         changing.
+        Starts with an entry for the current revision.
         """
         raise NotImplementedError
 
+    def get_previous(self):
+        """
+        Return the (path, rev, chg) tuple corresponding to the previous
+        revision for that node.
+        """
+        skip = True
+        for p in self.get_history(2):
+            if skip:
+                skip = False
+            else:
+                return p
+
     def get_properties(self):
         """
         Returns a dictionary containing the properties (meta-data) of the node.
diff -ruN -x .svn trac-0.9b2/trac/versioncontrol/cache.py intertrac-branch/trac/versioncontrol/cache.py
--- trac-0.9b2/trac/versioncontrol/cache.py	2005-09-25 16:47:58.000000000 +0200
+++ intertrac-branch/trac/versioncontrol/cache.py	2005-09-11 14:35:41.000000000 +0200
@@ -84,6 +84,9 @@
     def get_node(self, path, rev=None):
         return self.repos.get_node(path, rev)
 
+    def has_node(self, path, rev):
+        return self.repos.has_node(path, rev)
+
     def get_oldest_rev(self):
         return self.repos.oldest_rev
 
@@ -108,6 +111,9 @@
     def normalize_rev(self, rev):
         return self.repos.normalize_rev(rev)
 
+    def get_deltas(self, old_path, old_rev, new_path, new_rev, ignore_ancestry=1):
+        return self.repos.get_deltas(old_path, old_rev, new_path, new_rev, ignore_ancestry)
+
 
 class CachedChangeset(Changeset):
 
diff -ruN -x .svn trac-0.9b2/trac/versioncontrol/diff.py intertrac-branch/trac/versioncontrol/diff.py
--- trac-0.9b2/trac/versioncontrol/diff.py	2005-09-25 16:47:58.000000000 +0200
+++ intertrac-branch/trac/versioncontrol/diff.py	2005-09-23 09:51:56.000000000 +0200
@@ -218,6 +218,8 @@
                            ignore_space_changes)
     for group in _group_opcodes(opcodes, context):
         i1, i2, j1, j2 = group[0][1], group[-1][2], group[0][3], group[-1][4]
+        if i1 == 0 and i2 == 0:
+            i1, i2 = -1, -1 # support for 'A'dd changes
         yield '@@ -%d,%d +%d,%d @@' % (i1 + 1, i2 - i1, j1 + 1, j2 - j1)
         for tag, i1, i2, j1, j2 in group:
             if tag == 'equal':
diff -ruN -x .svn trac-0.9b2/trac/versioncontrol/svn_fs.py intertrac-branch/trac/versioncontrol/svn_fs.py
--- trac-0.9b2/trac/versioncontrol/svn_fs.py	2005-09-25 16:47:58.000000000 +0200
+++ intertrac-branch/trac/versioncontrol/svn_fs.py	2005-09-23 09:51:56.000000000 +0200
@@ -207,6 +207,11 @@
     def __del__(self):
         self.close()
 
+    def has_node(self, path, rev):
+        rev_root = fs.revision_root(self.fs_ptr, rev, self.pool())
+        node_type = fs.check_path(rev_root, path, self.pool())
+        return node_type in _kindmap
+
     def normalize_path(self, path):
         return (not path or path == '/') and '/' or path.strip('/')
 
@@ -298,9 +303,7 @@
         subpool = Pool(self.pool)
         while rev:
             subpool.clear()
-            rev_root = fs.revision_root(self.fs_ptr, rev, subpool())
-            node_type = fs.check_path(rev_root, path, subpool())
-            if node_type in _kindmap: # then path exists at that rev
+            if self.has_node(path, rev):
                 if expect_deletion:
                     # it was missing, now it's there again:
                     #  rev+1 must be a delete
@@ -330,6 +333,64 @@
                 expect_deletion = True
                 rev = self.previous_rev(rev)
 
+    def get_deltas(self, old_path, old_rev, new_path, new_rev,
+                   ignore_ancestry=0):
+        old_node = new_node = None
+        old_rev = self.normalize_rev(old_rev)
+        new_rev = self.normalize_rev(new_rev)
+        if self.has_node(old_path, old_rev):
+            old_node = self.get_node(old_path, old_rev)
+        else:
+            raise TracError, ('The Base for Diff is invalid: path %s'
+                              ' doesn\'t exist in revision %s' \
+                              % (old_path, old_rev))
+        if self.has_node(new_path, new_rev):
+            new_node = self.get_node(new_path, new_rev)
+        else:
+            raise TracError, ('The Target for Diff is invalid: path %s'
+                              ' doesn\'t exist in revision %s' \
+                              % (new_path, new_rev))
+        if new_node.kind != old_node.kind:
+            raise TracError, ('Diff mismatch: Base is a %s (%s in revision %s) '
+                              'and Target is a %s (%s in revision %s).' \
+                              % (old_node.kind, old_path, old_rev,
+                                 new_node.kind, new_path, new_rev))
+        subpool = Pool(self.pool)
+        if new_node.isdir:
+            editor = DiffChangeEditor()
+            e_ptr, e_baton = delta.make_editor(editor, subpool())
+            old_root = fs.revision_root(self.fs_ptr, old_rev, subpool())
+            new_root = fs.revision_root(self.fs_ptr, new_rev, subpool())
+            def authz_cb(root, path, pool): return 1
+            text_deltas = 0 # as this is anyway re-done in Diff.py...
+            entry_props = 0 # "... typically used only for working copy updates"
+            repos.svn_repos_dir_delta(old_root, old_path, '',
+                                      new_root, new_path,
+                                      e_ptr, e_baton, authz_cb,
+                                      text_deltas,
+                                      1, # directory
+                                      entry_props,
+                                      ignore_ancestry,
+                                      subpool())
+            for path, kind, change in editor.deltas:
+                old_node = new_node = None
+                if change != Changeset.ADD:
+                    old_node = self.get_node(posixpath.join(old_path, path),
+                                             old_rev)
+                if change != Changeset.DELETE:
+                    new_node = self.get_node(posixpath.join(new_path, path),
+                                             new_rev)
+                else:
+                    kind = _kindmap[fs.check_path(old_root, old_node.path,
+                                                  subpool())]
+                yield  (old_node, new_node, kind, change)
+        else:
+            old_root = fs.revision_root(self.fs_ptr, old_rev, subpool())
+            new_root = fs.revision_root(self.fs_ptr, new_rev, subpool())
+            if fs.contents_changed(old_root, old_path, new_root, new_path,
+                                   subpool()):
+                yield (old_node, new_node, Node.FILE, Changeset.EDIT)
+
 
 class SubversionNode(Node):
 
@@ -352,8 +413,12 @@
                                                self.pool())
         self.created_path = fs.node_created_path(self.root, self.scoped_path,
                                                  self.pool())
-        # 'created_path' differs from 'path' if the last operation is a copy,
-        # and furthermore, 'path' might not exist at 'create_rev'
+        # Note: 'created_path' differs from 'path' if the last change was a copy,
+        #        and furthermore, 'path' might not exist at 'create_rev'.
+        #        The only guarantees are:
+        #          * this node exists at (path,rev)
+        #          * the node existed at (created_path,created_rev)
+        # TODO: check node id
         self.rev = self.created_rev
         
         Node.__init__(self, path, self.rev, _kindmap[node_type])
@@ -393,6 +458,9 @@
         if newer:
             yield newer
 
+#    def get_previous(self):
+#        # FIXME: redo it with fs.node_history
+
     def get_properties(self):
         props = fs.node_proplist(self.root, self.scoped_path, self.pool())
         for name,value in props.items():
@@ -487,3 +555,47 @@
 
     def _get_prop(self, name):
         return fs.revision_prop(self.fs_ptr, self.rev, name, self.pool())
+
+
+#
+# Delta editor for diffs between arbitrary nodes
+#
+# Note 1: the 'copyfrom_path' and 'copyfrom_rev' information is not used
+#         because 'repos.svn_repos_dir_delta' *doesn't* provide it.
+#
+# Note 2: the 'dir_baton' is the path of the parent directory
+#
+
+class DiffChangeEditor(delta.Editor): 
+
+    def __init__(self):
+        self.deltas = []
+    
+    # -- svn.delta.Editor callbacks
+
+    def open_root(self, base_revision, dir_pool):
+        return ('/', Changeset.EDIT)
+
+    def add_directory(self, path, dir_baton, copyfrom_path, copyfrom_rev,
+                      dir_pool):
+        self.deltas.append((path, Node.DIRECTORY, Changeset.ADD))
+        return (path, Changeset.ADD)
+
+    def open_directory(self, path, dir_baton, base_revision, dir_pool):
+        return (path, dir_baton[1])
+
+    def change_dir_prop(self, dir_baton, name, value, pool):
+        path, change = dir_baton
+        if change != Changeset.ADD:
+            self.deltas.append((path, Node.DIRECTORY, change))
+
+    def delete_entry(self, path, revision, dir_baton, pool):
+        self.deltas.append((path, None, Changeset.DELETE))
+
+    def add_file(self, path, dir_baton, copyfrom_path, copyfrom_revision,
+                 dir_pool):
+        self.deltas.append((path, Node.FILE, Changeset.ADD))
+
+    def open_file(self, path, dir_baton, dummy_rev, file_pool):
+        self.deltas.append((path, Node.FILE, Changeset.EDIT))
+
diff -ruN -x .svn trac-0.9b2/trac/versioncontrol/tests/svn_fs.py intertrac-branch/trac/versioncontrol/tests/svn_fs.py
--- trac-0.9b2/trac/versioncontrol/tests/svn_fs.py	2005-09-25 16:47:58.000000000 +0200
+++ intertrac-branch/trac/versioncontrol/tests/svn_fs.py	2005-09-23 09:51:56.000000000 +0200
@@ -215,6 +215,72 @@
         self.assertEqual(('tags/v1', 7, 'unknown'), history.next())
         self.assertRaises(StopIteration, history.next)
 
+    # Diffs
+
+    def _cmp_diff(self, expected, got):
+        if expected[0]:
+            old = self.repos.get_node(*expected[0])
+            self.assertEqual((old.path, old.rev), (got[0].path, got[0].rev))
+        if expected[1]:
+            new = self.repos.get_node(*expected[1])
+            self.assertEqual((new.path, new.rev), (got[1].path, got[1].rev))
+        self.assertEqual(expected[2], (got[2], got[3]))
+        
+    def test_diff_file_different_revs(self):
+        diffs = self.repos.get_deltas('trunk/README.txt', 2, 'trunk/README.txt', 3)
+        self._cmp_diff((('trunk/README.txt', 2),
+                        ('trunk/README.txt', 3),
+                        (Node.FILE, Changeset.EDIT)), diffs.next())
+        self.assertRaises(StopIteration, diffs.next)
+
+    def test_diff_file_different_files(self):
+        diffs = self.repos.get_deltas('branches/v1x/README.txt', 12,
+                                      'branches/v1x/README2.txt', 12)
+        self._cmp_diff((('branches/v1x/README.txt', 12),
+                        ('branches/v1x/README2.txt', 12),
+                        (Node.FILE, Changeset.EDIT)), diffs.next())
+        self.assertRaises(StopIteration, diffs.next)
+
+    def test_diff_file_no_change(self):
+        diffs = self.repos.get_deltas('trunk/README.txt', 7,
+                                      'tags/v1/README.txt', 7)
+        self.assertRaises(StopIteration, diffs.next)
+ 
+    def test_diff_dir_different_revs(self):
+        diffs = self.repos.get_deltas('trunk', 4, 'trunk', 8)
+        self._cmp_diff((None, ('trunk/dir1/dir2', 8),
+                        (Node.DIRECTORY, Changeset.ADD)), diffs.next())
+        self._cmp_diff((None, ('trunk/dir1/dir3', 8),
+                        (Node.DIRECTORY, Changeset.ADD)), diffs.next())
+        self._cmp_diff((None, ('trunk/README2.txt', 6),
+                        (Node.FILE, Changeset.ADD)), diffs.next())
+        self._cmp_diff((('trunk/dir2', 4), None,
+                        (Node.DIRECTORY, Changeset.DELETE)), diffs.next())
+        self._cmp_diff((('trunk/dir3', 4), None,
+                        (Node.DIRECTORY, Changeset.DELETE)), diffs.next())
+        self.assertRaises(StopIteration, diffs.next)
+
+    def test_diff_dir_different_dirs(self):
+        diffs = self.repos.get_deltas('trunk', 1, 'branches/v1x', 12)
+        self._cmp_diff((None, ('branches/v1x/dir1', 12),
+                        (Node.DIRECTORY, Changeset.ADD)), diffs.next())
+        self._cmp_diff((None, ('branches/v1x/dir1/dir2', 12),
+                        (Node.DIRECTORY, Changeset.ADD)), diffs.next())
+        self._cmp_diff((None, ('branches/v1x/dir1/dir3', 12),
+                        (Node.DIRECTORY, Changeset.ADD)), diffs.next())
+        self._cmp_diff((None, ('branches/v1x/README.txt', 12),
+                        (Node.FILE, Changeset.ADD)), diffs.next())
+        self._cmp_diff((None, ('branches/v1x/README2.txt', 12),
+                        (Node.FILE, Changeset.ADD)), diffs.next())
+        self.assertRaises(StopIteration, diffs.next)
+
+    def test_diff_dir_no_change(self):
+        diffs = self.repos.get_deltas('trunk', 7,
+                                      'tags/v1', 7)
+        self.assertRaises(StopIteration, diffs.next)
+        
+    # Changesets
+
     def test_changeset_repos_creation(self):
         chgset = self.repos.get_changeset(0)
         self.assertEqual(0, chgset.rev)
diff -ruN -x .svn trac-0.9b2/trac/versioncontrol/web_ui/browser.py intertrac-branch/trac/versioncontrol/web_ui/browser.py
--- trac-0.9b2/trac/versioncontrol/web_ui/browser.py	2005-09-25 16:47:58.000000000 +0200
+++ intertrac-branch/trac/versioncontrol/web_ui/browser.py	2005-09-28 14:57:27.000000000 +0200
@@ -88,21 +88,29 @@
 
         repos = self.env.get_repository(req.authname)
         node = repos.get_node(path, rev)
+        rev = repos.normalize_rev(rev)
 
         hidden_properties = [p.strip() for p
                              in self.config.get('browser', 'hide_properties',
                                                 'svk:merge').split(',')]
+
         req.hdf['title'] = path
-        req.hdf['browser'] = {
+        browser_hdf = {
             'path': path,
-            'revision': rev or repos.youngest_rev,
+            'revision': rev,
             'props': dict([(util.escape(name), util.escape(value))
                            for name, value in node.get_properties().items()
-                           if not name in hidden_properties]),
-            'href': util.escape(self.env.href.browser(path, rev=rev or
-                                                      repos.youngest_rev)),
-            'log_href': util.escape(self.env.href.log(path))
-        }
+                           if not name in hidden_properties])
+            }
+        browser_hrefs = {
+            'href': self.env.href.browser(path,rev=rev),
+            'restr_changeset_href': self.env.href.changeset(node.rev, path),
+            'anydiff_href': self.env.href.anydiff(),
+            'log_href': self.env.href.log(path)
+            }
+        browser_hdf.update(dict([(key, util.escape(href)) for key, href in
+                                 browser_hrefs.items()]))
+        req.hdf['browser'] = browser_hdf
 
         path_links = get_path_links(self.env.href, path, rev)
         if len(path_links) > 1:
@@ -162,7 +170,14 @@
 
         req.hdf['browser.items'] = info
         req.hdf['browser.changes'] = changes
-
+        if node.path != '':
+            zip_href = self.env.href.diff(node.path, new=rev, old=rev,
+                                          old_path='/', # special case (#238)
+                                          format='zip')
+            add_link(req, 'alternate', zip_href, 'Zip Archive',
+                     'application/zip', 'zip')
+        
+        
     def _render_file(self, req, repos, node, rev=None):
         req.perm.assert_permission('FILE_VIEW')
 
diff -ruN -x .svn trac-0.9b2/trac/versioncontrol/web_ui/changeset.py intertrac-branch/trac/versioncontrol/web_ui/changeset.py
--- trac-0.9b2/trac/versioncontrol/web_ui/changeset.py	2005-09-25 16:47:58.000000000 +0200
+++ intertrac-branch/trac/versioncontrol/web_ui/changeset.py	2005-09-23 09:51:56.000000000 +0200
@@ -22,20 +22,19 @@
 
 from trac import mimeview, util
 from trac.core import *
-from trac.perm import IPermissionRequestor
 from trac.Search import ISearchSource, query_to_sql, shorten_result
 from trac.Timeline import ITimelineEventProvider
 from trac.versioncontrol import Changeset, Node
 from trac.versioncontrol.svn_authz import SubversionAuthorizer
-from trac.versioncontrol.diff import get_diff_options, hdf_diff, unified_diff
 from trac.web import IRequestHandler
-from trac.web.chrome import add_link, add_stylesheet, INavigationContributor
-from trac.wiki import wiki_to_html, wiki_to_oneliner, IWikiSyntaxProvider
+from trac.web.chrome import INavigationContributor
+from trac.wiki import wiki_to_html, wiki_to_oneliner, IWikiSyntaxProvider, \
+                      INTERTRAC_SCHEME
+from trac.versioncontrol.web_ui.diff import AbstractDiffModule
 
+class ChangesetModule(AbstractDiffModule):
 
-class ChangesetModule(Component):
-
-    implements(INavigationContributor, IPermissionRequestor, IRequestHandler,
+    implements(INavigationContributor, 
                ITimelineEventProvider, IWikiSyntaxProvider, ISearchSource)
 
     # INavigationContributor methods
@@ -46,53 +45,17 @@
     def get_navigation_items(self, req):
         return []
 
-    # IPermissionRequestor methods
-
-    def get_permission_actions(self):
-        return ['CHANGESET_VIEW']
-
-    # IRequestHandler methods
+    # (reimplemented) IRequestHandler methods
 
     def match_request(self, req):
-        match = re.match(r'/changeset/([0-9]+)$', req.path_info)
+        match = re.match(r'/changeset/([0-9]+)(/.*)?$', req.path_info)
         if match:
             req.args['rev'] = match.group(1)
+            path = match.group(2)
+            if path:
+                req.args['path'] = path
             return 1
 
-    def process_request(self, req):
-        req.perm.assert_permission('CHANGESET_VIEW')
-
-        rev = req.args.get('rev')
-        repos = self.env.get_repository(req.authname)
-        authzperm = SubversionAuthorizer(self.env, req.authname)
-        authzperm.assert_permission_for_changeset(rev)
-
-        diff_options = get_diff_options(req)
-        if req.args.has_key('update'):
-            req.redirect(self.env.href.changeset(rev))
-
-        chgset = repos.get_changeset(rev)
-        req.check_modified(chgset.date,
-                           diff_options[0] + ''.join(diff_options[1]))
-
-        format = req.args.get('format')
-        if format == 'diff':
-            self._render_diff(req, repos, chgset, diff_options)
-            return
-        elif format == 'zip':
-            self._render_zip(req, repos, chgset)
-            return
-
-        self._render_html(req, repos, chgset, diff_options)
-        add_link(req, 'alternate', '?format=diff', 'Unified Diff',
-                 'text/plain', 'diff')
-        add_link(req, 'alternate', '?format=zip', 'Zip Archive',
-                 'application/zip', 'zip')
-        add_stylesheet(req, 'common/css/changeset.css')
-        add_stylesheet(req, 'common/css/diff.css')
-        add_stylesheet(req, 'common/css/code.css')
-        return 'changeset.cs', None
-
     # ITimelineEventProvider methods
 
     def get_timeline_filters(self, req):
@@ -143,229 +106,41 @@
                           message
                 rev = repos.previous_rev(rev)
 
-    # Internal methods
-
-    def _render_html(self, req, repos, chgset, diff_options):
-        """HTML version"""
-        req.hdf['title'] = '[%s]' % chgset.rev
-        req.hdf['changeset'] = {
-            'revision': chgset.rev,
-            'time': util.format_datetime(chgset.date),
-            'author': util.escape(chgset.author or 'anonymous'),
-            'message': wiki_to_html(chgset.message or '--', self.env, req,
-                                    escape_newlines=True)
-        }
-
-        oldest_rev = repos.oldest_rev
-        if chgset.rev != oldest_rev:
-            add_link(req, 'first', self.env.href.changeset(oldest_rev),
-                     'Changeset %s' % oldest_rev)
-            previous_rev = repos.previous_rev(chgset.rev)
-            add_link(req, 'prev', self.env.href.changeset(previous_rev),
-                     'Changeset %s' % previous_rev)
-        youngest_rev = repos.youngest_rev
-        if str(chgset.rev) != str(youngest_rev):
-            next_rev = repos.next_rev(chgset.rev)
-            add_link(req, 'next', self.env.href.changeset(next_rev),
-                     'Changeset %s' % next_rev)
-            add_link(req, 'last', self.env.href.changeset(youngest_rev),
-                     'Changeset %s' % youngest_rev)
-
-        edits = []
-        idx = 0
-        for path, kind, change, base_path, base_rev in chgset.get_changes():
-            info = {'change': change}
-            if base_path:
-                info['path.old'] = base_path
-                info['rev.old'] = base_rev
-                info['browser_href.old'] = self.env.href.browser(base_path,
-                                                                 rev=base_rev)
-            if path:
-                info['path.new'] = path
-                info['rev.new'] = chgset.rev
-                info['browser_href.new'] = self.env.href.browser(path,
-                                                                 rev=chgset.rev)
-            if change in (Changeset.COPY, Changeset.EDIT, Changeset.MOVE):
-                edits.append((idx, path, kind, base_path, base_rev))
-            req.hdf['changeset.changes.%d' % idx] = info
-            idx += 1
-
-        hidden_properties = [p.strip() for p
-                             in self.config.get('browser', 'hide_properties',
-                                                'svk:merge').split(',')]
-
-        for idx, path, kind, base_path, base_rev in edits:
-            old_node = repos.get_node(base_path or path, base_rev)
-            new_node = repos.get_node(path, chgset.rev)
-
-            # Property changes
-            old_props = old_node.get_properties()
-            new_props = new_node.get_properties()
-            changed_props = {}
-            if old_props != new_props:
-                for k,v in old_props.items():
-                    if not k in new_props:
-                        changed_props[k] = {'old': v}
-                    elif v != new_props[k]:
-                        changed_props[k] = {'old': v, 'new': new_props[k]}
-                for k,v in new_props.items():
-                    if not k in old_props:
-                        changed_props[k] = {'new': v}
-                for k in hidden_properties:
-                    if k in changed_props:
-                        del changed_props[k]
-                req.hdf['changeset.changes.%d.props' % idx] = changed_props
-
-            if kind == Node.DIRECTORY:
-                continue
-
-            # Content changes
-            default_charset = self.config.get('trac', 'default_charset')
-            old_content = old_node.get_content().read()
-            if mimeview.is_binary(old_content):
-                continue
-            charset = mimeview.get_charset(old_node.content_type) or \
-                      default_charset
-            old_content = util.to_utf8(old_content, charset)
-
-            new_content = new_node.get_content().read()
-            if mimeview.is_binary(new_content):
-                continue
-            charset = mimeview.get_charset(new_node.content_type) or \
-                      default_charset
-            new_content = util.to_utf8(new_content, charset)
-
-            if old_content != new_content:
-                context = 3
-                for option in diff_options[1]:
-                    if option.startswith('-U'):
-                        context = int(option[2:])
-                        break
-                tabwidth = int(self.config.get('diff', 'tab_width',
-                                               self.config.get('mimeviewer',
-                                                               'tab_width')))
-                changes = hdf_diff(old_content.splitlines(),
-                                   new_content.splitlines(),
-                                   context, tabwidth,
-                                   ignore_blank_lines='-B' in diff_options[1],
-                                   ignore_case='-i' in diff_options[1],
-                                   ignore_space_changes='-b' in diff_options[1])
-                req.hdf['changeset.changes.%d.diff' % idx] = changes
-
-    def _render_diff(self, req, repos, chgset, diff_options):
-        """Raw Unified Diff version"""
-        req.send_response(200)
-        req.send_header('Content-Type', 'text/plain;charset=utf-8')
-        req.send_header('Content-Disposition',
-                        'filename=Changeset%s.diff' % req.args.get('rev'))
-        req.end_headers()
-
-        for path, kind, change, base_path, base_rev in chgset.get_changes():
-            if change == Changeset.ADD:
-                old_node = None
-            else:
-                old_node = repos.get_node(base_path or path, base_rev)
-            if change == Changeset.DELETE:
-                new_node = None
-            else:
-                new_node = repos.get_node(path, chgset.rev)
-
-            # TODO: Property changes
-
-            # Content changes
-            if kind == 'dir':
-                continue
-
-            default_charset = self.config.get('trac', 'default_charset')
-            new_content = old_content = ''
-            new_node_info = old_node_info = ('','')
-
-            if old_node:
-                charset = mimeview.get_charset(old_node.content_type) or \
-                          default_charset
-                old_content = util.to_utf8(old_node.get_content().read(),
-                                           charset)
-                old_node_info = (old_node.path, old_node.rev)
-            if mimeview.is_binary(old_content):
-                continue
-
-            if new_node:
-                charset = mimeview.get_charset(new_node.content_type) or \
-                          default_charset
-                new_content = util.to_utf8(new_node.get_content().read(),
-                                           charset)
-                new_node_info = (new_node.path, new_node.rev)
-            if mimeview.is_binary(new_content):
-                continue
-
-            if old_content != new_content:
-                context = 3
-                for option in diff_options[1]:
-                    if option.startswith('-U'):
-                        context = int(option[2:])
-                        break
-                req.write('Index: ' + path + util.CRLF)
-                req.write('=' * 67 + util.CRLF)
-                req.write('--- %s (revision %s)' % old_node_info +
-                          util.CRLF)
-                req.write('+++ %s (revision %s)' % new_node_info +
-                          util.CRLF)
-                for line in unified_diff(old_content.splitlines(),
-                                         new_content.splitlines(), context,
-                                         ignore_blank_lines='-B' in diff_options[1],
-                                         ignore_case='-i' in diff_options[1],
-                                         ignore_space_changes='-b' in diff_options[1]):
-                    req.write(line + util.CRLF)
-
-    def _render_zip(self, req, repos, chgset):
-        """ZIP archive with all the added and/or modified files."""
-        req.send_response(200)
-        req.send_header('Content-Type', 'application/zip')
-        req.send_header('Content-Disposition',
-                        'filename=Changeset%s.zip' % chgset.rev)
-        req.end_headers()
-
-        try:
-            from cStringIO import StringIO
-        except ImportError:
-            from StringIO import StringIO
-        from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED
-
-        buf = StringIO()
-        zipfile = ZipFile(buf, 'w', ZIP_DEFLATED)
-        for path, kind, change, base_path, base_rev in chgset.get_changes():
-            if kind == Node.FILE and change != Changeset.DELETE:
-                node = repos.get_node(path, chgset.rev)
-                zipinfo = ZipInfo()
-                zipinfo.filename = node.path
-                zipinfo.date_time = time.gmtime(node.last_modified)[:6]
-                zipinfo.compress_type = ZIP_DEFLATED
-                zipfile.writestr(zipinfo, node.get_content().read())
-        zipfile.close()
-        req.write(buf.getvalue())
 
     # IWikiSyntaxProvider methods
     
     def get_wiki_syntax(self):
-        yield (r"!?\[\d+\]|(?:\b|!)r\d+\b(?!:\d)",
+        yield (r"!?\[(?P<it_changeset>%s\s*)?\d+(?:/[^\]]*)?\]|" \
+               % INTERTRAC_SCHEME +                     # [1], [T1] or [trac 1]
+               r"(?:\b|!)r\d+\b(?!:\d)",                # r1 but not r1:2
                lambda x, y, z: self._format_link(x, 'changeset',
                                                  y[0] == 'r' and y[1:]
-                                                 or y[1:-1], y))
+                                                 or y[1:-1], y, z))
 
     def get_link_resolvers(self):
         yield ('changeset', self._format_link)
 
-    def _format_link(self, formatter, ns, rev, label):
+    def _format_link(self, formatter, ns, chgset, label, fullmatch=None):
+        intertrac = formatter.shorthand_intertrac_helper(ns, chgset, label,
+                                                         fullmatch)
+        if intertrac:
+            return intertrac
+        sep = chgset.find('/')
+        if sep > 0:
+            rev, path = chgset[:sep], chgset[sep:]
+        else:
+            rev, path = chgset, None
         cursor = formatter.db.cursor()
         cursor.execute('SELECT message FROM revision WHERE rev=%s', (rev,))
         row = cursor.fetchone()
         if row:
             return '<a class="changeset" title="%s" href="%s">%s</a>' \
                    % (util.escape(util.shorten_line(row[0])),
-                      formatter.href.changeset(rev), label)
+                      formatter.href.changeset(rev, path), label)
         else:
-            return '<a class="missing changeset" href="%s" rel="nofollow">%s</a>' \
-                   % (formatter.href.changeset(rev), label)
+            return '<a class="missing changeset" href="%s"' \
+                   ' rel="nofollow">%s</a>' \
+                   % (formatter.href.changeset(rev, path), label)
 
     # ISearchProvider methods
 
diff -ruN -x .svn trac-0.9b2/trac/versioncontrol/web_ui/diff.py intertrac-branch/trac/versioncontrol/web_ui/diff.py
--- trac-0.9b2/trac/versioncontrol/web_ui/diff.py	1970-01-01 01:00:00.000000000 +0100
+++ intertrac-branch/trac/versioncontrol/web_ui/diff.py	2005-09-28 14:57:26.000000000 +0200
@@ -0,0 +1,597 @@
+# -*- coding: iso8859-1 -*-
+#
+# Copyright (C) 2003-2005 Edgewall Software
+# Copyright (C) 2003-2005 Jonas Borgström <jonas@edgewall.com>
+# Copyright (C) 2004-2005 Christopher Lenz <cmlenz@gmx.de>
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://trac.edgewall.com/license.html.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://projects.edgewall.com/trac/.
+#
+# Author: Jonas Borgström <jonas@edgewall.com>
+#         Christopher Lenz <cmlenz@gmx.de>
+#         Christian Boos <cboos@neuf.fr>
+
+from __future__ import generators
+import time
+import re
+import posixpath
+from urllib import urlencode
+
+from trac import mimeview, util
+from trac.core import *
+from trac.perm import IPermissionRequestor
+from trac.versioncontrol import Changeset, Node
+from trac.versioncontrol.diff import get_diff_options, hdf_diff, unified_diff
+from trac.versioncontrol.svn_authz import SubversionAuthorizer
+from trac.web import IRequestHandler
+from trac.web.chrome import add_link, add_stylesheet
+from trac.wiki import wiki_to_html, IWikiSyntaxProvider
+
+class DiffArgs(dict):
+    def __getattr__(self,str):
+        return self[str]
+    
+
+class ChangesPermission(Component):
+    """Simple permission provider for changes related modules."""
+    
+    implements(IPermissionRequestor)
+    
+    def get_permission_actions(self):
+        return ['CHANGESET_VIEW']
+    
+
+class AbstractDiffModule(Component):
+    """Provide flexible functionality for showing sets of differences.
+
+    If the differences shown are coming from a specific changeset,
+    then that changeset informations can be shown too.
+
+    In addition, it is possible to show only a subset of the changeset:
+    Only the changes affecting a given path will be shown.
+    This is called the ''restricted'' changeset.
+
+    But the differences can also be computed in a more general way,
+    between two arbitrary paths and/or between two arbitrary revisions.
+    In that case, there's no changeset information displayed.
+    """
+
+    abstract = True
+
+    implements(IRequestHandler)
+
+    # IRequestHandler methods
+
+    def match_request(self, req):
+        raise NotImplementedError
+
+    def process_request(self, req):
+        """The appropriate mode of operation is inferred from
+        the request parameters:
+         * If `old` and `new` parameters are given, it will be an
+           arbitrary set of differences: `chgset` is False.
+           * If `old_path` is given and is different from `path`,
+             it's a generalized diff, from `old_path@old`
+             to `path@new`: `restricted` is False.
+           * Otherwise those are differences between two arbitrary revisions
+             of a given path: `restricted` is True.
+         * Otherwise, we are dealing with a changeset only: `chgset` is True.
+           * If the `path` is not empty or not the root, then only
+             the changes affecting that path (i.e. itself, children or
+             ancestors) will be considered: `restricted` is True.
+           * Otherwise, it's the full changeset: `restricted` is False.
+         
+        In any case, the given path@rev pair must exist.
+        """
+        req.perm.assert_permission('CHANGESET_VIEW')
+        
+        # -- retrieve arguments
+        path = req.args.get('path')
+        rev = req.args.get('rev')
+        old = req.args.get('old')
+        new = req.args.get('new')
+        old_path = req.args.get('old_path')
+
+        # -- normalize and check for special case
+        repos = self.env.get_repository(req.authname)
+        path = repos.normalize_path(path)
+        rev = repos.normalize_rev(rev)
+        if old_path: # Note: normalize_path now returns '/' if given 'None'
+            old_path = repos.normalize_path(old_path)
+        
+        authzperm = SubversionAuthorizer(self.env, req.authname)
+        authzperm.assert_permission_for_changeset(rev)
+
+        if old_path == path and old and old == new: # revert to Changeset
+            rev = old
+            old_path = old = new = None
+
+        diff_options = get_diff_options(req)
+
+        # -- setup the `chgset` and `restricted` flags, see docstring above.
+        chgset = not old and not new and not old_path
+        if chgset:
+            restricted = path != '' and path != '/' # (subset or not)
+        else:
+            restricted = old_path == path # (same path or not)
+
+        # -- redirect if changing the diff options
+        if req.args.has_key('update'):
+            if chgset:
+                if restricted:
+                    req.redirect(self.env.href.diff(path, rev=rev))
+                else:
+                    req.redirect(self.env.href.changeset(rev))
+            else:
+                req.redirect(self.env.href.diff(path, new=new,
+                                                old_path=old_path, old=old))
+
+        # -- preparing the diff arguments
+        if chgset:
+            prev = repos.get_node(path, rev).get_previous()
+            if prev:
+                prev_path, prev_rev = prev[:2]
+            else:
+                prev_path, prev_rev = path, repos.previous_rev(rev)
+            diff_args = DiffArgs(old_path=prev_path, old_rev=prev_rev,
+                                 new_path=path, new_rev=rev)
+        else:
+            if not new:
+                new = repos.youngest_rev
+            elif not old:
+                old = repos.youngest_rev
+            if not old_path:
+                old_path = path
+            diff_args = DiffArgs(old_path=old_path, old_rev=old,
+                                 new_path=path, new_rev=new)
+        if chgset:
+            chgset = repos.get_changeset(rev)
+            req.check_modified(chgset.date,
+                               diff_options[0] + ''.join(diff_options[1]))
+        else:
+            pass # FIXME: what date should we choose for a diff?
+
+        req.hdf['diff'] = diff_args
+
+        format = req.args.get('format')
+
+        if format in ['diff', 'zip']:
+            # choosing an appropriate filename
+            rpath = path.replace('/','_')
+            if chgset:
+                if restricted:
+                    filename = 'changeset_%s_r%s' % (rpath, rev)
+                else:
+                    filename = 'changeset_r%s' % rev
+            else:
+                if restricted:
+                    filename = 'diff-%s-from-r%s-to-r%s' \
+                                  % (rpath, old, new)
+                elif old_path == '/': # special case for download (#238)
+                    filename = '%s-r%s' % (rpath, old)
+                else:
+                    filename = 'diff-from-%s-r%s-to-%s-r%s' \
+                               % (old_path.replace('/','_'), old, rpath, new)
+            if format == 'diff':
+                self._render_diff(req, filename, repos, diff_args,
+                                  diff_options)
+                return
+            elif format == 'zip':
+                self._render_zip(req, filename, repos, diff_args)
+                return
+
+        # -- HTML format
+        self._render_html(req, repos, chgset, restricted,
+                          diff_args, diff_options)
+        if chgset:
+            diff_params = 'rev=%s' % rev
+        else:
+            diff_params = urlencode({'path': path,
+                                     'new': new,
+                                     'old_path': old_path,
+                                     'old': old})
+        add_link(req, 'alternate', '?format=diff&'+diff_params, 'Unified Diff',
+                 'text/plain', 'diff')
+        add_link(req, 'alternate', '?format=zip&'+diff_params, 'Zip Archive',
+                 'application/zip', 'zip')
+        add_stylesheet(req, 'common/css/changeset.css')
+        add_stylesheet(req, 'common/css/diff.css')
+        add_stylesheet(req, 'common/css/code.css')
+        return 'diff.cs', None
+
+
+    # Internal methods
+
+    def _render_html(self, req, repos, chgset, restricted, diff, diff_options):
+        """
+        HTML version
+        """
+        req.hdf['diff'] = {
+            'chgset': chgset and True,
+            'restricted': restricted,
+            'href': { 'new_rev': self.env.href.changeset(diff.new_rev),
+                      'old_rev': self.env.href.changeset(diff.old_rev),
+                      'new_path': self.env.href.browser(diff.new_path,
+                                                        rev=diff.new_rev),
+                      'old_path': self.env.href.browser(diff.old_path,
+                                                        rev=diff.old_rev)
+                      }
+            }
+        
+        if chgset: # Changeset Mode (possibly restricted on a path)
+            path, rev = diff.new_path, diff.new_rev
+
+            # -- getting the deltas from the Changeset.get_changes method
+            def get_deltas():
+                old_node = new_node = None
+                for npath, kind, change, opath, orev in chgset.get_changes():
+                    if restricted and \
+                           not (npath.startswith(path)      # npath is below
+                                or path.startswith(npath)): # npath is above
+                        continue
+                    if change != Changeset.ADD:
+                        old_node = repos.get_node(opath, orev)
+                    if change != Changeset.DELETE:
+                        new_node = repos.get_node(npath, rev)
+                    yield old_node, new_node, kind, change
+                    
+            def _changeset_title(rev):
+                if restricted:
+                    return 'Changeset %s for %s' % (rev, path)
+                else:
+                    return 'Changeset %s' % rev
+
+            title = _changeset_title(rev)
+            req.hdf['changeset'] = {
+                'revision': chgset.rev,
+                'time': util.format_datetime(chgset.date),
+                'author': util.escape(chgset.author or 'anonymous'),
+                'message': wiki_to_html(chgset.message or '--', self.env, req,
+                                        escape_newlines=True)
+                }
+            oldest_rev = repos.oldest_rev
+            if chgset.rev != oldest_rev:
+                if restricted:
+                    prev = repos.get_node(path, rev).get_previous()
+                    if prev:
+                        prev_path, prev_rev = prev[:2]
+                        prev_href = self.env.href.changeset(prev_rev, prev_path)
+                    else:
+                        prev_path = prev_rev = None
+                else:
+                    prev_path = diff.old_path
+                    prev_rev = repos.previous_rev(chgset.rev)
+                    add_link(req, 'first', self.env.href.changeset(oldest_rev),
+                             'Changeset %s' % oldest_rev)
+                    prev_href = self.env.href.changeset(prev_rev)
+                if prev_rev:
+                    add_link(req, 'prev', prev_href, _changeset_title(prev_rev))
+            youngest_rev = repos.youngest_rev
+            if str(chgset.rev) != str(youngest_rev):
+                if restricted:
+                    next_rev = next_href = None
+                    # FIXME: find an effective way to find the next rev
+                else:
+                    next_rev = repos.next_rev(chgset.rev)
+                    next_href = self.env.href.changeset(next_rev)
+                    add_link(req, 'last',
+                             self.env.href.diff(path, rev=youngest_rev),
+                             'Changeset %s' % youngest_rev)
+                if next_rev:
+                    add_link(req, 'next', next_href, _changeset_title(next_rev))
+
+        else: # Diff Mode
+            # -- getting the deltas from the Repository.get_deltas method
+            def get_deltas():
+                for d in repos.get_deltas(**diff):
+                    yield d
+                    
+            reverse_href = self.env.href.diff(diff.old_path,
+                                              new=diff.old_rev,
+                                              old_path=diff.new_path,
+                                              old=diff.new_rev)
+            req.hdf['diff.reverse_href'] = reverse_href
+            title = self.title_for_diff(diff)
+        req.hdf['title'] = title
+
+        def _change_info(old_node, new_node, change):
+            info = {'change': change}
+            if old_node:
+                info['path.old'] = old_node.path
+                info['rev.old'] = old_node.rev # this is the created rev.
+                old_href = self.env.href.browser(old_node.path,
+                                                 rev=diff.old_rev)
+                # Reminder: old_node.path may not exist at old_node.rev
+                info['browser_href.old'] = old_href
+            if new_node:
+                info['path.new'] = new_node.path
+                info['rev.new'] = new_node.rev # created rev.
+                new_href = self.env.href.browser(new_node.path,
+                                                 rev=diff.new_rev)
+                # (same remark as above)
+                info['browser_href.new'] = new_href
+            return info
+
+        hidden_properties = [p.strip() for p
+                             in self.config.get('browser', 'hide_properties',
+                                                'svk:merge').split(',')]
+
+        def _prop_changes(old_node, new_node):
+            old_props = old_node.get_properties()
+            new_props = new_node.get_properties()
+            changed_props = {}
+            if old_props != new_props:
+                for k,v in old_props.items():
+                    if not k in new_props:
+                        changed_props[k] = {'old': v}
+                    elif v != new_props[k]:
+                        changed_props[k] = {'old': v, 'new': new_props[k]}
+                for k,v in new_props.items():
+                    if not k in old_props:
+                        changed_props[k] = {'new': v}
+                for k in hidden_properties:
+                    if k in changed_props:
+                        del changed_props[k]
+            return changed_props
+
+        def _content_changes(old_node, new_node):
+            """
+            Returns the list of differences.
+            The list is empty when no differences between comparable files
+            are detected, but the return value is None for non-comparable files.
+            """
+            default_charset = self.config.get('trac', 'default_charset')
+            old_content = old_node.get_content().read()            
+            if mimeview.is_binary(old_content):
+                return None
+            charset = mimeview.get_charset(old_node.content_type) or \
+                      default_charset
+            old_content = util.to_utf8(old_content, charset)
+
+            new_content = new_node.get_content().read()
+            if mimeview.is_binary(new_content):
+                return None
+            charset = mimeview.get_charset(new_node.content_type) or \
+                      default_charset
+            new_content = util.to_utf8(new_content, charset)
+
+            if old_content != new_content:
+                context = 3
+                options = diff_options[1]
+                for option in options:
+                    if option.startswith('-U'):
+                        context = int(option[2:])
+                        break
+                tabwidth = int(self.config.get('diff', 'tab_width',
+                                               self.config.get('mimeviewer',
+                                                               'tab_width')))
+                return hdf_diff(old_content.splitlines(),
+                                new_content.splitlines(),
+                                context, tabwidth,
+                                ignore_blank_lines='-B' in options,
+                                ignore_case='-i' in options,
+                                ignore_space_changes='-b' in options)
+            else:
+                return []
+
+        idx = 0
+        for old_node, new_node, kind, change in get_deltas():
+            if change != Changeset.EDIT:
+                show_entry = True
+            else:
+                show_entry = False
+                assert old_node and new_node
+                props = _prop_changes(old_node, new_node)
+                if props:
+                    req.hdf['diff.changes.%d.props' % idx] = props
+                    show_entry = True
+                if kind == Node.FILE:
+                    diffs = _content_changes(old_node, new_node)
+                    if diffs != []:
+                        if diffs:
+                            req.hdf['diff.changes.%d.diff' % idx] = diffs
+                        # elif None (means: manually compare to (previous))
+                        show_entry = True
+            if show_entry:
+                info = _change_info(old_node, new_node, change)
+                req.hdf['diff.changes.%d' % idx] = info
+            idx += 1 # the sequence should be immutable
+
+    def _render_diff(self, req, filename, repos, diff, diff_options):
+        """Raw Unified Diff version"""
+        req.send_response(200)
+        req.send_header('Content-Type', 'text/plain;charset=utf-8')
+        req.send_header('Content-Disposition',
+                        'filename=%s.diff' % filename)
+        req.end_headers()
+
+        for old_node, new_node, kind, change in repos.get_deltas(**diff):
+            # TODO: Property changes
+
+            # Content changes
+            if kind == Node.DIRECTORY:
+                continue
+
+            default_charset = self.config.get('trac', 'default_charset')
+            new_content = old_content = ''
+            new_node_info = old_node_info = ('','')
+
+            if old_node:
+                charset = mimeview.get_charset(old_node.content_type) or \
+                          default_charset
+                old_content = util.to_utf8(old_node.get_content().read(),
+                                           charset)
+                old_node_info = (old_node.path, old_node.rev)
+                if mimeview.is_binary(old_content):
+                    continue
+
+            if new_node:
+                charset = mimeview.get_charset(new_node.content_type) or \
+                          default_charset
+                new_content = util.to_utf8(new_node.get_content().read(),
+                                           charset)
+                new_node_info = (new_node.path, new_node.rev)
+                if mimeview.is_binary(new_content):
+                    continue
+                new_path = new_node.path
+            else:
+                old_node_path = repos.normalize_path(old_node.path)
+                diff_old_path = repos.normalize_path(diff.old_path)
+                new_path = posixpath.join(diff.new_path,
+                                          old_node_path[len(diff_old_path)+1:])
+
+            if old_content != new_content:
+                context = 3
+                options = diff_options[1]
+                for option in options:
+                    if option.startswith('-U'):
+                        context = int(option[2:])
+                        break
+                if not old_node_info[0]:
+                    old_node_info = new_node_info # support for 'A'dd changes
+                req.write('Index: ' + new_path + util.CRLF)
+                req.write('=' * 67 + util.CRLF)
+                req.write('--- %s (revision %s)' % old_node_info +
+                          util.CRLF)
+                req.write('+++ %s (revision %s)' % new_node_info +
+                          util.CRLF)
+                for line in unified_diff(old_content.splitlines(),
+                                         new_content.splitlines(), context,
+                                         ignore_blank_lines='-B' in options,
+                                         ignore_case='-i' in options,
+                                         ignore_space_changes='-b' in options):
+                    req.write(line + util.CRLF)
+
+    def _render_zip(self, req, filename, repos, diff):
+        """ZIP archive with all the added and/or modified files."""
+        new_rev = diff.new_rev
+        req.send_response(200)
+        req.send_header('Content-Type', 'application/zip')
+        req.send_header('Content-Disposition',
+                        'filename=%s.zip' % filename)
+
+        try:
+            from cStringIO import StringIO
+        except ImportError:
+            from StringIO import StringIO
+        from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED
+
+        buf = StringIO()
+        zipfile = ZipFile(buf, 'w', ZIP_DEFLATED)
+        for old_node, new_node, kind, change in repos.get_deltas(**diff):
+            if kind == Node.FILE and change != Changeset.DELETE:
+                assert new_node
+                zipinfo = ZipInfo()
+                zipinfo.filename = new_node.path
+                zipinfo.date_time = time.gmtime(new_node.last_modified)[:6]
+                zipinfo.compress_type = ZIP_DEFLATED
+                zipfile.writestr(zipinfo, new_node.get_content().read())
+        zipfile.close()
+
+        buf.seek(0, 2) # be sure to be at the end
+        req.send_header("Content-Length", buf.tell())
+        req.end_headers()
+        
+        req.write(buf.getvalue())
+
+    def title_for_diff(self, diff):
+        if diff.new_path == diff.old_path: # ''diff between 2 revisions'' mode
+            return 'Diff r%s:%s for %s' \
+                   % (diff.old_rev or 'latest', diff.new_rev or 'latest',
+                      diff.new_path or '/')
+        else:                              # ''arbitrary diff'' mode
+            return 'Diff from %s@%s to %s@%s' \
+                   % (diff.old_path or '/', diff.old_rev or 'latest',
+                      diff.new_path or '/', diff.new_rev or 'latest')
+
+
+
+class DiffModule(AbstractDiffModule):
+
+    implements(IWikiSyntaxProvider)
+
+    # (reimplemented) IRequestHandler methods
+
+    def match_request(self, req):
+        match = re.match(r'/diff(?:(/.*)|$)', req.path_info)
+        if match:
+            if match.group(1):
+                req.args['path'] = match.group(1)
+            return 1
+
+    # IWikiSyntaxProvider methods
+    
+    def get_wiki_syntax(self):
+        return []
+
+    def get_link_resolvers(self):
+        yield ('diff', self._format_link)
+
+    def _format_link(self, formatter, ns, params, label):
+        def pathrev(path):
+            if '@' in path:
+                return path.split('@', 1)
+            else:
+                return (path, None)
+        if '//' in params:
+            p1, p2 = params.split('//', 1)
+            old, new = pathrev(p1), pathrev(p2)
+            diff = DiffArgs(old_path=old[0], old_rev=old[1],
+                            new_path=new[0], new_rev=new[1])
+        else: 
+            old_path, old_rev = pathrev(params)
+            new_rev = None
+            if old_rev and ':' in old_rev:
+                old_rev, new_rev = old_rev.split(':', 1)
+            diff = DiffArgs(old_path=old_path, old_rev=old_rev,
+                            new_path=old_path, new_rev=new_rev)
+        title = self.title_for_diff(diff)
+        href = formatter.href.diff(diff.new_path, new=diff.new_rev,
+                                   old_path=diff.old_path, old=diff.old_rev)
+        return '<a class="changeset" title="%s" href="%s">%s</a>' \
+               % (title, href, label)
+
+
+class AnyDiffModule(Component):
+
+    implements(IRequestHandler)
+
+    # IRequestHandler methods
+
+    def match_request(self, req):
+        return re.match(r'/anydiff$', req.path_info)
+
+    def process_request(self, req):
+        # -- retrieve arguments
+        new_path = req.args.get('new_path')
+        new_rev = req.args.get('new_rev')
+        old_path = req.args.get('old_path')
+        old_rev = req.args.get('old_rev')
+
+        # -- normalize 
+        repos = self.env.get_repository(req.authname)
+        new_path = repos.normalize_path(new_path)
+        new_rev = repos.normalize_rev(new_rev)
+        old_path = repos.normalize_path(old_path)
+        old_rev = repos.normalize_rev(old_rev)
+
+        authzperm = SubversionAuthorizer(self.env, req.authname)
+        authzperm.assert_permission_for_changeset(new_rev)
+        authzperm.assert_permission_for_changeset(old_rev)
+        
+        # -- prepare rendering
+        req.hdf['anydiff'] = {
+            'new_path': new_path,
+            'new_rev': new_rev,
+            'old_path': old_path,
+            'old_rev': old_rev,
+            'diff_href': self.env.href.diff(),
+            }
+
+        return 'anydiff.cs', None
diff -ruN -x .svn trac-0.9b2/trac/versioncontrol/web_ui/__init__.py intertrac-branch/trac/versioncontrol/web_ui/__init__.py
--- trac-0.9b2/trac/versioncontrol/web_ui/__init__.py	2005-09-25 16:47:58.000000000 +0200
+++ intertrac-branch/trac/versioncontrol/web_ui/__init__.py	2005-09-23 09:51:56.000000000 +0200
@@ -1,3 +1,4 @@
 from trac.versioncontrol.web_ui.browser import *
 from trac.versioncontrol.web_ui.changeset import *
 from trac.versioncontrol.web_ui.log import *
+from trac.versioncontrol.web_ui.diff import *
diff -ruN -x .svn trac-0.9b2/trac/versioncontrol/web_ui/log.py intertrac-branch/trac/versioncontrol/web_ui/log.py
--- trac-0.9b2/trac/versioncontrol/web_ui/log.py	2005-09-25 16:47:58.000000000 +0200
+++ intertrac-branch/trac/versioncontrol/web_ui/log.py	2005-09-28 14:57:27.000000000 +0200
@@ -67,6 +67,20 @@
         stop_rev = req.args.get('stop_rev')
         verbose = req.args.get('verbose')
         limit = LOG_LIMIT
+        old = req.args.get('old')
+        new = req.args.get('new')
+
+        repos = self.env.get_repository(req.authname)
+        normpath = repos.normalize_path(path)
+        rev = str(repos.normalize_rev(rev))
+
+        if old and new:
+            osep = util.unescape(old).rindex('#')
+            nsep = util.unescape(new).rindex('#')
+            old_path, old_rev = old[:osep], old[osep+1:]
+            new_path, new_rev = new[:nsep], new[nsep+1:]
+            req.redirect(self.env.href.diff(new_path, new=new_rev,
+                                            old_path=old_path, old=old_rev))
 
         req.hdf['title'] = path + ' (log)'
         req.hdf['log'] = {
@@ -83,9 +97,6 @@
         if path_links:
             add_link(req, 'up', path_links[-1]['href'], 'Parent directory')
 
-        repos = self.env.get_repository(req.authname)
-        normpath = repos.normalize_path(path)
-        rev = str(repos.normalize_rev(rev))
 
         # ''Node'' history uses `get_node()`,
         # ''Path'' history uses `get_path_history()`
diff -ruN -x .svn trac-0.9b2/trac/web/standalone.py intertrac-branch/trac/web/standalone.py
--- trac-0.9b2/trac/web/standalone.py	2005-09-25 16:48:00.000000000 +0200
+++ intertrac-branch/trac/web/standalone.py	2005-08-31 17:47:31.000000000 +0200
@@ -139,19 +139,24 @@
         self.auths = auths
 
         self.projects = {}
+        siblings = {}
         for env_path in env_paths:
             # Remove trailing slashes
             while env_path and not os.path.split(env_path)[1]:
                 env_path = os.path.split(env_path)[0]
             project = os.path.split(env_path)[1]
             self.projects[project] = env_path
+            env = get_environment(None, self.get_env_opts(project))
+            siblings[project] = env
+        for p in siblings.values():
+            p.siblings = siblings
 
     def get_env_opts(self, project=None):
         if self.env_parent_dir:
             opts = self.env_parent_dir.items()
         else:
             opts = [('TRAC_ENV', self.projects[project])]
-        return dict(opts + os.environ.items())
+        return dict(os.environ.items() + opts) # TODO: backport
 
     def send_project_index(self, req):
         if self.env_parent_dir:
diff -ruN -x .svn trac-0.9b2/trac/wiki/api.py intertrac-branch/trac/wiki/api.py
--- trac-0.9b2/trac/wiki/api.py	2005-09-25 16:47:59.000000000 +0200
+++ intertrac-branch/trac/wiki/api.py	2005-09-23 09:51:57.000000000 +0200
@@ -23,6 +23,7 @@
     import dummy_threading as threading
 import time
 import urllib
+import re
 
 from trac.core import *
 from trac.util import to_utf8, TRUE
@@ -65,6 +66,18 @@
     def get_link_resolvers():
         """Return an iterable over (namespace, formatter) tuples."""
  
+class IWikiPageNameSyntaxProvider(Interface):
+ 
+    def get_wiki_page_names_syntax():
+        """
+        Return an iterable that provides a regular expression for
+        matching wiki page names (see WikiPageNames)
+
+        Be careful to only allow __one__ implementation
+        (others should be listed in the ![disabled_components]
+        section of the TracIni)
+        """
+ 
 
 class WikiSystem(Component):
     """Represents the wiki system."""
@@ -74,6 +87,7 @@
     change_listeners = ExtensionPoint(IWikiChangeListener)
     macro_providers = ExtensionPoint(IWikiMacroProvider)
     syntax_providers = ExtensionPoint(IWikiSyntaxProvider)
+    wikipagenames_providers = ExtensionPoint(IWikiPageNameSyntaxProvider)
 
     INDEX_UPDATE_INTERVAL = 5 # seconds
 
@@ -81,6 +95,10 @@
         self._index = None
         self._last_index_update = 0
         self._index_lock = threading.RLock()
+        self._compiled_rules = None
+        self._link_resolvers = None
+        self._helper_patterns = None
+        self._external_handlers = None
 
     def _update_index(self):
         self._index_lock.acquire()
@@ -116,6 +134,52 @@
         self._update_index()
         return self._index.has_key(pagename)
 
+    def _get_rules(self):
+        self._prepare_rules()
+        return self._compiled_rules
+    rules = property(_get_rules)
+
+    def _get_helper_patterns(self):
+        self._prepare_rules()
+        return self._helper_patterns
+    helper_patterns = property(_get_helper_patterns)
+
+    def _get_external_handlers(self):
+        self._prepare_rules()
+        return self._external_handlers
+    external_handlers = property(_get_external_handlers)
+    
+    def _prepare_rules(self):
+        from trac.wiki.formatter import Formatter
+        if not self._compiled_rules:
+            helpers = []
+            handlers = {}
+            syntax = Formatter._pre_rules[:]
+            i = 0
+            for resolver in self.syntax_providers:
+                for regexp, handler in resolver.get_wiki_syntax():
+                    handlers['i'+str(i)] = handler
+                    syntax.append('(?P<i%d>%s)' % (i, regexp))
+                    i += 1
+            syntax += Formatter._post_rules[:]
+            helper_re = re.compile(r'\?P<([a-z\d_]+)>')
+            for rule in syntax:
+                helpers += helper_re.findall(rule)[1:]
+            rules = re.compile('(?:' + '|'.join(syntax) + ')')
+            self._external_handlers = handlers
+            self._helper_patterns = helpers
+            self._compiled_rules = rules
+
+    def _get_link_resolvers(self):
+        if not self._link_resolvers:
+            resolvers = {}
+            for resolver in self.syntax_providers:
+                for namespace, handler in resolver.get_link_resolvers():
+                    resolvers[namespace] = handler
+            self._link_resolvers = resolvers
+        return self._link_resolvers
+    link_resolvers = property(_get_link_resolvers)
+
     # IWikiChangeListener methods
 
     def wiki_page_added(self, page):
@@ -136,10 +200,23 @@
     def get_wiki_syntax(self):
         ignore_missing = self.config.get('wiki', 'ignore_missing_pages')
         ignore_missing = ignore_missing in TRUE
-        yield (r"!?(?<!/)\b[A-Z][a-z]+(?:[A-Z][a-z]*[a-z/])+"
-                "(?:#[A-Za-z0-9]+)?(?=\Z|\s|[.,;:!?\)}\]])",
-               lambda x, y, z: self._format_link(x, 'wiki', y, y,
-                                                 ignore_missing))
+        providers = []
+        for p in self.wikipagenames_providers:
+            if not providers:
+                yield (p.get_wiki_page_names_syntax(),
+                       lambda x, y, z: self._format_link(x, 'wiki', y, y,
+                                                         ignore_missing))
+            pc = p.__class__
+            providers.append('# %s\n%s.%s = yes' % (pc.__doc__.split('\n')[0],
+                                                    pc.__module__, pc.__name__)
+                                                    )
+        if len(providers) > 1:
+            self.log.warning('More than one IWikiPageNameSyntaxProvider '
+                             'implementation available:\n'
+                             'You should set one of the following to "no" '
+                             'in your trac.ini:\n\n'
+                             '[disabled_components]\n' +
+                             '\n'.join(providers) +'\n')
 
     def get_link_resolvers(self):
         yield ('wiki', self._format_fancy_link)
@@ -163,3 +240,59 @@
         else:
             return '<a class="wiki" href="%s">%s</a>' \
                    % (formatter.href.wiki(page) + anchor, label)
+
+
+WIKI_START = r"!?(?<!/)\b"
+WIKI_TARGET = r"(?:#[A-Za-z0-9]+)?"
+WIKI_END = r"(?=\Z|\s|[.,;:!?\)}\]])"
+WIKI_INTERWIKI = r"(?!:\S)"
+
+class StandardWikiPageNames(Component):
+    """Standard Trac WikiPageNames rule"""
+
+    implements(IWikiPageNameSyntaxProvider)
+
+    def get_wiki_page_names_syntax(self):
+        return (WIKI_START +                # where to start
+                r"[A-Z][a-z]+"              # initial WikiPageNames word
+                r"(?:[A-Z][a-z]*[a-z/])+" + # additional WikiPageNames word
+                WIKI_TARGET +               # optional trailing section link
+                WIKI_END +                  # where to end
+                WIKI_INTERWIKI)             # InterWiki support
+    
+class FlexibleWikiPageNames(Component):
+    """Standard Trac WikiPageNames rule, plus digits
+    and consecutive upper-case characters allowed.
+
+    More precisely, WikiPageNames are:
+     * either 2 or more starting upper case letter or digits,
+       followed by lower case letters
+     * either 1 or more starting upper case letter or digits,
+       followed by lower case letters, repeated at least 2 times
+       (with optionally '/' between repetitions)
+    """
+
+    implements(IWikiPageNameSyntaxProvider)
+
+    def get_wiki_page_names_syntax(self):
+        return (WIKI_START +
+                r"(?:[A-Z\d]{2,}[a-z]+"                    # 1st way
+                r"|[A-Z\d]+[a-z]+(?:/?[A-Z\d]+[a-z]*)+)" + # 2nd way
+                WIKI_TARGET + WIKI_END + WIKI_INTERWIKI)
+
+class SubWikiPageNames(Component):
+    """SubWiki-like rules for WikiPageNames.
+    
+    See http://www.webdav.org/wiki/projects/TextFormattingRules
+
+    Note that '/' in this style of WikiPageNames are not supported.
+    """
+
+    implements(IWikiPageNameSyntaxProvider)
+
+    def get_wiki_page_names_syntax(self):
+        return (WIKI_START +
+                r"(?:[A-Z][A-Z]+[a-z\d]+[A-Z]*"    # 1st and 3rd way
+                r"|[A-Z][a-z]+(?:[A-Z][a-z]+)+)" + # 2nd way
+                WIKI_TARGET + WIKI_END + WIKI_INTERWIKI)
+
diff -ruN -x .svn trac-0.9b2/trac/wiki/formatter.py intertrac-branch/trac/wiki/formatter.py
--- trac-0.9b2/trac/wiki/formatter.py	2005-09-25 16:47:59.000000000 +0200
+++ intertrac-branch/trac/wiki/formatter.py	2005-09-23 09:51:57.000000000 +0200
@@ -19,7 +19,6 @@
 from __future__ import generators
 import re
 import os
-import string
 import urllib
 
 try:
@@ -28,10 +27,12 @@
     from StringIO import StringIO
 
 from trac import util
+from trac.core import *
 from trac.mimeview import *
-from trac.wiki.api import WikiSystem
+from trac.wiki.api import WikiSystem, IWikiChangeListener, IWikiMacroProvider
 
-__all__ = ['wiki_to_html', 'wiki_to_oneliner', 'wiki_to_outline']
+__all__ = ['wiki_to_html', 'wiki_to_oneliner', 'wiki_to_outline',
+           'INTERTRAC_SCHEME' ]
 
 #
 # Customization of the Wiki syntax  ***use with care***
@@ -45,7 +46,8 @@
 SUPERSCRIPT_TOKEN = r"\^"
 INLINE_TOKEN = "`"
 
-LINK_SCHEME = r"[\w.+-]+" # as per RFC 2396
+LINK_SCHEME = r"[\w.+-]+?" # as per RFC 2396
+INTERTRAC_SCHEME = r"[a-zA-Z.+-]+?" # no digits (support for shorthand links)
 
 def system_message(msg, text):
     return """<div class="system-message">
@@ -136,7 +138,6 @@
 class Formatter(object):
     flavor = 'default'
 
-    _link_resolvers = None
     # Rules provided by IWikiSyntaxProviders are inserted between pre_rules and post_rules
     _pre_rules = [r"(?P<bolditalic>%s)" % BOLDITALIC_TOKEN,
                   r"(?P<bold>%s)" % BOLD_TOKEN,
@@ -165,9 +166,6 @@
                    r"(?P<last_table_cell>\|\|\s*$)",
                    r"(?P<table_cell>\|\|)"]
 
-    _compiled_rules = None
-    _helper_patterns = None
-    _external_handlers = None
     _processor_re = re.compile('#\!([\w+-][\w+-/]*)')
     _anchor_re = re.compile('[^\w\d\.-:]+', re.UNICODE)
     
@@ -194,47 +192,22 @@
     db = property(fget=_get_db)
 
     def _get_rules(self):
-        if not Formatter._compiled_rules:
-            helpers = []
-            handlers = {}
-            syntax = Formatter._pre_rules[:]
-            wiki = WikiSystem(self.env)
-            i = 0
-            for resolver in wiki.syntax_providers:
-                for regexp, handler in resolver.get_wiki_syntax():
-                    handlers['i'+str(i)] = handler
-                    syntax.append('(?P<i%d>%s)' % (i, regexp))
-                    i += 1
-            syntax += Formatter._post_rules[:]
-            helper_re = re.compile(r'\?P<([a-z\d]+)>')
-            for rule in syntax:
-                helpers += helper_re.findall(rule)[1:]
-            rules = re.compile('(?:' + string.join(syntax, '|') + ')')
-            Formatter._external_handlers = handlers
-            Formatter._helper_patterns = helpers
-            Formatter._compiled_rules = rules
-        return Formatter._compiled_rules
+        return WikiSystem(self.env).rules
     rules = property(_get_rules)
 
     def _get_link_resolvers(self):
-        if not self._link_resolvers:
-            resolvers = {}
-            wiki = WikiSystem(self.env)
-            for resolver in wiki.syntax_providers:
-                for namespace, handler in resolver.get_link_resolvers():
-                    resolvers[namespace] = handler
-            self._link_resolvers = resolvers
-        return self._link_resolvers
+        return WikiSystem(self.env).link_resolvers
     link_resolvers = property(_get_link_resolvers)
 
     def replace(self, fullmatch):
+        wiki = WikiSystem(self.env)        
         for itype, match in fullmatch.groupdict().items():
-            if match and not itype in Formatter._helper_patterns:
+            if match and not itype in wiki.helper_patterns:
                 # Check for preceding escape character '!'
                 if match[0] == '!':
                     return match[1:]
-                if itype in self._external_handlers:
-                    return self._external_handlers[itype](self, match, fullmatch)
+                if itype in wiki.external_handlers:
+                    return wiki.external_handlers[itype](self, match, fullmatch)
                 else:
                     return getattr(self, '_' + itype + '_formatter')(match, fullmatch)
 
@@ -307,12 +280,66 @@
             return self._make_link(ns, target, match, label)
 
     def _make_link(self, ns, target, match, label):
+        # check first for an alias defined in trac.ini
+        ns = self.env.config.get('intertrac', ns.upper(), ns)
         if ns in self.link_resolvers:
-            return self._link_resolvers[ns](self, ns, target, label)
+            return self.link_resolvers[ns](self, ns, target, label)
         elif target.startswith('//') or ns == "mailto":
             return self._make_ext_link(ns+':'+target, label)
         else:
-            return match
+            intertrac = self._make_intertrac_link(ns, target, label)
+            if intertrac:
+                return intertrac
+            else:
+                interwiki = self._make_interwiki_link(ns, target, label)
+                if interwiki:
+                    return interwiki
+                else:
+                    return match
+
+    def _make_intertrac_link(self, ns, target, label):
+        if self.env.siblings.has_key(ns):
+            sibling = self.env.siblings[ns]
+            if not hasattr(sibling, 'href'):
+                from trac.web.href import Href
+                def xchg_base(base):
+                    return '/'.join(base.split('/')[:-1] + [ns])
+                sibling.href = Href(xchg_base(self.env.href.base))
+                sibling.abs_href = Href(xchg_base(self.env.abs_href.base))
+            ref = wiki_to_oneliner(target, sibling)
+            return ref.replace('>%s' % target, '>%s' % label)
+        url = self.env.config.get('intertrac', ns.upper()+'.url')
+        if url:
+            name = self.env.config.get('intertrac', ns.upper()+'.title',
+                                       'Trac project %s' % ns)
+            sep = target.find(':')
+            if sep != -1:
+                url = '%s/%s/%s' % (url, target[:sep], target[sep+1:])
+            else: 
+                url = '%s/search?q=%s' % (url, urllib.quote_plus(target))
+            return self._make_ext_link(url, label, '%s in %s' % (target, name))
+        else:
+            return None
+
+    def shorthand_intertrac_helper(self, ns, target, label, fullmatch):
+        if fullmatch: # short form
+            it_grp = fullmatch.group('it_%s' % ns)
+            if it_grp:
+                alias = it_grp.strip()
+                intertrac = self.env.config.get('intertrac', alias.upper(),
+                                                alias)
+                target = '%s:%s' % (ns, target[len(it_grp):])
+                it = self._make_intertrac_link(intertrac, target, label)
+                return it or label
+        return None
+
+    def _make_interwiki_link(self, ns, target, label):
+        interwiki = InterWikiMap(self.env)
+        if interwiki.has_key(ns):
+            url, title = interwiki.url(ns, target)
+            return self._make_ext_link(url, label, '%s in %s' % (target, title))
+        else:
+            return None
 
     def _make_ext_link(self, url, text, title=''):
         title_attr = title and ' title="%s"' % title or ''
@@ -342,7 +369,8 @@
         if match[0] == '!':
             return match[1:]
         else:
-            return self.simple_tag_handler('<span class="underline">', '</span>')
+            return self.simple_tag_handler('<span class="underline">',
+                                           '</span>')
 
     def _strike_formatter(self, match, fullmatch):
         if match[0] == '!':
@@ -730,3 +758,98 @@
     OutlineFormatter(env, absurls, db).format(wikitext, out, max_depth,
                                               min_depth)
     return out.getvalue()
+
+
+# -- InterWiki support
+
+class InterWikiMap(Component):
+
+    implements(IWikiChangeListener, IWikiMacroProvider)
+
+    _page_name = 'InterMapTxt'
+    _interwiki_re = re.compile(r"(\w+)[ \t]+([^ \t]+)(?:[ \t]+#(.*))?",
+                               re.UNICODE)
+    _argspec_re = re.compile(r"\$\d")
+
+    def __init__(self):
+        self._interwiki_map = None
+        # This dictionary maps upper-cased namespaces
+        # to (namespace, prefix, title) values
+
+    def has_key(self, ns):
+        if not self._interwiki_map:
+            self._update()
+        return self._interwiki_map.has_key(ns.upper())
+
+    def url(self, ns, target):
+        ns, url, title = self._interwiki_map[ns.upper()]
+        args = target.split(':')
+        def setarg(match):
+            num = int(match.group()[1:])
+            return 0 < num <= len(args) and args[num-1] or ''
+        url_with_args = re.sub(InterWikiMap._argspec_re, setarg, url)
+        if url_with_args == url: 
+            return url + target, title
+        else:
+            return url_with_args, title
+
+    # IWikiChangeListener methods
+
+    def wiki_page_added(self, page):
+        if page.name == InterWikiMap._page_name:
+            self._update()
+
+    def wiki_page_changed(self, page, version, t, comment, author, ipnr):
+        if page.name == InterWikiMap._page_name:
+            self._update()
+
+    def wiki_page_deleted(self, page):
+        if page.name == InterWikiMap._page_name:
+            self._interwiki_map.clear()
+
+    def _update(self):
+        from trac.wiki.model import WikiPage
+        self._interwiki_map = {}
+        content = WikiPage(self.env, InterWikiMap._page_name).text
+        in_map = False
+        for line in content.split('\n'):
+            if in_map:
+                if line.startswith('----'):
+                    in_map = False
+                else:
+                    m = re.match(InterWikiMap._interwiki_re, line)
+                    if m:
+                        prefix, url, title = m.groups()
+                        url = url.strip()
+                        title = title and title.strip() or prefix
+                        self._interwiki_map[prefix.upper()] = (prefix, url,
+                                                               title)
+            elif line.startswith('----'):
+                in_map = True
+
+    # IWikiMacroProvider
+
+    def get_macros(self):
+        yield 'InterWiki'
+
+    def get_macro_description(self, name): 
+        return "Provide a description list for the known InterWiki prefixes."
+
+    def render_macro(self, req, name, content):
+        if not self._interwiki_map:
+            self._update()
+        keys = self._interwiki_map.keys()
+        keys.sort()
+        buf = StringIO()
+        buf.write('<table><tr><th>Prefix</th><td>Site</td></tr>\n')
+        for k in keys:
+            prefix, url, title = self._interwiki_map[k]
+            shortened_url = url and url[:-1]
+            description = title == prefix and shortened_url or title
+            buf.write('<tr>\n' +
+                      ('<td><a href="%sRecentChanges">%s</a></td>'
+                       '<td><a href="%s">%s</a></td>\n') \
+                      % (url, prefix, shortened_url, description) +
+                      '</tr>\n')
+        buf.write('</table>\n')
+        return buf.getvalue()
diff -ruN -x .svn trac-0.9b2/trac/wiki/tests/formatter.py intertrac-branch/trac/wiki/tests/formatter.py
--- trac-0.9b2/trac/wiki/tests/formatter.py	2005-09-25 16:47:59.000000000 +0200
+++ intertrac-branch/trac/wiki/tests/formatter.py	2005-09-23 09:51:57.000000000 +0200
@@ -53,8 +53,13 @@
                 self.config = Configuration(None)
                 self.href = Href('/')
                 self.abs_href = Href('http://www.example.com/')
-                self._wiki_pages = {}
                 self.path = ''
+                # -- intertrac support
+                self.siblings = {}
+                self.config.set('intertrac', 'trac.title', "Trac's Trac")
+                self.config.set('intertrac', 'trac.url',
+                                "http://projects.edgewall.com/trac")
+                self.config.set('intertrac', 't', 'trac')
             def component_activated(self, component):
                 component.env = self
                 component.config = self.config
diff -ruN -x .svn trac-0.9b2/trac/wiki/tests/wiki-tests.txt intertrac-branch/trac/wiki/tests/wiki-tests.txt
--- trac-0.9b2/trac/wiki/tests/wiki-tests.txt	2005-09-25 16:47:59.000000000 +0200
+++ intertrac-branch/trac/wiki/tests/wiki-tests.txt	2005-09-28 14:57:27.000000000 +0200
@@ -36,10 +36,16 @@
 <a class="ext-link" href="http://www.edgewall.com/"><span class="icon"></span>http://www.edgewall.com/</a>
 </p>
 ==============================
-#1, [1], r1, {1}
+#1, {1}
+[1], r1
+[1/README.txt]
+#12, [12], r12, {12}
 ------------------------------
 <p>
-<a class="missing ticket" href="/ticket/1" rel="nofollow">#1</a>, <a class="missing changeset" href="/changeset/1" rel="nofollow">[1]</a>, <a class="missing changeset" href="/changeset/1" rel="nofollow">r1</a>, <a class="report" href="/report/1">{1}</a>
+<a class="missing ticket" href="/ticket/1" rel="nofollow">#1</a>, <a class="report" href="/report/1">{1}</a>
+<a class="missing changeset" href="/changeset/1" rel="nofollow">[1]</a>, <a class="missing changeset" href="/changeset/1" rel="nofollow">r1</a>
+<a class="missing changeset" href="/changeset/1/README.txt" rel="nofollow">[1/README.txt]</a>
+<a class="missing ticket" href="/ticket/12" rel="nofollow">#12</a>, <a class="missing changeset" href="/changeset/12" rel="nofollow">[12]</a>, <a class="missing changeset" href="/changeset/12" rel="nofollow">r12</a>, <a class="report" href="/report/12">{12}</a>
 </p>
 ==============================
 !#1, ![1], !r1, !{1}
@@ -60,12 +66,14 @@
 [1:2], r1:2, [12:23], r12:23
 </p>
 ==============================
-ticket:1, changeset:1, report:1, source:foo/bar
+ticket:1, report:1, source:foo/bar
+changeset:1, changeset:1/README.txt
 
 Issue [ticket:1], CS[changeset:1], Listing [report:1], File [source:foo/bar]
 ------------------------------
 <p>
-<a class="missing ticket" href="/ticket/1" rel="nofollow">ticket:1</a>, <a class="missing changeset" href="/changeset/1" rel="nofollow">changeset:1</a>, <a class="report" href="/report/1">report:1</a>, <a class="source" href="/browser/foo/bar">source:foo/bar</a>
+<a class="missing ticket" href="/ticket/1" rel="nofollow">ticket:1</a>, <a class="report" href="/report/1">report:1</a>, <a class="source" href="/browser/foo/bar">source:foo/bar</a>
+<a class="missing changeset" href="/changeset/1" rel="nofollow">changeset:1</a>, <a class="missing changeset" href="/changeset/1/README.txt" rel="nofollow">changeset:1/README.txt</a>
 </p>
 <p>
 Issue <a class="missing ticket" href="/ticket/1" rel="nofollow">1</a>, CS<a class="missing changeset" href="/changeset/1" rel="nofollow">1</a>, Listing <a class="report" href="/report/1">1</a>, File <a class="source" href="/browser/foo/bar">foo/bar</a>
@@ -79,16 +87,28 @@
 <a class="source" href="/browser/foo/bar">source foo/bar</a>, <a class="ext-link" href="http://www.edgewall.com/"><span class="icon"></span>edgewall</a>
 </p>
 ==============================
+diff:trunk//branch
+diff:trunk@12//branch@23
+diff:trunk@12:23
+diff:@12:23
+------------------------------
+<p>
+<a class="changeset" title="Diff from trunk@latest to branch@latest" href="/diff/branch?old_path=trunk">diff:trunk//branch</a>
+<a class="changeset" title="Diff from trunk@12 to branch@23" href="/diff/branch?new=23&old=12&old_path=trunk">diff:trunk@12//branch@23</a>
+<a class="changeset" title="Diff r12:23 for trunk" href="/diff/trunk?new=23&old=12&old_path=trunk">diff:trunk@12:23</a>
+<a class="changeset" title="Diff r12:23 for /" href="/diff/?new=23&old=12&old_path=">diff:@12:23</a>
+</p>
+==============================
 CamelCase AlabamA ABc AlaBamA FooBar
 ------------------------------
 <p>
 <a class="missing wiki" href="/wiki/CamelCase" rel="nofollow">CamelCase?</a> AlabamA ABc AlaBamA <a class="missing wiki" href="/wiki/FooBar" rel="nofollow">FooBar?</a>
 </p>
 ==============================
-CamelCase,CamelCase.CamelCase:CamelCase
+CamelCase,CamelCase.CamelCase: CamelCase
 ------------------------------
 <p>
-<a class="missing wiki" href="/wiki/CamelCase" rel="nofollow">CamelCase?</a>,<a class="missing wiki" href="/wiki/CamelCase" rel="nofollow">CamelCase?</a>.CamelCase:CamelCase
+<a class="missing wiki" href="/wiki/CamelCase" rel="nofollow">CamelCase?</a>,<a class="missing wiki" href="/wiki/CamelCase" rel="nofollow">CamelCase?</a>.<a class="missing wiki" href="/wiki/CamelCase" rel="nofollow">CamelCase?</a>: <a class="missing wiki" href="/wiki/CamelCase" rel="nofollow">CamelCase?</a>
 </p>
 ==============================
 !CamelCase
@@ -605,3 +625,79 @@
 <tr><td> a 
 </td></tr><tr><td> b 
 </td></tr></table>
+==============================
+t:wiki:InterTrac
+trac:wiki:InterTrac
+[t:wiki:InterTrac intertrac]
+[trac:wiki:InterTrac intertrac]
+------------------------------
+<p>
+<a class="ext-link" href="http://projects.edgewall.com/trac/wiki/InterTrac" title="wiki:InterTrac in Trac's Trac"><span class="icon"></span>t:wiki:InterTrac</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/wiki/InterTrac" title="wiki:InterTrac in Trac's Trac"><span class="icon"></span>trac:wiki:InterTrac</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/wiki/InterTrac" title="wiki:InterTrac in Trac's Trac"><span class="icon"></span>intertrac</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/wiki/InterTrac" title="wiki:InterTrac in Trac's Trac"><span class="icon"></span>intertrac</a>
+</p>
+==============================
+trac:ticket:2041
+[trac:ticket:2041 Trac #2041]
+#T2041
+#trac2041
+------------------------------
+<p>
+<a class="ext-link" href="http://projects.edgewall.com/trac/ticket/2041" title="ticket:2041 in Trac's Trac"><span class="icon"></span>trac:ticket:2041</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/ticket/2041" title="ticket:2041 in Trac's Trac"><span class="icon"></span>Trac #2041</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/ticket/2041" title="ticket:2041 in Trac's Trac"><span class="icon"></span>#T2041</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/ticket/2041" title="ticket:2041 in Trac's Trac"><span class="icon"></span>#trac2041</a>
+</p>
+==============================
+trac:changeset:2081
+[trac:changeset:2081 Trac r2081]
+[T2081]
+[trac2081]
+[trac 2081]
+------------------------------
+<p>
+<a class="ext-link" href="http://projects.edgewall.com/trac/changeset/2081" title="changeset:2081 in Trac's Trac"><span class="icon"></span>trac:changeset:2081</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/changeset/2081" title="changeset:2081 in Trac's Trac"><span class="icon"></span>Trac r2081</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/changeset/2081" title="changeset:2081 in Trac's Trac"><span class="icon"></span>[T2081]</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/changeset/2081" title="changeset:2081 in Trac's Trac"><span class="icon"></span>[trac2081]</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/changeset/2081" title="changeset:2081 in Trac's Trac"><span class="icon"></span>[trac 2081]</a>
+</p>
+==============================
+trac:report:1
+[trac:report:1 Trac r1]
+{T1}
+{trac1}
+{trac 1}
+------------------------------
+<p>
+<a class="ext-link" href="http://projects.edgewall.com/trac/report/1" title="report:1 in Trac's Trac"><span class="icon"></span>trac:report:1</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/report/1" title="report:1 in Trac's Trac"><span class="icon"></span>Trac r1</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/report/1" title="report:1 in Trac's Trac"><span class="icon"></span>{T1}</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/report/1" title="report:1 in Trac's Trac"><span class="icon"></span>{trac1}</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/report/1" title="report:1 in Trac's Trac"><span class="icon"></span>{trac 1}</a>
+</p>
+==============================
+t:InterTrac
+trac:InterTrac
+[t:InterTrac intertrac]
+[trac:InterTrac intertrac]
+T:r2081
+trac:[2081]
+T:#2041
+trac:#2041
+T:{1}
+trac:{1}
+------------------------------
+<p>
+<a class="ext-link" href="http://projects.edgewall.com/trac/search?q=InterTrac" title="InterTrac in Trac's Trac"><span class="icon"></span>t:InterTrac</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/search?q=InterTrac" title="InterTrac in Trac's Trac"><span class="icon"></span>trac:InterTrac</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/search?q=InterTrac" title="InterTrac in Trac's Trac"><span class="icon"></span>intertrac</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/search?q=InterTrac" title="InterTrac in Trac's Trac"><span class="icon"></span>intertrac</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/search?q=r2081" title="r2081 in Trac's Trac"><span class="icon"></span>T:r2081</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/search?q=%5B2081%5D" title="[2081] in Trac's Trac"><span class="icon"></span>trac:[2081]</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/search?q=%232041" title="#2041 in Trac's Trac"><span class="icon"></span>T:#2041</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/search?q=%232041" title="#2041 in Trac's Trac"><span class="icon"></span>trac:#2041</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/search?q=%7B1%7D" title="{1} in Trac's Trac"><span class="icon"></span>T:{1}</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/search?q=%7B1%7D" title="{1} in Trac's Trac"><span class="icon"></span>trac:{1}</a>
+</p>
diff -ruN -x .svn trac-0.9b2/wiki-default/InterMapTxt intertrac-branch/wiki-default/InterMapTxt
--- trac-0.9b2/wiki-default/InterMapTxt	1970-01-01 01:00:00.000000000 +0100
+++ intertrac-branch/wiki-default/InterMapTxt	2005-09-28 14:57:26.000000000 +0200
@@ -0,0 +1,63 @@
+= InterMapTxt =
+== This is the place for defining InterWiki prefixes ==
+
+This page was modelled after the MeatBall:InterMapTxt page.
+In addition, an optional comment is allowed after the mapping.
+
+
+This page is interpreted in a special way by Trac, in order to support
+!InterWiki links in a flexible and dynamic way.
+
+The code block after the first line separator in this page
+will be interpreted as a list of !InterWiki specifications:
+{{{
+prefix <space> URL [<space> # comment]
+}}}
+
+By using `$1`, `$2`, etc. within the URL, it is possible to create 
+InterWiki links which support multiple arguments, e.g. Trac:ticket:40.
+The URL itself can be optionally followed by a comment, 
+which will subsequently be used for decorating the links 
+using that prefix.
+
+New !InterWiki links can be created by adding to that list, in real time.
+Note however that ''deletions'' are also taken into account immediately,
+so it may be better to use comments for disabling prefixes.
+
+Also note that !InterWiki prefixes are case insensitive.
+
+
+== List of Active Prefixes ==
+
+[[InterWiki]]
+
+----
+
+== Prefix Definitions ==
+
+{{{
+PEP     http://www.python.org/peps/pep-$1.html                                       # Python Enhancement Proposal 
+TracML  http://thread.gmane.org/gmane.comp.version-control.subversion.trac.general/  # Trac Mailing List
+
+#
+# A arbitrary pick of InterWiki prefixes...
+#
+Acronym          http://www.acronymfinder.com/af-query.asp?String=exact&Acronym=
+C2find           http://c2.com/cgi/wiki?FindPage&value=
+Cache            http://www.google.com/search?q=cache:
+CPAN             http://search.cpan.org/perldoc?
+DebianBug        http://bugs.debian.org/
+DebianPackage    http://packages.debian.org/
+Dictionary       http://www.dict.org/bin/Dict?Database=*&Form=Dict1&Strategy=*&Query=
+Google           http://www.google.com/search?q=
+GoogleGroups     http://groups.google.com/groups?q=
+JargonFile       http://downlode.org/perl/jargon-redirect.cgi?term=
+MeatBall         http://www.usemod.com/cgi-bin/mb.pl?
+MetaWiki         http://sunir.org/apps/meta.pl?
+MetaWikiPedia    http://meta.wikipedia.org/wiki/
+MoinMoin         http://moinmoin.wikiwikiweb.de/
+WhoIs            http://www.whois.sc/
+Why              http://clublet.com/c/c/why?
+Wiki             http://c2.com/cgi/wiki?
+WikiPedia        http://en.wikipedia.org/wiki/
+}}}
diff -ruN -x .svn trac-0.9b2/wiki-default/InterTrac intertrac-branch/wiki-default/InterTrac
--- trac-0.9b2/wiki-default/InterTrac	1970-01-01 01:00:00.000000000 +0100
+++ intertrac-branch/wiki-default/InterTrac	2005-09-28 14:57:26.000000000 +0200
@@ -0,0 +1,108 @@
+= InterTrac Extension for TracLinks =
+
+''This is a proposal for implementing #234''.
+''It's candidate for integration in the trunk, after the 0.9 release (see #2041)''
+
+== Definitions ==
+
+An InterTrac link is used for referring to a Trac object 
+(Wiki page, changeset, ticket, ...) located in another
+Trac environment.
+
+== Link Syntax ==
+
+{{{
+<target_environment>:<TracLinks>
+}}}
+
+The link is composed by the target environment name, 
+followed by a colon (e.g. `trac:`),
+followed by a regular TracLinks, of any flavor.
+
+That target environment name is either the real name of the 
+environment, or an alias for it. 
+The aliases are defined in `trac.ini` (see below).
+The prefix is case insensitive.
+
+For convenience, there's also an alternative short-hand form, 
+where one can use an alias as an immediate prefix 
+for the identifier of a ticket, changeset or report:
+(e.g. `#T234`, `[T1508]`, `[trac 1508]`, ...)
+
+== Examples ==
+
+Besides the other environments run by the same server process
+(called ''sibling'' environments), which are automatically detected,
+(''Note: currently only in `tracd`''),
+it is necessary to setup a configuration for the InterTrac facility:
+ * in order to refer to a remote Trac
+ * for defining environment aliases
+
+This is done quite simply in an `[intertrac]` section
+within the `trac.ini` file.
+
+Example configuration:
+{{{
+...
+[intertrac]
+## -- Example of setting up an alias:
+t = trac
+
+## -- Link to an external Trac:
+trac.title = Edgewall's Trac for Trac
+trac.url = http://projects.edgewall.com/trac
+
+#trac.svn = http://repos.edgewall.com/projects/trac 
+# Hint: .svn information could be used in the future to support svn:externals...
+}}}
+
+Now, given this configuration, one could create the following links:
+ * to the current InterTrac page:
+   * `trac:wiki:InterTrac` ->
+     [http://projects.edgewall.com/trac/wiki/InterTrac trac:wiki:InterTrac]
+   * `t:wiki:InterTrac` ->
+     [http://projects.edgewall.com/trac/wiki/InterTrac t:wiki:InterTrac]
+   * Keys are case insensitive: `T:wiki:InterTrac` -> 
+     [http://projects.edgewall.com/trac/wiki/InterTrac T:wiki:InterTrac]
+ * to the ticket #234:
+   * `trac:ticket:234` ->
+     [http://projects.edgewall.com/trac/ticket/234 trac:ticket:234]
+   * `trac:#234` ->
+     [http://projects.edgewall.com/trac/ticket/234 trac:#234]
+   * `#T234` ->
+     [http://projects.edgewall.com/trac/search?q=#234 #T234]
+ * to the changeset [1912]:
+   * `trac:changeset:1912` ->
+     [http://projects.edgewall.com/trac/changeset/1912 trac:changeset:1912]
+   * `trac:[1912]` ->
+     [http:"//projects.edgewall.com/trac/search?q=[1912]" "trac:[1912]"]
+   * `[T1912]` ->
+     [http://projects.edgewall.com/trac/changeset/1912 "[T1912]"]
+
+Anything not given as explicit links (intertrac_prefix:module:id)
+is interpreted by the remote Trac, relying on its quickjump
+facility.
+
+
+
+See also: TracLinks, InterWiki
+
+----
+== Implementation Notes ==
+
+Currently, the `[intertrac]` configuration has to be repeated 
+for each Trac environment, but there's work in progress concerning a 
+[ticket:1051 centralized trac.ini], which would help greatly here.
+
+This idea was first proposed as a patch for #234, and 
+has been implemented in the following branch: 
+source:branches/cboos-dev/intertrac-branch
+
+The general idea is that any `[a-zA-Z.+-]+:` prefix, 
+followed by anything which is not a space, has to be interpreted as:
+ 1. maybe an alias to something else; if yes, it is dereferenced
+    before going on
+ 1. then, it's maybe a link prefix given by an `IWikiSyntaxProvider` component 
+ 1. if not, it's maybe an environment name (!InterTrac link)
+ 1. if not, it's maybe an !InterWiki link
+ 1. if not, it is not a link
diff -ruN -x .svn trac-0.9b2/wiki-default/InterWiki intertrac-branch/wiki-default/InterWiki
--- trac-0.9b2/wiki-default/InterWiki	1970-01-01 01:00:00.000000000 +0100
+++ intertrac-branch/wiki-default/InterWiki	2005-09-28 14:57:26.000000000 +0200
@@ -0,0 +1,68 @@
+= Support for InterWiki links =
+
+''This is a proposal for implementing #40 and #1414''
+''It's candidate for integration in the trunk, after the 0.9 release (see #2041)''
+
+== Definition ==
+
+An InterWiki link can be used for referring to a Wiki page
+located in another Wiki system, and by extension, to any object
+located in any other Web application, provided a simple URL 
+mapping can be done.
+
+== Link Syntax ==
+
+{{{
+<target_wiki>(:<identifier>)+
+}}}
+
+The link is composed by the targeted Wiki (or system) name,
+followed by a column (e.g. {{{MeatBall:}}}),
+followed by a page specification in the target.
+Note that, as for InterTrac prefixes, InterWiki prefixes are case insensitive.
+
+The target Wiki URL is looked up in a the InterMapTxt wiki page, 
+modelled after
+[http://www.usemod.com/cgi-bin/mb.pl?InterMapTxt MeatBall:InterMapTxt].
+
+An addition to traditional InterWiki links, where the target
+is simply ''appended'' to the URL, 
+Trac supports parametric InterWiki URLs:
+identifiers `$1`, `$2`, ... in the URL
+will be replaced by corresponding arguments from a list
+made up from the page specification split by the ":" token.
+
+== Examples ==
+
+If the following is an excerpt of the InterMapTxt page:
+
+{{{
+= InterMapTxt =
+== This is the place for defining InterWiki prefixes ==
+
+Currently active prefixes: [[InterWiki]]
+
+This page is modelled after the MeatBall:InterMapTxt page.
+In addition, an optional c