Edgewall Software

Ticket #2041: trac-0.9b1-intertrac.patch

File trac-0.9b1-intertrac.patch, 117.8 KB (added by cboos, 6 years ago)

Adds InterTrac and InterWiki (r2241) functionality to Trac 0.9b1. Patch applies against the .tar.gz (see note below for the .zip). Note that this patch also contains the TracDiff features (see #2028).

  • htdocs/css/browser.css

    diff -urN -x .svn trac-0.9b1/htdocs/css/browser.css intertrac-branch/htdocs/css/browser.css
    old new  
    4545#dirlist td.name a, #dirlist td.rev a { border-bottom: none; display: block } 
    4646#dirlist td.change * { font-size: 9px } 
    4747 
     48/* Log */ 
     49tr.diff input {  
     50 padding: 0 1em 0 1em; 
     51 margin: 0;  
     52} 
     53 
     54div.buttons { 
     55 clear: left; 
     56} 
     57 
     58#anydiff { 
     59 margin: 0 0 1em; 
     60 float: left; 
     61} 
     62#anydiff form, #anydiff div, #anydiff h2 { 
     63 display: inline; 
     64} 
     65#anydiff input {  
     66 vertical-align: baseline; 
     67 margin: 0 -0.5em 0 1em; 
     68} 
     69 
     70 
    4871/* Styles for the revision log table 
    4972   (extends the styles for "table.listing") */ 
    5073#chglist { margin-top: 0 } 
  • htdocs/css/changeset.css

    diff -urN -x .svn trac-0.9b1/htdocs/css/changeset.css intertrac-branch/htdocs/css/changeset.css
    old new  
    2626 
    2727.diff ul.props { font-size: 90%; list-style: disc; margin: .5em 0 0; padding: 0 .5em 1em 2em } 
    2828.diff ul.props li { margin: 0; padding: 0 } 
     29 
     30 
     31#title dl { 
     32 display: inline; 
     33 font-size: 110% 
     34} 
     35#title dt {  
     36  font-size: 110%; 
     37  font-weight: bold; 
     38  display: inline;  
     39  margin-left: 3em; 
     40} 
     41#title dd {  
     42  display: inline; 
     43  margin-left: 0.4em; 
     44} 
  • templates/anydiff.cs

    diff -urN -x .svn trac-0.9b1/templates/anydiff.cs intertrac-branch/templates/anydiff.cs
    old new  
     1<?cs include "header.cs"?> 
     2 
     3<div id="ctxtnav" class="nav"> 
     4 <h2>Navigation</h2><?cs 
     5 with:links = chrome.links ?> 
     6  <ul> 
     7  </ul><?cs 
     8 /with ?> 
     9</div> 
     10 
     11<div id="content" class="changeset"> 
     12 <div id="title"> 
     13    <h1>Select Base and Target for Diff:</h1> 
     14 </div> 
     15 
     16 <div id="anydiff"> 
     17  <form action="<?cs var:anydiff.diff_href ?>" method="post"> 
     18   <table> 
     19    <tr> 
     20     <th><label for="old_path">From:</label></th> 
     21     <td> 
     22      <input type="text" id="old_path" name="old_path" value="<?cs 
     23         var:anydiff.old_path ?>" size="44" /> 
     24      <label for="old_rev">at Revision:</label> 
     25      <input type="text" id="old_rev" name="old" value="<?cs 
     26         var:anydiff.old_rev ?>" size="4" /> 
     27     </td> 
     28    </tr> 
     29    <tr> 
     30     <th><label for="new_path">To:</label></th> 
     31     <td> 
     32      <input type="text" id="new_path" name="path" value="<?cs 
     33         var:anydiff.new_path ?>" size="44" /> 
     34      <label for="new_rev">at Revision:</label> 
     35      <input type="text" id="new_rev" name="new" value="<?cs 
     36         var:anydiff.new_rev ?>" size="4" /> 
     37     </td> 
     38    </tr> 
     39   </table> 
     40   <div class="buttons"> 
     41      <input type="submit" value="View changes" /> 
     42   </div> 
     43  </form> 
     44 </div> 
     45</div> 
     46 
     47<?cs include "footer.cs"?> 
  • templates/browser.cs

    diff -urN -x .svn trac-0.9b1/templates/browser.cs intertrac-branch/templates/browser.cs
    old new  
    33 
    44<div id="ctxtnav" class="nav"> 
    55 <ul> 
    6   <li class="last"><a href="<?cs var:browser.log_href ?>">Revision Log</a></li> 
     6  <li class="first"><a href="<?cs var:browser.restricted_changeset_href ?>"> 
     7   Last Change</a></li> 
     8  <li class="last"><a href="<?cs var:browser.log_href ?>"> 
     9   Revision Log</a></li> 
    710 </ul> 
    811</div> 
    912 
     13 
    1014<div id="content" class="browser"> 
    1115 <h1><?cs call:browser_path_links(browser.path, browser) ?></h1> 
    1216 
    1317 <div id="jumprev"> 
    14   <form action="" method="get"><div> 
    15    <label for="rev">View revision:</label> 
    16    <input type="text" id="rev" name="rev" value="<?cs 
    17      var:browser.revision ?>" size="4" /> 
    18   </div></form> 
     18  <form action="" method="get"> 
     19   <div> 
     20    <label for="rev">View revision:</label> 
     21    <input type="text" id="rev" name="rev" value="<?cs 
     22       var:browser.revision ?>" size="4" /> 
     23   </div> 
     24  </form> 
    1925 </div> 
    2026 
    2127 <?cs if:browser.is_dir ?> 
     
    114120  ?>/TracBrowser">TracBrowser</a> for help on using the browser. 
    115121 </div> 
    116122 
     123  <div id="anydiff"><?cs 
     124   if len(browser.path) > #1 ?> 
     125    <form action="<?cs var:browser.anydiff_href ?>" method="get"> 
     126     <input type="hidden" name="new_path" value="<?cs var:browser.path ?>" /> 
     127     <input type="hidden" name="old_path" value="<?cs var:browser.path ?>" /> 
     128     <input type="hidden" name="new_rev" value="<?cs var:browser.revision ?>" /> 
     129     <input type="hidden" name="old_rev" value="<?cs var:browser.revision ?>" /> 
     130     <div class="buttons"> 
     131      <input type="submit" value="View changes..." title="Prepare an Arbitrary Diff" /> 
     132     </div> 
     133    </form><?cs 
     134   /if ?> 
     135  </div> 
     136 
    117137</div> 
    118138<?cs include:"footer.cs"?> 
  • templates/changeset.cs

    diff -urN -x .svn trac-0.9b1/templates/changeset.cs intertrac-branch/templates/changeset.cs
    old new  
    1 <?cs include "header.cs"?> 
    2 <?cs include "macros.cs"?> 
    3  
    4 <div id="ctxtnav" class="nav"> 
    5  <h2>Changeset Navigation</h2><?cs 
    6  with:links = chrome.links ?> 
    7   <ul><?cs 
    8    if:len(links.prev) ?> 
    9     <li class="first<?cs if:!len(links.next) ?> last<?cs /if ?>"> 
    10      <a class="prev" href="<?cs var:links.prev.0.href ?>" title="<?cs 
    11        var:links.prev.0.title ?>">Previous Changeset</a> 
    12     </li><?cs 
    13    /if ?><?cs 
    14    if:len(links.next) ?> 
    15     <li class="<?cs if:len(links.prev) ?>first <?cs /if ?>last"> 
    16      <a class="next" href="<?cs var:links.next.0.href ?>" title="<?cs 
    17        var:links.next.0.title ?>">Next Changeset</a> 
    18     </li><?cs 
    19    /if ?> 
    20   </ul><?cs 
    21  /with ?> 
    22 </div> 
    23  
    24 <div id="content" class="changeset"> 
    25 <h1>Changeset <?cs var:changeset.revision ?></h1> 
    26  
    27 <?cs each:change = changeset.changes ?><?cs 
    28  if:len(change.diff) ?><?cs 
    29   set:has_diffs = 1 ?><?cs 
    30  /if ?><?cs 
    31 /each ?><?cs if:has_diffs || diff.options.ignoreblanklines  
    32   || diff.options.ignorecase || diff.options.ignorewhitespace ?> 
    33 <form method="post" id="prefs" action=""> 
    34  <div> 
    35   <label for="style">View differences</label> 
    36   <select id="style" name="style"> 
    37    <option value="inline"<?cs 
    38      if:diff.style == 'inline' ?> selected="selected"<?cs 
    39      /if ?>>inline</option> 
    40    <option value="sidebyside"<?cs 
    41      if:diff.style == 'sidebyside' ?> selected="selected"<?cs 
    42      /if ?>>side by side</option> 
    43   </select> 
    44   <div class="field"> 
    45    Show <input type="text" name="contextlines" id="contextlines" size="2" 
    46      maxlength="2" value="<?cs var:diff.options.contextlines ?>" /> 
    47    <label for="contextlines">lines around each change</label> 
    48   </div> 
    49   <fieldset id="ignore"> 
    50    <legend>Ignore:</legend> 
    51    <div class="field"> 
    52     <input type="checkbox" id="blanklines" name="ignoreblanklines"<?cs 
    53       if:diff.options.ignoreblanklines ?> checked="checked"<?cs /if ?> /> 
    54     <label for="blanklines">Blank lines</label> 
    55    </div> 
    56    <div class="field"> 
    57     <input type="checkbox" id="case" name="ignorecase"<?cs 
    58       if:diff.options.ignorecase ?> checked="checked"<?cs /if ?> /> 
    59     <label for="case">Case changes</label> 
    60    </div> 
    61    <div class="field"> 
    62     <input type="checkbox" id="whitespace" name="ignorewhitespace"<?cs 
    63       if:diff.options.ignorewhitespace ?> checked="checked"<?cs /if ?> /> 
    64     <label for="whitespace">White space changes</label> 
    65    </div> 
    66   </fieldset> 
    67   <div class="buttons"> 
    68    <input type="submit" name="update" value="Update" /> 
    69   </div> 
    70  </div> 
    71 </form><?cs /if ?> 
    72  
    73 <?cs def:node_change(item,cl,kind) ?><?cs  
    74   set:ndiffs = len(item.diff) ?><?cs 
    75   set:nprops = len(item.props) ?> 
    76   <div class="<?cs var:cl ?>"></div><?cs  
    77   if:cl == "rem" ?> 
    78    <a title="Show what was removed (rev. <?cs var:item.rev.old ?>)" href="<?cs 
    79      var:item.browser_href.old ?>"><?cs var:item.path.old ?></a><?cs 
    80   else ?> 
    81    <a title="Show entry in browser" href="<?cs 
    82      var:item.browser_href.new ?>"><?cs var:item.path.new ?></a><?cs 
    83   /if ?> 
    84   <span class="comment">(<?cs var:kind ?>)</span><?cs 
    85   if:item.path.old && item.change == 'copy' || item.change == 'move' ?> 
    86    <small><em>(<?cs var:kind ?> from <a href="<?cs 
    87     var:item.browser_href.old ?>" title="Show original file (rev. <?cs 
    88     var:item.rev.old ?>)"><?cs var:item.path.old ?></a>)</em></small><?cs 
    89   /if ?><?cs 
    90   if:$ndiffs + $nprops > #0 ?> 
    91     (<a href="#file<?cs var:name(item) ?>" title="Show differences"><?cs 
    92       if:$ndiffs > #0 ?><?cs var:ndiffs ?>&nbsp;diff<?cs if:$ndiffs > #1 ?>s<?cs /if ?><?cs  
    93       /if ?><?cs 
    94       if:$ndiffs && $nprops ?>, <?cs /if ?><?cs  
    95       if:$nprops > #0 ?><?cs var:nprops ?>&nbsp;prop<?cs if:$nprops > #1 ?>s<?cs /if ?><?cs 
    96       /if ?></a>)<?cs 
    97   elif:cl == "mod" ?> 
    98     (<a href="<?cs var:item.browser_href.old ?>" 
    99         title="Show previous version in browser">previous</a>)<?cs 
    100   /if ?> 
    101 <?cs /def ?> 
    102  
    103 <dl id="overview"> 
    104  <dt class="time">Timestamp:</dt> 
    105  <dd class="time"><?cs var:changeset.time ?></dd> 
    106  <dt class="author">Author:</dt> 
    107  <dd class="author"><?cs var:changeset.author ?></dd> 
    108  <dt class="message">Message:</dt> 
    109  <dd class="message" id="searchable"><?cs var:changeset.message ?></dd> 
    110  <dt class="files">Files:</dt> 
    111  <dd class="files"> 
    112   <ul><?cs each:item = changeset.changes ?> 
    113    <li><?cs 
    114     if:item.change == 'add' ?><?cs 
    115      call:node_change(item, 'add', 'added') ?><?cs 
    116     elif:item.change == 'delete' ?><?cs 
    117      call:node_change(item, 'rem', 'deleted') ?><?cs 
    118     elif:item.change == 'copy' ?><?cs 
    119      call:node_change(item, 'cp', 'copied') ?><?cs 
    120     elif:item.change == 'move' ?><?cs 
    121      call:node_change(item, 'mv', 'moved') ?><?cs 
    122     elif:item.change == 'edit' ?><?cs 
    123      call:node_change(item, 'mod', 'modified') ?><?cs 
    124     /if ?> 
    125    </li> 
    126   <?cs /each ?></ul> 
    127  </dd> 
    128 </dl> 
    129  
    130 <div class="diff"> 
    131  <div id="legend"> 
    132   <h3>Legend:</h3> 
    133   <dl> 
    134    <dt class="unmod"></dt><dd>Unmodified</dd> 
    135    <dt class="add"></dt><dd>Added</dd> 
    136    <dt class="rem"></dt><dd>Removed</dd> 
    137    <dt class="mod"></dt><dd>Modified</dd> 
    138    <dt class="cp"></dt><dd>Copied</dd> 
    139    <dt class="mv"></dt><dd>Moved</dd> 
    140   </dl> 
    141  </div> 
    142  <ul class="entries"><?cs 
    143  each:item = changeset.changes ?><?cs 
    144   if:len(item.diff) || len(item.props) ?><li class="entry" id="file<?cs 
    145    var:name(item) ?>"><h2><a href="<?cs 
    146    var:item.browser_href.new ?>" title="Show new revision <?cs 
    147    var:item.rev.new ?> of this file in browser"><?cs 
    148    var:item.path.new ?></a></h2><?cs 
    149    if:len(item.props) ?><ul class="props"><?cs 
    150     each:prop = item.props ?><li>Property <strong><?cs 
    151      var:name(prop) ?></strong> <?cs 
    152      if:prop.old && prop.new ?>changed from <?cs 
    153      elif:!prop.old ?>set<?cs 
    154      else ?>deleted<?cs 
    155      /if ?><?cs 
    156      if:prop.old && prop.new ?><em><tt><?cs var:prop.old ?></tt></em><?cs /if ?><?cs 
    157      if:prop.new ?> to <em><tt><?cs var:prop.new ?></tt></em><?cs /if ?></li><?cs 
    158     /each ?></ul><?cs 
    159    /if ?><?cs 
    160    if:len(item.diff) ?><table class="<?cs 
    161     var:diff.style ?>" summary="Differences" cellspacing="0"><?cs 
    162     if:diff.style == 'sidebyside' ?> 
    163      <colgroup class="l"><col class="lineno" /><col class="content" /></colgroup> 
    164      <colgroup class="r"><col class="lineno" /><col class="content" /></colgroup> 
    165      <thead><tr> 
    166       <th colspan="2"><a href="<?cs 
    167        var:item.browser_href.old ?>" title="Show old rev. <?cs 
    168        var:item.rev.old ?> of <?cs var:item.path.old ?>">Revision <?cs 
    169        var:item.rev.old ?></a></th> 
    170       <th colspan="2"><a href="<?cs 
    171        var:item.browser_href.new ?>" title="Show new rev. <?cs 
    172        var:item.rev.old ?> of <?cs var:item.path.new ?>">Revision <?cs 
    173        var:item.rev.new ?></a></th> 
    174       </tr> 
    175      </thead><?cs 
    176      each:change = item.diff ?><tbody><?cs 
    177       call:diff_display(change, diff.style) ?></tbody><?cs 
    178       if:name(change) < len(item.diff) - 1 ?><tbody class="skipped"><tr> 
    179        <th>&hellip;</th><td>&nbsp;</td><th>&hellip;</th><td>&nbsp;</td> 
    180       </tr></tbody><?cs /if ?><?cs 
    181      /each ?><?cs 
    182     else ?> 
    183      <colgroup><col class="lineno" /><col class="lineno" /><col class="content" /></colgroup> 
    184      <thead><tr> 
    185       <th title="Revision <?cs var:item.rev.old ?>"><a href="<?cs 
    186        var:item.browser_href.old ?>" title="Show old version of <?cs 
    187        var:item.path.old ?>">r<?cs var:item.rev.old ?></a></th> 
    188       <th title="Revision <?cs var:item.rev.new ?>"><a href="<?cs 
    189        var:item.browser_href.new ?>" title="Show new version of <?cs 
    190        var:item.path.new ?>">r<?cs var:item.rev.new ?></a></th> 
    191       <th>&nbsp;</th></tr> 
    192      </thead><?cs 
    193      each:change = item.diff ?><?cs 
    194       call:diff_display(change, diff.style) ?><?cs 
    195       if:name(change) < len(item.diff) - 1 ?><tbody class="skipped"><tr> 
    196        <th>&hellip;</th><th>&hellip;</th><td>&nbsp;</td> 
    197       </tr></tbody><?cs /if ?><?cs 
    198      /each ?><?cs 
    199     /if ?></table><?cs 
    200    /if ?></li><?cs 
    201   /if ?><?cs 
    202  /each ?></ul> 
    203 </div> 
    204  
    205 </div> 
    206 <?cs include "footer.cs"?> 
  • templates/diff.cs

    diff -urN -x .svn trac-0.9b1/templates/diff.cs intertrac-branch/templates/diff.cs
    old new  
     1<?cs include "header.cs"?> 
     2<?cs include "macros.cs"?> 
     3 
     4<div id="ctxtnav" class="nav"> 
     5 <h2>Navigation</h2><?cs 
     6 with:links = chrome.links ?> 
     7  <ul><?cs 
     8   if:diff.chgset ?><?cs 
     9    if:len(links.prev) ?> 
     10     <li class="first<?cs if:!len(links.next) ?> last<?cs /if ?>"> 
     11      <a class="prev" href="<?cs var:links.prev.0.href ?>" title="<?cs 
     12        var:links.prev.0.title ?>">Previous <?cs  
     13         if:diff.restricted ?>Change<?cs else ?>Changeset<?cs /if ?></a> 
     14     </li><?cs 
     15    /if ?><?cs 
     16    if:len(links.next) ?> 
     17     <li class="<?cs if:len(links.prev) ?>first <?cs /if ?>last"> 
     18      <a class="next" href="<?cs var:links.next.0.href ?>" title="<?cs 
     19        var:links.next.0.title ?>">Next <?cs  
     20         if:diff.restricted ?>Change<?cs else ?>Changeset<?cs /if ?></a> 
     21     </li><?cs 
     22    /if ?><?cs 
     23   else ?> 
     24    <li class="first"><a href="<?cs var:diff.reverse_href ?>">Reverse Diff</a></li><?cs 
     25   /if ?> 
     26  </ul><?cs 
     27 /with ?> 
     28</div> 
     29 
     30<div id="content" class="changeset"> 
     31 <div id="title"><?cs 
     32  if:diff.chgset ?><?cs 
     33   if:diff.restricted ?> 
     34    <h1>Changeset <a title="Show full changeset" href="<?cs var:diff.href.new_rev ?>"> 
     35      <?cs var:diff.new_rev ?></a>  
     36     for <a title="Show entry in browser" href="<?cs var:diff.href.new_path ?>"> 
     37      <?cs var:diff.new_path ?></a>  
     38    </h1><?cs 
     39   else ?> 
     40    <h1>Changeset <?cs var:diff.new_rev ?></h1><?cs 
     41   /if ?><?cs 
     42  else ?><?cs 
     43    if:diff.restricted ?> 
     44    <h1>Changes in <a title="Show entry in browser" href="<?cs var:diff.href.new_path ?>"> 
     45      <?cs var:diff.new_path ?></a> 
     46     from revision <a title="Show full changeset" href="<?cs var:diff.href.old_rev ?>"> 
     47      <?cs var:diff.old_rev ?></a> 
     48     to <a title="Show full changeset" href="<?cs var:diff.href.new_rev ?>"> 
     49      <?cs var:diff.new_rev ?></a> 
     50    </h1><?cs 
     51   else ?> 
     52    <h1>Changes from <a title="Show entry in browser" href="<?cs var:diff.href.old_path ?>"> 
     53      <?cs var:diff.old_path ?></a>  
     54     at revision <a title="Show full changeset" href="<?cs var:diff.href.old_rev ?>"> 
     55      <?cs var:diff.old_rev ?></a> 
     56     to <a title="Show entry in browser" href="<?cs var:diff.href.new_path ?>"> 
     57     <?cs var:diff.new_path ?></a>  
     58     at revision <a title="Show full changeset" href="<?cs var:diff.href.new_rev ?>"> 
     59     <?cs var:diff.new_rev ?></a> 
     60    </h1><?cs 
     61   /if ?><?cs 
     62  /if ?> 
     63 </div> 
     64 
     65<?cs each:change = diff.changes ?><?cs 
     66 if:len(change.diff) ?><?cs 
     67  set:has_diffs = 1 ?><?cs 
     68 /if ?><?cs 
     69/each ?><?cs if:has_diffs || diff.options.ignoreblanklines  
     70  || diff.options.ignorecase || diff.options.ignorewhitespace ?> 
     71<form method="post" id="prefs" action=""> 
     72 <div><?cs  
     73  if:!diff.chgset ?> 
     74   <input type="hidden" name="old_path" value="<?cs var:diff.old_path ?>" /> 
     75   <input type="hidden" name="path" value="<?cs var:diff.new_path ?>" /> 
     76   <input type="hidden" name="old" value="<?cs var:diff.old_rev ?>" /> 
     77   <input type="hidden" name="new" value="<?cs var:diff.new_rev ?>" /><?cs 
     78  /if ?> 
     79  <label for="style">View differences</label> 
     80  <select id="style" name="style"> 
     81   <option value="inline"<?cs 
     82     if:diff.style == 'inline' ?> selected="selected"<?cs 
     83     /if ?>>inline</option> 
     84   <option value="sidebyside"<?cs 
     85     if:diff.style == 'sidebyside' ?> selected="selected"<?cs 
     86     /if ?>>side by side</option> 
     87  </select> 
     88  <div class="field"> 
     89   Show <input type="text" name="contextlines" id="contextlines" size="2" 
     90     maxlength="2" value="<?cs var:diff.options.contextlines ?>" /> 
     91   <label for="contextlines">lines around each change</label> 
     92  </div> 
     93  <fieldset id="ignore"> 
     94   <legend>Ignore:</legend> 
     95   <div class="field"> 
     96    <input type="checkbox" id="blanklines" name="ignoreblanklines"<?cs 
     97      if:diff.options.ignoreblanklines ?> checked="checked"<?cs /if ?> /> 
     98    <label for="blanklines">Blank lines</label> 
     99   </div> 
     100   <div class="field"> 
     101    <input type="checkbox" id="case" name="ignorecase"<?cs 
     102      if:diff.options.ignorecase ?> checked="checked"<?cs /if ?> /> 
     103    <label for="case">Case changes</label> 
     104   </div> 
     105   <div class="field"> 
     106    <input type="checkbox" id="whitespace" name="ignorewhitespace"<?cs 
     107      if:diff.options.ignorewhitespace ?> checked="checked"<?cs /if ?> /> 
     108    <label for="whitespace">White space changes</label> 
     109   </div> 
     110  </fieldset> 
     111  <div class="buttons"> 
     112   <input type="submit" name="update" value="Update" /> 
     113  </div> 
     114 </div> 
     115</form><?cs /if ?> 
     116 
     117<?cs def:node_change(item,cl,kind) ?><?cs  
     118  set:ndiffs = len(item.diff) ?><?cs 
     119  set:nprops = len(item.props) ?> 
     120  <div class="<?cs var:cl ?>"></div><?cs  
     121  if:cl == "rem" ?> 
     122   <a title="Show what was removed (rev. <?cs var:item.rev.old ?>)" href="<?cs 
     123     var:item.browser_href.old ?>"><?cs var:item.path.old ?></a><?cs 
     124  else ?> 
     125   <a title="Show entry in browser" href="<?cs 
     126     var:item.browser_href.new ?>"><?cs var:item.path.new ?></a><?cs 
     127  /if ?> 
     128  <span class="comment">(<?cs var:kind ?>)</span><?cs 
     129  if:item.path.old && item.change == 'copy' || item.change == 'move' ?> 
     130   <small><em>(<?cs var:kind ?> from <a href="<?cs 
     131    var:item.browser_href.old ?>" title="Show original file (rev. <?cs 
     132    var:item.rev.old ?>)"><?cs var:item.path.old ?></a>)</em></small><?cs 
     133  /if ?><?cs 
     134  if:$ndiffs + $nprops > #0 ?> 
     135    (<a href="#file<?cs var:name(item) ?>" title="Show differences"><?cs 
     136      if:$ndiffs > #0 ?><?cs var:ndiffs ?>&nbsp;diff<?cs if:$ndiffs > #1 ?>s<?cs /if ?><?cs  
     137      /if ?><?cs 
     138      if:$ndiffs && $nprops ?>, <?cs /if ?><?cs  
     139      if:$nprops > #0 ?><?cs var:nprops ?>&nbsp;prop<?cs if:$nprops > #1 ?>s<?cs /if ?><?cs 
     140      /if ?></a>)<?cs 
     141  elif:cl == "mod" ?> 
     142    (<a href="<?cs var:item.browser_href.old ?>" 
     143        title="Show previous version in browser">previous</a>)<?cs 
     144  /if ?> 
     145<?cs /def ?> 
     146 
     147<dl id="overview"><?cs 
     148 if:diff.chgset ?> 
     149 <dt class="time">Timestamp:</dt> 
     150 <dd class="time"><?cs var:changeset.time ?></dd> 
     151 <dt class="author">Author:</dt> 
     152 <dd class="author"><?cs var:changeset.author ?></dd> 
     153 <dt class="message">Message:</dt> 
     154 <dd class="message" id="searchable"><?cs var:changeset.message ?></dd><?cs 
     155 /if ?> 
     156 <dt class="files"><?cs  
     157  if:len(diff.changes) > #0 ?> 
     158   Files:<?cs 
     159  else ?> 
     160   (None)<?cs 
     161  /if ?> 
     162 </dt> 
     163 <dd class="files"> 
     164  <ul><?cs each:item = diff.changes ?> 
     165   <li><?cs 
     166    if:item.change == 'add' ?><?cs 
     167     call:node_change(item, 'add', 'added') ?><?cs 
     168    elif:item.change == 'delete' ?><?cs 
     169     call:node_change(item, 'rem', 'deleted') ?><?cs 
     170    elif:item.change == 'copy' ?><?cs 
     171     call:node_change(item, 'cp', 'copied') ?><?cs 
     172    elif:item.change == 'move' ?><?cs 
     173     call:node_change(item, 'mv', 'moved') ?><?cs 
     174    elif:item.change == 'edit' ?><?cs 
     175     call:node_change(item, 'mod', 'modified') ?><?cs 
     176    /if ?> 
     177   </li> 
     178  <?cs /each ?></ul> 
     179 </dd> 
     180</dl> 
     181 
     182<div class="diff"> 
     183 <div id="legend"> 
     184  <h3>Legend:</h3> 
     185  <dl> 
     186   <dt class="unmod"></dt><dd>Unmodified</dd> 
     187   <dt class="add"></dt><dd>Added</dd> 
     188   <dt class="rem"></dt><dd>Removed</dd> 
     189   <dt class="mod"></dt><dd>Modified</dd> 
     190   <dt class="cp"></dt><dd>Copied</dd> 
     191   <dt class="mv"></dt><dd>Moved</dd> 
     192  </dl> 
     193 </div> 
     194 <ul class="entries"><?cs 
     195 each:item = diff.changes ?><?cs 
     196  if:len(item.diff) || len(item.props) ?><li class="entry" id="file<?cs 
     197   var:name(item) ?>"><h2><a href="<?cs 
     198   var:item.browser_href.new ?>" title="Show new revision <?cs 
     199   var:item.rev.new ?> of this file in browser"><?cs 
     200   var:item.path.new ?></a></h2><?cs 
     201   if:len(item.props) ?><ul class="props"><?cs 
     202    each:prop = item.props ?><li>Property <strong><?cs 
     203     var:name(prop) ?></strong> <?cs 
     204     if:prop.old && prop.new ?>changed from <?cs 
     205     elif:!prop.old ?>set<?cs 
     206     else ?>deleted<?cs 
     207     /if ?><?cs 
     208     if:prop.old && prop.new ?><em><tt><?cs var:prop.old ?></tt></em><?cs /if ?><?cs 
     209     if:prop.new ?> to <em><tt><?cs var:prop.new ?></tt></em><?cs /if ?></li><?cs 
     210    /each ?></ul><?cs 
     211   /if ?><?cs 
     212   if:len(item.diff) ?><table class="<?cs 
     213    var:diff.style ?>" summary="Differences" cellspacing="0"><?cs 
     214    if:diff.style == 'sidebyside' ?> 
     215     <colgroup class="l"><col class="lineno" /><col class="content" /></colgroup> 
     216     <colgroup class="r"><col class="lineno" /><col class="content" /></colgroup> 
     217     <thead><tr> 
     218      <th colspan="2"><a href="<?cs 
     219       var:item.browser_href.old ?>" title="Show old rev. <?cs 
     220       var:item.rev.old ?> of <?cs var:item.path.old ?>">Revision <?cs 
     221       var:item.rev.old ?></a></th> 
     222      <th colspan="2"><a href="<?cs 
     223       var:item.browser_href.new ?>" title="Show new rev. <?cs 
     224       var:item.rev.old ?> of <?cs var:item.path.new ?>">Revision <?cs 
     225       var:item.rev.new ?></a></th> 
     226      </tr> 
     227     </thead><?cs 
     228     each:change = item.diff ?><tbody><?cs 
     229      call:diff_display(change, diff.style) ?></tbody><?cs 
     230      if:name(change) < len(item.diff) - 1 ?><tbody class="skipped"><tr> 
     231       <th>&hellip;</th><td>&nbsp;</td><th>&hellip;</th><td>&nbsp;</td> 
     232      </tr></tbody><?cs /if ?><?cs 
     233     /each ?><?cs 
     234    else ?> 
     235     <colgroup><col class="lineno" /><col class="lineno" /><col class="content" /></colgroup> 
     236     <thead><tr> 
     237      <th title="Revision <?cs var:item.rev.old ?>"><a href="<?cs 
     238       var:item.browser_href.old ?>" title="Show old version of <?cs 
     239       var:item.path.old ?>">r<?cs var:item.rev.old ?></a></th> 
     240      <th title="Revision <?cs var:item.rev.new ?>"><a href="<?cs 
     241       var:item.browser_href.new ?>" title="Show new version of <?cs 
     242       var:item.path.new ?>">r<?cs var:item.rev.new ?></a></th> 
     243      <th>&nbsp;</th></tr> 
     244     </thead><?cs 
     245     each:change = item.diff ?><?cs 
     246      call:diff_display(change, diff.style) ?><?cs 
     247      if:name(change) < len(item.diff) - 1 ?><tbody class="skipped"><tr> 
     248       <th>&hellip;</th><th>&hellip;</th><td>&nbsp;</td> 
     249      </tr></tbody><?cs /if ?><?cs 
     250     /each ?><?cs 
     251    /if ?></table><?cs 
     252   /if ?></li><?cs 
     253  /if ?><?cs 
     254 /each ?></ul> 
     255</div> 
     256 
     257</div> 
     258<?cs include "footer.cs"?> 
  • templates/log.cs

    diff -urN -x .svn trac-0.9b1/templates/log.cs intertrac-branch/templates/log.cs
    old new  
    33 
    44<div id="ctxtnav" class="nav"> 
    55 <ul> 
    6   <li class="last"><a href="<?cs 
    7     var:log.browser_href ?>">View Latest Revision</a></li><?cs 
     6  <li class="last"> 
     7   <a href="<?cs var:log.browser_href ?>">View Latest Revision</a> 
     8  </li><?cs 
    89  if:len(chrome.links.prev) ?> 
    910   <li class="first<?cs if:!len(chrome.links.next) ?> last<?cs /if ?>"> 
    1011    &larr; <a href="<?cs var:chrome.links.prev.0.href ?>" title="<?cs 
     
    6162          title="Warning: by updating, you will clear the page history" /> 
    6263  </div> 
    6364 </form> 
     65 
    6466 <div class="diff"> 
    6567  <div id="legend"> 
    6668   <h3>Legend:</h3> 
     
    7476   </dl> 
    7577  </div> 
    7678 </div> 
     79 
     80 <form action="<?cs var:log.href ?>" method="post"> 
     81  <div class="buttons"><input type="submit" value="View changes"  
     82       title="Diff from Old Revision to New Revision (select them below)" /> 
     83 </div> 
    7784 <table id="chglist" class="listing"> 
    7885  <thead> 
    7986   <tr> 
     87    <th>Old</th> 
     88    <th>New</th> 
    8089    <th class="change"></th> 
    8190    <th class="data">Date</th> 
    8291    <th class="rev">Rev</th> 
     
    8796  </thead> 
    8897  <tbody><?cs 
    8998   set:indent = #1 ?><?cs 
     99   set:idx = #0 ?><?cs 
    90100   each:item = log.items ?><?cs 
    91101    if:item.copyfrom_path ?> 
    92102     <tr class="<?cs if:name(item) % #2 ?>even<?cs else ?>odd<?cs /if ?>"> 
    93       <td class="copyfrom_path" colspan="6" style="padding-left: <?cs var:indent ?>em"> 
     103      <td class="copyfrom_path" colspan="8" style="padding-left: <?cs var:indent ?>em"> 
    94104       copied from <a href="<?cs var:item.browser_href ?>"?><?cs var:item.copyfrom_path ?></a>: 
    95105      </td> 
    96106     </tr><?cs 
     
    99109      set:indent = #1 ?><?cs 
    100110    /if ?> 
    101111    <tr class="<?cs if:name(item) % #2 ?>even<?cs else ?>odd<?cs /if ?>"> 
     112     <td><input type="radio" name="old"  
     113                value="<?cs var:item.path ?>#<?cs var:item.rev ?>" <?cs 
     114          if:idx == #1 ?> checked="checked" <?cs /if ?> /></td> 
     115     <td><input type="radio" name="new"  
     116                value="<?cs var:item.path ?>#<?cs var:item.rev ?>" <?cs 
     117          if:idx == #0 ?> checked="checked" <?cs /if ?> /></td> 
    102118     <td class="change" style="padding-left:<?cs var:indent ?>em"> 
    103119      <a title="View log starting at this revision" href="<?cs var:item.log_href ?>"> 
    104120       <span class="<?cs var:item.change ?>"></span> 
     
    115131     <td class="author"><?cs var:log.changes[item.rev].author ?></td> 
    116132     <td class="summary"><?cs var:log.changes[item.rev].message ?></td> 
    117133    </tr><?cs 
     134    set:idx = idx + 1 ?><?cs 
    118135   /each ?> 
    119136  </tbody> 
    120  </table><?cs 
     137 </table> 
     138 <div class="buttons"><input type="submit" value="View changes"  
     139      title="Diff from Old Revision to New Revision (select them above)" /> 
     140 </div> 
     141 </form><?cs 
    121142 if:len(links.prev) || len(links.next) ?><div id="paging" class="nav"><ul><?cs 
    122143  if:len(links.prev) ?><li class="first<?cs 
    123144   if:!len(links.next) ?> last<?cs /if ?>">&larr; <a href="<?cs 
  • templates/wiki.cs

    diff -urN -x .svn trac-0.9b1/templates/wiki.cs intertrac-branch/templates/wiki.cs
    old new  
    151151    var:wiki.page_name ?></a></h1> 
    152152  <?cs if:len(wiki.history) ?><form method="get" action=""> 
    153153   <input type="hidden" name="action" value="diff" /> 
     154   <div class="buttons"> 
     155    <input type="submit" value="View changes" /> 
     156   </div> 
    154157   <table id="wikihist" class="listing" summary="Change history"> 
    155158    <thead><tr> 
    156159     <th class="diff"></th> 
  • trac/db_default.py

    diff -urN -x .svn trac-0.9b1/trac/db_default.py intertrac-branch/trac/db_default.py
    old new  
    432432  ('timeline', 'default_daysback', '30'), 
    433433  ('browser', 'hide_properties', 'svk:merge'), 
    434434  ('wiki', 'ignore_missing_pages', 'false'), 
     435  ('disabled_components', 'trac.wiki.api.StandardWikiPageNames', 'no'), 
     436  ('disabled_components', 'trac.wiki.api.FlexibleWikiPageNames', 'yes'), 
     437  ('disabled_components', 'trac.wiki.api.SubWikiPageNames', 'yes'), 
    435438) 
    436439 
    437440default_components = ('trac.About', 'trac.attachment',  
  • trac/env.py

    diff -urN -x .svn trac-0.9b1/trac/env.py intertrac-branch/trac/env.py
    old new  
    7373            pass 
    7474 
    7575        self.path = path 
     76        self.siblings = {} 
    7677        self.__cnx_pool = None 
    7778        if create: 
    7879            self.create(db_str) 
  • trac/ticket/api.py

    diff -urN -x .svn trac-0.9b1/trac/ticket/api.py intertrac-branch/trac/ticket/api.py
    old new  
    149149                ('ticket', self._format_link)] 
    150150 
    151151    def get_wiki_syntax(self): 
    152         yield (r"!?#\d+", 
    153                lambda x, y, z: self._format_link(x, 'ticket', y[1:], y)) 
     152        yield (r"!?#(?P<it_ticket>[a-zA-Z_-]{0,3})\d+", 
     153               lambda x, y, z: self._format_link(x, 'ticket', y[1:], y, z)) 
    154154 
    155     def _format_link(self, formatter, ns, target, label): 
     155    def _format_link(self, formatter, ns, target, label, fullmatch=None): 
     156        intertrac = formatter.shorthand_intertrac_helper(ns, target, label, 
     157                                                         fullmatch) 
     158        if intertrac: 
     159            return intertrac 
    156160        cursor = formatter.db.cursor() 
    157161        cursor.execute("SELECT summary,status FROM ticket WHERE id=%s", 
    158162                       (target,)) 
  • trac/versioncontrol/api.py

    diff -urN -x .svn trac-0.9b1/trac/versioncontrol/api.py intertrac-branch/trac/versioncontrol/api.py
    old new  
    3838        """ 
    3939        raise NotImplementedError 
    4040 
     41    def has_node(self, path, rev): 
     42        """ 
     43        Tell if there's a node at the specified (path,rev) combination. 
     44        """ 
     45        raise NotImplementedError 
     46     
    4147    def get_node(self, path, rev=None): 
    4248        """ 
    4349        Retrieve a Node (directory or file) from the repository at the 
     
    110116        'None' is a valid revision value and represents the youngest revision. 
    111117        """ 
    112118        return NotImplementedError 
    113          
     119 
     120    def get_deltas(self, old_path, old_rev, new_path, new_rev, ignore_ancestry=1): 
     121        """ 
     122        Generator that yields change tuples (old_node, new_node, kind, change) 
     123        for each node change between the two arbitrary (path,rev) pairs. 
     124 
     125        The old_node is assumed to be None when the change is an ADD, 
     126        the new_node is assumed to be None when the change is a DELETE. 
     127        """ 
     128        raise NotImplementedError 
     129 
    114130 
    115131class Node(object): 
    116132    """ 
     
    148164        node (if the underlying version control system supports that), which 
    149165        will be indicated by the first element of the tuple (i.e. the path) 
    150166        changing. 
     167        Starts with an entry for the current revision. 
    151168        """ 
    152169        raise NotImplementedError 
    153170 
     171    def get_previous(self): 
     172        """ 
     173        Return the (path, rev, chg) tuple corresponding to the previous 
     174        revision for that node. 
     175        """ 
     176        skip = True 
     177        for p in self.get_history(2): 
     178            if skip: 
     179                skip = False 
     180            else: 
     181                return p 
     182 
    154183    def get_properties(self): 
    155184        """ 
    156185        Returns a dictionary containing the properties (meta-data) of the node. 
  • trac/versioncontrol/cache.py

    diff -urN -x .svn trac-0.9b1/trac/versioncontrol/cache.py intertrac-branch/trac/versioncontrol/cache.py
    old new  
    8484    def get_node(self, path, rev=None): 
    8585        return self.repos.get_node(path, rev) 
    8686 
     87    def has_node(self, path, rev): 
     88        return self.repos.has_node(path, rev) 
     89 
    8790    def get_oldest_rev(self): 
    8891        return self.repos.oldest_rev 
    8992 
     
    108111    def normalize_rev(self, rev): 
    109112        return self.repos.normalize_rev(rev) 
    110113 
     114    def get_deltas(self, old_path, old_rev, new_path, new_rev, ignore_ancestry=1): 
     115        return self.repos.get_deltas(old_path, old_rev, new_path, new_rev, ignore_ancestry) 
     116 
    111117 
    112118class CachedChangeset(Changeset): 
    113119 
  • trac/versioncontrol/diff.py

    diff -urN -x .svn trac-0.9b1/trac/versioncontrol/diff.py intertrac-branch/trac/versioncontrol/diff.py
    old new  
    215215                           ignore_space_changes) 
    216216    for group in _group_opcodes(opcodes, context): 
    217217        i1, i2, j1, j2 = group[0][1], group[-1][2], group[0][3], group[-1][4] 
     218        if i1 == 0 and i2 == 0: 
     219            i1, i2 = -1, -1 # support for 'A'dd changes 
    218220        yield '@@ -%d,%d +%d,%d @@' % (i1 + 1, i2 - i1, j1 + 1, j2 - j1) 
    219221        for tag, i1, i2, j1, j2 in group: 
    220222            if tag == 'equal': 
  • trac/versioncontrol/svn_fs.py

    diff -urN -x .svn trac-0.9b1/trac/versioncontrol/svn_fs.py intertrac-branch/trac/versioncontrol/svn_fs.py
    old new  
    2222import os.path 
    2323import time 
    2424import weakref 
     25import posixpath 
    2526 
    2627from svn import fs, repos, core, delta 
    2728 
     
    203204    def __del__(self): 
    204205        self.close() 
    205206 
     207    def has_node(self, path, rev): 
     208        rev_root = fs.revision_root(self.fs_ptr, rev, self.pool()) 
     209        node_type = fs.check_path(rev_root, path, self.pool()) 
     210        return node_type in _kindmap 
     211 
    206212    def normalize_path(self, path): 
    207         return path == '/' and path or path.strip('/') 
     213        return path == '/' and path or path and path.strip('/') or '' 
    208214 
    209215    def normalize_rev(self, rev): 
    210216        try: 
     
    293299        subpool = Pool(self.pool) 
    294300        while rev: 
    295301            subpool.clear() 
    296             rev_root = fs.revision_root(self.fs_ptr, rev, subpool()) 
    297             node_type = fs.check_path(rev_root, path, subpool()) 
    298             if node_type in _kindmap: # then path exists at that rev 
     302            if self.has_node(path, rev): 
    299303                if expect_deletion: 
    300304                    # it was missing, now it's there again: rev+1 must be a delete 
    301305                    yield path, rev+1, Changeset.DELETE 
     
    320324                expect_deletion = True 
    321325                rev = self.previous_rev(rev) 
    322326 
     327    def get_deltas(self, old_path, old_rev, new_path, new_rev, 
     328                   ignore_ancestry=0): 
     329        old_node = new_node = None 
     330        old_rev = self.normalize_rev(old_rev) 
     331        new_rev = self.normalize_rev(new_rev) 
     332        if self.has_node(old_path, old_rev): 
     333            old_node = self.get_node(old_path, old_rev) 
     334        else: 
     335            raise TracError, ('The Base for Diff is invalid: path %s' 
     336                              ' doesn\'t exist in revision %s' \ 
     337                              % (old_path, old_rev)) 
     338        if self.has_node(new_path, new_rev): 
     339            new_node = self.get_node(new_path, new_rev) 
     340        else: 
     341            raise TracError, ('The Target for Diff is invalid: path %s' 
     342                              ' doesn\'t exist in revision %s' \ 
     343                              % (new_path, new_rev)) 
     344        if new_node.kind != old_node.kind: 
     345            raise TracError, ('Diff mismatch: Base is a %s (%s in revision %s) ' 
     346                              'and Target is a %s (%s in revision %s).' \ 
     347                              % (old_node.kind, old_path, old_rev, 
     348                                 new_node.kind, new_path, new_rev)) 
     349        subpool = Pool(self.pool) 
     350        if new_node.isdir: 
     351            editor = DiffChangeEditor() 
     352            e_ptr, e_baton = delta.make_editor(editor, subpool()) 
     353            old_root = fs.revision_root(self.fs_ptr, old_rev, subpool()) 
     354            new_root = fs.revision_root(self.fs_ptr, new_rev, subpool()) 
     355            def authz_cb(root, path, pool): return 1 
     356            text_deltas = 0 # as this is anyway re-done in Diff.py... 
     357            entry_props = 0 # "... typically used only for working copy updates" 
     358            repos.svn_repos_dir_delta(old_root, old_path, '', 
     359                                      new_root, new_path, 
     360                                      e_ptr, e_baton, authz_cb, 
     361                                      text_deltas, 
     362                                      1, # directory 
     363                                      entry_props, 
     364                                      ignore_ancestry, 
     365                                      subpool()) 
     366            for path, kind, change in editor.deltas: 
     367                old_node = new_node = None 
     368                if change != Changeset.ADD: 
     369                    old_node = self.get_node(posixpath.join(old_path, path), 
     370                                             old_rev) 
     371                if change != Changeset.DELETE: 
     372                    new_node = self.get_node(posixpath.join(new_path, path), 
     373                                             new_rev) 
     374                else: 
     375                    kind = _kindmap[fs.check_path(old_root, old_node.path, 
     376                                                  subpool())] 
     377                yield  (old_node, new_node, kind, change) 
     378        else: 
     379            old_root = fs.revision_root(self.fs_ptr, old_rev, subpool()) 
     380            new_root = fs.revision_root(self.fs_ptr, new_rev, subpool()) 
     381            if fs.contents_changed(old_root, old_path, new_root, new_path, 
     382                                   subpool()): 
     383                yield (old_node, new_node, Node.FILE, Changeset.EDIT) 
     384 
    323385 
    324386class SubversionNode(Node): 
    325387 
     
    340402            raise TracError, "No node at %s in revision %s" % (path, rev) 
    341403        self.created_rev = fs.node_created_rev(self.root, self.scoped_path, self.pool()) 
    342404        self.created_path = fs.node_created_path(self.root, self.scoped_path, self.pool()) 
    343         # 'created_path' differs from 'path' if the last operation is a copy, 
    344         # and furthermore, 'path' might not exist at 'create_rev' 
     405        # Note: 'created_path' differs from 'path' if the last change was a copy, 
     406        #        and furthermore, 'path' might not exist at 'create_rev'. 
     407        #        The only guarantees are: 
     408        #          * this node exists at (path,rev) 
     409        #          * the node existed at (created_path,created_rev) 
     410        # TODO: check node id 
    345411        self.rev = self.created_rev 
    346412         
    347413        Node.__init__(self, path, self.rev, _kindmap[node_type]) 
     
    380446        if newer: 
    381447            yield newer 
    382448 
     449#    def get_previous(self): 
     450#        # FIXME: redo it with fs.node_history 
     451 
    383452    def get_properties(self): 
    384453        props = fs.node_proplist(self.root, self.scoped_path, self.pool()) 
    385454        for name,value in props.items(): 
     
    474543 
    475544    def _get_prop(self, name): 
    476545        return fs.revision_prop(self.fs_ptr, self.rev, name, self.pool()) 
     546 
     547 
     548# 
     549# Delta editor for diffs between arbitrary nodes 
     550# 
     551# Note 1: the 'copyfrom_path' and 'copyfrom_rev' information is not used 
     552#         because 'repos.svn_repos_dir_delta' *doesn't* provide it. 
     553# 
     554# Note 2: the 'dir_baton' is the path of the parent directory 
     555# 
     556 
     557class DiffChangeEditor(delta.Editor):  
     558 
     559    def __init__(self): 
     560        self.deltas = [] 
     561     
     562    # -- svn.delta.Editor callbacks 
     563 
     564    def open_root(self, base_revision, dir_pool): 
     565        return ('/', Changeset.EDIT) 
     566 
     567    def add_directory(self, path, dir_baton, copyfrom_path, copyfrom_rev, 
     568                      dir_pool): 
     569        self.deltas.append((path, Node.DIRECTORY, Changeset.ADD)) 
     570        return (path, Changeset.ADD) 
     571 
     572    def open_directory(self, path, dir_baton, base_revision, dir_pool): 
     573        return (path, dir_baton[1]) 
     574 
     575    def change_dir_prop(self, dir_baton, name, value, pool): 
     576        path, change = dir_baton 
     577        if change != Changeset.ADD: 
     578            self.deltas.append((path, Node.DIRECTORY, change)) 
     579 
     580    def delete_entry(self, path, revision, dir_baton, pool): 
     581        self.deltas.append((path, None, Changeset.DELETE)) 
     582 
     583    def add_file(self, path, dir_baton, copyfrom_path, copyfrom_revision, 
     584                 dir_pool): 
     585        self.deltas.append((path, Node.FILE, Changeset.ADD)) 
     586 
     587    def open_file(self, path, dir_baton, dummy_rev, file_pool): 
     588        self.deltas.append((path, Node.FILE, Changeset.EDIT)) 
     589 
  • trac/versioncontrol/tests/svn_fs.py

    diff -urN -x .svn trac-0.9b1/trac/versioncontrol/tests/svn_fs.py intertrac-branch/trac/versioncontrol/tests/svn_fs.py
    old new  
    198198        self.assertEqual(('tags/v1', 7, 'unknown'), history.next()) 
    199199        self.assertRaises(StopIteration, history.next) 
    200200 
     201    # Diffs 
     202 
     203    def _cmp_diff(self, expected, got): 
     204        if expected[0]: 
     205            old = self.repos.get_node(*expected[0]) 
     206            self.assertEqual((old.path, old.rev), (got[0].path, got[0].rev)) 
     207        if expected[1]: 
     208            new = self.repos.get_node(*expected[1]) 
     209            self.assertEqual((new.path, new.rev), (got[1].path, got[1].rev)) 
     210        self.assertEqual(expected[2], (got[2], got[3])) 
     211         
     212    def test_diff_file_different_revs(self): 
     213        diffs = self.repos.get_deltas('trunk/README.txt', 2, 'trunk/README.txt', 3) 
     214        self._cmp_diff((('trunk/README.txt', 2), 
     215                        ('trunk/README.txt', 3), 
     216                        (Node.FILE, Changeset.EDIT)), diffs.next()) 
     217        self.assertRaises(StopIteration, diffs.next) 
     218 
     219    def test_diff_file_different_files(self): 
     220        diffs = self.repos.get_deltas('branches/v1x/README.txt', 12, 
     221                                      'branches/v1x/README2.txt', 12) 
     222        self._cmp_diff((('branches/v1x/README.txt', 12), 
     223                        ('branches/v1x/README2.txt', 12), 
     224                        (Node.FILE, Changeset.EDIT)), diffs.next()) 
     225        self.assertRaises(StopIteration, diffs.next) 
     226 
     227    def test_diff_file_no_change(self): 
     228        diffs = self.repos.get_deltas('trunk/README.txt', 7, 
     229                                      'tags/v1/README.txt', 7) 
     230        self.assertRaises(StopIteration, diffs.next) 
     231  
     232    def test_diff_dir_different_revs(self): 
     233        diffs = self.repos.get_deltas('trunk', 4, 'trunk', 8) 
     234        self._cmp_diff((None, ('trunk/dir1/dir2', 8), 
     235                        (Node.DIRECTORY, Changeset.ADD)), diffs.next()) 
     236        self._cmp_diff((None, ('trunk/dir1/dir3', 8), 
     237                        (Node.DIRECTORY, Changeset.ADD)), diffs.next()) 
     238        self._cmp_diff((None, ('trunk/README2.txt', 6), 
     239                        (Node.FILE, Changeset.ADD)), diffs.next()) 
     240        self._cmp_diff((('trunk/dir2', 4), None, 
     241                        (Node.DIRECTORY, Changeset.DELETE)), diffs.next()) 
     242        self._cmp_diff((('trunk/dir3', 4), None, 
     243                        (Node.DIRECTORY, Changeset.DELETE)), diffs.next()) 
     244        self.assertRaises(StopIteration, diffs.next) 
     245 
     246    def test_diff_dir_different_dirs(self): 
     247        diffs = self.repos.get_deltas('trunk', 1, 'branches/v1x', 12) 
     248        self._cmp_diff((None, ('branches/v1x/dir1', 12), 
     249                        (Node.DIRECTORY, Changeset.ADD)), diffs.next()) 
     250        self._cmp_diff((None, ('branches/v1x/dir1/dir2', 12), 
     251                        (Node.DIRECTORY, Changeset.ADD)), diffs.next()) 
     252        self._cmp_diff((None, ('branches/v1x/dir1/dir3', 12), 
     253                        (Node.DIRECTORY, Changeset.ADD)), diffs.next()) 
     254        self._cmp_diff((None, ('branches/v1x/README.txt', 12), 
     255                        (Node.FILE, Changeset.ADD)), diffs.next()) 
     256        self._cmp_diff((None, ('branches/v1x/README2.txt', 12), 
     257                        (Node.FILE, Changeset.ADD)), diffs.next()) 
     258        self.assertRaises(StopIteration, diffs.next) 
     259 
     260    def test_diff_dir_no_change(self): 
     261        diffs = self.repos.get_deltas('trunk', 7, 
     262                                      'tags/v1', 7) 
     263        self.assertRaises(StopIteration, diffs.next) 
     264         
     265    # Changesets 
     266 
    201267    def test_changeset_repos_creation(self): 
    202268        chgset = self.repos.get_changeset(0) 
    203269        self.assertEqual(0, chgset.rev) 
  • trac/versioncontrol/web_ui/browser.py

    diff -urN -x .svn trac-0.9b1/trac/versioncontrol/web_ui/browser.py intertrac-branch/trac/versioncontrol/web_ui/browser.py
    old new  
    8989 
    9090        repos = self.env.get_repository(req.authname) 
    9191        node = repos.get_node(path, rev) 
     92        rev = repos.normalize_rev(rev) 
    9293 
    9394        hidden_properties = [p.strip() for p 
    9495                             in self.config.get('browser', 'hide_properties', 
    9596                                                'svk:merge').split(',')] 
     97 
    9698        req.hdf['title'] = path 
    97         req.hdf['browser'] = { 
     99        browser_hdf = { 
    98100            'path': path, 
    99             'revision': rev or repos.youngest_rev, 
     101            'revision': rev, 
    100102            'props': dict([(util.escape(name), util.escape(value)) 
    101103                           for name, value in node.get_properties().items() 
    102                            if not name in hidden_properties]), 
    103             'href': util.escape(self.env.href.browser(path, rev=rev or 
    104                                                       repos.youngest_rev)), 
    105             'log_href': util.escape(self.env.href.log(path)) 
    106         } 
     104                           if not name in hidden_properties]) 
     105            } 
     106        browser_hrefs = { 
     107            'href': self.env.href.browser(path,rev=rev), 
     108            'restricted_changeset_href': self.env.href.changeset(node.rev, 
     109                                                                 path=path), 
     110            'anydiff_href': self.env.href.anydiff(), 
     111            'log_href': self.env.href.log(path) 
     112            } 
     113        browser_hdf.update(dict([(key, util.escape(href)) for key, href in 
     114                                 browser_hrefs.items()])) 
     115        req.hdf['browser'] = browser_hdf 
    107116 
    108117        path_links = get_path_links(self.env.href, path, rev) 
    109118        if len(path_links) > 1: 
     
    163172 
    164173        req.hdf['browser.items'] = info 
    165174        req.hdf['browser.changes'] = changes 
    166  
     175        if node.path != '': 
     176            zip_href = self.env.href.diff(node.path, new=rev, old=rev, 
     177                                          old_path='/', # special case (#238) 
     178                                          format='zip') 
     179            add_link(req, 'alternate', zip_href, 'Zip Archive', 
     180                     'application/zip', 'zip') 
     181         
     182         
    167183    def _render_file(self, req, repos, node, rev=None): 
    168184        req.perm.assert_permission('FILE_VIEW') 
    169185 
  • trac/versioncontrol/web_ui/changeset.py

    diff -urN -x .svn trac-0.9b1/trac/versioncontrol/web_ui/changeset.py intertrac-branch/trac/versioncontrol/web_ui/changeset.py
    old new  
    2222 
    2323from trac import mimeview, util 
    2424from trac.core import * 
    25 from trac.perm import IPermissionRequestor 
    2625from trac.Search import ISearchSource, query_to_sql, shorten_result 
    2726from trac.Timeline import ITimelineEventProvider 
    2827from trac.versioncontrol import Changeset, Node 
    2928from trac.versioncontrol.svn_authz import SubversionAuthorizer 
    30 from trac.versioncontrol.diff import get_diff_options, hdf_diff, unified_diff 
    3129from trac.web import IRequestHandler 
    32 from trac.web.chrome import add_link, add_stylesheet, INavigationContributor 
     30from trac.web.chrome import INavigationContributor 
    3331from trac.wiki import wiki_to_html, wiki_to_oneliner, IWikiSyntaxProvider 
     32from trac.versioncontrol.web_ui.diff import AbstractDiffModule 
    3433 
     34class ChangesetModule(AbstractDiffModule): 
    3535 
    36 class ChangesetModule(Component): 
    37  
    38     implements(INavigationContributor, IPermissionRequestor, IRequestHandler, 
     36    implements(INavigationContributor,  
    3937               ITimelineEventProvider, IWikiSyntaxProvider, ISearchSource) 
    4038 
    4139    # INavigationContributor methods 
     
    4644    def get_navigation_items(self, req): 
    4745        return [] 
    4846 
    49     # IPermissionRequestor methods 
    50  
    51     def get_permission_actions(self): 
    52         return ['CHANGESET_VIEW'] 
    53  
    54     # IRequestHandler methods 
     47    # (reimplemented) IRequestHandler methods 
    5548 
    5649    def match_request(self, req): 
    5750        match = re.match(r'/changeset/([0-9]+)$', req.path_info) 
     
    5952            req.args['rev'] = match.group(1) 
    6053            return 1 
    6154 
    62     def process_request(self, req): 
    63         req.perm.assert_permission('CHANGESET_VIEW') 
    64  
    65         rev = req.args.get('rev') 
    66         repos = self.env.get_repository(req.authname) 
    67  
    68         diff_options = get_diff_options(req) 
    69         if req.args.has_key('update'): 
    70             req.redirect(self.env.href.changeset(rev)) 
    71  
    72         chgset = repos.get_changeset(rev) 
    73         req.check_modified(chgset.date, 
    74                            diff_options[0] + ''.join(diff_options[1])) 
    75  
    76         format = req.args.get('format') 
    77         if format == 'diff': 
    78             self._render_diff(req, repos, chgset, diff_options) 
    79             return 
    80         elif format == 'zip': 
    81             self._render_zip(req, repos, chgset) 
    82             return 
    83  
    84         self._render_html(req, repos, chgset, diff_options) 
    85         add_link(req, 'alternate', '?format=diff', 'Unified Diff', 
    86                  'text/plain', 'diff') 
    87         add_link(req, 'alternate', '?format=zip', 'Zip Archive', 
    88                  'application/zip', 'zip') 
    89         add_stylesheet(req, 'common/css/changeset.css') 
    90         add_stylesheet(req, 'common/css/diff.css') 
    91         add_stylesheet(req, 'common/css/code.css') 
    92         return 'changeset.cs', None 
    93  
    9455    # ITimelineEventProvider methods 
    9556 
    9657    def get_timeline_filters(self, req): 
     
    13697                          message 
    13798                rev = repos.previous_rev(rev) 
    13899 
    139     # Internal methods 
    140  
    141     def _render_html(self, req, repos, chgset, diff_options): 
    142         """HTML version""" 
    143         req.hdf['title'] = '[%s]' % chgset.rev 
    144         req.hdf['changeset'] = { 
    145             'revision': chgset.rev, 
    146             'time': time.strftime('%c', time.localtime(chgset.date)), 
    147             'author': util.escape(chgset.author or 'anonymous'), 
    148             'message': wiki_to_html(chgset.message or '--', self.env, req, 
    149                                     escape_newlines=True) 
    150         } 
    151  
    152         oldest_rev = repos.oldest_rev 
    153         if chgset.rev != oldest_rev: 
    154             add_link(req, 'first', self.env.href.changeset(oldest_rev), 
    155                      'Changeset %s' % oldest_rev) 
    156             previous_rev = repos.previous_rev(chgset.rev) 
    157             add_link(req, 'prev', self.env.href.changeset(previous_rev), 
    158                      'Changeset %s' % previous_rev) 
    159         youngest_rev = repos.youngest_rev 
    160         if str(chgset.rev) != str(youngest_rev): 
    161             next_rev = repos.next_rev(chgset.rev) 
    162             add_link(req, 'next', self.env.href.changeset(next_rev), 
    163                      'Changeset %s' % next_rev) 
    164             add_link(req, 'last', self.env.href.changeset(youngest_rev), 
    165                      'Changeset %s' % youngest_rev) 
    166  
    167         edits = [] 
    168         idx = 0 
    169         for path, kind, change, base_path, base_rev in chgset.get_changes(): 
    170             info = {'change': change} 
    171             if base_path: 
    172                 info['path.old'] = base_path 
    173                 info['rev.old'] = base_rev 
    174                 info['browser_href.old'] = self.env.href.browser(base_path, 
    175                                                                  rev=base_rev) 
    176             if path: 
    177                 info['path.new'] = path 
    178                 info['rev.new'] = chgset.rev 
    179                 info['browser_href.new'] = self.env.href.browser(path, 
    180                                                                  rev=chgset.rev) 
    181             if change in (Changeset.COPY, Changeset.EDIT, Changeset.MOVE): 
    182                 edits.append((idx, path, kind, base_path, base_rev)) 
    183             req.hdf['changeset.changes.%d' % idx] = info 
    184             idx += 1 
    185  
    186         hidden_properties = [p.strip() for p 
    187                              in self.config.get('browser', 'hide_properties', 
    188                                                 'svk:merge').split(',')] 
    189  
    190         for idx, path, kind, base_path, base_rev in edits: 
    191             old_node = repos.get_node(base_path or path, base_rev) 
    192             new_node = repos.get_node(path, chgset.rev) 
    193  
    194             # Property changes 
    195             old_props = old_node.get_properties() 
    196             new_props = new_node.get_properties() 
    197             changed_props = {} 
    198             if old_props != new_props: 
    199                 for k,v in old_props.items(): 
    200                     if not k in new_props: 
    201                         changed_props[k] = {'old': v} 
    202                     elif v != new_props[k]: 
    203                         changed_props[k] = {'old': v, 'new': new_props[k]} 
    204                 for k,v in new_props.items(): 
    205                     if not k in old_props: 
    206                         changed_props[k] = {'new': v} 
    207                 for k in hidden_properties: 
    208                     if k in changed_props: 
    209                         del changed_props[k] 
    210                 req.hdf['changeset.changes.%d.props' % idx] = changed_props 
    211  
    212             if kind == Node.DIRECTORY: 
    213                 continue 
    214  
    215             # Content changes 
    216             default_charset = self.config.get('trac', 'default_charset') 
    217             old_content = old_node.get_content().read() 
    218             if mimeview.is_binary(old_content): 
    219                 continue 
    220             charset = mimeview.get_charset(old_node.content_type) or \ 
    221                       default_charset 
    222             old_content = util.to_utf8(old_content, charset) 
    223  
    224             new_content = new_node.get_content().read() 
    225             if mimeview.is_binary(new_content): 
    226                 continue 
    227             charset = mimeview.get_charset(new_node.content_type) or \ 
    228                       default_charset 
    229             new_content = util.to_utf8(new_content, charset) 
    230  
    231             if old_content != new_content: 
    232                 context = 3 
    233                 for option in diff_options[1]: 
    234                     if option.startswith('-U'): 
    235                         context = int(option[2:]) 
    236                         break 
    237                 tabwidth = int(self.config.get('diff', 'tab_width', 
    238                                                self.config.get('mimeviewer', 
    239                                                                'tab_width'))) 
    240                 changes = hdf_diff(old_content.splitlines(), 
    241                                    new_content.splitlines(), 
    242                                    context, tabwidth, 
    243                                    ignore_blank_lines='-B' in diff_options[1], 
    244                                    ignore_case='-i' in diff_options[1], 
    245                                    ignore_space_changes='-b' in diff_options[1]) 
    246                 req.hdf['changeset.changes.%d.diff' % idx] = changes 
    247  
    248     def _render_diff(self, req, repos, chgset, diff_options): 
    249         """Raw Unified Diff version""" 
    250         req.send_response(200) 
    251         req.send_header('Content-Type', 'text/plain;charset=utf-8') 
    252         req.send_header('Content-Disposition', 
    253                         'filename=Changeset%s.diff' % req.args.get('rev')) 
    254         req.end_headers() 
    255  
    256         for path, kind, change, base_path, base_rev in chgset.get_changes(): 
    257             if change == Changeset.ADD: 
    258                 old_node = None 
    259             else: 
    260                 old_node = repos.get_node(base_path or path, base_rev) 
    261             if change == Changeset.DELETE: 
    262                 new_node = None 
    263             else: 
    264                 new_node = repos.get_node(path, chgset.rev) 
    265  
    266             # TODO: Property changes 
    267  
    268             # Content changes 
    269             if kind == 'dir': 
    270                 continue 
    271  
    272             default_charset = self.config.get('trac', 'default_charset') 
    273             new_content = old_content = '' 
    274             new_node_info = old_node_info = ('','') 
    275  
    276             if old_node: 
    277                 charset = mimeview.get_charset(old_node.content_type) or \ 
    278                           default_charset 
    279                 old_content = util.to_utf8(old_node.get_content().read(), 
    280                                            charset) 
    281                 old_node_info = (old_node.path, old_node.rev) 
    282             if mimeview.is_binary(old_content): 
    283                 continue 
    284  
    285             if new_node: 
    286                 charset = mimeview.get_charset(new_node.content_type) or \ 
    287                           default_charset 
    288                 new_content = util.to_utf8(new_node.get_content().read(), 
    289                                            charset) 
    290                 new_node_info = (new_node.path, new_node.rev) 
    291             if mimeview.is_binary(new_content): 
    292                 continue 
    293  
    294             if old_content != new_content: 
    295                 context = 3 
    296                 for option in diff_options[1]: 
    297                     if option.startswith('-U'): 
    298                         context = int(option[2:]) 
    299                         break 
    300                 req.write('Index: ' + path + util.CRLF) 
    301                 req.write('=' * 67 + util.CRLF) 
    302                 req.write('--- %s (revision %s)' % old_node_info + 
    303                           util.CRLF) 
    304                 req.write('+++ %s (revision %s)' % new_node_info + 
    305                           util.CRLF) 
    306                 for line in unified_diff(old_content.splitlines(), 
    307                                          new_content.splitlines(), context, 
    308                                          ignore_blank_lines='-B' in diff_options[1], 
    309                                          ignore_case='-i' in diff_options[1], 
    310                                          ignore_space_changes='-b' in diff_options[1]): 
    311                     req.write(line + util.CRLF) 
    312  
    313     def _render_zip(self, req, repos, chgset): 
    314         """ZIP archive with all the added and/or modified files.""" 
    315         req.send_response(200) 
    316         req.send_header('Content-Type', 'application/zip') 
    317         req.send_header('Content-Disposition', 
    318                         'filename=Changeset%s.zip' % chgset.rev) 
    319         req.end_headers() 
    320  
    321         try: 
    322             from cStringIO import StringIO 
    323         except ImportError: 
    324             from StringIO import StringIO 
    325         from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED 
    326  
    327         buf = StringIO() 
    328         zipfile = ZipFile(buf, 'w', ZIP_DEFLATED) 
    329         for path, kind, change, base_path, base_rev in chgset.get_changes(): 
    330             if kind == Node.FILE and change != Changeset.DELETE: 
    331                 node = repos.get_node(path, chgset.rev) 
    332                 zipinfo = ZipInfo() 
    333                 zipinfo.filename = node.path 
    334                 zipinfo.date_time = time.gmtime(node.last_modified)[:6] 
    335                 zipinfo.compress_type = ZIP_DEFLATED 
    336                 zipfile.writestr(zipinfo, node.get_content().read()) 
    337         zipfile.close() 
    338         req.write(buf.getvalue()) 
    339100 
    340101    # IWikiSyntaxProvider methods 
    341102     
    342103    def get_wiki_syntax(self): 
    343         yield (r"!?\[\d+\]|(?:\b|!)r\d+\b(?!:\d)", 
     104        yield (r"!?\[(?P<it_changeset>[a-zA-Z_-]{0,3})\d+\]|" # [1] or [T1] 
     105               r"(?:\b|!)r\d+\b(?!:\d)",                      # r1 but not r1:2 
    344106               lambda x, y, z: self._format_link(x, 'changeset', 
    345107                                                 y[0] == 'r' and y[1:] 
    346                                                  or y[1:-1], y)) 
     108                                                 or y[1:-1], y, z)) 
    347109 
    348110    def get_link_resolvers(self): 
    349111        yield ('changeset', self._format_link) 
    350112 
    351     def _format_link(self, formatter, ns, rev, label): 
     113    def _format_link(self, formatter, ns, rev, label, fullmatch=None): 
     114        intertrac = formatter.shorthand_intertrac_helper(ns, rev, label, 
     115                                                         fullmatch) 
     116        if intertrac: 
     117            return intertrac 
    352118        cursor = formatter.db.cursor() 
    353119        cursor.execute('SELECT message FROM revision WHERE rev=%s', (rev,)) 
    354120        row = cursor.fetchone() 
  • trac/versioncontrol/web_ui/diff.py

    diff -urN -x .svn trac-0.9b1/trac/versioncontrol/web_ui/diff.py intertrac-branch/trac/versioncontrol/web_ui/diff.py
    old new  
     1# -*- coding: iso8859-1 -*- 
     2# 
     3# Copyright (C) 2003-2005 Edgewall Software 
     4# Copyright (C) 2003-2005 Jonas Borgström <jonas@edgewall.com> 
     5# Copyright (C) 2004-2005 Christopher Lenz <cmlenz@gmx.de> 
     6# All rights reserved. 
     7# 
     8# This software is licensed as described in the file COPYING, which 
     9# you should have received as part of this distribution. The terms 
     10# are also available at http://trac.edgewall.com/license.html. 
     11# 
     12# This software consists of voluntary contributions made by many 
     13# individuals. For the exact contribution history, see the revision 
     14# history and logs, available at http://projects.edgewall.com/trac/. 
     15# 
     16# Author: Jonas Borgström <jonas@edgewall.com> 
     17#         Christopher Lenz <cmlenz@gmx.de> 
     18#         Christian Boos <cboos@neuf.fr> 
     19 
     20from __future__ import generators 
     21import time 
     22import re 
     23import posixpath 
     24from urllib import urlencode 
     25 
     26from trac import mimeview, util 
     27from trac.core import * 
     28from trac.perm import IPermissionRequestor 
     29from trac.versioncontrol import Changeset, Node 
     30from trac.versioncontrol.diff import get_diff_options, hdf_diff, unified_diff 
     31from trac.web import IRequestHandler 
     32from trac.web.chrome import add_link, add_stylesheet 
     33from trac.wiki import wiki_to_html, IWikiSyntaxProvider 
     34 
     35class DiffArgs(dict): 
     36    def __getattr__(self,str): 
     37        return self[str] 
     38     
     39 
     40class ChangesPermission(Component): 
     41    """Simple permission provider for changes related modules.""" 
     42     
     43    implements(IPermissionRequestor) 
     44     
     45    def get_permission_actions(self): 
     46        return ['CHANGESET_VIEW'] 
     47     
     48 
     49class AbstractDiffModule(Component): 
     50    """Provide flexible functionality for showing sets of differences. 
     51 
     52    If the differences shown are coming from a specific changeset, 
     53    then that changeset informations can be shown too. 
     54 
     55    In addition, it is possible to show only a subset of the changeset: 
     56    Only the changes affecting a given path will be shown. 
     57    This is called the ''restricted'' changeset. 
     58 
     59    But the differences can also be computed in a more general way, 
     60    between two arbitrary paths and/or between two arbitrary revisions. 
     61    In that case, there's no changeset information displayed. 
     62    """ 
     63 
     64    abstract = True 
     65 
     66    implements(IRequestHandler) 
     67 
     68    # IRequestHandler methods 
     69 
     70    def match_request(self, req): 
     71        raise NotImplementedError 
     72 
     73    def process_request(self, req): 
     74        """The appropriate mode of operation is inferred from 
     75        the request parameters: 
     76         * If `old` and `new` parameters are given, it will be an 
     77           arbitrary set of differences: `chgset` is False. 
     78           * If `old_path` is given and is different from `path`, 
     79             it's a generalized diff, from `old_path@old` 
     80             to `path@new`: `restricted` is False. 
     81           * Otherwise those are differences between two arbitrary revisions 
     82             of a given path: `restricted` is True. 
     83         * Otherwise, we are dealing with a changeset only: `chgset` is True. 
     84           * If the `path` is not empty or not the root, then only 
     85             the changes affecting that path (i.e. itself, children or 
     86             ancestors) will be considered: `restricted` is True. 
     87           * Otherwise, it's the full changeset: `restricted` is False. 
     88          
     89        In any case, the given path@rev pair must exist. 
     90        """ 
     91        req.perm.assert_permission('CHANGESET_VIEW') 
     92         
     93        # -- retrieve arguments 
     94        path = req.args.get('path') 
     95        rev = req.args.get('rev') 
     96        old = req.args.get('old') 
     97        new = req.args.get('new') 
     98        old_path = req.args.get('old_path') 
     99 
     100        # -- normalize and check for special case 
     101        repos = self.env.get_repository(req.authname) 
     102        path = repos.normalize_path(path) 
     103        rev = repos.normalize_rev(rev) 
     104        old_path = repos.normalize_path(old_path) 
     105         
     106        if old_path == path and old and old == new: # revert to Changeset 
     107            rev = old 
     108            old_path = old = new = None 
     109 
     110        diff_options = get_diff_options(req) 
     111 
     112        # -- setup the `chgset` and `restricted` flags, see docstring above. 
     113        chgset = not old and not new and not old_path 
     114        if chgset: 
     115            restricted = path != '' and path != '/' # (subset or not) 
     116        else: 
     117            restricted = old_path == path # (same path or not) 
     118 
     119        # -- redirect if changing the diff options 
     120        if req.args.has_key('update'): 
     121            if chgset: 
     122                if restricted: 
     123                    req.redirect(self.env.href.diff(path, rev=rev)) 
     124                else: 
     125                    req.redirect(self.env.href.changeset(rev)) 
     126            else: 
     127                req.redirect(self.env.href.diff(path, new=new, 
     128                                                old_path=old_path, old=old)) 
     129 
     130        # -- preparing the diff arguments 
     131        if chgset: 
     132            prev = repos.get_node(path, rev).get_previous() 
     133            if prev: 
     134                prev_path, prev_rev = prev[:2] 
     135            else: 
     136                prev_path, prev_rev = path, repos.previous_rev(rev) 
     137            diff_args = DiffArgs(old_path=prev_path, old_rev=prev_rev, 
     138                                 new_path=path, new_rev=rev) 
     139        else: 
     140            if not new: 
     141                new = repos.youngest_rev 
     142            elif not old: 
     143                old = repos.youngest_rev 
     144            if not old_path: 
     145                old_path = path 
     146            diff_args = DiffArgs(old_path=old_path, old_rev=old, 
     147                                 new_path=path, new_rev=new) 
     148        if chgset: 
     149            chgset = repos.get_changeset(rev) 
     150            req.check_modified(chgset.date, 
     151                               diff_options[0] + ''.join(diff_options[1])) 
     152        else: 
     153            pass # FIXME: what date should we choose for a diff? 
     154 
     155        req.hdf['diff'] = diff_args 
     156 
     157        format = req.args.get('format') 
     158 
     159        if format in ['diff', 'zip']: 
     160            # choosing an appropriate filename 
     161            rpath = path.replace('/','_') 
     162            if chgset: 
     163                if restricted: 
     164                    filename = 'changeset_%s_r%s' % (rpath, rev) 
     165                else: 
     166                    filename = 'changeset_r%s' % rev 
     167            else: 
     168                if restricted: 
     169                    filename = 'diff-%s-from-r%s-to-r%s' \ 
     170                                  % (rpath, old, new) 
     171                elif old_path == '/': # special case for download (#238) 
     172                    filename = '%s-r%s' % (rpath, old) 
     173                else: 
     174                    filename = 'diff-from-%s-r%s-to-%s-r%s' \ 
     175                               % (old_path.replace('/','_'), old, rpath, new) 
     176            if format == 'diff': 
     177                self._render_diff(req, filename, repos, diff_args, 
     178                                  diff_options) 
     179                return 
     180            elif format == 'zip': 
     181                self._render_zip(req, filename, repos, diff_args) 
     182                return 
     183 
     184        # -- HTML format 
     185        self._render_html(req, repos, chgset, restricted, 
     186                          diff_args, diff_options) 
     187        if chgset: 
     188            diff_params = 'rev=%s' % rev 
     189        else: 
     190            diff_params = urlencode({'path': path, 
     191                                     'new': new, 
     192                                     'old_path': old_path, 
     193                                     'old': old}) 
     194        add_link(req, 'alternate', '?format=diff&'+diff_params, 'Unified Diff', 
     195                 'text/plain', 'diff') 
     196        add_link(req, 'alternate', '?format=zip&'+diff_params, 'Zip Archive', 
     197                 'application/zip', 'zip') 
     198        add_stylesheet(req, 'common/css/changeset.css') 
     199        add_stylesheet(req, 'common/css/diff.css') 
     200        add_stylesheet(req, 'common/css/code.css') 
     201        return 'diff.cs', None 
     202 
     203 
     204    # Internal methods 
     205 
     206    def _render_html(self, req, repos, chgset, restricted, diff, diff_options): 
     207        """ 
     208        HTML version 
     209        """ 
     210        req.hdf['diff'] = { 
     211            'chgset': chgset and True, 
     212            'restricted': restricted, 
     213            'href': { 'new_rev': self.env.href.changeset(diff.new_rev), 
     214                      'old_rev': self.env.href.changeset(diff.old_rev), 
     215                      'new_path': self.env.href.browser(diff.new_path, 
     216                                                        rev=diff.new_rev), 
     217                      'old_path': self.env.href.browser(diff.old_path, 
     218                                                        rev=diff.old_rev) 
     219                      } 
     220            } 
     221         
     222        if chgset: # Changeset Mode (possibly restricted on a path) 
     223            path, rev = diff.new_path, diff.new_rev 
     224 
     225            # -- getting the deltas from the Changeset.get_changes method 
     226            def get_deltas(): 
     227                old_node = new_node = None 
     228                for npath, kind, change, opath, orev in chgset.get_changes(): 
     229                    if restricted and \ 
     230                           not (npath.startswith(path)      # npath is below 
     231                                or path.startswith(npath)): # npath is above 
     232                        continue 
     233                    if change != Changeset.ADD: 
     234                        old_node = repos.get_node(opath, orev) 
     235                    if change != Changeset.DELETE: 
     236                        new_node = repos.get_node(npath, rev) 
     237                    yield old_node, new_node, kind, change 
     238                     
     239            def _changeset_title(rev): 
     240                if restricted: 
     241                    return 'Changeset %s for %s' % (rev, path) 
     242                else: 
     243                    return 'Changeset %s' % rev 
     244 
     245            title = _changeset_title(rev) 
     246            req.hdf['changeset'] = { 
     247                'revision': chgset.rev, 
     248                'time': time.strftime('%c', time.localtime(chgset.date)), 
     249                'author': util.escape(chgset.author or 'anonymous'), 
     250                'message': wiki_to_html(chgset.message or '--', self.env, req, 
     251                                        escape_newlines=True) 
     252                } 
     253            oldest_rev = repos.oldest_rev 
     254            if chgset.rev != oldest_rev: 
     255                if restricted: 
     256                    prev = repos.get_node(path, rev).get_previous() 
     257                    if prev: 
     258                        prev_path, prev_rev = prev[:2] 
     259                        prev_href = self.env.href.changeset(prev_rev, 
     260                                                            path=prev_path) 
     261                    else: 
     262                        prev_path = prev_rev = None 
     263                else: 
     264                    prev_path = diff.old_path 
     265                    prev_rev = repos.previous_rev(chgset.rev) 
     266                    add_link(req, 'first', self.env.href.changeset(oldest_rev), 
     267                             'Changeset %s' % oldest_rev) 
     268                    prev_href = self.env.href.changeset(prev_rev) 
     269                if prev_rev: 
     270                    add_link(req, 'prev', prev_href, _changeset_title(prev_rev)) 
     271            youngest_rev = repos.youngest_rev 
     272            if str(chgset.rev) != str(youngest_rev): 
     273                if restricted: 
     274                    next_rev = next_href = None 
     275                    # FIXME: find an effective way to find the next rev 
     276                else: 
     277                    next_rev = repos.next_rev(chgset.rev) 
     278                    next_href = self.env.href.changeset(next_rev) 
     279                    add_link(req, 'last', 
     280                             self.env.href.diff(path, rev=youngest_rev), 
     281                             'Changeset %s' % youngest_rev) 
     282                if next_rev: 
     283                    add_link(req, 'next', next_href, _changeset_title(next_rev)) 
     284 
     285        else: # Diff Mode 
     286            # -- getting the deltas from the Repository.get_deltas method 
     287            def get_deltas(): 
     288                for d in repos.get_deltas(**diff): 
     289                    yield d 
     290                     
     291            reverse_href = self.env.href.diff(diff.old_path, 
     292                                              new=diff.old_rev, 
     293                                              old_path=diff.new_path, 
     294                                              old=diff.new_rev) 
     295            req.hdf['diff.reverse_href'] = reverse_href 
     296            if restricted:              # 'diff between 2 revisions' mode 
     297                title = 'Diff r%s:%s for %s' % (diff.old_rev, diff.new_rev, 
     298                                                diff.new_path) 
     299            else:                       # 'arbitrary diff' mode 
     300                title = 'Diff from %s @ %s to %s @ %s' % (diff.old_path, 
     301                                                          diff.old_rev, 
     302                                                          diff.new_path, 
     303                                                          diff.new_rev) 
     304        req.hdf['title'] = title 
     305 
     306        def _change_info(old_node, new_node, change): 
     307            info = {'change': change} 
     308            if old_node: 
     309                info['path.old'] = old_node.path 
     310                info['rev.old'] = old_node.rev # this is the created rev. 
     311                old_href = self.env.href.browser(old_node.path, 
     312                                                 rev=diff.old_rev) 
     313                # Reminder: old_node.path may not exist at old_node.rev 
     314                info['browser_href.old'] = old_href 
     315            if new_node: 
     316                info['path.new'] = new_node.path 
     317                info['rev.new'] = new_node.rev # created rev. 
     318                new_href = self.env.href.browser(new_node.path, 
     319                                                 rev=diff.new_rev) 
     320                # (same remark as above) 
     321                info['browser_href.new'] = new_href 
     322            return info 
     323 
     324        hidden_properties = [p.strip() for p 
     325                             in self.config.get('browser', 'hide_properties', 
     326                                                'svk:merge').split(',')] 
     327 
     328        def _prop_changes(old_node, new_node): 
     329            old_props = old_node.get_properties() 
     330            new_props = new_node.get_properties() 
     331            changed_props = {} 
     332            if old_props != new_props: 
     333                for k,v in old_props.items(): 
     334                    if not k in new_props: 
     335                        changed_props[k] = {'old': v} 
     336                    elif v != new_props[k]: 
     337                        changed_props[k] = {'old': v, 'new': new_props[k]} 
     338                for k,v in new_props.items(): 
     339                    if not k in old_props: 
     340                        changed_props[k] = {'new': v} 
     341                for k in hidden_properties: 
     342                    if k in changed_props: 
     343                        del changed_props[k] 
     344            return changed_props 
     345 
     346        def _content_changes(old_node, new_node): 
     347            """ 
     348            Returns the list of differences. 
     349            The list is empty when no differences between comparable files 
     350            are detected, but the return value is None for non-comparable files. 
     351            """ 
     352            default_charset = self.config.get('trac', 'default_charset') 
     353            old_content = old_node.get_content().read()             
     354            if mimeview.is_binary(old_content): 
     355                return None 
     356            charset = mimeview.get_charset(old_node.content_type) or \ 
     357                      default_charset 
     358            old_content = util.to_utf8(old_content, charset) 
     359 
     360            new_content = new_node.get_content().read() 
     361            if mimeview.is_binary(new_content): 
     362                return None 
     363            charset = mimeview.get_charset(new_node.content_type) or \ 
     364                      default_charset 
     365            new_content = util.to_utf8(new_content, charset) 
     366 
     367            if old_content != new_content: 
     368                context = 3 
     369                options = diff_options[1] 
     370                for option in options: 
     371                    if option.startswith('-U'): 
     372                        context = int(option[2:]) 
     373                        break 
     374                tabwidth = int(self.config.get('diff', 'tab_width', 
     375                                               self.config.get('mimeviewer', 
     376                                                               'tab_width'))) 
     377                return hdf_diff(old_content.splitlines(), 
     378                                new_content.splitlines(), 
     379                                context, tabwidth, 
     380                                ignore_blank_lines='-B' in options, 
     381                                ignore_case='-i' in options, 
     382                                ignore_space_changes='-b' in options) 
     383            else: 
     384                return [] 
     385 
     386        idx = 0 
     387        for old_node, new_node, kind, change in get_deltas(): 
     388            if change != Changeset.EDIT: 
     389                show_entry = True 
     390            else: 
     391                show_entry = False 
     392                assert old_node and new_node 
     393                props = _prop_changes(old_node, new_node) 
     394                if props: 
     395                    req.hdf['diff.changes.%d.props' % idx] = props 
     396                    show_entry = True 
     397                if kind == Node.FILE: 
     398                    diffs = _content_changes(old_node, new_node) 
     399                    if diffs != []: 
     400                        if diffs: 
     401                            req.hdf['diff.changes.%d.diff' % idx] = diffs 
     402                        # elif None (means: manually compare to (previous)) 
     403                        show_entry = True 
     404            if show_entry: 
     405                info = _change_info(old_node, new_node, change) 
     406                req.hdf['diff.changes.%d' % idx] = info 
     407            idx += 1 # the sequence should be immutable 
     408 
     409    def _render_diff(self, req, filename, repos, diff, diff_options): 
     410        """Raw Unified Diff version""" 
     411        req.send_response(200) 
     412        req.send_header('Content-Type', 'text/plain;charset=utf-8') 
     413        req.send_header('Content-Disposition', 
     414                        'filename=%s.diff' % filename) 
     415        req.end_headers() 
     416 
     417        for old_node, new_node, kind, change in repos.get_deltas(**diff): 
     418            # TODO: Property changes 
     419 
     420            # Content changes 
     421            if kind == Node.DIRECTORY: 
     422                continue 
     423 
     424            default_charset = self.config.get('trac', 'default_charset') 
     425            new_content = old_content = '' 
     426            new_node_info = old_node_info = ('','') 
     427 
     428            if old_node: 
     429                charset = mimeview.get_charset(old_node.content_type) or \ 
     430                          default_charset 
     431                old_content = util.to_utf8(old_node.get_content().read(), 
     432                                           charset) 
     433                old_node_info = (old_node.path, old_node.rev) 
     434                if mimeview.is_binary(old_content): 
     435                    continue 
     436 
     437            if new_node: 
     438                charset = mimeview.get_charset(new_node.content_type) or \ 
     439                          default_charset 
     440                new_content = util.to_utf8(new_node.get_content().read(), 
     441                                           charset) 
     442                new_node_info = (new_node.path, new_node.rev) 
     443                if mimeview.is_binary(new_content): 
     444                    continue 
     445                new_path = new_node.path 
     446            else: 
     447                old_node_path = repos.normalize_path(old_node.path) 
     448                diff_old_path = repos.normalize_path(diff.old_path) 
     449                new_path = posixpath.join(diff.new_path, 
     450                                          old_node_path[len(diff_old_path)+1:]) 
     451 
     452            if old_content != new_content: 
     453                context = 3 
     454                options = diff_options[1] 
     455                for option in options: 
     456                    if option.startswith('-U'): 
     457                        context = int(option[2:]) 
     458                        break 
     459                if not old_node_info[0]: 
     460                    old_node_info = new_node_info # support for 'A'dd changes 
     461                req.write('Index: ' + new_path + util.CRLF) 
     462                req.write('=' * 67 + util.CRLF) 
     463                req.write('--- %s (revision %s)' % old_node_info + 
     464                          util.CRLF) 
     465                req.write('+++ %s (revision %s)' % new_node_info + 
     466                          util.CRLF) 
     467                for line in unified_diff(old_content.splitlines(), 
     468                                         new_content.splitlines(), context, 
     469                                         ignore_blank_lines='-B' in options, 
     470                                         ignore_case='-i' in options, 
     471                                         ignore_space_changes='-b' in options): 
     472                    req.write(line + util.CRLF) 
     473 
     474    def _render_zip(self, req, filename, repos, diff): 
     475        """ZIP archive with all the added and/or modified files.""" 
     476        new_rev = diff.new_rev 
     477        req.send_response(200) 
     478        req.send_header('Content-Type', 'application/zip') 
     479        req.send_header('Content-Disposition', 
     480                        'filename=%s.zip' % filename) 
     481 
     482        try: 
     483            from cStringIO import StringIO 
     484        except ImportError: 
     485            from StringIO import StringIO 
     486        from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED 
     487 
     488        buf = StringIO() 
     489        zipfile = ZipFile(buf, 'w', ZIP_DEFLATED) 
     490        for old_node, new_node, kind, change in repos.get_deltas(**diff): 
     491            if kind == Node.FILE and change != Changeset.DELETE: 
     492                assert new_node 
     493                zipinfo = ZipInfo() 
     494                zipinfo.filename = new_node.path 
     495                zipinfo.date_time = time.gmtime(new_node.last_modified)[:6] 
     496                zipinfo.compress_type = ZIP_DEFLATED 
     497                zipfile.writestr(zipinfo, new_node.get_content().read()) 
     498        zipfile.close() 
     499 
     500        buf.seek(0, 2) # be sure to be at the end 
     501        req.send_header("Content-Length", buf.tell()) 
     502        req.end_headers() 
     503         
     504        req.write(buf.getvalue()) 
     505 
     506 
     507class DiffModule(AbstractDiffModule): 
     508 
     509    implements(IWikiSyntaxProvider) 
     510 
     511    # (reimplemented) IRequestHandler methods 
     512 
     513    def match_request(self, req): 
     514        match = re.match(r'/diff(?:(/.*)|$)', req.path_info) 
     515        if match: 
     516            if match.group(1): 
     517                req.args['path'] = match.group(1) 
     518            return 1 
     519 
     520    # IWikiSyntaxProvider methods 
     521     
     522    def get_wiki_syntax(self): 
     523        return [] 
     524 
     525    def get_link_resolvers(self): 
     526        yield ('diff', self._format_link) 
     527 
     528    def _format_link(self, formatter, ns, params, label): 
     529        def pathrev(path): 
     530            irev = path.find('#') 
     531            if irev > 0: 
     532                return (path[:irev], path[irev+1:]) 
     533            else: 
     534                return (path, None) 
     535        ianydiff = params.find('//') 
     536        if ianydiff > 0: 
     537            old_path, old_rev = pathrev(params[:ianydiff]) 
     538            new_path, new_rev = pathrev(params[ianydiff+2:]) 
     539        else:  
     540            old_path, old_rev = pathrev(params) 
     541            new_path = old_path 
     542            new_rev = None 
     543            if old_rev: 
     544                isep = old_rev.find(':') 
     545                if isep > 0: 
     546                    old_rev = old_rev[:isep] 
     547                    new_rev = old_rev[isep+1:] 
     548        href = formatter.href.diff(new_path, new=new_rev, 
     549                                   old_path=old_path, old=old_rev) 
     550        return '<a class="changeset" title="%s" href="%s">%s</a>' \ 
     551                   % ('Diff', href, label) 
     552 
     553 
     554class AnyDiffModule(Component): 
     555 
     556    implements(IRequestHandler) 
     557 
     558    # IRequestHandler methods 
     559 
     560    def match_request(self, req): 
     561        return re.match(r'/anydiff$', req.path_info) 
     562 
     563    def process_request(self, req): 
     564        # -- retrieve arguments 
     565        new_path = req.args.get('new_path') 
     566        new_rev = req.args.get('new_rev') 
     567        old_path = req.args.get('old_path') 
     568        old_rev = req.args.get('old_rev') 
     569 
     570        # -- normalize  
     571        repos = self.env.get_repository(req.authname) 
     572        new_path = repos.normalize_path(new_path) 
     573        new_rev = repos.normalize_rev(new_rev) 
     574        old_path = repos.normalize_path(old_path) 
     575        old_rev = repos.normalize_rev(old_rev) 
     576 
     577        # -- prepare rendering 
     578        req.hdf['anydiff'] = { 
     579            'new_path': new_path, 
     580            'new_rev': new_rev, 
     581            'old_path': old_path, 
     582            'old_rev': old_rev, 
     583            'diff_href': self.env.href.diff(), 
     584            } 
     585 
     586        return 'anydiff.cs', None 
  • trac/versioncontrol/web_ui/__init__.py

    diff -urN -x .svn trac-0.9b1/trac/versioncontrol/web_ui/__init__.py intertrac-branch/trac/versioncontrol/web_ui/__init__.py
    old new  
    1 from trac.versioncontrol.web_ui.browser import * 
    2 from trac.versioncontrol.web_ui.changeset import * 
    3 from trac.versioncontrol.web_ui.log import * 
     1from trac.versioncontrol.web_ui.browser import * 
     2from trac.versioncontrol.web_ui.changeset import * 
     3from trac.versioncontrol.web_ui.log import * 
     4from trac.versioncontrol.web_ui.diff import * 
     5 
  • trac/versioncontrol/web_ui/log.py

    diff -urN -x .svn trac-0.9b1/trac/versioncontrol/web_ui/log.py intertrac-branch/trac/versioncontrol/web_ui/log.py
    old new  
    6767        stop_rev = req.args.get('stop_rev') 
    6868        verbose = req.args.get('verbose') 
    6969        limit = LOG_LIMIT 
     70        old = req.args.get('old') 
     71        new = req.args.get('new') 
     72 
     73        repos = self.env.get_repository(req.authname) 
     74        normpath = repos.normalize_path(path) 
     75        rev = str(repos.normalize_rev(rev)) 
     76 
     77        if old and new: 
     78            osep = util.unescape(old).rindex('#') 
     79            nsep = util.unescape(new).rindex('#') 
     80            old_path, old_rev = old[:osep], old[osep+1:] 
     81            new_path, new_rev = new[:nsep], new[nsep+1:] 
     82            req.redirect(self.env.href.diff(new_path, new=new_rev, 
     83                                            old_path=old_path, old=old_rev)) 
    7084 
    7185        req.hdf['title'] = path + ' (log)' 
    7286        req.hdf['log'] = { 
     
    8397        if path_links: 
    8498            add_link(req, 'up', path_links[-1]['href'], 'Parent directory') 
    8599 
    86         repos = self.env.get_repository(req.authname) 
    87         normpath = repos.normalize_path(path) 
    88         rev = str(repos.normalize_rev(rev)) 
    89100 
    90101        # ''Node'' history uses `get_node()`, 
    91102        # ''Path'' history uses `get_path_history()` 
  • trac/web/standalone.py

    diff -urN -x .svn trac-0.9b1/trac/web/standalone.py intertrac-branch/trac/web/standalone.py
    old new  
    139139        self.auths = auths 
    140140 
    141141        self.projects = {} 
     142        siblings = {} 
    142143        for env_path in env_paths: 
    143144            # Remove trailing slashes 
    144145            while env_path and not os.path.split(env_path)[1]: 
    145146                env_path = os.path.split(env_path)[0] 
    146147            project = os.path.split(env_path)[1] 
    147148            self.projects[project] = env_path 
     149            env = get_environment(None, self.get_env_opts(project)) 
     150            siblings[project] = env 
     151        for p in siblings.values(): 
     152            p.siblings = siblings 
    148153 
    149154    def get_env_opts(self, project=None): 
    150155        if self.env_parent_dir: 
    151156            opts = self.env_parent_dir.items() 
    152157        else: 
    153158            opts = [('TRAC_ENV', self.projects[project])] 
    154         return dict(opts + os.environ.items()) 
     159        return dict(os.environ.items() + opts) # TODO: backport 
    155160 
    156161    def send_project_index(self, req): 
    157162        if self.env_parent_dir: 
  • trac/wiki/api.py

    diff -urN -x .svn trac-0.9b1/trac/wiki/api.py intertrac-branch/trac/wiki/api.py
    old new  
    2323    import dummy_threading as threading 
    2424import time 
    2525import urllib 
     26import re 
    2627 
    2728from trac.core import * 
    2829from trac.util import to_utf8, TRUE 
     
    6566    def get_link_resolvers(): 
    6667        """Return an iterable over (namespace, formatter) tuples.""" 
    6768  
     69class IWikiPageNameSyntaxProvider(Interface): 
     70  
     71    def get_wiki_page_names_syntax(): 
     72        """ 
     73        Return an iterable that provides a regular expression for 
     74        matching wiki page names (see WikiPageNames) 
     75 
     76        Be careful to only allow __one__ implementation 
     77        (others should be listed in the ![disabled_components] 
     78        section of the TracIni) 
     79        """ 
     80  
    6881 
    6982class WikiSystem(Component): 
    7083    """Represents the wiki system.""" 
     
    7487    change_listeners = ExtensionPoint(IWikiChangeListener) 
    7588    macro_providers = ExtensionPoint(IWikiMacroProvider) 
    7689    syntax_providers = ExtensionPoint(IWikiSyntaxProvider) 
     90    wikipagenames_providers = ExtensionPoint(IWikiPageNameSyntaxProvider) 
    7791 
    7892    INDEX_UPDATE_INTERVAL = 5 # seconds 
    7993 
     
    8195        self._index = None 
    8296        self._last_index_update = 0 
    8397        self._index_lock = threading.RLock() 
     98        self._compiled_rules = None 
     99        self._link_resolvers = None 
     100        self._helper_patterns = None 
     101        self._external_handlers = None 
    84102 
    85103    def _update_index(self): 
    86104        self._index_lock.acquire() 
     
    116134        self._update_index() 
    117135        return pagename in self._index.keys() 
    118136 
     137    def _get_rules(self): 
     138        self._prepare_rules() 
     139        return self._compiled_rules 
     140    rules = property(_get_rules) 
     141 
     142    def _get_helper_patterns(self): 
     143        self._prepare_rules() 
     144        return self._helper_patterns 
     145    helper_patterns = property(_get_helper_patterns) 
     146 
     147    def _get_external_handlers(self): 
     148        self._prepare_rules() 
     149        return self._external_handlers 
     150    external_handlers = property(_get_external_handlers) 
     151     
     152    def _prepare_rules(self): 
     153        from trac.wiki.formatter import Formatter 
     154        if not self._compiled_rules: 
     155            helpers = [] 
     156            handlers = {} 
     157            syntax = Formatter._pre_rules[:] 
     158            i = 0 
     159            for resolver in self.syntax_providers: 
     160                for regexp, handler in resolver.get_wiki_syntax(): 
     161                    handlers['i'+str(i)] = handler 
     162                    syntax.append('(?P<i%d>%s)' % (i, regexp)) 
     163                    i += 1 
     164            syntax += Formatter._post_rules[:] 
     165            helper_re = re.compile(r'\?P<([a-z\d_]+)>') 
     166            for rule in syntax: 
     167                helpers += helper_re.findall(rule)[1:] 
     168            rules = re.compile('(?:' + '|'.join(syntax) + ')') 
     169            self._external_handlers = handlers 
     170            self._helper_patterns = helpers 
     171            self._compiled_rules = rules 
     172 
     173    def _get_link_resolvers(self): 
     174        if not self._link_resolvers: 
     175            resolvers = {} 
     176            for resolver in self.syntax_providers: 
     177                for namespace, handler in resolver.get_link_resolvers(): 
     178                    resolvers[namespace] = handler 
     179            self._link_resolvers = resolvers 
     180        return self._link_resolvers 
     181    link_resolvers = property(_get_link_resolvers) 
     182 
    119183    # IWikiChangeListener methods 
    120184 
    121185    def wiki_page_added(self, page): 
     
    136200    def get_wiki_syntax(self): 
    137201        ignore_missing = self.config.get('wiki', 'ignore_missing_pages') 
    138202        ignore_missing = ignore_missing in TRUE 
    139         yield (r"!?(?<!/)\b[A-Z][a-z]+(?:[A-Z][a-z]*[a-z/])+" 
    140                 "(?:#[A-Za-z0-9]+)?(?=\Z|\s|[.,;:!?\)}\]])", 
    141                lambda x, y, z: self._format_link(x, 'wiki', y, y, 
    142                                                  ignore_missing)) 
     203        providers = [] 
     204        for p in self.wikipagenames_providers: 
     205            if not providers: 
     206                yield (p.get_wiki_page_names_syntax(), 
     207                       lambda x, y, z: self._format_link(x, 'wiki', y, y, 
     208                                                         ignore_missing)) 
     209            pc = p.__class__ 
     210            providers.append('# %s\n%s.%s = yes' % (pc.__doc__.split('\n')[0], 
     211                                                    pc.__module__, pc.__name__) 
     212                                                    ) 
     213        if len(providers) > 1: 
     214            self.log.warning('More than one IWikiPageNameSyntaxProvider ' 
     215                             'implementation available:\n' 
     216                             'You should set one of the following to "no" ' 
     217                             'in your trac.ini:\n\n' 
     218                             '[disabled_components]\n' + 
     219                             '\n'.join(providers) +'\n') 
    143220 
    144221    def get_link_resolvers(self): 
    145222        yield ('wiki', self._format_fancy_link) 
     
    163240        else: 
    164241            return '<a class="wiki" href="%s">%s</a>' \ 
    165242                   % (formatter.href.wiki(page) + anchor, label) 
     243 
     244 
     245WIKI_START = r"!?(?<!/)\b" 
     246WIKI_TARGET = r"(?:#[A-Za-z0-9]+)?" 
     247WIKI_END = r"(?=\Z|\s|[.,;:!?\)}\]])" 
     248WIKI_INTERWIKI = r"(?!:\S)" 
     249 
     250class StandardWikiPageNames(Component): 
     251    """Standard Trac WikiPageNames rule""" 
     252 
     253    implements(IWikiPageNameSyntaxProvider) 
     254 
     255    def get_wiki_page_names_syntax(self): 
     256        return (WIKI_START +                # where to start 
     257                r"[A-Z][a-z]+"              # initial WikiPageNames word 
     258                r"(?:[A-Z][a-z]*[a-z/])+" + # additional WikiPageNames word 
     259                WIKI_TARGET +               # optional trailing section link 
     260                WIKI_END +                  # where to end 
     261                WIKI_INTERWIKI)             # InterWiki support 
     262     
     263class FlexibleWikiPageNames(Component): 
     264    """Standard Trac WikiPageNames rule, plus digits 
     265    and consecutive upper-case characters allowed. 
     266 
     267    More precisely, WikiPageNames are: 
     268     * either 2 or more starting upper case letter or digits, 
     269       followed by lower case letters 
     270     * either 1 or more starting upper case letter or digits, 
     271       followed by lower case letters, repeated at least 2 times 
     272       (with optionally '/' between repetitions) 
     273    """ 
     274 
     275    implements(IWikiPageNameSyntaxProvider) 
     276 
     277    def get_wiki_page_names_syntax(self): 
     278        return (WIKI_START + 
     279                r"(?:[A-Z\d]{2,}[a-z]+"                    # 1st way 
     280                r"|[A-Z\d]+[a-z]+(?:/?[A-Z\d]+[a-z]*)+)" + # 2nd way 
     281                WIKI_TARGET + WIKI_END + WIKI_INTERWIKI) 
     282 
     283class SubWikiPageNames(Component): 
     284    """SubWiki-like rules for WikiPageNames. 
     285     
     286    See http://www.webdav.org/wiki/projects/TextFormattingRules 
     287 
     288    Note that '/' in this style of WikiPageNames are not supported. 
     289    """ 
     290 
     291    implements(IWikiPageNameSyntaxProvider) 
     292 
     293    def get_wiki_page_names_syntax(self): 
     294        return (WIKI_START + 
     295                r"(?:[A-Z][A-Z]+[a-z\d]+[A-Z]*"    # 1st and 3rd way 
     296                r"|[A-Z][a-z]+(?:[A-Z][a-z]+)+)" + # 2nd way 
     297                WIKI_TARGET + WIKI_END + WIKI_INTERWIKI) 
     298 
  • trac/wiki/formatter.py

    diff -urN -x .svn trac-0.9b1/trac/wiki/formatter.py intertrac-branch/trac/wiki/formatter.py
    old new  
    1919from __future__ import generators 
    2020import re 
    2121import os 
    22 import string 
    2322import urllib 
    2423 
    2524try: 
     
    2827    from StringIO import StringIO 
    2928 
    3029from trac import util 
     30from trac.core import * 
    3131from trac.mimeview import * 
    32 from trac.wiki.api import WikiSystem 
     32from trac.wiki.api import WikiSystem, IWikiChangeListener, IWikiMacroProvider 
    3333 
    3434__all__ = ['wiki_to_html', 'wiki_to_oneliner', 'wiki_to_outline'] 
    3535 
     
    136136class Formatter(object): 
    137137    flavor = 'default' 
    138138 
    139     _link_resolvers = None 
    140139    # Rules provided by IWikiSyntaxProviders are inserted between pre_rules and post_rules 
    141140    _pre_rules = [r"(?P<bolditalic>%s)" % BOLDITALIC_TOKEN, 
    142141                  r"(?P<bold>%s)" % BOLD_TOKEN, 
     
    165164                   r"(?P<last_table_cell>\|\|\s*$)", 
    166165                   r"(?P<table_cell>\|\|)"] 
    167166 
    168     _compiled_rules = None 
    169     _helper_patterns = None 
    170     _external_handlers = None 
    171167    _processor_re = re.compile('#\!([\w+-][\w+-/]*)') 
    172168    _anchor_re = re.compile('[^\w\d\.-:]+', re.UNICODE) 
    173169     
     
    194190    db = property(fget=_get_db) 
    195191 
    196192    def _get_rules(self): 
    197         if not Formatter._compiled_rules: 
    198             helpers = [] 
    199             handlers = {} 
    200             syntax = Formatter._pre_rules[:] 
    201             wiki = WikiSystem(self.env) 
    202             i = 0 
    203             for resolver in wiki.syntax_providers: 
    204                 for regexp, handler in resolver.get_wiki_syntax(): 
    205                     handlers['i'+str(i)] = handler 
    206                     syntax.append('(?P<i%d>%s)' % (i, regexp)) 
    207                     i += 1 
    208             syntax += Formatter._post_rules[:] 
    209             helper_re = re.compile(r'\?P<([a-z\d]+)>') 
    210             for rule in syntax: 
    211                 helpers += helper_re.findall(rule)[1:] 
    212             rules = re.compile('(?:' + string.join(syntax, '|') + ')') 
    213             Formatter._external_handlers = handlers 
    214             Formatter._helper_patterns = helpers 
    215             Formatter._compiled_rules = rules 
    216         return Formatter._compiled_rules 
     193        return WikiSystem(self.env).rules 
    217194    rules = property(_get_rules) 
    218195 
    219196    def _get_link_resolvers(self): 
    220         if not self._link_resolvers: 
    221             resolvers = {} 
    222             wiki = WikiSystem(self.env) 
    223             for resolver in wiki.syntax_providers: 
    224                 for namespace, handler in resolver.get_link_resolvers(): 
    225                     resolvers[namespace] = handler 
    226             self._link_resolvers = resolvers 
    227         return self._link_resolvers 
     197        return WikiSystem(self.env).link_resolvers 
    228198    link_resolvers = property(_get_link_resolvers) 
    229199 
    230200    def replace(self, fullmatch): 
     201        wiki = WikiSystem(self.env)         
    231202        for itype, match in fullmatch.groupdict().items(): 
    232             if match and not itype in Formatter._helper_patterns: 
     203            if match and not itype in wiki.helper_patterns: 
    233204                # Check for preceding escape character '!' 
    234205                if match[0] == '!': 
    235206                    return match[1:] 
    236                 if itype in self._external_handlers: 
    237                     return self._external_handlers[itype](self, match, fullmatch) 
     207                if itype in wiki.external_handlers: 
     208                    return wiki.external_handlers[itype](self, match, fullmatch) 
    238209                else: 
    239210                    return getattr(self, '_' + itype + '_formatter')(match, fullmatch) 
    240211 
     
    297268            return self._make_link(ns, target, match, label) 
    298269 
    299270    def _make_link(self, ns, target, match, label): 
     271        # check first for an alias defined in trac.ini 
     272        ns = self.env.config.get('intertrac', ns.upper(), ns) 
    300273        if ns in self.link_resolvers: 
    301             return self._link_resolvers[ns](self, ns, target, label) 
     274            return self.link_resolvers[ns](self, ns, target, label) 
    302275        elif target.startswith('//') or ns == "mailto": 
    303276            return self._make_ext_link(ns+':'+target, label) 
    304277        else: 
    305             return match 
     278            intertrac = self._make_intertrac_link(ns, target, label) 
     279            if intertrac: 
     280                return intertrac 
     281            else: 
     282                interwiki = self._make_interwiki_link(ns, target, label) 
     283                if interwiki: 
     284                    return interwiki 
     285                else: 
     286                    return match 
     287 
     288    def _make_intertrac_link(self, ns, target, label): 
     289        if self.env.siblings.has_key(ns): 
     290            sibling = self.env.siblings[ns] 
     291            if not hasattr(sibling, 'href'): 
     292                from trac.web.href import Href 
     293                def xchg_base(base): 
     294                    return '/'.join(base.split('/')[:-1] + [ns]) 
     295                sibling.href = Href(xchg_base(self.env.href.base)) 
     296                sibling.abs_href = Href(xchg_base(self.env.abs_href.base)) 
     297            ref = wiki_to_oneliner(target, sibling) 
     298            return ref.replace('>%s' % target, '>%s' % label) 
     299        url = self.env.config.get('intertrac', ns.upper()+'.url') 
     300        if url: 
     301            name = self.env.config.get('intertrac', ns.upper()+'.title', 
     302                                       'Trac project %s' % ns) 
     303            sep = target.find(':') 
     304            if sep != -1: 
     305                url = '%s/%s/%s' % (url, target[:sep], target[sep+1:]) 
     306            else:  
     307                url = '%s/search?q=%s' % (url, urllib.quote_plus(target)) 
     308            return self._make_ext_link(url, label, '%s in %s' % (target, name)) 
     309        else: 
     310            return None 
     311 
     312    def shorthand_intertrac_helper(self, ns, target, label, fullmatch): 
     313        if fullmatch: # short form 
     314            alias = fullmatch.group('it_%s' % ns) 
     315            if alias: 
     316                intertrac = self.env.config.get('intertrac', alias.upper(), alias) 
     317                target = '%s:%s' % (ns, target[len(alias):]) 
     318                it = self._make_intertrac_link(intertrac, target, label) 
     319                return it or label 
     320        return None 
     321 
     322    def _make_interwiki_link(self, ns, target, label): 
     323        interwiki = InterWikiMap(self.env) 
     324        if interwiki.has_key(ns): 
     325            url, title = interwiki.url(ns, target) 
     326            return self._make_ext_link(url, label, '%s in %s' % (target, title)) 
     327        else: 
     328            return None 
    306329 
    307330    def _make_ext_link(self, url, text, title=''): 
    308331        title_attr = title and ' title="%s"' % title or '' 
     
    332355        if match[0] == '!': 
    333356            return match[1:] 
    334357        else: 
    335             return self.simple_tag_handler('<span class="underline">', '</span>') 
     358            return self.simple_tag_handler('<span class="underline">', 
     359                                           '</span>') 
    336360 
    337361    def _strike_formatter(self, match, fullmatch): 
    338362        if match[0] == '!': 
     
    723747    out = StringIO() 
    724748    OutlineFormatter(env, absurls, db).format(wikitext, out, max_depth) 
    725749    return out.getvalue() 
     750 
     751 
     752# -- InterWiki support 
     753 
     754class InterWikiMap(Component): 
     755 
     756    implements(IWikiChangeListener, IWikiMacroProvider) 
     757 
     758    _page_name = 'InterMapTxt' 
     759    _interwiki_re = re.compile(r"(\w+)[ \t]+([^ \t]+)(?:[ \t]+#(.*))?", 
     760                               re.UNICODE) 
     761    _argspec_re = re.compile(r"\$\d") 
     762 
     763    def __init__(self): 
     764        self._interwiki_map = None 
     765        # This dictionary maps upper-cased namespaces 
     766        # to (namespace, prefix, title) values 
     767 
     768    def has_key(self, ns): 
     769        if not self._interwiki_map: 
     770            self._update() 
     771        return self._interwiki_map.has_key(ns.upper()) 
     772 
     773    def url(self, ns, target): 
     774        ns, url, title = self._interwiki_map[ns.upper()] 
     775        args = target.split(':') 
     776        def setarg(match): 
     777            num = int(match.group()[1:]) 
     778            return 0 < num <= len(args) and args[num-1] or '' 
     779        url_with_args = re.sub(InterWikiMap._argspec_re, setarg, url) 
     780        if url_with_args == url:  
     781            return url + target, title 
     782        else: 
     783            return url_with_args, title 
     784 
     785    # IWikiChangeListener methods 
     786 
     787    def wiki_page_added(self, page): 
     788        if page.name == InterWikiMap._page_name: 
     789            self._update() 
     790 
     791    def wiki_page_changed(self, page, version, t, comment, author, ipnr): 
     792        if page.name == InterWikiMap._page_name: 
     793            self._update() 
     794 
     795    def wiki_page_deleted(self, page): 
     796        if page.name == InterWikiMap._page_name: 
     797            self._interwiki_map.clear() 
     798 
     799    def _update(self): 
     800        from trac.wiki.model import WikiPage 
     801        self._interwiki_map = {} 
     802        content = WikiPage(self.env, InterWikiMap._page_name).text 
     803        in_map = False 
     804        for line in content.split('\n'): 
     805            if in_map: 
     806                if line.startswith('----'): 
     807                    in_map = False 
     808                else: 
     809                    m = re.match(InterWikiMap._interwiki_re, line) 
     810                    if m: 
     811                        prefix, url, title = m.groups() 
     812                        url = url.strip() 
     813                        title = title and title.strip() or prefix 
     814                        self._interwiki_map[prefix.upper()] = (prefix, url, 
     815                                                               title) 
     816            elif line.startswith('----'): 
     817                in_map = True 
     818 
     819    # IWikiMacroProvider 
     820 
     821    def get_macros(self): 
     822        yield 'InterWiki' 
     823 
     824    def get_macro_description(self, name):  
     825        return "Provide a description list for the known InterWiki prefixes." 
     826 
     827    def render_macro(self, req, name, content): 
     828        if not self._interwiki_map: 
     829            self._update() 
     830        keys = self._interwiki_map.keys() 
     831        keys.sort() 
     832        buf = StringIO() 
     833        buf.write('<table><tr><th>Prefix</th><td>Site</td></tr>\n') 
     834        for k in keys: 
     835            prefix, url, title = self._interwiki_map[k] 
     836            shortened_url = url and url[:-1] 
     837            description = title == prefix and shortened_url or title 
     838            buf.write('<tr>\n' + 
     839                      ('<td><a href="%sRecentChanges">%s</a></td>' 
     840                       '<td><a href="%s">%s</a></td>\n') \ 
     841                      % (url, prefix, shortened_url, description) + 
     842                      '</tr>\n') 
     843        buf.write('</table>\n') 
     844        return buf.getvalue() 
  • trac/wiki/tests/formatter.py

    diff -urN -x .svn trac-0.9b1/trac/wiki/tests/formatter.py intertrac-branch/trac/wiki/tests/formatter.py
    old new  
    5353                self.config = Configuration(None) 
    5454                self.href = Href('/') 
    5555                self.abs_href = Href('http://www.example.com/') 
    56                 self._wiki_pages = {} 
    5756                self.path = '' 
     57                self.siblings = {} 
    5858            def component_activated(self, component): 
    5959                component.env = self 
    6060                component.config = self.config 
  • trac/wiki/tests/wiki-tests.txt

    diff -urN -x .svn trac-0.9b1/trac/wiki/tests/wiki-tests.txt intertrac-branch/trac/wiki/tests/wiki-tests.txt
    old new  
    8080<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> 
    8181</p> 
    8282============================== 
    83 CamelCase,CamelCase.CamelCase:CamelCase 
     83CamelCase,CamelCase.CamelCase: CamelCase 
    8484------------------------------ 
    8585<p> 
    86 <a class="missing wiki" href="/wiki/CamelCase" rel="nofollow">CamelCase?</a>,<a class="missing wiki" href="/wiki/CamelCase" rel="nofollow">CamelCase?</a>.CamelCase:CamelCase 
     86<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> 
    8787</p> 
    8888============================== 
    8989!CamelCase 
  • wiki-default/checkwiki.py

    diff -urN -x .svn trac-0.9b1/wiki-default/checkwiki.py intertrac-branch/wiki-default/checkwiki.py
    old new  
     1#!/usr/bin/python 
     2# 
     3# Check/update default wiki pages from the Trac project website. 
     4# 
     5# Note: This is a development tool used in Trac packaging/QA, not something 
     6#       particularly useful for end-users. 
     7# 
     8# Author: Daniel Lundin <daniel@edgewall.com> 
     9 
     10import httplib 
     11import re 
     12import sys 
     13import getopt 
     14 
     15# Pages to include in distribution 
     16wiki_pages = [ 
     17 "CamelCase", 
     18 "RecentChanges", 
     19 "TitleIndex", 
     20 "TracAccessibility", 
     21 "TracAdmin", 
     22 "TracBackup", 
     23 "TracBrowser", 
     24 "TracCgi", 
     25 "TracChangeset", 
     26 "TracEnvironment", 
     27 "TracFastCgi", 
     28 "TracGuide", 
     29 "TracIni", 
     30 "TracInstall", 
     31 "TracImport", 
     32 "TracLinks", 
     33 "TracLogging", 
     34 "TracModPython", 
     35 "TracMultipleProjects", 
     36 "TracNotification", 
     37 "TracPermissions", 
     38 "TracPlugins", 
     39 "TracQuery", 
     40 "TracReports", 
     41 "TracRoadmap", 
     42 "TracRss", 
     43 "TracSearch", 
     44 "TracStandalone", 
     45 "TracSupport", 
     46 "TracSyntaxColoring", 
     47 "TracTickets", 
     48 "TracTicketsCustomFields", 
     49 "TracTimeline", 
     50 "TracUnicode", 
     51 "TracUpgrade", 
     52 "TracWiki", 
     53 "WikiDeletePage", 
     54 "WikiFormatting", 
     55 "WikiHtml", 
     56 "WikiMacros", 
     57 "WikiNewPage", 
     58 "WikiPageNames", 
     59 "WikiProcessors", 
     60 "WikiRestructuredText", 
     61 "WikiRestructuredTextLinks" 
     62 ] 
     63 
     64def get_page_from_file (pname): 
     65    d = '' 
     66    try: 
     67        f = open(pname ,'r') 
     68        d = f.read() 
     69        f.close() 
     70    except: 
     71        print "Missing page: %s" % pname 
     72    return d 
     73 
     74def get_page_from_web (pname): 
     75    host = "projects.edgewall.com" 
     76    rfile = "/trac/wiki/%s?format=txt" % pname 
     77    c = httplib.HTTPConnection(host) 
     78    c.request("GET", rfile) 
     79    r = c.getresponse() 
     80    d = r.read() 
     81    if r.status != 200 or d == ("describe %s here\n" % pname): 
     82        c.close() 
     83        print "Missing page: %s" % pname 
     84    c.close() 
     85    f = open(pname, 'w+') 
     86    f.write(d) 
     87    f.close() 
     88    return d 
     89 
     90def check_links (data): 
     91    def get_refs(t, refs=[]): 
     92        r = "(?P<wikilink>(^|(?<=[^A-Za-z]))[!]?[A-Z][a-z/]+(?:[A-Z][a-z/]+)+)" 
     93        m = re.search (r, t) 
     94        if not m: 
     95            refs.sort() 
     96            result = [] 
     97            orf = None 
     98            for rf in refs: 
     99                if rf != orf: 
     100                    result.append(rf) 
     101                    orf = rf 
     102            return result 
     103        refs.append(m.group()) 
     104        return get_refs( t[m.end():], refs) 
     105    for p in data.keys(): 
     106        links = get_refs(data[p], []) 
     107        for l in links: 
     108            if l not in data.keys(): 
     109                print "Broken link:  %s -> %s" % (p, l) 
     110 
     111if __name__ == '__main__': 
     112    try: 
     113        opts, args = getopt.getopt(sys.argv[1:], "ds") 
     114    except getopt.GetoptError: 
     115        # print help information and exit: 
     116        print "%s [-d]" % sys.argv[0] 
     117        print "\t-d  -- Download pages from the main project wiki." 
     118        sys.exit() 
     119    get_page = get_page_from_file 
     120    for o,a in opts: 
     121        if o == '-d': 
     122            get_page = get_page_from_web 
     123    data = {} 
     124    for p in wiki_pages: 
     125        data[p] = get_page (p) 
     126    check_links(data) 
     127 
  • wiki-default/InterMapTxt

    diff -urN -x .svn trac-0.9b1/wiki-default/InterMapTxt intertrac-branch/wiki-default/InterMapTxt
    old new  
     1= InterMapTxt = 
     2 
     3This is the InterMapTxt wiki page, modelled after the MeatBall:InterMapTxt page. 
     4 
     5This page is interpreted in a special way by Trac, in order to support 
     6!InterWiki links in a flexible and dynamic way. 
     7 
     8The code block after the first line separator in this page 
     9will be interpreted as a list of !InterWiki specifications: 
     10{{{ 
     11prefix <space> URL [<space> # comment] 
     12}}} 
     13 
     14By using `$1`, `$2`, etc. within the URL, it is possible to create  
     15InterWiki links which support multiple arguments, e.g. Trac:ticket:40. 
     16The URL itself can be optionally followed by a comment,  
     17which will subsequently be used for decorating the links  
     18using that prefix. 
     19 
     20New !InterWiki links can be created by adding to that list, in real time. 
     21Note however that ''deletions'' are also taken into account immediately, 
     22so it may be better to use comments for disabling prefixes. 
     23 
     24Also note that !InterWiki prefixes are case insensitive. 
     25 
     26 
     27== List of Active Prefixes == 
     28 
     29[[InterWiki]] 
     30 
     31---- 
     32 
     33== Prefix Definitions == 
     34 
     35{{{ 
     36Trac http://projects.edgewall.com/trac/$1/$2   # The Official Trac for Trac 
     37TracML http://thread.gmane.org/gmane.comp.version-control.subversion.trac.general/ # Trac Mailing List Archive 
     38 
     39# 
     40# From MeatBall:InterMapTxt, rev 276 (19th of August 2005): 
     41# 
     42# TODO: this list requires some cleanup... many wikis are not active anymore 
     43# 
     44AbbeNormal http://ourpla.net/cgi/pikie? 
     45AcadWiki http://xarch.tu-graz.ac.at/autocad/wiki/ 
     46Acronym http://www.acronymfinder.com/af-query.asp?String=exact&Acronym= 
     47Advogato http://www.advogato.org/ 
     48ALife http://news.alife.org/wiki/index.php? 
     49AnnotationWiki http://www.seedwiki.com/page.cfm?wikiid=368&doc= 
     50AtomWiki http://intertwingly.net/wiki/pie/ 
     51BeatWiki: http://wiki.doebe.li/ 
     52BookShelved http://bookshelved.org/cgi-bin/wiki.pl? 
     53BridgesWiki http://c2.com:8000/ 
     54C2find http://c2.com/cgi/wiki?FindPage&value= 
     55Cache http://www.google.com/search?q=cache: 
     56CarpeWiki http://carpe.com/wiki/wiki.pl? 
     57CommunityWiki http://www.emacswiki.org/cgi-bin/community? 
     58CPAN http://search.cpan.org/perldoc? 
     59CraoWiki http://wiki.crao.net/index.php/ 
     60CreationMatters http://www.ourpla.net/cgi-bin/wiki.pl? 
     61DebianBug http://bugs.debian.org/ 
     62DebianPackage http://packages.debian.org/ 
     63Dictionary http://www.dict.org/bin/Dict?Database=*&Form=Dict1&Strategy=*&Query= 
     64DseWiki http://www.wikiservice.at/dse/wiki.cgi? 
     65EmacsWiki http://www.emacswiki.org/cgi-bin/wiki.pl? 
     66FIRSTwiki http://www.firstwiki.org/ 
     67Foldoc http://www.foldoc.org/foldoc/foldoc.cgi? 
     68FoxWiki http://fox.wikis.com/wc.dll?Wiki~ 
     69FractalWiki http://www.wikiservice.at/fractal/wikidev.cgi? 
     70Google http://www.google.com/search?q= 
     71GoogleGroups http://groups.google.com/groups?q= 
     72HammondWiki http://www.dairiki.org/HammondWiki/ 
     73HerzKinderWiki http://www.herzkinderinfo.de/Mediawiki/index.php/ 
     74h2g2 http://www.bbc.co.uk/dna/h2g2/ 
     75IAWiki http://www.IAwiki.net/ 
     76IMDB http://us.imdb.com/Title? 
     77JargonFile http://downlode.org/perl/jargon-redirect.cgi?term= 
     78JspWiki http://www.ecyrd.com/JSPWiki/Wiki.jsp?page= 
     79JuraWiki http://jurawiki.de/ 
     80KnowHow http://www2.iro.umontreal.ca/~paquetse/cgi-bin/wiki.cgi? 
     81LinuxWiki http://linuxwiki.org/ 
     82LiveJournal http://livejournal.com/users/ 
     83Login http://www.usemod.com/cgi-bin/mb.pl?action=login&p_userid= 
     84MbTest http://www.usemod.com/cgi-bin/mbtest.pl? 
     85MeatBall http://www.usemod.com/cgi-bin/mb.pl? 
     86MetaWiki http://sunir.org/apps/meta.pl? 
     87MetaWikiPedia http://meta.wikipedia.org/wiki/ 
     88Mineralienatlas http://www.min