diff -r c1db312f6768 htdocs/css/ticket.css
--- a/htdocs/css/ticket.css	Tue May 09 01:49:12 2006 +0200
+++ b/htdocs/css/ticket.css	Tue May 09 02:18:43 2006 +0200
@@ -51,6 +51,11 @@
  font-size: 100%;
  font-weight: normal;
 }
+span.commentcmd { 
+ padding-top: 0.5em; 
+ float: right; 
+ font-size: 85%;
+}
 #changelog .changes { list-style: square; margin-left: 2em; padding: 0 }
 #changelog .comment { margin-left: 2em }
 
diff -r c1db312f6768 htdocs/css/trac.css
--- a/htdocs/css/trac.css	Tue May 09 01:49:12 2006 +0200
+++ b/htdocs/css/trac.css	Tue May 09 02:18:43 2006 +0200
@@ -40,6 +40,19 @@ h1 :link, h1 :visited ,h2 :link, h2 :vis
 h1 :link, h1 :visited ,h2 :link, h2 :visited, h3 :link, h3 :visited,
 h4 :link, h4 :visited, h5 :link, h5 :visited, h6 :link, h6 :visited {
  color: inherit;
+}
+
+/* Heading anchors */
+.anchor:link, .anchor:visited {
+ border: none;
+ color: #d7d7d7;
+ font-size: .8em;
+ vertical-align: text-top;
+ visibility: hidden;
+}
+h1:hover .anchor, h2:hover .anchor, h3:hover .anchor,
+h4:hover .anchor, h5:hover .anchor, h6:hover .anchor {
+ visibility: visible;
 }
 
 @media screen {
diff -r c1db312f6768 htdocs/css/wiki.css
--- a/htdocs/css/wiki.css	Tue May 09 01:49:12 2006 +0200
+++ b/htdocs/css/wiki.css	Tue May 09 02:18:43 2006 +0200
@@ -21,19 +21,6 @@
 #overview .multi { color: #999 }
 #overview .ipnr { color: #999; font-size: 80% }
 #overview .comment { padding: 1em 0 0 }
-
-/* Heading anchors */
-.anchor:link, .anchor:visited {
- border: none;
- color: #d7d7d7;
- font-size: .8em;
- vertical-align: text-top;
- visibility: hidden;
-}
-h1:hover .anchor, h2:hover .anchor, h3:hover .anchor,
-h4:hover .anchor, h5:hover .anchor, h6:hover .anchor {
- visibility: visible;
-}
 
 /* Styles for the page history table
    (extends the styles for "table.listing") */
diff -r c1db312f6768 templates/ticket.cs
--- a/templates/ticket.cs	Tue May 09 01:49:12 2006 +0200
+++ b/templates/ticket.cs	Tue May 09 02:18:43 2006 +0200
@@ -100,8 +100,15 @@
 <?cs if:len(ticket.changes) ?><h2>Change History</h2>
 <div id="changelog"><?cs
  each:change = ticket.changes ?>
-  <h3 id="change_<?cs var:name(change) ?>" class="change"><?cs
-   var:change.date ?>: Modified by <?cs var:change.author ?></h3><?cs
+  <h3 <?cs if:change.cnum ?>id="comment:<?cs var:change.cnum ?>"<?cs /if ?> 
+      class="change"><?cs
+   var:change.date ?>: Modified by <?cs var:change.author ?><?cs
+   if:change.cnum ?><a href="#comment:<?cs var:change.cnum ?>" class="anchor"
+      title="Permalink to comment:<?cs var:change.cnum ?>">
+    &para;</a>
+    <span class="commentcmd"><a href="<?cs var:ticket.href ?>?replyto=<?cs var:change.cnum ?>#comment"
+       title="Reply to this comment" ?>[Reply]</a></span><?cs 
+   /if ?></h3><?cs
   if:len(change.fields) ?>
    <ul class="changes"><?cs
    each:field = change.fields ?>
diff -r c1db312f6768 trac/ticket/api.py
--- a/trac/ticket/api.py	Tue May 09 01:49:12 2006 +0200
+++ b/trac/ticket/api.py	Tue May 09 02:18:43 2006 +0200
@@ -186,7 +186,8 @@ class TicketSystem(Component):
 
     def get_link_resolvers(self):
         return [('bug', self._format_link),
-                ('ticket', self._format_link)]
+                ('ticket', self._format_link),
+                ('comment', self._format_comment_link)]
 
     def get_wiki_syntax(self):
         yield (
@@ -215,6 +216,29 @@ class TicketSystem(Component):
         return html.A(class_='missing ticket', rel='nofollow',
                       href=formatter.href.ticket(target))[label]
 
+    def _format_comment_link(self, formatter, ns, target, label):
+        type, id, cnum = 'ticket', '1', 0
+        href = None
+        if ':' in target:
+            elts = target.split(':')
+            if len(elts) == 3:
+                type, id, cnum = elts
+                href = formatter.href(type, id)
+        else:
+            # FIXME: the formatter should know which object the text being
+            #        formatted belongs to
+            if formatter.req:
+                path_info = formatter.req.path_info.strip('/').split('/', 2)
+                if len(path_info) == 2:
+                    type, id = path_info[:2]
+                    href = formatter.href(type, id)
+                    cnum = target
+        if href:
+            return html.A(label, href="%s#comment:%s" % (href, cnum),
+                          title="Comment %s for %s:%s" % (cnum, type, id))
+        else:
+            return label
+ 
     # ISearchSource methods
 
     def get_search_filters(self, req):
diff -r c1db312f6768 trac/ticket/model.py
--- a/trac/ticket/model.py	Tue May 09 01:49:12 2006 +0200
+++ b/trac/ticket/model.py	Tue May 09 02:18:43 2006 +0200
@@ -265,29 +265,30 @@ class Ticket(object):
         db = self._get_db(db)
         cursor = db.cursor()
         if when:
-            cursor.execute("SELECT time,author,field,oldvalue,newvalue "
+            cursor.execute("SELECT time,author,field,oldvalue,newvalue,1 "
                            "FROM ticket_change WHERE ticket=%s AND time=%s "
                            "UNION "
-                           "SELECT time,author,'attachment',null,filename "
+                           "SELECT time,author,'attachment',null,filename,0 "
                            "FROM attachment WHERE id=%s AND time=%s "
                            "UNION "
-                           "SELECT time,author,'comment',null,description "
+                           "SELECT time,author,'comment',null,description,0 "
                            "FROM attachment WHERE id=%s AND time=%s "
                            "ORDER BY time",
                            (self.id, when, str(self.id), when, self.id, when))
         else:
-            cursor.execute("SELECT time,author,field,oldvalue,newvalue "
+            cursor.execute("SELECT time,author,field,oldvalue,newvalue,1 "
                            "FROM ticket_change WHERE ticket=%s "
                            "UNION "
-                           "SELECT time,author,'attachment',null,filename "
+                           "SELECT time,author,'attachment',null,filename,0 "
                            "FROM attachment WHERE id=%s "
                            "UNION "
-                           "SELECT time,author,'comment',null,description "
+                           "SELECT time,author,'comment',null,description,0 "
                            "FROM attachment WHERE id=%s "
                            "ORDER BY time", (self.id,  str(self.id), self.id))
         log = []
-        for t, author, field, oldvalue, newvalue in cursor:
-            log.append((int(t), author, field, oldvalue or '', newvalue or ''))
+        for t, author, field, oldvalue, newvalue, permanent in cursor:
+            log.append((int(t), author, field, oldvalue or '', newvalue or '',
+                        permanent))
         return log
 
     def delete(self, db=None):
diff -r c1db312f6768 trac/ticket/notification.py
--- a/trac/ticket/notification.py	Tue May 09 01:49:12 2006 +0200
+++ b/trac/ticket/notification.py	Tue May 09 02:18:43 2006 +0200
@@ -75,7 +75,7 @@ class TicketNotifyEmail(NotifyEmail):
         changes = ''
         if not self.newticket and modtime:  # Ticket change
             changelog = ticket.get_changelog(modtime)
-            for date, author, field, old, new in changelog:
+            for date, author, field, old, new, permanent in changelog:
                 self.hdf.set_unescaped('ticket.change.author', author)
                 pfx = 'ticket.change.%s' % field
                 newv = ''
diff -r c1db312f6768 trac/ticket/web_ui.py
--- a/trac/ticket/web_ui.py	Tue May 09 01:49:12 2006 +0200
+++ b/trac/ticket/web_ui.py	Tue May 09 02:18:43 2006 +0200
@@ -226,6 +226,7 @@ class TicketModule(TicketModuleBase):
 
         ticket = Ticket(self.env, id, db=db)
         reporter_id = get_reporter_id(req)
+        replyto = req.args.get('replyto')
 
         if req.method == 'POST':
             if not req.args.has_key('preview'):
@@ -253,7 +254,7 @@ class TicketModule(TicketModuleBase):
             # Store a timestamp in order to detect "mid air collisions"
             req.hdf['ticket.ts'] = ticket.time_changed
 
-        self._insert_ticket_data(req, db, ticket, reporter_id)
+        self._insert_ticket_data(req, db, ticket, reporter_id, replyto)
 
         # If the ticket is being shown in the context of a query, add
         # links to help navigate in the query result set
@@ -426,7 +427,7 @@ class TicketModule(TicketModuleBase):
 
         req.redirect(req.href.ticket(ticket.id))
 
-    def _insert_ticket_data(self, req, db, ticket, reporter_id):
+    def _insert_ticket_data(self, req, db, ticket, reporter_id, replyto):
         """Insert ticket data into the hdf"""
         req.hdf['ticket'] = ticket.values
         req.hdf['ticket.id'] = ticket.id
@@ -462,8 +463,9 @@ class TicketModule(TicketModuleBase):
         changelog = ticket.get_changelog(db=db)
         curr_author = None
         curr_date   = 0
+        cnum = 0
         changes = []
-        for date, author, field, old, new in changelog:
+        for date, author, field, old, new, permanent in changelog:
             if date != curr_date or author != curr_author:
                 changes.append({
                     'date': format_datetime(date),
@@ -472,7 +474,14 @@ class TicketModule(TicketModuleBase):
                 })
                 curr_date = date
                 curr_author = author
+                if permanent:
+                    cnum += 1
+                    changes[-1]['cnum'] = cnum
             if field == 'comment':
+                if permanent and replyto == str(cnum):
+                    req.hdf['ticket.comment'] = '\n'.join(
+                        ['Replying to [comment:%s %s:]' % (replyto, author)] +
+                        ['>%s' % line for line in new.splitlines()] + [''])
                 changes[-1]['comment'] = wiki_to_html(new, self.env, req, db)
             elif field == 'description':
                 changes[-1]['fields'][field] = ''

