Index: trunk/ippTools/src/chiptool.c
===================================================================
--- trunk/ippTools/src/chiptool.c	(revision 21443)
+++ trunk/ippTools/src/chiptool.c	(revision 21463)
@@ -42,4 +42,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);
@@ -54,6 +55,4 @@
 static bool topurgedimfileMode(pxConfig *config);
 
-static bool chipProcessedCompleteExp(pxConfig *config);
-
 # define MODECASE(caseName, func) \
     case caseName: \
@@ -80,4 +79,5 @@
         MODECASE(CHIPTOOL_MODE_REVERTPROCESSEDIMFILE,   revertprocessedimfileMode);
         MODECASE(CHIPTOOL_MODE_UPDATEPROCESSEDIMFILE,   updateprocessedimfileMode);
+        MODECASE(CHIPTOOL_MODE_PROMOTEEXP,              promoteexpMode);
         MODECASE(CHIPTOOL_MODE_BLOCK,                   blockMode);
         MODECASE(CHIPTOOL_MODE_MASKED,                  maskedMode);
@@ -495,4 +495,5 @@
     }
 
+#if 0
     // 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
@@ -508,4 +509,5 @@
         return false;
     }
+#endif
 
     if (!psDBCommit(config->dbh)) {
@@ -1074,7 +1076,12 @@
 
 
-static bool chipProcessedCompleteExp(pxConfig *config)
+static bool promoteexpMode(pxConfig *config)
 {
     PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-label", "label", "==");
 
     // look for completed chipPendingExp
@@ -1086,4 +1093,18 @@
     }
 
+    if (where && psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereSQL(where, NULL);
+        psStringAppend(&query, " %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");
@@ -1108,4 +1129,9 @@
 
         chipRunRow *chipRun = chipRunObjectFromMetadata(row);
+        if (!psDBTransaction(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        }
+
         // set chipRun.state to 'stop'
         if (!pxchipRunSetState(config, chipRun->chip_id, "full")) {
@@ -1119,4 +1145,9 @@
         // NULL for end_stage means go as far as possible
         if (chipRun->end_stage && psStrcasestr(chipRun->end_stage, "chip")) {
+            if (!psDBCommit(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+                return false;
+            }
+
             psFree(chipRun);
             continue;
@@ -1136,6 +1167,5 @@
                     chipRun->end_stage
         )) {
-            // rollback
-            if (!psDBRollback(config->dbh)) {
+           if (!psDBRollback(config->dbh)) {
                 psError(PS_ERR_UNKNOWN, false, "database error");
             }
@@ -1145,4 +1175,8 @@
             return false;
         }
+        if (!psDBCommit(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        }
         psFree(chipRun);
     }
Index: trunk/ippTools/src/chiptool.h
===================================================================
--- trunk/ippTools/src/chiptool.h	(revision 21443)
+++ trunk/ippTools/src/chiptool.h	(revision 21463)
@@ -32,4 +32,5 @@
     CHIPTOOL_MODE_REVERTPROCESSEDIMFILE,
     CHIPTOOL_MODE_UPDATEPROCESSEDIMFILE,
+    CHIPTOOL_MODE_PROMOTEEXP,
     CHIPTOOL_MODE_BLOCK,
     CHIPTOOL_MODE_MASKED,
Index: trunk/ippTools/src/chiptoolConfig.c
===================================================================
--- trunk/ippTools/src/chiptoolConfig.c	(revision 21443)
+++ trunk/ippTools/src/chiptoolConfig.c	(revision 21463)
@@ -176,4 +176,10 @@
     psMetadataAddS16(updateprocessedimfileArgs, PS_LIST_TAIL, "-code",  0,            "set fault code (required)", 0);
 
+    // -promoteexp
+    psMetadata *promoteexpArgs = psMetadataAlloc();
+    psMetadataAddS64(promoteexpArgs, PS_LIST_TAIL, "-chip_id",  0,          "search by chip ID", 0);
+    psMetadataAddStr(promoteexpArgs, PS_LIST_TAIL, "-label",  0,            "promote exposures for specified label", NULL);
+    psMetadataAddU64(promoteexpArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+
     // -block
     psMetadata *blockArgs = psMetadataAlloc();
@@ -248,4 +254,5 @@
     PXOPT_ADD_MODE("-updateprocessedimfile","change procesed imfile properties",    CHIPTOOL_MODE_UPDATEPROCESSEDIMFILE,updateprocessedimfileArgs);
     PXOPT_ADD_MODE("-revertprocessedimfile","undo a processed imfile",              CHIPTOOL_MODE_REVERTPROCESSEDIMFILE,revertprocessedimfileArgs);
+    PXOPT_ADD_MODE("-promoteexp",           "promote completed exposures",          CHIPTOOL_MODE_PROMOTEEXP,           promoteexpArgs);
     PXOPT_ADD_MODE("-block",                "set a label block",                    CHIPTOOL_MODE_BLOCK,                blockArgs);
     PXOPT_ADD_MODE("-masked",               "show blocked labels",                  CHIPTOOL_MODE_MASKED,               maskedArgs);
Index: trunk/ippTools/src/faketool.c
===================================================================
--- trunk/ippTools/src/faketool.c	(revision 21443)
+++ 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;
         }
Index: trunk/ippTools/src/faketool.h
===================================================================
--- trunk/ippTools/src/faketool.h	(revision 21443)
+++ trunk/ippTools/src/faketool.h	(revision 21463)
@@ -33,4 +33,5 @@
     FAKETOOL_MODE_REVERTPROCESSEDIMFILE,
     FAKETOOL_MODE_UPDATEPROCESSEDIMFILE,
+    FAKETOOL_MODE_PROMOTEEXP,
     FAKETOOL_MODE_BLOCK,
     FAKETOOL_MODE_MASKED,
Index: trunk/ippTools/src/faketoolConfig.c
===================================================================
--- trunk/ippTools/src/faketoolConfig.c	(revision 21443)
+++ trunk/ippTools/src/faketoolConfig.c	(revision 21463)
@@ -241,4 +241,11 @@
     psMetadataAddS16(revertprocessedimfileArgs, PS_LIST_TAIL, "-code",  0,            "search by fault code", 0);
 
+
+    // -promoteexp
+    psMetadata *promoteexpArgs = psMetadataAlloc();
+    psMetadataAddS64(promoteexpArgs, PS_LIST_TAIL, "-fake_id", 0,      "search by fake ID", 0);
+    psMetadataAddStr(promoteexpArgs, PS_LIST_TAIL, "-label",  0,       "search by label ", NULL);
+    psMetadataAddU64(promoteexpArgs, PS_LIST_TAIL, "-limit",  0,       "limit exposures to promote to N items", 0);
+
     // -block
     psMetadata *blockArgs = psMetadataAlloc();
@@ -307,4 +314,5 @@
     PXOPT_ADD_MODE("-updateprocessedimfile","change procesed imfile properties",     FAKETOOL_MODE_UPDATEPROCESSEDIMFILE,    updateprocessedimfileArgs);
     PXOPT_ADD_MODE("-revertprocessedimfile", "undo a processed imfile",              FAKETOOL_MODE_REVERTPROCESSEDIMFILE,    revertprocessedimfileArgs);
+    PXOPT_ADD_MODE("-promoteexp",            "promote completed exposoures",         FAKETOOL_MODE_PROMOTEEXP,          promoteexpArgs);
     PXOPT_ADD_MODE("-block",                 "set a label block",                    FAKETOOL_MODE_BLOCK,          blockArgs);
     PXOPT_ADD_MODE("-masked",                "show blocked labels",                  FAKETOOL_MODE_MASKED,         maskedArgs);
