Edgewall Software

Ticket #2028: trac-0.9b2-anydiff-r2306.patch

File trac-0.9b2-anydiff-r2306.patch, 92.1 KB (added by cboos, 6 years ago)

Patch adding the TracDiff features to trac-0.9b2

  • htdocs/css/browser.css

    diff -ruN -x .svn trac-0.9b2/htdocs/css/browser.css anydiff-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 -ruN -x .svn trac-0.9b2/htdocs/css/changeset.css anydiff-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 -ruN -x .svn trac-0.9b2/templates/anydiff.cs anydiff-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 -ruN -x .svn trac-0.9b2/templates/browser.cs anydiff-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.restr_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 -ruN -x .svn trac-0.9b2/templates/changeset.cs anydiff-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.new ?> 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 -ruN -x .svn trac-0.9b2/templates/diff.cs anydiff-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.new ?> 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 -ruN -x .svn trac-0.9b2/templates/log.cs anydiff-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> 
     
    117133     <td class="author"><?cs var:log.changes[item.rev].author ?></td> 
    118134     <td class="summary"><?cs var:log.changes[item.rev].message ?></td> 
    119135    </tr><?cs 
     136    set:idx = idx + 1 ?><?cs 
    120137   /each ?> 
    121138  </tbody> 
    122  </table><?cs 
     139 </table> 
     140 <div class="buttons"><input type="submit" value="View changes"  
     141      title="Diff from Old Revision to New Revision (select them above)" /> 
     142 </div> 
     143 </form><?cs 
    123144 if:len(links.prev) || len(links.next) ?><div id="paging" class="nav"><ul><?cs 
    124145  if:len(links.prev) ?><li class="first<?cs 
    125146   if:!len(links.next) ?> last<?cs /if ?>">&larr; <a href="<?cs 
  • templates/wiki.cs

    diff -ruN -x .svn trac-0.9b2/templates/wiki.cs anydiff-branch/templates/wiki.cs
    old new  
    154154    var:wiki.page_name ?></a></h1> 
    155155  <?cs if:len(wiki.history) ?><form method="get" action=""> 
    156156   <input type="hidden" name="action" value="diff" /> 
     157   <div class="buttons"> 
     158    <input type="submit" value="View changes" /> 
     159   </div> 
    157160   <table id="wikihist" class="listing" summary="Change history"> 
    158161    <thead><tr> 
    159162     <th class="diff"></th> 
  • trac/__init__.py

    diff -ruN -x .svn trac-0.9b2/trac/__init__.py anydiff-branch/trac/__init__.py
    old new  
    1010""" 
    1111__docformat__ = 'epytext en' 
    1212 
    13 __version__ = '0.9b2' 
     13__version__ = '0.9b2-anydiff' 
    1414__url__ = 'http://trac.edgewall.com/' 
    1515__copyright__ = '(C) 2003-2005 Edgewall Software' 
    1616__license__ = 'BSD' 
  • trac/versioncontrol/api.py

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

    diff -ruN -x .svn trac-0.9b2/trac/versioncontrol/cache.py anydiff-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 -ruN -x .svn trac-0.9b2/trac/versioncontrol/diff.py anydiff-branch/trac/versioncontrol/diff.py
    old new  
    218218                           ignore_space_changes) 
    219219    for group in _group_opcodes(opcodes, context): 
    220220        i1, i2, j1, j2 = group[0][1], group[-1][2], group[0][3], group[-1][4] 
     221        if i1 == 0 and i2 == 0: 
     222            i1, i2 = -1, -1 # support for 'A'dd changes 
    221223        yield '@@ -%d,%d +%d,%d @@' % (i1 + 1, i2 - i1, j1 + 1, j2 - j1) 
    222224        for tag, i1, i2, j1, j2 in group: 
    223225            if tag == 'equal': 
  • trac/versioncontrol/svn_fs.py

    diff -ruN -x .svn trac-0.9b2/trac/versioncontrol/svn_fs.py anydiff-branch/trac/versioncontrol/svn_fs.py
    old new  
    207207    def __del__(self): 
    208208        self.close() 
    209209 
     210    def has_node(self, path, rev): 
     211        rev_root = fs.revision_root(self.fs_ptr, rev, self.pool()) 
     212        node_type = fs.check_path(rev_root, path, self.pool()) 
     213        return node_type in _kindmap 
     214 
    210215    def normalize_path(self, path): 
    211216        return (not path or path == '/') and '/' or path.strip('/') 
    212217 
     
    298303        subpool = Pool(self.pool) 
    299304        while rev: 
    300305            subpool.clear() 
    301             rev_root = fs.revision_root(self.fs_ptr, rev, subpool()) 
    302             node_type = fs.check_path(rev_root, path, subpool()) 
    303             if node_type in _kindmap: # then path exists at that rev 
     306            if self.has_node(path, rev): 
    304307                if expect_deletion: 
    305308                    # it was missing, now it's there again: 
    306309                    #  rev+1 must be a delete 
     
    330333                expect_deletion = True 
    331334                rev = self.previous_rev(rev) 
    332335 
     336    def get_deltas(self, old_path, old_rev, new_path, new_rev, 
     337                   ignore_ancestry=0): 
     338        old_node = new_node = None 
     339        old_rev = self.normalize_rev(old_rev) 
     340        new_rev = self.normalize_rev(new_rev) 
     341        if self.has_node(old_path, old_rev): 
     342            old_node = self.get_node(old_path, old_rev) 
     343        else: 
     344            raise TracError, ('The Base for Diff is invalid: path %s' 
     345                              ' doesn\'t exist in revision %s' \ 
     346                              % (old_path, old_rev)) 
     347        if self.has_node(new_path, new_rev): 
     348            new_node = self.get_node(new_path, new_rev) 
     349        else: 
     350            raise TracError, ('The Target for Diff is invalid: path %s' 
     351                              ' doesn\'t exist in revision %s' \ 
     352                              % (new_path, new_rev)) 
     353        if new_node.kind != old_node.kind: 
     354            raise TracError, ('Diff mismatch: Base is a %s (%s in revision %s) ' 
     355                              'and Target is a %s (%s in revision %s).' \ 
     356                              % (old_node.kind, old_path, old_rev, 
     357                                 new_node.kind, new_path, new_rev)) 
     358        subpool = Pool(self.pool) 
     359        if new_node.isdir: 
     360            editor = DiffChangeEditor() 
     361            e_ptr, e_baton = delta.make_editor(editor, subpool()) 
     362            old_root = fs.revision_root(self.fs_ptr, old_rev, subpool()) 
     363            new_root = fs.revision_root(self.fs_ptr, new_rev, subpool()) 
     364            def authz_cb(root, path, pool): return 1 
     365            text_deltas = 0 # as this is anyway re-done in Diff.py... 
     366            entry_props = 0 # "... typically used only for working copy updates" 
     367            repos.svn_repos_dir_delta(old_root, old_path, '', 
     368                                      new_root, new_path, 
     369                                      e_ptr, e_baton, authz_cb, 
     370                                      text_deltas, 
     371                                      1, # directory 
     372                                      entry_props, 
     373                                      ignore_ancestry, 
     374                                      subpool()) 
     375            for path, kind, change in editor.deltas: 
     376                old_node = new_node = None 
     377                if change != Changeset.ADD: 
     378                    old_node = self.get_node(posixpath.join(old_path, path), 
     379                                             old_rev) 
     380                if change != Changeset.DELETE: 
     381                    new_node = self.get_node(posixpath.join(new_path, path), 
     382                                             new_rev) 
     383                else: 
     384                    kind = _kindmap[fs.check_path(old_root, old_node.path, 
     385                                                  subpool())] 
     386                yield  (old_node, new_node, kind, change) 
     387        else: 
     388            old_root = fs.revision_root(self.fs_ptr, old_rev, subpool()) 
     389            new_root = fs.revision_root(self.fs_ptr, new_rev, subpool()) 
     390            if fs.contents_changed(old_root, old_path, new_root, new_path, 
     391                                   subpool()): 
     392                yield (old_node, new_node, Node.FILE, Changeset.EDIT) 
     393 
    333394 
    334395class SubversionNode(Node): 
    335396 
     
    352413                                               self.pool()) 
    353414        self.created_path = fs.node_created_path(self.root, self.scoped_path, 
    354415                                                 self.pool()) 
    355         # 'created_path' differs from 'path' if the last operation is a copy, 
    356         # and furthermore, 'path' might not exist at 'create_rev' 
     416        # Note: 'created_path' differs from 'path' if the last change was a copy, 
     417        #        and furthermore, 'path' might not exist at 'create_rev'. 
     418        #        The only guarantees are: 
     419        #          * this node exists at (path,rev) 
     420        #          * the node existed at (created_path,created_rev) 
     421        # TODO: check node id 
    357422        self.rev = self.created_rev 
    358423         
    359424        Node.__init__(self, path, self.rev, _kindmap[node_type]) 
     
    393458        if newer: 
    394459            yield newer 
    395460 
     461#    def get_previous(self): 
     462#        # FIXME: redo it with fs.node_history 
     463 
    396464    def get_properties(self): 
    397465        props = fs.node_proplist(self.root, self.scoped_path, self.pool()) 
    398466        for name,value in props.items(): 
     
    487555 
    488556    def _get_prop(self, name): 
    489557        return fs.revision_prop(self.fs_ptr, self.rev, name, self.pool()) 
     558 
     559 
     560# 
     561# Delta editor for diffs between arbitrary nodes 
     562# 
     563# Note 1: the 'copyfrom_path' and 'copyfrom_rev' information is not used 
     564#         because 'repos.svn_repos_dir_delta' *doesn't* provide it. 
     565# 
     566# Note 2: the 'dir_baton' is the path of the parent directory 
     567# 
     568 
     569class DiffChangeEditor(delta.Editor):  
     570 
     571    def __init__(self): 
     572        self.deltas = [] 
     573     
     574    # -- svn.delta.Editor callbacks 
     575 
     576    def open_root(self, base_revision, dir_pool): 
     577        return ('/', Changeset.EDIT) 
     578 
     579    def add_directory(self, path, dir_baton, copyfrom_path, copyfrom_rev, 
     580                      dir_pool): 
     581        self.deltas.append((path, Node.DIRECTORY, Changeset.ADD)) 
     582        return (path, Changeset.ADD) 
     583 
     584    def open_directory(self, path, dir_baton, base_revision, dir_pool): 
     585        return (path, dir_baton[1]) 
     586 
     587    def change_dir_prop(self, dir_baton, name, value, pool): 
     588        path, change = dir_baton 
     589        if change != Changeset.ADD: 
     590            self.deltas.append((path, Node.DIRECTORY, change)) 
     591 
     592    def delete_entry(self, path, revision, dir_baton, pool): 
     593        self.deltas.append((path, None, Changeset.DELETE)) 
     594 
     595    def add_file(self, path, dir_baton, copyfrom_path, copyfrom_revision, 
     596                 dir_pool): 
     597        self.deltas.append((path, Node.FILE, Changeset.ADD)) 
     598 
     599    def open_file(self, path, dir_baton, dummy_rev, file_pool): 
     600        self.deltas.append((path, Node.FILE, Changeset.EDIT)) 
     601 
  • trac/versioncontrol/tests/svn_fs.py

    diff -ruN -x .svn trac-0.9b2/trac/versioncontrol/tests/svn_fs.py anydiff-branch/trac/versioncontrol/tests/svn_fs.py
    old new  
    215215        self.assertEqual(('tags/v1', 7, 'unknown'), history.next()) 
    216216        self.assertRaises(StopIteration, history.next) 
    217217 
     218    # Diffs 
     219 
     220    def _cmp_diff(self, expected, got): 
     221        if expected[0]: 
     222            old = self.repos.get_node(*expected[0]) 
     223            self.assertEqual((old.path, old.rev), (got[0].path, got[0].rev)) 
     224        if expected[1]: 
     225            new = self.repos.get_node(*expected[1]) 
     226            self.assertEqual((new.path, new.rev), (got[1].path, got[1].rev)) 
     227        self.assertEqual(expected[2], (got[2], got[3])) 
     228         
     229    def test_diff_file_different_revs(self): 
     230        diffs = self.repos.get_deltas('trunk/README.txt', 2, 'trunk/README.txt', 3) 
     231        self._cmp_diff((('trunk/README.txt', 2), 
     232                        ('trunk/README.txt', 3), 
     233                        (Node.FILE, Changeset.EDIT)), diffs.next()) 
     234        self.assertRaises(StopIteration, diffs.next) 
     235 
     236    def test_diff_file_different_files(self): 
     237        diffs = self.repos.get_deltas('branches/v1x/README.txt', 12, 
     238                                      'branches/v1x/README2.txt', 12) 
     239        self._cmp_diff((('branches/v1x/README.txt', 12), 
     240                        ('branches/v1x/README2.txt', 12), 
     241                        (Node.FILE, Changeset.EDIT)), diffs.next()) 
     242        self.assertRaises(StopIteration, diffs.next) 
     243 
     244    def test_diff_file_no_change(self): 
     245        diffs = self.repos.get_deltas('trunk/README.txt', 7, 
     246                                      'tags/v1/README.txt', 7) 
     247        self.assertRaises(StopIteration, diffs.next) 
     248  
     249    def test_diff_dir_different_revs(self): 
     250        diffs = self.repos.get_deltas('trunk', 4, 'trunk', 8) 
     251        self._cmp_diff((None, ('trunk/dir1/dir2', 8), 
     252                        (Node.DIRECTORY, Changeset.ADD)), diffs.next()) 
     253        self._cmp_diff((None, ('trunk/dir1/dir3', 8), 
     254                        (Node.DIRECTORY, Changeset.ADD)), diffs.next()) 
     255        self._cmp_diff((None, ('trunk/README2.txt', 6), 
     256                        (Node.FILE, Changeset.ADD)), diffs.next()) 
     257        self._cmp_diff((('trunk/dir2', 4), None, 
     258                        (Node.DIRECTORY, Changeset.DELETE)), diffs.next()) 
     259        self._cmp_diff((('trunk/dir3', 4), None, 
     260                        (Node.DIRECTORY, Changeset.DELETE)), diffs.next()) 
     261        self.assertRaises(StopIteration, diffs.next) 
     262 
     263    def test_diff_dir_different_dirs(self): 
     264        diffs = self.repos.get_deltas('trunk', 1, 'branches/v1x', 12) 
     265        self._cmp_diff((None, ('branches/v1x/dir1', 12), 
     266                        (Node.DIRECTORY, Changeset.ADD)), diffs.next()) 
     267        self._cmp_diff((None, ('branches/v1x/dir1/dir2', 12), 
     268                        (Node.DIRECTORY, Changeset.ADD)), diffs.next()) 
     269        self._cmp_diff((None, ('branches/v1x/dir1/dir3', 12), 
     270                        (Node.DIRECTORY, Changeset.ADD)), diffs.next()) 
     271        self._cmp_diff((None, ('branches/v1x/README.txt', 12), 
     272                        (Node.FILE, Changeset.ADD)), diffs.next()) 
     273        self._cmp_diff((None, ('branches/v1x/README2.txt', 12), 
     274                        (Node.FILE, Changeset.ADD)), diffs.next()) 
     275        self.assertRaises(StopIteration, diffs.next) 
     276 
     277    def test_diff_dir_no_change(self): 
     278        diffs = self.repos.get_deltas('trunk', 7, 
     279                                      'tags/v1', 7) 
     280        self.assertRaises(StopIteration, diffs.next) 
     281         
     282    # Changesets 
     283 
    218284    def test_changeset_repos_creation(self): 
    219285        chgset = self.repos.get_changeset(0) 
    220286        self.assertEqual(0, chgset.rev) 
  • trac/versioncontrol/web_ui/browser.py

    diff -ruN -x .svn trac-0.9b2/trac/versioncontrol/web_ui/browser.py anydiff-branch/trac/versioncontrol/web_ui/browser.py
    old new  
    8888 
    8989        repos = self.env.get_repository(req.authname) 
    9090        node = repos.get_node(path, rev) 
     91        rev = repos.normalize_rev(rev) 
    9192 
    9293        hidden_properties = [p.strip() for p 
    9394                             in self.config.get('browser', 'hide_properties', 
    9495                                                'svk:merge').split(',')] 
     96 
    9597        req.hdf['title'] = path 
    96         req.hdf['browser'] = { 
     98        browser_hdf = { 
    9799            'path': path, 
    98             'revision': rev or repos.youngest_rev, 
     100            'revision': rev, 
    99101            'props': dict([(util.escape(name), util.escape(value)) 
    100102                           for name, value in node.get_properties().items() 
    101                            if not name in hidden_properties]), 
    102             'href': util.escape(self.env.href.browser(path, rev=rev or 
    103                                                       repos.youngest_rev)), 
    104             'log_href': util.escape(self.env.href.log(path)) 
    105         } 
     103                           if not name in hidden_properties]) 
     104            } 
     105        browser_hrefs = { 
     106            'href': self.env.href.browser(path,rev=rev), 
     107            'restr_changeset_href': self.env.href.changeset(node.rev, path), 
     108            'anydiff_href': self.env.href.anydiff(), 
     109            'log_href': self.env.href.log(path) 
     110            } 
     111        browser_hdf.update(dict([(key, util.escape(href)) for key, href in 
     112                                 browser_hrefs.items()])) 
     113        req.hdf['browser'] = browser_hdf 
    106114 
    107115        path_links = get_path_links(self.env.href, path, rev) 
    108116        if len(path_links) > 1: 
     
    162170 
    163171        req.hdf['browser.items'] = info 
    164172        req.hdf['browser.changes'] = changes 
    165  
     173        if node.path != '': 
     174            zip_href = self.env.href.diff(node.path, new=rev, old=rev, 
     175                                          old_path='/', # special case (#238) 
     176                                          format='zip') 
     177            add_link(req, 'alternate', zip_href, 'Zip Archive', 
     178                     'application/zip', 'zip') 
     179         
     180         
    166181    def _render_file(self, req, repos, node, rev=None): 
    167182        req.perm.assert_permission('FILE_VIEW') 
    168183 
  • trac/versioncontrol/web_ui/changeset.py

    diff -ruN -x .svn trac-0.9b2/trac/versioncontrol/web_ui/changeset.py anydiff-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): 
    57         match = re.match(r'/changeset/([0-9]+)$', req.path_info) 
     50        match = re.match(r'/changeset/([0-9]+)(/.*)?$', req.path_info) 
    5851        if match: 
    5952            req.args['rev'] = match.group(1) 
     53            path = match.group(2) 
     54            if path: 
     55                req.args['path'] = path 
    6056            return 1 
    6157 
    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         authzperm = SubversionAuthorizer(self.env, req.authname) 
    68         authzperm.assert_permission_for_changeset(rev) 
    69  
    70         diff_options = get_diff_options(req) 
    71         if req.args.has_key('update'): 
    72             req.redirect(self.env.href.changeset(rev)) 
    73  
    74         chgset = repos.get_changeset(rev) 
    75         req.check_modified(chgset.date, 
    76                            diff_options[0] + ''.join(diff_options[1])) 
    77  
    78         format = req.args.get('format') 
    79         if format == 'diff': 
    80             self._render_diff(req, repos, chgset, diff_options) 
    81             return 
    82         elif format == 'zip': 
    83             self._render_zip(req, repos, chgset) 
    84             return 
    85  
    86         self._render_html(req, repos, chgset, diff_options) 
    87         add_link(req, 'alternate', '?format=diff', 'Unified Diff', 
    88                  'text/plain', 'diff') 
    89         add_link(req, 'alternate', '?format=zip', 'Zip Archive', 
    90                  'application/zip', 'zip') 
    91         add_stylesheet(req, 'common/css/changeset.css') 
    92         add_stylesheet(req, 'common/css/diff.css') 
    93         add_stylesheet(req, 'common/css/code.css') 
    94         return 'changeset.cs', None 
    95  
    9658    # ITimelineEventProvider methods 
    9759 
    9860    def get_timeline_filters(self, req): 
     
    143105                          message 
    144106                rev = repos.previous_rev(rev) 
    145107 
    146     # Internal methods 
    147  
    148     def _render_html(self, req, repos, chgset, diff_options): 
    149         """HTML version""" 
    150         req.hdf['title'] = '[%s]' % chgset.rev 
    151         req.hdf['changeset'] = { 
    152             'revision': chgset.rev, 
    153             'time': util.format_datetime(chgset.date), 
    154             'author': util.escape(chgset.author or 'anonymous'), 
    155             'message': wiki_to_html(chgset.message or '--', self.env, req, 
    156                                     escape_newlines=True) 
    157         } 
    158  
    159         oldest_rev = repos.oldest_rev 
    160         if chgset.rev != oldest_rev: 
    161             add_link(req, 'first', self.env.href.changeset(oldest_rev), 
    162                      'Changeset %s' % oldest_rev) 
    163             previous_rev = repos.previous_rev(chgset.rev) 
    164             add_link(req, 'prev', self.env.href.changeset(previous_rev), 
    165                      'Changeset %s' % previous_rev) 
    166         youngest_rev = repos.youngest_rev 
    167         if str(chgset.rev) != str(youngest_rev): 
    168             next_rev = repos.next_rev(chgset.rev) 
    169             add_link(req, 'next', self.env.href.changeset(next_rev), 
    170                      'Changeset %s' % next_rev) 
    171             add_link(req, 'last', self.env.href.changeset(youngest_rev), 
    172                      'Changeset %s' % youngest_rev) 
    173  
    174         edits = [] 
    175         idx = 0 
    176         for path, kind, change, base_path, base_rev in chgset.get_changes(): 
    177             info = {'change': change} 
    178             if base_path: 
    179                 info['path.old'] = base_path 
    180                 info['rev.old'] = base_rev 
    181                 info['browser_href.old'] = self.env.href.browser(base_path, 
    182                                                                  rev=base_rev) 
    183             if path: 
    184                 info['path.new'] = path 
    185                 info['rev.new'] = chgset.rev 
    186                 info['browser_href.new'] = self.env.href.browser(path, 
    187                                                                  rev=chgset.rev) 
    188             if change in (Changeset.COPY, Changeset.EDIT, Changeset.MOVE): 
    189                 edits.append((idx, path, kind, base_path, base_rev)) 
    190             req.hdf['changeset.changes.%d' % idx] = info 
    191             idx += 1 
    192  
    193         hidden_properties = [p.strip() for p 
    194                              in self.config.get('browser', 'hide_properties', 
    195                                                 'svk:merge').split(',')] 
    196  
    197         for idx, path, kind, base_path, base_rev in edits: 
    198             old_node = repos.get_node(base_path or path, base_rev) 
    199             new_node = repos.get_node(path, chgset.rev) 
    200  
    201             # Property changes 
    202             old_props = old_node.get_properties() 
    203             new_props = new_node.get_properties() 
    204             changed_props = {} 
    205             if old_props != new_props: 
    206                 for k,v in old_props.items(): 
    207                     if not k in new_props: 
    208                         changed_props[k] = {'old': v} 
    209                     elif v != new_props[k]: 
    210                         changed_props[k] = {'old': v, 'new': new_props[k]} 
    211                 for k,v in new_props.items(): 
    212                     if not k in old_props: 
    213                         changed_props[k] = {'new': v} 
    214                 for k in hidden_properties: 
    215                     if k in changed_props: 
    216                         del changed_props[k] 
    217                 req.hdf['changeset.changes.%d.props' % idx] = changed_props 
    218  
    219             if kind == Node.DIRECTORY: 
    220                 continue 
    221  
    222             # Content changes 
    223             default_charset = self.config.get('trac', 'default_charset') 
    224             old_content = old_node.get_content().read() 
    225             if mimeview.is_binary(old_content): 
    226                 continue 
    227             charset = mimeview.get_charset(old_node.content_type) or \ 
    228                       default_charset 
    229             old_content = util.to_utf8(old_content, charset) 
    230  
    231             new_content = new_node.get_content().read() 
    232             if mimeview.is_binary(new_content): 
    233                 continue 
    234             charset = mimeview.get_charset(new_node.content_type) or \ 
    235                       default_charset 
    236             new_content = util.to_utf8(new_content, charset) 
    237  
    238             if old_content != new_content: 
    239                 context = 3 
    240                 for option in diff_options[1]: 
    241                     if option.startswith('-U'): 
    242                         context = int(option[2:]) 
    243                         break 
    244                 tabwidth = int(self.config.get('diff', 'tab_width', 
    245                                                self.config.get('mimeviewer', 
    246                                                                'tab_width'))) 
    247                 changes = hdf_diff(old_content.splitlines(), 
    248                                    new_content.splitlines(), 
    249                                    context, tabwidth, 
    250                                    ignore_blank_lines='-B' in diff_options[1], 
    251                                    ignore_case='-i' in diff_options[1], 
    252                                    ignore_space_changes='-b' in diff_options[1]) 
    253                 req.hdf['changeset.changes.%d.diff' % idx] = changes 
    254  
    255     def _render_diff(self, req, repos, chgset, diff_options): 
    256         """Raw Unified Diff version""" 
    257         req.send_response(200) 
    258         req.send_header('Content-Type', 'text/plain;charset=utf-8') 
    259         req.send_header('Content-Disposition', 
    260                         'filename=Changeset%s.diff' % req.args.get('rev')) 
    261         req.end_headers() 
    262  
    263         for path, kind, change, base_path, base_rev in chgset.get_changes(): 
    264             if change == Changeset.ADD: 
    265                 old_node = None 
    266             else: 
    267                 old_node = repos.get_node(base_path or path, base_rev) 
    268             if change == Changeset.DELETE: 
    269                 new_node = None 
    270             else: 
    271                 new_node = repos.get_node(path, chgset.rev) 
    272  
    273             # TODO: Property changes 
    274  
    275             # Content changes 
    276             if kind == 'dir': 
    277                 continue 
    278  
    279             default_charset = self.config.get('trac', 'default_charset') 
    280             new_content = old_content = '' 
    281             new_node_info = old_node_info = ('','') 
    282  
    283             if old_node: 
    284                 charset = mimeview.get_charset(old_node.content_type) or \ 
    285                           default_charset 
    286                 old_content = util.to_utf8(old_node.get_content().read(), 
    287                                            charset) 
    288                 old_node_info = (old_node.path, old_node.rev) 
    289             if mimeview.is_binary(old_content): 
    290                 continue 
    291  
    292             if new_node: 
    293                 charset = mimeview.get_charset(new_node.content_type) or \ 
    294                           default_charset 
    295                 new_content = util.to_utf8(new_node.get_content().read(), 
    296                                            charset) 
    297                 new_node_info = (new_node.path, new_node.rev) 
    298             if mimeview.is_binary(new_content): 
    299                 continue 
    300  
    301             if old_content != new_content: 
    302                 context = 3 
    303                 for option in diff_options[1]: 
    304                     if option.startswith('-U'): 
    305                         context = int(option[2:]) 
    306                         break 
    307                 req.write('Index: ' + path + util.CRLF) 
    308                 req.write('=' * 67 + util.CRLF) 
    309                 req.write('--- %s (revision %s)' % old_node_info + 
    310                           util.CRLF) 
    311                 req.write('+++ %s (revision %s)' % new_node_info + 
    312                           util.CRLF) 
    313                 for line in unified_diff(old_content.splitlines(), 
    314                                          new_content.splitlines(), context, 
    315                                          ignore_blank_lines='-B' in diff_options[1], 
    316                                          ignore_case='-i' in diff_options[1], 
    317                                          ignore_space_changes='-b' in diff_options[1]): 
    318                     req.write(line + util.CRLF) 
    319  
    320     def _render_zip(self, req, repos, chgset): 
    321         """ZIP archive with all the added and/or modified files.""" 
    322         req.send_response(200) 
    323         req.send_header('Content-Type', 'application/zip') 
    324         req.send_header('Content-Disposition', 
    325                         'filename=Changeset%s.zip' % chgset.rev) 
    326         req.end_headers() 
    327  
    328         try: 
    329             from cStringIO import StringIO 
    330         except ImportError: 
    331             from StringIO import StringIO 
    332         from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED 
    333  
    334         buf = StringIO() 
    335         zipfile = ZipFile(buf, 'w', ZIP_DEFLATED) 
    336         for path, kind, change, base_path, base_rev in chgset.get_changes(): 
    337             if kind == Node.FILE and change != Changeset.DELETE: 
    338                 node = repos.get_node(path, chgset.rev) 
    339                 zipinfo = ZipInfo() 
    340                 zipinfo.filename = node.path 
    341                 zipinfo.date_time = time.gmtime(node.last_modified)[:6] 
    342                 zipinfo.compress_type = ZIP_DEFLATED 
    343                 zipfile.writestr(zipinfo, node.get_content().read()) 
    344         zipfile.close() 
    345         req.write(buf.getvalue()) 
    346108 
    347109    # IWikiSyntaxProvider methods 
    348110     
    349111    def get_wiki_syntax(self): 
    350         yield (r"!?\[\d+\]|(?:\b|!)r\d+\b(?!:\d)", 
     112        yield (r"!?\[\d+(?:/[^\]]*)?\]|(?:\b|!)r\d+\b(?!:\d)", 
    351113               lambda x, y, z: self._format_link(x, 'changeset', 
    352114                                                 y[0] == 'r' and y[1:] 
    353                                                  or y[1:-1], y)) 
     115                                                 or y[1:-1], y, z)) 
    354116 
    355117    def get_link_resolvers(self): 
    356118        yield ('changeset', self._format_link) 
    357119 
    358     def _format_link(self, formatter, ns, rev, label): 
     120    def _format_link(self, formatter, ns, chgset, label, fullmatch=None): 
     121        sep = chgset.find('/') 
     122        if sep > 0: 
     123            rev, path = chgset[:sep], chgset[sep:] 
     124        else: 
     125            rev, path = chgset, None 
    359126        cursor = formatter.db.cursor() 
    360127        cursor.execute('SELECT message FROM revision WHERE rev=%s', (rev,)) 
    361128        row = cursor.fetchone() 
    362129        if row: 
    363130            return '<a class="changeset" title="%s" href="%s">%s</a>' \ 
    364131                   % (util.escape(util.shorten_line(row[0])), 
    365                       formatter.href.changeset(rev), label) 
     132                      formatter.href.changeset(rev, path), label) 
    366133        else: 
    367             return '<a class="missing changeset" href="%s" rel="nofollow">%s</a>' \ 
    368                    % (formatter.href.changeset(rev), label) 
     134            return '<a class="missing changeset" href="%s"' \ 
     135                   ' rel="nofollow">%s</a>' \ 
     136                   % (formatter.href.changeset(rev, path), label) 
    369137 
    370138    # ISearchProvider methods 
    371139 
  • trac/versioncontrol/web_ui/diff.py

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

    diff -ruN -x .svn trac-0.9b2/trac/versioncontrol/web_ui/__init__.py anydiff-branch/trac/versioncontrol/web_ui/__init__.py
    old new  
    11from trac.versioncontrol.web_ui.browser import * 
    22from trac.versioncontrol.web_ui.changeset import * 
    33from trac.versioncontrol.web_ui.log import * 
     4from trac.versioncontrol.web_ui.diff import * 
  • trac/versioncontrol/web_ui/log.py

    diff -ruN -x .svn trac-0.9b2/trac/versioncontrol/web_ui/log.py anydiff-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/wiki/tests/wiki-tests.txt

    diff -ruN -x .svn trac-0.9b2/trac/wiki/tests/wiki-tests.txt anydiff-branch/trac/wiki/tests/wiki-tests.txt
    old new  
    3636<a class="ext-link" href="http://www.edgewall.com/"><span class="icon"></span>http://www.edgewall.com/</a> 
    3737</p> 
    3838============================== 
    39 #1, [1], r1, {1} 
     39#1, {1} 
     40[1], r1 
     41[1/README.txt] 
    4042------------------------------ 
    4143<p> 
    42 <a class="missing ticket" href="/ticket/1" rel="nofollow">#1</a>, <a class="missing changeset" href="/changeset/1" rel="nofollow">[1]</a>, <a class="missing changeset" href="/changeset/1" rel="nofollow">r1</a>, <a class="report" href="/report/1">{1}</a> 
     44<a class="missing ticket" href="/ticket/1" rel="nofollow">#1</a>, <a class="report" href="/report/1">{1}</a> 
     45<a class="missing changeset" href="/changeset/1" rel="nofollow">[1]</a>, <a class="missing changeset" href="/changeset/1" rel="nofollow">r1</a> 
     46<a class="missing changeset" href="/changeset/1/README.txt" rel="nofollow">[1/README.txt]</a> 
    4347</p> 
    4448============================== 
    4549!#1, ![1], !r1, !{1} 
     
    6064[1:2], r1:2, [12:23], r12:23 
    6165</p> 
    6266============================== 
    63 ticket:1, changeset:1, report:1, source:foo/bar 
     67ticket:1, report:1, source:foo/bar 
     68changeset:1, changeset:1/README.txt 
    6469 
    6570Issue [ticket:1], CS[changeset:1], Listing [report:1], File [source:foo/bar] 
    6671------------------------------ 
    6772<p> 
    68 <a class="missing ticket" href="/ticket/1" rel="nofollow">ticket:1</a>, <a class="missing changeset" href="/changeset/1" rel="nofollow">changeset:1</a>, <a class="report" href="/report/1">report:1</a>, <a class="source" href="/browser/foo/bar">source:foo/bar</a> 
     73<a class="missing ticket" href="/ticket/1" rel="nofollow">ticket:1</a>, <a class="report" href="/report/1">report:1</a>, <a class="source" href="/browser/foo/bar">source:foo/bar</a> 
     74<a class="missing changeset" href="/changeset/1" rel="nofollow">changeset:1</a>, <a class="missing changeset" href="/changeset/1/README.txt" rel="nofollow">changeset:1/README.txt</a> 
    6975</p> 
    7076<p> 
    7177Issue <a class="missing ticket" href="/ticket/1" rel="nofollow">1</a>, CS<a class="missing changeset" href="/changeset/1" rel="nofollow">1</a>, Listing <a class="report" href="/report/1">1</a>, File <a class="source" href="/browser/foo/bar">foo/bar</a> 
     
    7985<a class="source" href="/browser/foo/bar">source foo/bar</a>, <a class="ext-link" href="http://www.edgewall.com/"><span class="icon"></span>edgewall</a> 
    8086</p> 
    8187============================== 
     88diff:trunk//branch 
     89diff:trunk@12//branch@23 
     90diff:trunk@12:23 
     91diff:@12:23 
     92------------------------------ 
     93<p> 
     94<a class="changeset" title="Diff from trunk@latest to branch@latest" href="/diff/branch?old_path=trunk">diff:trunk//branch</a> 
     95<a class="changeset" title="Diff from trunk@12 to branch@23" href="/diff/branch?new=23&old=12&old_path=trunk">diff:trunk@12//branch@23</a> 
     96<a class="changeset" title="Diff r12:23 for trunk" href="/diff/trunk?new=23&old=12&old_path=trunk">diff:trunk@12:23</a> 
     97<a class="changeset" title="Diff r12:23 for /" href="/diff/?new=23&old=12&old_path=">diff:@12:23</a> 
     98</p> 
     99============================== 
    82100CamelCase AlabamA ABc AlaBamA FooBar 
    83101------------------------------ 
    84102<p> 
  • wiki-default/WikiStart

    diff -ruN -x .svn trac-0.9b2/wiki-default/WikiStart anydiff-branch/wiki-default/WikiStart
    old new  
    1 = Welcome to Trac 0.9b2 = 
    2  
    3 Trac is a '''minimalistic''' approach to '''web-based''' management of 
    4 '''software projects'''. Its goal is to simplify effective tracking and handling of software issues, enhancements and overall progress. 
    5  
    6 All aspects of Trac have been designed with the single goal to  
    7 '''help developers write great software''' while '''staying out of the way''' 
    8 and imposing as little as possible on a team's established process and 
    9 culture. 
    10  
    11 As all Wiki pages, this page is editable, this means that you can 
    12 modify the contents of this page simply by using your 
    13 web-browser. Simply click on the "Edit this page" link at the bottom 
    14 of the page. WikiFormatting will give you a detailed description of 
    15 available Wiki formatting commands. 
    16  
    17 "[wiki:TracAdmin trac-admin] ''yourenvdir'' initenv" created 
    18 a new Trac environment, containing a default set of wiki pages and some sample 
    19 data. This newly created environment also contains  
    20 [wiki:TracGuide documentation] to help you get started with your project. 
    21  
    22 You can use [wiki:TracAdmin trac-admin] to configure 
    23 [http://trac.edgewall.com/ Trac] to better fit your project, especially in 
    24 regard to ''components'', ''versions'' and ''milestones''.  
    25  
    26  
    27 TracGuide is a good place to start. 
    28  
    29 Enjoy! [[BR]] 
    30 ''The Trac Team'' 
    31  
    32 == Starting Points == 
    33  
    34  * TracGuide --  Built-in Documentation 
    35  * [http://projects.edgewall.com/trac/ The Trac project] -- Trac Open Source Project 
    36  * [http://projects.edgewall.com/trac/wiki/TracFaq Trac FAQ] -- Frequently Asked Questions 
    37  * TracSupport --  Trac Support 
    38  
    39 For a complete list of local wiki pages, see TitleIndex. 
    40  
    41 Trac is brought to you by [http://www.edgewall.com/ Edgewall Software], 
    42 providing professional Linux and software development services to clients 
    43 worldwide. Visit http://www.edgewall.com/ for more information. 
    44  
     1= Welcome to Trac 0.9b2-anydiff = 
     2 
     3Trac is a '''minimalistic''' approach to '''web-based''' management of 
     4'''software projects'''. Its goal is to simplify effective tracking and handling of software issues, enhancements and overall progress. 
     5 
     6All aspects of Trac have been designed with the single goal to  
     7'''help developers write great software''' while '''staying out of the way''' 
     8and imposing as little as possible on a team's established process and 
     9culture. 
     10 
     11As all Wiki pages, this page is editable, this means that you can 
     12modify the contents of this page simply by using your 
     13web-browser. Simply click on the "Edit this page" link at the bottom 
     14of the page. WikiFormatting will give you a detailed description of 
     15available Wiki formatting commands. 
     16 
     17"[wiki:TracAdmin trac-admin] ''yourenvdir'' initenv" created 
     18a new Trac environment, containing a default set of wiki pages and some sample 
     19data. This newly created environment also contains  
     20[wiki:TracGuide documentation] to help you get started with your project. 
     21 
     22You can use [wiki:TracAdmin trac-admin] to configure 
     23[http://trac.edgewall.com/ Trac] to better fit your project, especially in 
     24regard to ''components'', ''versions'' and ''milestones''.  
     25 
     26 
     27TracGuide is a good place to start. 
     28 
     29Enjoy! [[BR]] 
     30''The Trac Team'' 
     31 
     32== Starting Points == 
     33 
     34 * TracGuide --  Built-in Documentation 
     35 * [http://projects.edgewall.com/trac/ The Trac project] -- Trac Open Source Project 
     36 * [http://projects.edgewall.com/trac/wiki/TracFaq Trac FAQ] -- Frequently Asked Questions 
     37 * TracSupport --  Trac Support 
     38 
     39For a complete list of local wiki pages, see TitleIndex. 
     40 
     41Trac is brought to you by [http://www.edgewall.com/ Edgewall Software], 
     42providing professional Linux and software development services to clients 
     43worldwide. Visit http://www.edgewall.com/ for more information. 
     44