Index: trac/db/util.py
===================================================================
--- trac/db/util.py	(revision 8664)
+++ trac/db/util.py	(working copy)
@@ -15,6 +15,36 @@
 #
 # Author: Christopher Lenz <cmlenz@gmx.de>
 
+def with_transaction(env, db):
+    """
+    Transaction decorator for simple use-once transactions.
+    Will be replaced by a context manager once python 2.4 support is dropped.
+    Usage:
+    -- def api_method(p1, p2):
+    --     result[0] = value1
+    --     @with_transaction(env, db)
+    --     def implementation_method(db):
+    --         # implementation
+    --         result[0] = value2
+    --
+    """
+    def transaction_wrapper(fn):
+        if db:
+            try:
+                return fn(db)
+            except:
+                raise
+        else:
+            dbtmp = env.get_db_cnx()
+            try:
+                fn(dbtmp)
+                dbtmp.commit()
+            except:
+                dbtmp.rollback()
+                raise
+    return transaction_wrapper
+
+
 def sql_escape_percent(sql):
     import re
     return re.sub("'((?:[^']|(?:''))*)'",
Index: trac/wiki/model.py
===================================================================
--- trac/wiki/model.py	(revision 8664)
+++ trac/wiki/model.py	(working copy)
@@ -24,6 +24,7 @@
 from trac.util.datefmt import utc, to_timestamp
 from trac.util.translation import _
 from trac.wiki.api import WikiSystem
+from trac.db.util import with_transaction
 
 
 class WikiPage(object):
@@ -53,18 +54,19 @@
     def _fetch(self, name, version=None, db=None):
         if not db:
             db = self.env.get_db_cnx()
-        cursor = db.cursor()
+        cur = db.cursor()
         if version is not None:
-            cursor.execute("SELECT version,time,author,text,comment,readonly "
-                           "FROM wiki "
-                           "WHERE name=%s AND version=%s",
-                           (name, int(version)))
+            cur.execute("SELECT version,time,author,text,comment,readonly "
+                        "FROM wiki "
+                        "WHERE name=%s AND version=%s",
+                        (name, int(version)))
         else:
-            cursor.execute("SELECT version,time,author,text,comment,readonly "
-                           "FROM wiki "
-                           "WHERE name=%s ORDER BY version DESC LIMIT 1",
-                           (name,))
-        row = cursor.fetchone()
+            cur.execute("SELECT version,time,author,text,comment,readonly "
+                        "FROM wiki "
+                        "WHERE name=%s ORDER BY version DESC LIMIT 1",
+                        (name,))
+        row = cur.fetchone()
+
         if row:
             version,time,author,text,comment,readonly = row
             self.version = int(version)
@@ -78,42 +80,36 @@
             self.text = self.comment = self.author = ''
             self.time = None
             self.readonly = 0
-
+            
     exists = property(fget=lambda self: self.version > 0)
 
     def delete(self, version=None, db=None):
         assert self.exists, 'Cannot delete non-existent page'
-        if not db:
-            db = self.env.get_db_cnx()
-            handle_ta = True
-        else:
-            handle_ta = False
-
-        cursor = db.cursor()
-        if version is None:
-            # Delete a wiki page completely
-            cursor.execute("DELETE FROM wiki WHERE name=%s", (self.name,))
-            self.env.log.info('Deleted page %s' % self.name)
-        else:
-            # Delete only a specific page version
-            cursor.execute("DELETE FROM wiki WHERE name=%s and version=%s",
+        
+        @with_transaction(self.env, db)
+        def do_delete(db):
+            cur = db.cursor()
+            if version is None:
+                # Delete a wiki page completely
+                cur.execute("DELETE FROM wiki WHERE name=%s", (self.name,))
+                self.env.log.info('Deleted page %s' % self.name)
+            else:
+                # Delete only a specific page version
+                cur.execute("DELETE FROM wiki WHERE name=%s and version=%s",
                            (self.name, version))
-            self.env.log.info('Deleted version %d of page %s'
-                              % (version, self.name))
+                self.env.log.info('Deleted version %d of page %s'
+                                  % (version, self.name))
 
-        if version is None or version == self.version:
-            self._fetch(self.name, None, db)
+            if version is None or version == self.version:
+                self._fetch(self.name, None, db)
 
-        if not self.exists:
-            # Invalidate page name cache
-            WikiSystem(self.env).pages.invalidate(db)
-            # Delete orphaned attachments
-            from trac.attachment import Attachment
-            Attachment.delete_all(self.env, 'wiki', self.name, db)
+            if not self.exists:
+                # Invalidate page name cache
+                WikiSystem(self.env).pages.invalidate(db)
+                # Delete orphaned attachments
+                from trac.attachment import Attachment
+                Attachment.delete_all(self.env, 'wiki', self.name, db)
 
-        if handle_ta:
-            db.commit()
-
         # Let change listeners know about the deletion
         if not self.exists:
             for listener in WikiSystem(self.env).change_listeners:
@@ -125,38 +121,29 @@
 
 
     def save(self, author, comment, remote_addr, t=None, db=None):
-        if not db:
-            db = self.env.get_db_cnx()
-            handle_ta = True
-        else:
-            handle_ta = False
-
-        if t is None:
-            t = datetime.now(utc)
-
-        if self.text != self.old_text:
-            cursor = db.cursor()
-            cursor.execute("INSERT INTO wiki (name,version,time,author,ipnr,"
-                           "text,comment,readonly) VALUES (%s,%s,%s,%s,%s,%s,"
-                           "%s,%s)", (self.name, self.version + 1,
-                                      to_timestamp(t), author, remote_addr,
-                                      self.text, comment, self.readonly))
-            self.version += 1
-            self.resource = self.resource(version=self.version)
-        elif self.readonly != self.old_readonly:
-            cursor = db.cursor()
-            cursor.execute("UPDATE wiki SET readonly=%s WHERE name=%s",
-                           (self.readonly, self.name))
-        else:
+        new_text = self.text != self.old_text
+        if not new_text and self.readonly == self.old_readonly:
             raise TracError(_('Page not modified'))
+        t = t or datetime.now(utc)
 
-        if self.version == 1:
-            # Invalidate page name cache
-            WikiSystem(self.env).pages.invalidate(db)
+        @with_transaction(self.env, db)
+        def do_save(db):
+            cur = db.cursor()
+            if(new_text):
+                cur.execute("INSERT INTO wiki (name,version,time,author,ipnr,"
+                            "text,comment,readonly) VALUES (%s,%s,%s,%s,%s,%s,"
+                            "%s,%s)", (self.name, self.version + 1,
+                                       to_timestamp(t), author, remote_addr,
+                                       self.text, comment, self.readonly))
+                self.version += 1
+                self.resource = self.resource(version=self.version)
+            else:
+                cur.execute("UPDATE wiki SET readonly=%s WHERE name=%s",
+                            (self.readonly, self.name))
+            if self.version == 1:
+                # Invalidate page name cache
+                WikiSystem(self.env).pages.invalidate(db)
         
-        if handle_ta:
-            db.commit()
-
         for listener in WikiSystem(self.env).change_listeners:
             if self.version == 1:
                 listener.wiki_page_added(self)
