Fossil

Check-in [22aa74dc]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:The "stash" functionality is now in place. Need to test more prior to merging into trunk.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | experimental
Files: files | file ages | folders
SHA1:22aa74dcdf505ca90ce0f642027de3ad57400f25
User & Date: drh 2010-12-18 18:56:34
Context
2010-12-18
20:39
Many bug fixes while testing stash: Fix "revert" so that it updates the file status correctly. Fix several cases of "//" being used as a file separator instead of just "/". Fix undo on stash apply. Make "stash drop" undoable. Update documentation for undo and stash. check-in: 8a6aa0a1 user: drh tags: experimental
18:56
The "stash" functionality is now in place. Need to test more prior to merging into trunk. check-in: 22aa74dc user: drh tags: experimental
17:24
Begin adding code to implement the "stash" command. The code compiles and runs but is currently incomplete. This is an incremental check-in to preserve state. (Ironically, if "stash" were working, I'd have probably just done a "stash snapshot" to capture this state, rather than an experimental branch.) check-in: b3dadcc4 user: drh tags: experimental
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/blob.c.

277
278
279
280
281
282
283
284

285
286
287
288
289
290
291
  blob_is_init(p);
  if( p->nUsed==0 ) return "";
  p->aData[p->nUsed] = 0;
  return p->aData;
}

/*
** Compare two blobs.

*/
int blob_compare(Blob *pA, Blob *pB){
  int szA, szB, sz, rc;
  blob_is_init(pA);
  blob_is_init(pB);
  szA = blob_size(pA);
  szB = blob_size(pB);







|
>







277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
  blob_is_init(p);
  if( p->nUsed==0 ) return "";
  p->aData[p->nUsed] = 0;
  return p->aData;
}

/*
** Compare two blobs.  Return negative, zero, or positive if the first
** blob is less then, equal to, or greater than the second.
*/
int blob_compare(Blob *pA, Blob *pB){
  int szA, szB, sz, rc;
  blob_is_init(pA);
  blob_is_init(pB);
  szA = blob_size(pA);
  szB = blob_size(pB);

Changes to src/merge.c.

436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
  }
  db_finalize(&q);


  /* Report on conflicts
  */
  if( nConflict && !nochangeFlag ){
    printf(
      "WARNING: merge conflicts - see messages above for details.\n"
      "HINT:    The \"fossil undo\" command will back out this merge if "
                "you want\n");
  }
  
  /*
  ** Clean up the mid and pid VFILE entries.  Then commit the changes.
  */
  db_multi_exec("DELETE FROM vfile WHERE vid!=%d", vid);
  if( !pickFlag ){
    db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(0,%d)", mid);
  }
  undo_finish();
  db_end_transaction(nochangeFlag);
}







<
|
<
<












436
437
438
439
440
441
442

443


444
445
446
447
448
449
450
451
452
453
454
455
  }
  db_finalize(&q);


  /* Report on conflicts
  */
  if( nConflict && !nochangeFlag ){

    printf("WARNING: merge conflicts - see messages above for details.\n");


  }
  
  /*
  ** Clean up the mid and pid VFILE entries.  Then commit the changes.
  */
  db_multi_exec("DELETE FROM vfile WHERE vid!=%d", vid);
  if( !pickFlag ){
    db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(0,%d)", mid);
  }
  undo_finish();
  db_end_transaction(nochangeFlag);
}

Changes to src/stash.c.

157
158
159
160
161
162
163




























































































164
165
166
167
168
169
170
...
205
206
207
208
209
210
211

212
213
214
215
216
217
218
219
220
221
222
223
224

225
226
227
228
229
230
231

232
233
234
235
236
237
238

239
240
241
242
243
244
245
246
247
248
249
250
251

252
253



254
255






256
257





258
259






260
261
262
263
264
265
    for(i=3; i<g.argc; i++){
      stash_add_file_or_dir(stashid, vid, g.argv[i]);
    }
  }else{
    stash_add_file_or_dir(stashid, vid, ".");
  } 
}





























































































/*
** COMMAND: stash
**
** Usage: %fossil COMMAND ARGS...
**
**    fossil stash
................................................................................
**         Save the current changes in the working tress as a new stash
**         but, unlike "save", do not revert those changes.
*/
void stash_cmd(void){
  const char *zDb = "localdb";
  const char *zCmd;
  int nCmd;

  db_must_be_within_tree();
  db_begin_transaction();
  if( strcmp(g.zMainDbType, zDb)==0 ) zDb = "main";
  db_multi_exec(zStashInit, zDb, zDb);
  if( g.argc<=2 ){
    zCmd = "save";
  }else{
    zCmd = g.argv[2];
  }
  nCmd = strlen(zCmd);
  if( memcmp(zCmd, "save", nCmd)==0 ){
    stash_create();
    undo_disable();

    revert_cmd();
  }else
  if( memcmp(zCmd, "snapshot", nCmd)==0 ){
    stash_create();
  }else
  if( memcmp(zCmd, "list", nCmd)==0 ){
    Stmt q;

    verify_all_options();
    db_prepare(&q,
       "SELECT stashid, (SELECT uuid FROM blob WHERE rid=vid),"
       "       comment, datetime(ctime) FROM stash"
       " ORDER BY ctime DESC"
    );
    while( db_step(&q)==SQLITE_ROW ){

      const char *zCom;
      printf("%5d: [%.14s] on %s\n",
        db_column_int(&q, 0),
        db_column_text(&q, 1),
        db_column_text(&q, 3)
      );
      zCom = db_column_text(&q, 2);
      if( zCom && zCom[0] ){
        printf("      ");
        comment_print(zCom, 7, 79);
      }
    }
    db_finalize(&q);

  }else
  if( memcmp(zCmd, "drop", nCmd)==0 ){



  }else
  if( memcmp(zCmd, "pop", nCmd)==0 ){






  }else
  if( memcmp(zCmd, "apply", nCmd)==0 ){





  }else
  if( memcmp(zCmd, "goto", nCmd)==0 ){






  }else
  {
    usage("apply|drop|goto|list|pop|save|snapshot ARGS...");
  }
  db_end_transaction(0);
}







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>













>







>







>








|




>


>
>
>


>
>
>
>
>
>


>
>
>
>
>


>
>
>
>
>
>






157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
...
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
    for(i=3; i<g.argc; i++){
      stash_add_file_or_dir(stashid, vid, g.argv[i]);
    }
  }else{
    stash_add_file_or_dir(stashid, vid, ".");
  } 
}

/*
** Apply a stash to the current check-out.
*/
static void stash_apply(int stashid, int nConflict){
  Stmt q;
  db_prepare(&q,
     "SELECT rid, isRemoved, isExec, origname, newname, delta"
     "  FROM stashfile WHERE stashid=%d",
     stashid
  );
  while( db_step(&q)==SQLITE_ROW ){
    int rid = db_column_int(&q, 0);
    int isRemoved = db_column_int(&q, 1);
    const char *zOrig = db_column_text(&q, 3);
    const char *zNew = db_column_text(&q, 4);
    char *zOPath = mprintf("%s/%s", g.zLocalRoot, zOrig);
    char *zNPath = mprintf("%s/%s", g.zLocalRoot, zNew);
    undo_save(zNPath);
    Blob delta;
    if( rid==0 ){
      db_ephemeral_blob(&q, 5, &delta);
      blob_write_to_file(&delta, zNPath);
      printf("ADD %s\n", zNew);
    }else if( isRemoved ){
      printf("DELETE %s\n", zOrig);
      unlink(zOPath);
    }else{
      Blob a, b, out, disk;
      db_ephemeral_blob(&q, 5, &delta);
      blob_read_from_file(&disk, zOPath);     
      content_get(rid, &a);
      blob_delta_apply(&a, &delta, &b);
      if( blob_compare(&disk, &a)==0 ){
        blob_write_to_file(&b, zNPath);
        printf("UPDATE %s\n", zNew);
      }else{
        int rc = blob_merge(&a, &disk, &b, &out);
        blob_write_to_file(&out, zNPath);
        if( rc ){
          printf("CONFLICT %s\n", zNew);
          nConflict++;
        }else{
          printf("MERGE %s\n", zNew);
        }
        blob_reset(&out);
      }
      blob_reset(&a);
      blob_reset(&b);
      blob_reset(&delta);
    }
    if( strcmp(zOrig,zNew)!=0 ){
      undo_save(zOPath);
      unlink(zOPath);
    }
  }
  db_finalize(&q);
  if( nConflict ){
    printf("WARNING: merge conflicts - see messages above for details.\n");
  }
}

/*
** Drop the indicates stash
*/
static void stash_drop(int stashid){
  db_multi_exec(
    "DELETE FROM stash WHERE stashid=%d;"
    "DELETE FROM stashfile WHERE stashid=%d;",
    stashid, stashid
  );
}

/*
** If zStashId is non-NULL then interpret is as a stash number and
** return that number.  Or throw a fatal error if it is not a valid
** stash number.  If it is NULL, return the most recent stash or
** throw an error if the stash is empty.
*/
static int stash_get_id(const char *zStashId){
  int stashid = 0;
  if( zStashId==0 ){
    stashid = db_int(0, "SELECT max(stashid) FROM stash");
    if( stashid==0 ) fossil_fatal("empty stash");
  }else{
    stashid = atoi(zStashId);
    if( !db_exists("SELECT 1 FROM stash WHERE stashid=%d", stashid) ){
      fossil_fatal("no such stash: %d\n", stashid);
    }
  }
  return stashid;
}

/*
** COMMAND: stash
**
** Usage: %fossil COMMAND ARGS...
**
**    fossil stash
................................................................................
**         Save the current changes in the working tress as a new stash
**         but, unlike "save", do not revert those changes.
*/
void stash_cmd(void){
  const char *zDb = "localdb";
  const char *zCmd;
  int nCmd;
  undo_capture_command_line();
  db_must_be_within_tree();
  db_begin_transaction();
  if( strcmp(g.zMainDbType, zDb)==0 ) zDb = "main";
  db_multi_exec(zStashInit, zDb, zDb);
  if( g.argc<=2 ){
    zCmd = "save";
  }else{
    zCmd = g.argv[2];
  }
  nCmd = strlen(zCmd);
  if( memcmp(zCmd, "save", nCmd)==0 ){
    stash_create();
    undo_disable();
    g.argc = 2;
    revert_cmd();
  }else
  if( memcmp(zCmd, "snapshot", nCmd)==0 ){
    stash_create();
  }else
  if( memcmp(zCmd, "list", nCmd)==0 ){
    Stmt q;
    int n = 0;
    verify_all_options();
    db_prepare(&q,
       "SELECT stashid, (SELECT uuid FROM blob WHERE rid=vid),"
       "       comment, datetime(ctime) FROM stash"
       " ORDER BY ctime DESC"
    );
    while( db_step(&q)==SQLITE_ROW ){
      n++;
      const char *zCom;
      printf("%5d: [%.14s] on %s\n",
        db_column_int(&q, 0),
        db_column_text(&q, 1),
        db_column_text(&q, 3)
      );
      zCom = db_column_text(&q, 2);
      if( zCom && zCom[0] ){
        printf("       ");
        comment_print(zCom, 7, 79);
      }
    }
    db_finalize(&q);
    if( n==0 ) printf("empty stash\n");
  }else
  if( memcmp(zCmd, "drop", nCmd)==0 ){
    if( g.argc>4 ) usage("stash apply STASHID");
    int stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0);
    stash_drop(stashid);
  }else
  if( memcmp(zCmd, "pop", nCmd)==0 ){
    if( g.argc>3 ) usage("stash pop");
    int stashid = stash_get_id(0);
    undo_begin();
    stash_apply(stashid, 0);
    undo_finish();
    stash_drop(stashid);
  }else
  if( memcmp(zCmd, "apply", nCmd)==0 ){
    if( g.argc>4 ) usage("stash apply STASHID");
    int stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0);
    undo_begin();
    stash_apply(stashid, 0);
    undo_finish();
  }else
  if( memcmp(zCmd, "goto", nCmd)==0 ){
    if( g.argc>4 ) usage("stash apply STASHID");
    int stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0);
    undo_begin();
    update_to(db_int(0, "SELECT vid FROM stash WHERE stashid=%d", stashid));
    stash_apply(stashid, 0);
    undo_finish();
  }else
  {
    usage("apply|drop|goto|list|pop|save|snapshot ARGS...");
  }
  db_end_transaction(0);
}

Changes to src/undo.c.

161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
** Capture the current command-line and store it as part of the undo
** state.  This routine is called before options are extracted from the
** command-line so that we can record the complete command-line.
*/
void undo_capture_command_line(void){
  Blob cmdline;
  int i;
  assert( undoCmd==0 );
  if( undoDisable ) return;
  blob_zero(&cmdline);
  for(i=1; i<g.argc; i++){
    if( i>1 ) blob_append(&cmdline, " ", 1);
    blob_append(&cmdline, g.argv[i], -1);
  }
  undoCmd = blob_str(&cmdline);
}







<
|







161
162
163
164
165
166
167

168
169
170
171
172
173
174
175
** Capture the current command-line and store it as part of the undo
** state.  This routine is called before options are extracted from the
** command-line so that we can record the complete command-line.
*/
void undo_capture_command_line(void){
  Blob cmdline;
  int i;

  if( undoCmd!=0 || undoDisable ) return;
  blob_zero(&cmdline);
  for(i=1; i<g.argc; i++){
    if( i>1 ) blob_append(&cmdline, " ", 1);
    blob_append(&cmdline, g.argv[i], -1);
  }
  undoCmd = blob_str(&cmdline);
}

Changes to src/update.c.

24
25
26
27
28
29
30






























31
32
33
34
35
36
37
..
65
66
67
68
69
70
71

72
73

74
75
76
77
78
79
80
81
82
83
84
85
86
87


88
89
90
91
92
93
94
95
...
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
...
367
368
369
370
371
372
373
374


375
376
377

378
379
380
381
382
383
384
...
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
...
527
528
529
530
531
532
533

534
535
536

/*
** Return true if artifact rid is a version
*/
int is_a_version(int rid){
  return db_exists("SELECT 1 FROM event WHERE objid=%d AND type='ci'", rid);
}































/*
** COMMAND: update
**
** Usage: %fossil update ?VERSION? ?FILES...?
**
** Change the version of the current checkout to VERSION.  Any uncommitted
................................................................................
  int verboseFlag;      /* -v or --verbose.  Output extra information */
  int debugFlag;        /* --debug option */
  int nChng;            /* Number of file renames */
  int *aChng;           /* Array of file renames */
  int i;                /* Loop counter */
  int nConflict = 0;    /* Number of merge conflicts */


  undo_capture_command_line();
  url_proxy_options();

  latestFlag = find_option("latest",0, 0)!=0;
  nochangeFlag = find_option("nochange","n",0)!=0;
  verboseFlag = find_option("verbose","v",0)!=0;
  debugFlag = find_option("debug",0,0)!=0;
  db_must_be_within_tree();
  vid = db_lget_int("checkout", 0);
  if( vid==0 ){
    fossil_fatal("cannot find current version");
  }
  if( !nochangeFlag && db_exists("SELECT 1 FROM vmerge") ){
    fossil_fatal("cannot update an uncommitted merge");
  }
  if( !nochangeFlag ) autosync(AUTOSYNC_PULL);



  if( g.argc>=3 ){
    if( strcmp(g.argv[2], "current")==0 ){
      /* If VERSION is "current", then use the same algorithm to find the
      ** target as if VERSION were omitted. */
    }else if( strcmp(g.argv[2], "latest")==0 ){
      /* If VERSION is "latest", then use the same algorithm to find the
      ** target as if VERSION were omitted and the --latest flag is present.
      */
................................................................................
                    " WHERE event.objid=leaves.rid"
                    " ORDER BY event.mtime DESC"); 
  }

  if( !verboseFlag && (tid==vid)) return;  /* Nothing to update */
  db_begin_transaction();
  vfile_check_signature(vid, 1, 0);
  if( !nochangeFlag ) undo_begin();
  load_vfile_from_rid(tid);

  /*
  ** The record.fn field is used to match files against each other.  The
  ** FV table contains one row for each each unique filename in
  ** in the current checkout, the pivot, and the version being merged.
  */
................................................................................
  db_finalize(&q);
  printf("--------------\n");
  show_common_info(tid, "updated-to:", 1, 0);

  /* Report on conflicts
  */
  if( nConflict && !nochangeFlag ){
    printf(


      "WARNING: merge conflicts - see messages above for details.\n"
      "HINT:    The \"fossil undo\" command will back out this update if "
                "you want\n");

  }
  
  /*
  ** Clean up the mid and pid VFILE entries.  Then commit the changes.
  */
  if( nochangeFlag ){
    db_end_transaction(1);  /* With --nochange, rollback changes */
................................................................................
      manifest_to_disk(tid);
      db_lset_int("checkout", tid);
    }else{
      /* A subset of files have been checked out.  Keep the current
      ** checkout unchanged. */
      db_multi_exec("DELETE FROM vfile WHERE vid!=%d", vid);
    }
    undo_finish();
    db_end_transaction(0);
  }
}


/*
** Get the contents of a file within the checking "revision".  If
................................................................................
      blob_write_to_file(&record, zFull);
      printf("REVERTED: %s\n", zFile);
      free(zFull);
    }
    blob_reset(&record);
  }
  db_finalize(&q);

  undo_finish();
  db_end_transaction(0);
}







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
|
|
>












|

>
>
|







 







|







 







|
>
>
|
<
<
>







 







|







 







>



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
..
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
...
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
...
401
402
403
404
405
406
407
408
409
410
411


412
413
414
415
416
417
418
419
...
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
...
562
563
564
565
566
567
568
569
570
571
572

/*
** Return true if artifact rid is a version
*/
int is_a_version(int rid){
  return db_exists("SELECT 1 FROM event WHERE objid=%d AND type='ci'", rid);
}

/* This variable is set if we are doing an internal update.  It is clear
** when running the "update" command.
*/
static int internalUpdate = 0;
static int internalConflictCnt = 0;

/*
** Do an update to version vid.  
**
** Start an undo session but do not terminate it.  Do not autosync.
*/
int update_to(int vid){
  int savedArgc;
  char **savedArgv;
  char *newArgv[3];
  newArgv[0] = g.argv[0];
  newArgv[1] = "update";
  newArgv[2] = 0;
  savedArgv = g.argv;
  savedArgc = g.argc;
  g.argc = 2;
  g.argv = newArgv;
  internalUpdate = vid;
  internalConflictCnt = 0;
  update_cmd();
  g.argc = savedArgc;
  g.argv = savedArgv;
  return internalConflictCnt;
}

/*
** COMMAND: update
**
** Usage: %fossil update ?VERSION? ?FILES...?
**
** Change the version of the current checkout to VERSION.  Any uncommitted
................................................................................
  int verboseFlag;      /* -v or --verbose.  Output extra information */
  int debugFlag;        /* --debug option */
  int nChng;            /* Number of file renames */
  int *aChng;           /* Array of file renames */
  int i;                /* Loop counter */
  int nConflict = 0;    /* Number of merge conflicts */

  if( !internalUpdate ){
    undo_capture_command_line();
    url_proxy_options();
  }
  latestFlag = find_option("latest",0, 0)!=0;
  nochangeFlag = find_option("nochange","n",0)!=0;
  verboseFlag = find_option("verbose","v",0)!=0;
  debugFlag = find_option("debug",0,0)!=0;
  db_must_be_within_tree();
  vid = db_lget_int("checkout", 0);
  if( vid==0 ){
    fossil_fatal("cannot find current version");
  }
  if( !nochangeFlag && db_exists("SELECT 1 FROM vmerge") ){
    fossil_fatal("cannot update an uncommitted merge");
  }
  if( !nochangeFlag && !internalUpdate ) autosync(AUTOSYNC_PULL);

  if( internalUpdate ){
    tid = internalUpdate;
  }else if( g.argc>=3 ){
    if( strcmp(g.argv[2], "current")==0 ){
      /* If VERSION is "current", then use the same algorithm to find the
      ** target as if VERSION were omitted. */
    }else if( strcmp(g.argv[2], "latest")==0 ){
      /* If VERSION is "latest", then use the same algorithm to find the
      ** target as if VERSION were omitted and the --latest flag is present.
      */
................................................................................
                    " WHERE event.objid=leaves.rid"
                    " ORDER BY event.mtime DESC"); 
  }

  if( !verboseFlag && (tid==vid)) return;  /* Nothing to update */
  db_begin_transaction();
  vfile_check_signature(vid, 1, 0);
  if( !nochangeFlag && !internalUpdate ) undo_begin();
  load_vfile_from_rid(tid);

  /*
  ** The record.fn field is used to match files against each other.  The
  ** FV table contains one row for each each unique filename in
  ** in the current checkout, the pivot, and the version being merged.
  */
................................................................................
  db_finalize(&q);
  printf("--------------\n");
  show_common_info(tid, "updated-to:", 1, 0);

  /* Report on conflicts
  */
  if( nConflict && !nochangeFlag ){
    if( internalUpdate ){
      internalConflictCnt = nConflict;
    }else{
      printf("WARNING: merge conflicts - see messages above for details.\n");


    }
  }
  
  /*
  ** Clean up the mid and pid VFILE entries.  Then commit the changes.
  */
  if( nochangeFlag ){
    db_end_transaction(1);  /* With --nochange, rollback changes */
................................................................................
      manifest_to_disk(tid);
      db_lset_int("checkout", tid);
    }else{
      /* A subset of files have been checked out.  Keep the current
      ** checkout unchanged. */
      db_multi_exec("DELETE FROM vfile WHERE vid!=%d", vid);
    }
    if( !internalUpdate ) undo_finish();
    db_end_transaction(0);
  }
}


/*
** Get the contents of a file within the checking "revision".  If
................................................................................
      blob_write_to_file(&record, zFull);
      printf("REVERTED: %s\n", zFile);
      free(zFull);
    }
    blob_reset(&record);
  }
  db_finalize(&q);
  db_multi_exec("UPDATE vfile SET mtime=0 WHERE pathname IN torevert");
  undo_finish();
  db_end_transaction(0);
}