Index: trunk/ippTools/src/faketool.c
===================================================================
--- trunk/ippTools/src/faketool.c	(revision 21402)
+++ trunk/ippTools/src/faketool.c	(revision 21463)
@@ -41,4 +41,5 @@
 static bool revertprocessedimfileMode(pxConfig *config);
 static bool updateprocessedimfileMode(pxConfig *config);
+static bool promoteexpMode(pxConfig *config);
 static bool blockMode(pxConfig *config);
 static bool maskedMode(pxConfig *config);
@@ -52,6 +53,4 @@
 static bool topurgedimfileMode(pxConfig *config);
 
-static bool fakeProcessedCompleteExp(pxConfig *config);
-
 # define MODECASE(caseName, func) \
     case caseName: \
@@ -79,4 +78,5 @@
         MODECASE(FAKETOOL_MODE_REVERTPROCESSEDIMFILE,   revertprocessedimfileMode);
         MODECASE(FAKETOOL_MODE_UPDATEPROCESSEDIMFILE,   updateprocessedimfileMode);
+        MODECASE(FAKETOOL_MODE_PROMOTEEXP,              promoteexpMode);
         MODECASE(FAKETOOL_MODE_BLOCK,                   blockMode);
         MODECASE(FAKETOOL_MODE_MASKED,                  maskedMode);
@@ -520,18 +520,4 @@
     }
 
-    // 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.
-
-    if (!fakeProcessedCompleteExp(config)) {
-        // rollback
-        if (!psDBRollback(config->dbh)) {
-            psError(PS_ERR_UNKNOWN, false, "database error");
-        }
-        psError(PS_ERR_UNKNOWN, false, "database error");
-        return false;
-    }
-
     if (!psDBCommit(config->dbh)) {
         psError(PS_ERR_UNKNOWN, false, "database error");
@@ -1062,7 +1048,13 @@
 }
 
-static bool fakeProcessedCompleteExp(pxConfig *config)
+static bool promoteexpMode(pxConfig *config)
 {
     PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-fake_id", "fake_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-label", "label", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
 
     // look for completed fakePendingExp
@@ -1074,4 +1066,18 @@
     }
 
+    if (where && psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&query, " WHERE %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
     if (!p_psDBRunQuery(config->dbh, query)) {
         psError(PS_ERR_UNKNOWN, false, "database error");
@@ -1096,7 +1102,15 @@
 
         fakeRunRow *fakeRun = fakeRunObjectFromMetadata(row);
+        if (!psDBTransaction(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        }
+
         // set fakeRun.state to 'full'
         if (!pxfakeRunSetState(config, fakeRun->fake_id, "full")) {
             psError(PS_ERR_UNKNOWN, false, "failed to change fakeRun.state for fake_id: %" PRId64, fakeRun->fake_id);
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
             psFree(fakeRun);
             psFree(output);
@@ -1104,14 +1118,17 @@
         }
 
-        // should we stop here or proceed on to the cam stage?
+        // should we stop here or proceed on to the warp stage?
         // NULL for end_stage means go as far as possible
         if ((fakeRun->end_stage && psStrcasestr(fakeRun->end_stage, "fake")) || !fakeRun->tess_id) {
             psFree(fakeRun);
+            if (!psDBCommit(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+                return false;
+            }
             continue;
         }
         // else continue on...
 
-        // camQueueFakeID() can only be run after fakeRun.state has been set to
-        // stop
+        // pxwarpQueueByFakeID() can only be run after fakeRun.state has been set to stop
         if (!pxwarpQueueByFakeID(config,
                     fakeRun->fake_id,
@@ -1122,7 +1139,15 @@
                     fakeRun->end_stage
         )) {
-            psError(PS_ERR_UNKNOWN, false, "failed to queue camPendingExp");
+            psError(PS_ERR_UNKNOWN, false, "failed to queue warpRun");
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
             psFree(fakeRun);
             psFree(output);
+            return false;
+        }
+        if (!psDBCommit(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(fakeRun);
             return false;
         }
