--- patch.py.old	2007-04-20 09:41:45.000000000 -0400
+++ patch.py	2008-05-26 22:01:06.000000000 -0400
@@ -15,6 +15,7 @@
 # Author: Christopher Lenz <cmlenz@gmx.de>
 #         Ludvig Strigeus
 
+import os
 from trac.core import *
 from trac.mimeview.api import content_to_unicode, IHTMLPreviewRenderer, Mimeview
 from trac.util.html import escape, Markup
@@ -32,27 +33,77 @@
 
     diff_cs = """
 <?cs include:'macros.cs' ?>
-<div class="diff"><ul class="entries"><?cs
- each:file = diff.files ?><li class="entry">
-  <h2><?cs var:file.filename ?></h2>
-  <table class="inline" summary="Differences" cellspacing="0">
-   <colgroup><col class="lineno" /><col class="lineno" /><col class="content" /></colgroup>
-   <thead><tr>
-    <th><?cs var:file.oldrev ?></th>
-    <th><?cs var:file.newrev ?></th>
-    <th>&nbsp;</th>
-   </tr></thead><?cs
-   each:change = file.diff ?><?cs
-    call:diff_display(change, diff.style) ?><?cs
-    if:name(change) < len(file.diff) - 1 ?>
-     <tbody class="skipped">
-      <tr><th>&hellip;</th><th>&hellip;</th><td>&nbsp;</td></tr>
-     </tbody><?cs
-    /if ?><?cs
-   /each ?>
-  </table>
- </li><?cs /each ?>
-</ul></div>
+<div class="diff">
+ <ul class="entries">
+  <?cs each:item = diff.files ?>
+   <?cs if:len(item.diffs) ?>
+    <li class="entry">
+     <h2><?cs var:item.new.path ?></h2>
+      <table class="inline" summary="Differences" cellspacing="0">
+       <colgroup><col class="lineno" /><col class="lineno" /><col class="content" /></colgroup>
+        <thead><tr>
+         <th><?cs var:item.old.shortrev ?></th>
+         <th><?cs var:item.new.shortrev ?></th>
+         <th>&nbsp;</th>
+        </tr></thead>
+        <?cs each:blocks = item.diffs ?>
+         <?cs each:block = blocks ?>
+          <tbody class="<?cs var:block.type ?>">
+           <?cs if block.type == 'unmod'?>
+            <?cs each:line = block.base.lines ?>
+             <tr>
+              <th><?cs var:#block.base.offset + name(line) + 1 ?></th>
+              <th><?cs var:#block.changed.offset + name(line) + 1 ?></th>
+              <td class="l"><span><?cs var:line ?></span>&nbsp;</td>
+             </tr>
+            <?cs /each ?>
+           <?cs elif block.type == 'add'?>
+            <?cs each:line = block.changed.lines ?>
+             <tr<?cs call:diff_line_class(block.changed, line) ?>>
+              <th>&nbsp;</th>
+              <th><?cs var:#block.changed.offset + name(line) + 1 ?></th>
+              <td class="r"><ins><?cs var:line ?></ins>&nbsp;</td>
+             </tr>
+            <?cs /each ?>
+           <?cs elif block.type == 'rem'?>
+            <?cs each:line = block.base.lines ?>
+             <tr<?cs call:diff_line_class(block.base, line) ?>>
+              <th><?cs var:#block.base.offset + name(line) + 1 ?></th>
+              <th>&nbsp;</th>
+              <td class="l"><del><?cs var:line ?></del>&nbsp;</td>
+             </tr>
+            <?cs /each ?>
+           <?cs elif block.type == 'mod'?>
+             <!--! First show the "old" lines -->
+            <?cs each:line = block.base.lines ?>
+             <tr<?cs if:name(line) == 0 ?> class="first"<?cs /if ?>>
+              <th><?cs var:#block.base.offset + name(line) + 1 ?></th>
+              <th>&nbsp;</th>
+              <td class="l"><span><?cs var:line ?></span>&nbsp;</td>
+             </tr>
+            <?cs /each ?>
+            <?cs each:line = block.changed.lines ?>
+             <tr<?cs if:name(line) + 1 == len(block.changed.lines) ?> class="last"<?cs /if ?>>
+              <th>&nbsp;</th>
+              <th><?cs var:#block.changed.offset + name(line) + 1 ?></th>
+              <td class="r"><span><?cs var:line ?></span>&nbsp;</td>
+             </tr>
+            <?cs /each ?>
+           <?cs /if ?>
+          </tbody>
+          <?cs if:name(blocks) < len(item.diffs) - 1 ?>
+           <tbody class="skipped">
+            <tr><th>&hellip;</th><th>&hellip;</th><td>&nbsp;</td></tr>
+           </tbody>
+          <?cs /if ?>
+        <?cs /each ?>
+       <?cs /each ?>
+      </table>
+    </li>
+   <?cs /if ?>
+  <?cs /each ?>
+ </ul>
+</div>
 """ # diff_cs
 
     # IHTMLPreviewRenderer methods
@@ -109,49 +160,88 @@
             div, mod = divmod(len(match.group(0)), 2)
             return div * '&nbsp; ' + mod * '&nbsp;'
 
-        output = []
-        filename, groups = None, None
+        comments = []
+        changes = []
         lines = iter(difflines)
         try:
             line = lines.next()
             while True:
                 if not line.startswith('--- '):
+                    if not line.startswith('Index: ') and line != '='*67: 
+                        comments.append(line) 
                     line = lines.next()
                     continue
 
+                oldpath = oldrev = newpath = newrev = ''
+
                 # Base filename/version
-                words = line.split(None, 2)
-                filename, fromrev = words[1], 'old'
-                groups, blocks = None, None
+                oldinfo = line.split(None, 2) 
+                if len(oldinfo) > 1: 
+                    oldpath = oldinfo[1] 
+                    if len(oldinfo) > 2: 
+                        oldrev = oldinfo[2] 
 
                 # Changed filename/version
                 line = lines.next()
                 if not line.startswith('+++ '):
                     return None
 
-                words = line.split(None, 2)
-                if len(words[1]) < len(filename):
-                    # Always use the shortest filename for display
-                    filename = words[1]
+                newinfo = line.split(None, 2) 
+                if len(newinfo) > 1: 
+                    newpath = newinfo[1] 
+                    if len(newinfo) > 2: 
+                        newrev = newinfo[2] 
+ 
+                shortrev = ('old', 'new') 
+                if oldpath or newpath: 
+                    sep = re.compile(r'([/.~\\])') 
+                    commonprefix = ''.join(os.path.commonprefix( 
+                        [sep.split(newpath), sep.split(oldpath)])) 
+                    commonsuffix = ''.join(os.path.commonprefix( 
+                        [sep.split(newpath)[::-1], 
+                         sep.split(oldpath)[::-1]])[::-1]) 
+                    if len(commonprefix) > len(commonsuffix): 
+                        common = commonprefix 
+                    elif commonsuffix: 
+                        common = commonsuffix.lstrip('/') 
+                        a = oldpath[:-len(commonsuffix)] 
+                        b = newpath[:-len(commonsuffix)] 
+                        if len(a) < 4 and len(b) < 4: 
+                            shortrev = (a, b) 
+                    else: 
+                        common = '(a) %s vs. (b) %s' % (oldpath, newpath) 
+                        shortrev = ('a', 'b') 
+                else: 
+                    common = '' 
+ 
                 groups = []
-                output.append({'filename' : filename, 'oldrev' : fromrev,
-                               'newrev' : 'new', 'diff' : groups})
-
-                for line in lines:
-                    # @@ -333,10 +329,8 @@
-                    r = re.match(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@', line)
+                changes.append({'change': 'edit', 'props': [], 
+                                'comments': '\n'.join(comments), 
+                                'diffs': groups, 
+                                'old': {'path': common, 
+                                        'rev': ' '.join(oldinfo[1:]), 
+                                        'shortrev': shortrev[0]}, 
+                                'new': {'path': common, 
+                                        'rev': ' '.join(newinfo[1:]), 
+                                        'shortrev': shortrev[1]}}) 
+                comments = [] 
+                line = lines.next() 
+                while line: 
+                    # "@@ -333,10 +329,8 @@" or "@@ -1 +1 @@" 
+                    r = re.match(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@', 
+                                 line) 
                     if not r:
                         break
                     blocks = []
                     groups.append(blocks)
-                    fromline,fromend,toline,toend = map(int, r.groups())
-                    last_type = None
+                    fromline, fromend, toline, toend = [int(x or 1) 
+                                                        for x in r.groups()] 
+                    last_type = last_change = extra = None
 
                     fromend += fromline
                     toend += toline
-
-                    while fromline < fromend or toline < toend:
-                        line = lines.next()
+                    line = lines.next() 
+                    while fromline < fromend or toline < toend or extra: 
 
                         # First character is the command
                         command = ' '
@@ -160,34 +250,47 @@
                         # Make a new block?
                         if (command == ' ') != last_type:
                             last_type = command == ' '
-                            blocks.append({'type': last_type and 'unmod' or 'mod',
-                                           'base.offset': fromline - 1,
-                                           'base.lines': [],
-                                           'changed.offset': toline - 1,
-                                           'changed.lines': []})
+                            kind = last_type and 'unmod' or 'mod' 
+                            block = {'type': kind, 
+                                     'base': {'offset': fromline - 1, 
+                                              'lines': []}, 
+                                     'changed': {'offset': toline - 1, 
+                                                 'lines': []}} 
+                            blocks.append(block) 
+                        else: 
+                            block = blocks[-1] 
                         if command == ' ':
-                            blocks[-1]['changed.lines'].append(line)
-                            blocks[-1]['base.lines'].append(line)
-                            fromline += 1
-                            toline += 1
+                            sides = ['base', 'changed'] 
                         elif command == '+':
-                            blocks[-1]['changed.lines'].append(line)
-                            toline += 1
+                            last_side = 'changed' 
+                            sides = [last_side] 
                         elif command == '-':
-                            blocks[-1]['base.lines'].append(line)
-                            fromline += 1
+                            last_side = 'base' 
+                            sides = [last_side] 
+                        elif command == '\\' and last_side: 
+                            meta = block[last_side].setdefault('meta', {}) 
+                            meta[len(block[last_side]['lines'])] = True 
+                            sides = [last_side] 
                         else:
                             return None
-                line = lines.next()
+                        for side in sides: 
+                            if side == 'base': 
+                                fromline += 1 
+                            else: 
+                                toline += 1 
+                            block[side]['lines'].append(line) 
+                        line = lines.next() 
+                        extra = line and line[0] == '\\' 
         except StopIteration:
             pass
 
         # Go through all groups/blocks and mark up intraline changes, and
         # convert to html
-        for o in output:
-            for group in o['diff']:
+        for o in changes:
+            for group in o['diffs']:
                 for b in group:
-                    f, t = b['base.lines'], b['changed.lines']
+                    base, changed = b['base'], b['changed'] 
+                    f, t = base['lines'], changed['lines'] 
                     if b['type'] == 'mod':
                         if len(f) == 0:
                             b['type'] = 'add'
@@ -197,16 +300,20 @@
                             _markup_intraline_change(f, t)
                     for i in xrange(len(f)):
                         line = f[i].expandtabs(tabwidth)
-                        line = escape(line)
-                        line = '<del>'.join([space_re.sub(htmlify, seg)
-                                             for seg in line.split('\0')])
-                        line = line.replace('\1', '</del>')
-                        f[i] = Markup(line)
+                        line = escape(line, quotes=False) 
+                        line = '<del>'.join([space_re.sub(htmlify, seg) 
+                                             for seg in line.split('\0')]) 
+                        line = line.replace('\1', '</del>') 
+                        f[i] = Markup(line) 
+                        if 'meta' in base and i in base['meta']: 
+                            f[i] = Markup('<em>%s</em>') % f[i] 
                     for i in xrange(len(t)):
                         line = t[i].expandtabs(tabwidth)
-                        line = escape(line)
-                        line = '<ins>'.join([space_re.sub(htmlify, seg)
-                                             for seg in line.split('\0')])
-                        line = line.replace('\1', '</ins>')
-                        t[i] = Markup(line)
-        return output
+                        line = escape(line, quotes=False) 
+                        line = '<ins>'.join([space_re.sub(htmlify, seg) 
+                                             for seg in line.split('\0')]) 
+                        line = line.replace('\1', '</ins>') 
+                        t[i] = Markup(line) 
+                        if 'meta' in changed and i in changed['meta']: 
+                            t[i] = Markup('<em>%s</em>') % t[i] 
+        return changes 

