diff --git a/trac/attachment.py b/trac/attachment.py
index a3ed370..2b8b303 100644
--- a/trac/attachment.py
+++ b/trac/attachment.py
@@ -275,6 +275,72 @@ class Attachment(object):
             except OSError, e:
                 env.log.error("Can't delete attachment directory %s: %s",
                     attachment_dir, exception_to_unicode(e, traceback=True))
+
+    @classmethod
+    def reparent_all(cls, env, old_realm, old_id, new_realm, new_id, db=None):
+        """Reparent all attachments of a given resource to another resource.
+        
+        :param old_realm: old parent's realm
+        :param old_id: old parent's id
+        :param new_realm: new parent's realm
+        :param new_id: new parent's id
+        :param env: the environent
+        :param db: the database connection
+        """
+        old_dir = os.path.join(os.path.normpath(env.path), 'attachments',
+                               old_realm, old_id)
+        if not os.path.exists(old_dir):
+            return
+
+        new_dir = os.path.join(os.path.normpath(env.path), 'attachments',
+                               new_realm, new_id)
+        
+        @with_transaction(env, db)
+        def do_reparent(db):
+            cursor = db.cursor()
+            cursor.execute("""
+                SELECT filename FROM attachment
+                WHERE type=%s AND id=%s
+                """, (old_realm, old_id))
+            
+            renames = []
+            for filename, in cursor:
+                old_filename = os.path.join(old_dir, filename)
+                new_filename = os.path.join(new_dir, filename)
+                if os.path.exists(new_filename):
+                    raise TracError(_("Can't reparent attachment '%(att)s' as "
+                                      "it already exists in %(realm)s:%(id)s", 
+                                      att=new_filename,
+                                      realm=new_realm, id=new_id))
+                renames.append((old_filename, new_filename))
+            
+            try:
+                if not os.path.exists(new_dir):
+                    os.makedirs(new_dir)
+            except OSError, e:
+                env.log.error("Can't create attachment directory '%s'",
+                              new_dir, exc_info=True)
+                raise TracError(_("Can't create attachment folder for "
+                                  "%(realm)s:%(id)s: %(err)",
+                                  realm=new_realm, id=new_id, err=e))
+            
+            for old_filename, new_filename in renames:
+                try:
+                    os.rename(old_filename, new_filename)
+                except OSError, e:
+                    env.log.error("Can't move attachment from '%s' to '%s'",
+                                  old_filename, new_filename, exc_info=True)
+                    raise TracError(_("Can't move attachment '%(att)s'",
+                                      att=os.path.basename(new_filename)))
+            cursor.execute("""
+                UPDATE attachment SET type=%s, id=%s
+                WHERE type=%s AND id=%s
+                """, (new_realm, new_id, old_realm, old_id))
+            
+        env.log.info('Attachments reparented to: %s:%s', new_realm, new_id)
+
+        
+        return
             
     def open(self):
         self.env.log.debug('Trying to open attachment at %s', self.path)
diff --git a/trac/wiki/api.py b/trac/wiki/api.py
index e95c8c1..e79e98f 100644
--- a/trac/wiki/api.py
+++ b/trac/wiki/api.py
@@ -42,6 +42,9 @@ class IWikiChangeListener(Interface):
     def wiki_page_deleted(page):
         """Called when a page has been deleted."""
 
+    def wiki_page_renamed(page,old_page_name): 
+        """Called when a page has been renamed in-place.""" 
+
     def wiki_page_version_deleted(page):
         """Called when a version of a page has been deleted."""
 
diff --git a/trac/wiki/model.py b/trac/wiki/model.py
index d8183df..7d10da5 100644
--- a/trac/wiki/model.py
+++ b/trac/wiki/model.py
@@ -18,6 +18,7 @@
 
 from datetime import datetime
 
+from trac.attachment import Attachment
 from trac.core import *
 from trac.db.util import with_transaction
 from trac.resource import Resource
@@ -157,6 +158,37 @@ class WikiPage(object):
         self.old_readonly = self.readonly
         self.old_text = self.text
 
+    def rename(self, new_name, db=None):
+        """Rename wiki page in-place, keeping the history intact.
+        Renaming a page this way will eventually leave dangling references
+        to the old page - which litterally doesn't exist anymore.
+        """
+        assert self.exists, 'Cannot rename non-existent page'
+
+        old_name = self.name
+        
+        @with_transaction(self.env, db)
+        def do_rename(db):
+            cursor = db.cursor()
+            new_page = WikiPage(self.env, new_name, version=None, db=db)
+            if new_page.exists:
+                raise TracError(_("Can't rename to existing %(nn)s page.",
+                                  nn=new_name))
+
+            cursor.execute("UPDATE wiki SET name=%s WHERE name=%s",
+                           (new_name, old_name))
+            self.name = new_name
+            self._fetch(self.name, None, db)
+            Attachment.reparent_all(self.env, 'wiki', old_name,
+                                    'wiki', self.name, db)
+
+        self.env.log.info('Renamed page %s in-place to %s', old_name, new_name)
+        
+        for listener in WikiSystem(self.env).change_listeners:
+            if hasattr(listener, 'wiki_page_renamed'):
+                listener.wiki_page_renamed(self, old_name)
+
+
     def get_history(self, db=None):
         if not db:
             db = self.env.get_db_cnx()
diff --git a/trac/wiki/tests/model.py b/trac/wiki/tests/model.py
index 3099e2f..cbbc093 100644
--- a/trac/wiki/tests/model.py
+++ b/trac/wiki/tests/model.py
@@ -1,6 +1,8 @@
 from datetime import datetime
+import tempfile
 import unittest
 
+from trac.attachment import Attachment
 from trac.core import *
 from trac.test import EnvironmentStub
 from trac.util.datefmt import utc, to_timestamp
@@ -14,6 +16,7 @@ class TestWikiChangeListener(Component):
         self.changed = []
         self.deleted = []
         self.deleted_version = []
+        self.renamed = {}
 
     def wiki_page_added(self, page):
         self.added.append(page)
@@ -27,6 +30,9 @@ class TestWikiChangeListener(Component):
     def wiki_page_version_deleted(self, page):
         self.deleted_version.append(page)
 
+    def wiki_page_renamed(self, page, old_page_name):
+        self.renamed[old_page_name] = page
+
 
 class WikiPageTestCase(unittest.TestCase):
 
@@ -194,6 +200,40 @@ class WikiPageTestCase(unittest.TestCase):
         listener = TestWikiChangeListener(self.env)
         self.assertEqual(page, listener.deleted[0])
 
+    def test_rename_page(self):
+        cursor = self.db.cursor()
+        data = (1, 42, 'joe', '::1', 'Bla bla', 'Testing', 0)
+        cursor.execute("INSERT INTO wiki VALUES(%s,%s,%s,%s,%s,%s,%s,%s)",
+                       ('TestPage',) + data)
+        
+        page = WikiPage(self.env, 'TestPage')
+
+        attachment = Attachment(self.env, 'wiki', 'TestPage')
+        attachment.insert('foo.txt', tempfile.TemporaryFile(), 0, 1)
+
+        page.rename('PageRenamed')
+        
+        cursor.execute("SELECT version,time,author,ipnr,text,comment,"
+                       "readonly FROM wiki WHERE name=%s", ('PageRenamed',))
+        self.assertEqual(data, cursor.fetchone())
+        self.assertEqual(None, cursor.fetchone())
+        
+        attachments = Attachment.select(self.env, 'wiki', 'PageRenamed')
+        self.assertEqual('foo.txt', attachments.next().filename)
+        self.assertRaises(StopIteration, attachments.next)
+        Attachment.delete_all(self.env, 'wiki', 'PageRenamed', self.db)
+
+
+        old_page = WikiPage(self.env, 'TestPage')
+        
+        cursor.execute("SELECT version,time,author,ipnr,text,comment,"
+                       "readonly FROM wiki WHERE name=%s", ('TestPage',))
+        self.assertEqual(None, cursor.fetchone())
+        
+        listener = TestWikiChangeListener(self.env)
+        self.assertEqual({'TestPage': page}, listener.renamed)
+
+
 
 def suite():
     return unittest.makeSuite(WikiPageTestCase, 'test')

