Index: templates/changeset.cs
===================================================================
--- templates/changeset.cs	(Revision 2436)
+++ templates/changeset.cs	(working copy)
@@ -22,7 +22,7 @@
 </div>
 
 <div id="content" class="changeset">
-<h1>Changeset <?cs var:changeset.revision ?></h1>
+<h1><a href="<?cs var:changeset.href ?>">Changeset <?cs var:changeset.revision ?></a></h1>
 
 <?cs each:change = changeset.changes ?><?cs
  if:len(change.diff) ?><?cs
@@ -100,108 +100,147 @@
   /if ?>
 <?cs /def ?>
 
-<dl id="overview">
- <dt class="time">Timestamp:</dt>
- <dd class="time"><?cs var:changeset.time ?></dd>
- <dt class="author">Author:</dt>
- <dd class="author"><?cs var:changeset.author ?></dd>
- <dt class="message">Message:</dt>
- <dd class="message" id="searchable"><?cs
-  alt:changeset.message ?>&nbsp;<?cs /alt ?></dd>
- <dt class="files">Files:</dt>
- <dd class="files">
-  <ul><?cs each:item = changeset.changes ?>
-   <li><?cs
-    if:item.change == 'add' ?><?cs
-     call:node_change(item, 'add', 'added') ?><?cs
-    elif:item.change == 'delete' ?><?cs
-     call:node_change(item, 'rem', 'deleted') ?><?cs
-    elif:item.change == 'copy' ?><?cs
-     call:node_change(item, 'cp', 'copied') ?><?cs
-    elif:item.change == 'move' ?><?cs
-     call:node_change(item, 'mv', 'moved') ?><?cs
-    elif:item.change == 'edit' ?><?cs
-     call:node_change(item, 'mod', 'modified') ?><?cs
-    /if ?>
-   </li>
-  <?cs /each ?></ul>
- </dd>
-</dl>
+<?cs if:changeset.mode == "view" ?>
+ <dl id="overview">
+  <dt class="time">Timestamp:</dt>
+  <dd class="time"><?cs var:changeset.time ?></dd>
+  <dt class="author">Author:</dt>
+  <dd class="author"><?cs var:changeset.author ?></dd>
+  <dt class="message"><a href="?action=edit" title="Edit Message">Message:</a></dt>
+  <dd class="message" id="searchable"><?cs
+   alt:changeset.message_html ?>&nbsp;<?cs /alt ?></dd>
+  <dt class="files">Files:</dt>
+  <dd class="files">
+   <ul><?cs each:item = changeset.changes ?>
+    <li><?cs
+     if:item.change == 'add' ?><?cs
+      call:node_change(item, 'add', 'added') ?><?cs
+     elif:item.change == 'delete' ?><?cs
+      call:node_change(item, 'rem', 'deleted') ?><?cs
+     elif:item.change == 'copy' ?><?cs
+      call:node_change(item, 'cp', 'copied') ?><?cs
+     elif:item.change == 'move' ?><?cs
+      call:node_change(item, 'mv', 'moved') ?><?cs
+     elif:item.change == 'edit' ?><?cs
+      call:node_change(item, 'mod', 'modified') ?><?cs
+     /if ?>
+    </li>
+   <?cs /each ?></ul>
+  </dd>
+ </dl>
 
-<div class="diff">
- <div id="legend">
-  <h3>Legend:</h3>
-  <dl>
-   <dt class="unmod"></dt><dd>Unmodified</dd>
-   <dt class="add"></dt><dd>Added</dd>
-   <dt class="rem"></dt><dd>Removed</dd>
-   <dt class="mod"></dt><dd>Modified</dd>
-   <dt class="cp"></dt><dd>Copied</dd>
-   <dt class="mv"></dt><dd>Moved</dd>
-  </dl>
+ <div class="diff">
+  <div id="legend">
+   <h3>Legend:</h3>
+   <dl>
+    <dt class="unmod"></dt><dd>Unmodified</dd>
+    <dt class="add"></dt><dd>Added</dd>
+    <dt class="rem"></dt><dd>Removed</dd>
+    <dt class="mod"></dt><dd>Modified</dd>
+    <dt class="cp"></dt><dd>Copied</dd>
+    <dt class="mv"></dt><dd>Moved</dd>
+   </dl>
+  </div>
+  <ul class="entries"><?cs
+  each:item = changeset.changes ?><?cs
+   if:len(item.diff) || len(item.props) ?><li class="entry" id="file<?cs
+    var:name(item) ?>"><h2><a href="<?cs
+    var:item.browser_href.new ?>" title="Show new revision <?cs
+    var:item.rev.new ?> of this file in browser"><?cs
+    var:item.path.new ?></a></h2><?cs
+    if:len(item.props) ?><ul class="props"><?cs
+     each:prop = item.props ?><li>Property <strong><?cs
+      var:name(prop) ?></strong> <?cs
+      if:prop.old && prop.new ?>changed from <?cs
+      elif:!prop.old ?>set<?cs
+      else ?>deleted<?cs
+      /if ?><?cs
+      if:prop.old && prop.new ?><em><tt><?cs var:prop.old ?></tt></em><?cs /if ?><?cs
+      if:prop.new ?> to <em><tt><?cs var:prop.new ?></tt></em><?cs /if ?></li><?cs
+     /each ?></ul><?cs
+    /if ?><?cs
+    if:len(item.diff) ?><table class="<?cs
+     var:diff.style ?>" summary="Differences" cellspacing="0"><?cs
+     if:diff.style == 'sidebyside' ?>
+      <colgroup class="l"><col class="lineno" /><col class="content" /></colgroup>
+      <colgroup class="r"><col class="lineno" /><col class="content" /></colgroup>
+      <thead><tr>
+       <th colspan="2"><a href="<?cs
+        var:item.browser_href.old ?>" title="Show old rev. <?cs
+        var:item.rev.old ?> of <?cs var:item.path.old ?>">Revision <?cs
+        var:item.rev.old ?></a></th>
+       <th colspan="2"><a href="<?cs
+        var:item.browser_href.new ?>" title="Show new rev. <?cs
+        var:item.rev.new ?> of <?cs var:item.path.new ?>">Revision <?cs
+        var:item.rev.new ?></a></th>
+       </tr>
+      </thead><?cs
+      each:change = item.diff ?><tbody><?cs
+       call:diff_display(change, diff.style) ?></tbody><?cs
+       if:name(change) < len(item.diff) - 1 ?><tbody class="skipped"><tr>
+        <th>&hellip;</th><td>&nbsp;</td><th>&hellip;</th><td>&nbsp;</td>
+       </tr></tbody><?cs /if ?><?cs
+      /each ?><?cs
+     else ?>
+      <colgroup><col class="lineno" /><col class="lineno" /><col class="content" /></colgroup>
+      <thead><tr>
+       <th title="Revision <?cs var:item.rev.old ?>"><a href="<?cs
+        var:item.browser_href.old ?>" title="Show old version of <?cs
+        var:item.path.old ?>">r<?cs var:item.rev.old ?></a></th>
+       <th title="Revision <?cs var:item.rev.new ?>"><a href="<?cs
+        var:item.browser_href.new ?>" title="Show new version of <?cs
+        var:item.path.new ?>">r<?cs var:item.rev.new ?></a></th>
+       <th>&nbsp;</th></tr>
+      </thead><?cs
+      each:change = item.diff ?><?cs
+       call:diff_display(change, diff.style) ?><?cs
+       if:name(change) < len(item.diff) - 1 ?><tbody class="skipped"><tr>
+        <th>&hellip;</th><th>&hellip;</th><td>&nbsp;</td>
+       </tr></tbody><?cs /if ?><?cs
+      /each ?><?cs
+     /if ?></table><?cs
+    /if ?></li><?cs
+   /if ?><?cs
+  /each ?></ul>
  </div>
- <ul class="entries"><?cs
- each:item = changeset.changes ?><?cs
-  if:len(item.diff) || len(item.props) ?><li class="entry" id="file<?cs
-   var:name(item) ?>"><h2><a href="<?cs
-   var:item.browser_href.new ?>" title="Show new revision <?cs
-   var:item.rev.new ?> of this file in browser"><?cs
-   var:item.path.new ?></a></h2><?cs
-   if:len(item.props) ?><ul class="props"><?cs
-    each:prop = item.props ?><li>Property <strong><?cs
-     var:name(prop) ?></strong> <?cs
-     if:prop.old && prop.new ?>changed from <?cs
-     elif:!prop.old ?>set<?cs
-     else ?>deleted<?cs
-     /if ?><?cs
-     if:prop.old && prop.new ?><em><tt><?cs var:prop.old ?></tt></em><?cs /if ?><?cs
-     if:prop.new ?> to <em><tt><?cs var:prop.new ?></tt></em><?cs /if ?></li><?cs
-    /each ?></ul><?cs
-   /if ?><?cs
-   if:len(item.diff) ?><table class="<?cs
-    var:diff.style ?>" summary="Differences" cellspacing="0"><?cs
-    if:diff.style == 'sidebyside' ?>
-     <colgroup class="l"><col class="lineno" /><col class="content" /></colgroup>
-     <colgroup class="r"><col class="lineno" /><col class="content" /></colgroup>
-     <thead><tr>
-      <th colspan="2"><a href="<?cs
-       var:item.browser_href.old ?>" title="Show old rev. <?cs
-       var:item.rev.old ?> of <?cs var:item.path.old ?>">Revision <?cs
-       var:item.rev.old ?></a></th>
-      <th colspan="2"><a href="<?cs
-       var:item.browser_href.new ?>" title="Show new rev. <?cs
-       var:item.rev.new ?> of <?cs var:item.path.new ?>">Revision <?cs
-       var:item.rev.new ?></a></th>
-      </tr>
-     </thead><?cs
-     each:change = item.diff ?><tbody><?cs
-      call:diff_display(change, diff.style) ?></tbody><?cs
-      if:name(change) < len(item.diff) - 1 ?><tbody class="skipped"><tr>
-       <th>&hellip;</th><td>&nbsp;</td><th>&hellip;</th><td>&nbsp;</td>
-      </tr></tbody><?cs /if ?><?cs
-     /each ?><?cs
-    else ?>
-     <colgroup><col class="lineno" /><col class="lineno" /><col class="content" /></colgroup>
-     <thead><tr>
-      <th title="Revision <?cs var:item.rev.old ?>"><a href="<?cs
-       var:item.browser_href.old ?>" title="Show old version of <?cs
-       var:item.path.old ?>">r<?cs var:item.rev.old ?></a></th>
-      <th title="Revision <?cs var:item.rev.new ?>"><a href="<?cs
-       var:item.browser_href.new ?>" title="Show new version of <?cs
-       var:item.path.new ?>">r<?cs var:item.rev.new ?></a></th>
-      <th>&nbsp;</th></tr>
-     </thead><?cs
-     each:change = item.diff ?><?cs
-      call:diff_display(change, diff.style) ?><?cs
-      if:name(change) < len(item.diff) - 1 ?><tbody class="skipped"><tr>
-       <th>&hellip;</th><th>&hellip;</th><td>&nbsp;</td>
-      </tr></tbody><?cs /if ?><?cs
-     /each ?><?cs
-    /if ?></table><?cs
-   /if ?></li><?cs
-  /if ?><?cs
- /each ?></ul>
-</div>
+<?cs elif:changeset.mode == "edit" ?>
+ <table id="chglist" class="listing">
+  <thead>
+   <tr>
+    <th class="date">Date</th>
+    <th class="author">Author</th>
+    <th class="summary">Log Message</th>
+   </tr>
+  </thead>
+  <tbody>
+   <?cs each change = changeset.change ?>
+     <tr class="<?cs if:name(change) % #2 ?>even<?cs else ?>odd<?cs /if ?>">
+    <td><?cs var:change.time ?></td>
+    <td><?cs var:change.author ?></td>
+    <td><?cs var:change.msg ?></td></tr>
+   <?cs /each ?>
+  </tbody>
+ </table>
+ <br />
+ <form class="mod" id="modcomp" method="post">
+  <fieldset>
+   <legend>Change Log Message:</legend>
+   <div class="field">
+    <fieldset class="iefix">
+     <label for="message">New Message (you may use <a tabindex="42" href="<?cs
+       var:trac.href.wiki ?>/WikiFormatting">WikiFormatting</a> here):</label>
+     <p><textarea id="message" name="message" class="wikitext" rows="6" cols="60"><?cs
+     var:changeset.message ?></textarea></p>
+    </fieldset>
+   </div>
+   <script type="text/javascript" src="<?cs
+     var:chrome.href ?>/common/js/wikitoolbar.js"></script>
+   <div class="buttons">
+    <input type="submit" name="save" value="Save" />
+   </div>
+  </fieldset>
+ </form>
+<?cs /if ?>
 
 </div>
 <?cs include "footer.cs"?>
Index: trac/db_default.py
===================================================================
--- trac/db_default.py	(Revision 2436)
+++ trac/db_default.py	(working copy)
@@ -83,6 +83,12 @@
         Column('author'),
         Column('message'),
         Index(['time'])],
+    Table('log_change', key=('rev', 'time'))[
+        Column('rev'),
+        Column('time', type='int'),
+        Column('author'),
+        Column('message'),
+        Index(['rev', 'time'])],
     Table('node_change', key=('rev', 'path', 'change'))[
         Column('rev'),
         Column('path'),
Index: trac/versioncontrol/cache.py
===================================================================
--- trac/versioncontrol/cache.py	(Revision 2436)
+++ trac/versioncontrol/cache.py	(working copy)
@@ -125,6 +125,13 @@
     def normalize_rev(self, rev):
         return self.repos.normalize_rev(rev)
 
+    def change_log(self, rev, message, author):
+        # update svn fs
+        self.repos.get_changeset(rev).change_log(self.db, message, author)
+        # update cached repo
+        cursor = self.db.cursor()
+        cursor.execute("UPDATE revision SET message=%s WHERE rev=%s", (message, rev))
+        self.db.commit()
 
 class CachedChangeset(Changeset):
 
@@ -153,3 +160,14 @@
             kind = _kindmap[kind]
             change = _actionmap[change]
             yield path, kind, change, base_path, base_rev
+
+    def last_modified(self):
+            cursor = self.db.cursor()
+            cursor.execute("SELECT time FROM log_change WHERE rev=%s", (self.rev,))
+            rows = cursor.fetchall() or []
+
+            def cmp_func(a, b):
+                return cmp(b[0], a[0])
+
+            rows.sort(cmp_func)
+            return rows[0][0] or self.date
\ No newline at end of file
Index: trac/versioncontrol/svn_fs.py
===================================================================
--- trac/versioncontrol/svn_fs.py	(Revision 2436)
+++ trac/versioncontrol/svn_fs.py	(working copy)
@@ -16,7 +16,7 @@
 
 from __future__ import generators
 
-from trac.util import TracError
+from trac.util import TracError, get_reporter_id
 from trac.versioncontrol import Changeset, Node, Repository
 
 import os.path
@@ -504,5 +504,12 @@
         for change in changes:
             yield tuple(change)
 
+    def change_log(self, db, message, author):
+        fs.change_rev_prop(self.fs_ptr, self.rev, core.SVN_PROP_REVISION_LOG, message, self.pool())
+        cursor = db.cursor()
+        cursor.execute("INSERT INTO log_change (rev,time,author,message) "
+                       "VALUES (%s,%s,%s,%s)", (str(self.rev),
+                       int(time.time()), author, self.message))
+
     def _get_prop(self, name):
-        return fs.revision_prop(self.fs_ptr, self.rev, name, self.pool())
+        return fs.revision_prop(self.fs_ptr, self.rev, name, self.pool())
\ No newline at end of file
Index: trac/versioncontrol/web_ui/changeset.py
===================================================================
--- trac/versioncontrol/web_ui/changeset.py	(Revision 2436)
+++ trac/versioncontrol/web_ui/changeset.py	(working copy)
@@ -72,9 +72,17 @@
             req.redirect(self.env.href.changeset(rev))
 
         chgset = repos.get_changeset(rev)
-        req.check_modified(chgset.date,
+        req.check_modified(chgset.last_modified(),
                            diff_options[0] + ''.join(diff_options[1]))
 
+        action = req.args.get('action')
+
+        if req.method == 'POST':
+            # test for message change
+            if req.args.get('message') != chgset.message:
+                repos.change_log(rev, req.args.get('message'), util.get_reporter_id(req))
+            req.redirect(self.env.href.changeset(rev))
+
         format = req.args.get('format')
         if format == 'diff':
             self._render_diff(req, repos, chgset, diff_options)
@@ -155,10 +163,40 @@
             'revision': chgset.rev,
             'time': util.format_datetime(chgset.date),
             'author': util.escape(chgset.author or 'anonymous'),
-            'message': wiki_to_html(chgset.message or '--', self.env, req,
-                                    escape_newlines=True)
+            'message_html': wiki_to_html(chgset.message or '--', self.env, req,
+                                         escape_newlines=True),
+            'message': chgset.message or '--',
+            'href': self.env.href.changeset(chgset.rev)
         }
 
+        action = req.args.get('action')
+        req.hdf['changeset.mode'] = action or 'view'
+
+        if action == 'edit':
+            rev = req.args.get('rev')
+            db = self.env.get_db_cnx()
+            cursor = db.cursor()
+            cursor.execute("SELECT time, author, message FROM log_change WHERE rev=%s", (rev,))
+            rows = cursor.fetchall() or []
+
+            # sort log message history by date (descending)
+            def cmp_func(a, b):
+                return cmp(b[0], a[0])
+
+            rows.sort(cmp_func)
+
+            idx = 0
+            last_message = chgset.message
+            for date, author, msg in rows:
+                req.hdf['changeset.change.%d.time' % idx] = time.strftime('%x %X', time.localtime(date))
+                req.hdf['changeset.change.%d.author' % idx] = author
+                req.hdf['changeset.change.%d.msg' % idx] = wiki_to_oneliner(util.shorten_line(last_message), self.env, db)
+                last_message = msg
+                idx = idx + 1
+            req.hdf['changeset.change.%d.time' % idx] = util.format_datetime(chgset.date)
+            req.hdf['changeset.change.%d.author' % idx] = chgset.author or 'anonymous'
+            req.hdf['changeset.change.%d.msg' % idx] = wiki_to_oneliner(util.shorten_line(last_message), self.env, db)
+
         oldest_rev = repos.oldest_rev
         if chgset.rev != oldest_rev:
             add_link(req, 'first', self.env.href.changeset(oldest_rev),

