Edgewall Software

Ticket #2028: trac-0.9b1.tar.gz-anydiff.patch

File trac-0.9b1.tar.gz-anydiff.patch, 83.6 KB (added by cboos, 6 years ago)

Adds TracDiff (r2210) functionality to Trac 0.9b1 (applies cleanly to the trac-0.9b1.tar.gz archive)

  • htdocs/css/browser.css

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

    diff -urN -x .svn trac-0.9b1/templates/changeset.cs 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.old ?> of <?cs var:item.path.new ?>">Revision <?cs 
    173        var:item.rev.new ?></a></th> 
    174       </tr> 
    175      </thead><?cs 
    176      each:change = item.diff ?><tbody><?cs 
    177       call:diff_display(change, diff.style) ?></tbody><?cs 
    178       if:name(change) < len(item.diff) - 1 ?><tbody class="skipped"><tr> 
    179        <th>&hellip;</th><td>&nbsp;</td><th>&hellip;</th><td>&nbsp;</td> 
    180       </tr></tbody><?cs /if ?><?cs 
    181      /each ?><?cs 
    182     else ?> 
    183      <colgroup><col class="lineno" /><col class="lineno" /><col class="content" /></colgroup> 
    184      <thead><tr> 
    185       <th title="Revision <?cs var:item.rev.old ?>"><a href="<?cs 
    186        var:item.browser_href.old ?>" title="Show old version of <?cs 
    187        var:item.path.old ?>">r<?cs var:item.rev.old ?></a></th> 
    188       <th title="Revision <?cs var:item.rev.new ?>"><a href="<?cs 
    189        var:item.browser_href.new ?>" title="Show new version of <?cs 
    190        var:item.path.new ?>">r<?cs var:item.rev.new ?></a></th> 
    191       <th>&nbsp;</th></tr> 
    192      </thead><?cs 
    193      each:change = item.diff ?><?cs 
    194       call:diff_display(change, diff.style) ?><?cs 
    195       if:name(change) < len(item.diff) - 1 ?><tbody class="skipped"><tr> 
    196        <th>&hellip;</th><th>&hellip;</th><td>&nbsp;</td> 
    197       </tr></tbody><?cs /if ?><?cs 
    198      /each ?><?cs 
    199     /if ?></table><?cs 
    200    /if ?></li><?cs 
    201   /if ?><?cs 
    202  /each ?></ul> 
    203 </div> 
    204  
    205 </div> 
    206 <?cs include "footer.cs"?> 
  • templates/diff.cs

    diff -urN -x .svn trac-0.9b1/templates/diff.cs 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.old ?> of <?cs var:item.path.new ?>">Revision <?cs 
     225       var:item.rev.new ?></a></th> 
     226      </tr> 
     227     </thead><?cs 
     228     each:change = item.diff ?><tbody><?cs 
     229      call:diff_display(change, diff.style) ?></tbody><?cs 
     230      if:name(change) < len(item.diff) - 1 ?><tbody class="skipped"><tr> 
     231       <th>&hellip;</th><td>&nbsp;</td><th>&hellip;</th><td>&nbsp;</td> 
     232      </tr></tbody><?cs /if ?><?cs 
     233     /each ?><?cs 
     234    else ?> 
     235     <colgroup><col class="lineno" /><col class="lineno" /><col class="content" /></colgroup> 
     236     <thead><tr> 
     237      <th title="Revision <?cs var:item.rev.old ?>"><a href="<?cs 
     238       var:item.browser_href.old ?>" title="Show old version of <?cs 
     239       var:item.path.old ?>">r<?cs var:item.rev.old ?></a></th> 
     240      <th title="Revision <?cs var:item.rev.new ?>"><a href="<?cs 
     241       var:item.browser_href.new ?>" title="Show new version of <?cs 
     242       var:item.path.new ?>">r<?cs var:item.rev.new ?></a></th> 
     243      <th>&nbsp;</th></tr> 
     244     </thead><?cs 
     245     each:change = item.diff ?><?cs 
     246      call:diff_display(change, diff.style) ?><?cs 
     247      if:name(change) < len(item.diff) - 1 ?><tbody class="skipped"><tr> 
     248       <th>&hellip;</th><th>&hellip;</th><td>&nbsp;</td> 
     249      </tr></tbody><?cs /if ?><?cs 
     250     /each ?><?cs 
     251    /if ?></table><?cs 
     252   /if ?></li><?cs 
     253  /if ?><?cs 
     254 /each ?></ul> 
     255</div> 
     256 
     257</div> 
     258<?cs include "footer.cs"?> 
  • templates/log.cs

    diff -urN -x .svn trac-0.9b1/templates/log.cs 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> 
     
    115131     <td class="author"><?cs var:log.changes[item.rev].author ?></td> 
    116132     <td class="summary"><?cs var:log.changes[item.rev].message ?></td> 
    117133    </tr><?cs 
     134    set:idx = idx + 1 ?><?cs 
    118135   /each ?> 
    119136  </tbody> 
    120  </table><?cs 
     137 </table> 
     138 <div class="buttons"><input type="submit" value="View changes"  
     139      title="Diff from Old Revision to New Revision (select them above)" /> 
     140 </div> 
     141 </form><?cs 
    121142 if:len(links.prev) || len(links.next) ?><div id="paging" class="nav"><ul><?cs 
    122143  if:len(links.prev) ?><li class="first<?cs 
    123144   if:!len(links.next) ?> last<?cs /if ?>">&larr; <a href="<?cs 
  • templates/wiki.cs

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

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

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

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

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

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

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

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

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

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

    diff -urN -x .svn trac-0.9b1/trac/versioncontrol/web_ui/log.py 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()` 
  • wiki-default/WikiStart

    diff -urN -x .svn trac-0.9b1/wiki-default/WikiStart anydiff-branch/wiki-default/WikiStart
    old new  
    1 = Welcome to Trac 0.9b1 = 
     1= Welcome to Trac 0.9b1-anydiff = 
    22 
    33Trac is a '''minimalistic''' approach to '''web-based''' management of 
    44'''software projects'''. Its goal is to simplify effective tracking and handling of software issues, enhancements and overall progress.