Index: trunk/ippTools/src/chiptool.c
===================================================================
--- trunk/ippTools/src/chiptool.c	(revision 8073)
+++ trunk/ippTools/src/chiptool.c	(revision 8120)
@@ -241,15 +241,16 @@
     psFree(pendingImfiles);
 
-    // point of no return for p2PendingImfile -> p2DoneImfile 
-    if (!psDBCommit(config->dbh)) {
-        psError(PS_ERR_UNKNOWN, false, "database error");
-        return false;
-    }
-
-    // XXX make exposure state transition transactional
+    // XXX I've decided to make the transaction cover the Exp migration as
+    // well.  Otherwise, if the last imfile in an exp is moved and the exp
+    // migration fails then the data base is left in a satuation where the exp
+    // migration can't happen.
 
     // look for pending exposures
     psArray *pendingExps = p2searchPendingExp(config);
     if (!pendingExps) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
         psError(PS_ERR_UNKNOWN, false, "no p2PendingExps found");
         return false;
@@ -265,29 +266,52 @@
         if (!pendingImfiles) {
             // exp has no coresponding imfiles
-            psArray *nukeMe = psArrayAlloc(1);
-            nukeMe->n = 0;
-            psArrayAdd(nukeMe, 0, pendingExp);
-            bool status = p2PendingExpDeleteRowObjects(config->dbh, nukeMe, MAX_ROWS);
-            psFree(nukeMe);
-            if (!status) {
+            psMetadata *where = psMetadataAlloc();
+            psMetadataAddStr(where, PS_LIST_TAIL, "exp_id", 0, "==", pendingExp->exp_id);
+            long long count = p2PendingExpDelete(config->dbh, where, MAX_ROWS);
+            psFree(where);
+            if (count != 0) {
+                // rollback
+                if (!psDBRollback(config->dbh)) {
+                    psError(PS_ERR_UNKNOWN, false, "database error");
+                }
                 psError(PS_ERR_UNKNOWN, false, "dbh access failed");
+                psFree(pendingExps);
                 return false;
             }
-            // XXX need a func to convert from p2PendingExp -> p2DoneExp
 
             p2DoneExpRow *doneExp = p2pendingToDoneExp(pendingExp);
             if (!doneExp) {
+                // rollback
+                if (!psDBRollback(config->dbh)) {
+                    psError(PS_ERR_UNKNOWN, false, "database error");
+                }
                 psError(PS_ERR_UNKNOWN, false, "p2PendingExp -> p2DoneExp failed");
+                psFree(pendingExps);
                 return false;
             }
             if (!p2DoneExpInsertObject(config->dbh, doneExp)) {
+                // rollback
+                if (!psDBRollback(config->dbh)) {
+                    psError(PS_ERR_UNKNOWN, false, "database error");
+                }
                 psError(PS_ERR_UNKNOWN, false, "dbh access failed");
+                psFree(doneExp);
+                psFree(pendingExps);
                 return false;
             }
-        }
-        // skip phase 3 for the time being
+            psFree(doneExp);
+        }
+        // XXX skip phase 3 for the time being
         psFree(pendingImfiles);
     }
 
+    psFree(pendingExps);
+
+    // point of no return for p2PendingImfile -> p2DoneImfile 
+    // point of no return for p2PendingExp -> p2DoneExp
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
     /*
     psArray *doneFrames = p2pendingToDone(config, pendingFrames);
@@ -328,5 +352,4 @@
         uri,
         "my recipe", // recipe
-        "my stats", // stats
         imfile->p1_version,
         imfile->p2_version
