Fossil

Check-in [3ad119b7]
Login

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

Overview
Comment:All of the execute permission bit handling appears to be working now, though beta testing would be good. Ticket [baf9b6b11e08c1].
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1:3ad119b7036f8f489eb7f309771093582f6f7eae
User & Date: drh 2011-02-28 14:18:55
Context
2011-02-28
15:01
Add documentation on the private branch sync feature. check-in: c97c6aa4 user: drh tags: trunk
14:18
All of the execute permission bit handling appears to be working now, though beta testing would be good. Ticket [baf9b6b11e08c1]. check-in: 3ad119b7 user: drh tags: trunk
13:26
Fix the "revert" command so that it distinguishes between files that were added versus files imported by merge. Added files are not unlinked. Ticket [baf9b6b11e08c1]. Closed-Leaf check-in: 157eed29 user: drh tags: exe-permission-fix
2011-02-27
23:31
Change the ZIP file generator so that it sets the execute bit approprately. Ticket [baf9b6b11e08c1d]. check-in: b57bc473 user: drh tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/add.c.

146
147
148
149
150
151
152
153
154

155
156
157
158
159
160
161
#else
    if( db_exists("SELECT 1 FROM vfile WHERE pathname=%Q", zPath) ){
      db_multi_exec("UPDATE vfile SET deleted=0 WHERE pathname=%Q", zPath);
    }
#endif
    else{
      db_multi_exec(
        "INSERT INTO vfile(vid,deleted,rid,mrid,pathname)"
        "VALUES(%d,0,0,0,%Q)", vid, zPath);

    }
    printf("ADDED  %s\n", zPath);
  }
  blob_reset(&pathname);
}

/*







|
|
>







146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
#else
    if( db_exists("SELECT 1 FROM vfile WHERE pathname=%Q", zPath) ){
      db_multi_exec("UPDATE vfile SET deleted=0 WHERE pathname=%Q", zPath);
    }
#endif
    else{
      db_multi_exec(
        "INSERT INTO vfile(vid,deleted,rid,mrid,pathname,isexe)"
        "VALUES(%d,0,0,0,%Q,%d)",
        vid, zPath,file_isexe(zName));
    }
    printf("ADDED  %s\n", zPath);
  }
  blob_reset(&pathname);
}

/*

Changes to src/file.c.

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
    fwrite(zBuf, 1, got, out);
  }
  fclose(in);
  fclose(out);
}

/*
** Set or clear the execute bit on a file.

*/
void file_setexe(const char *zFilename, int onoff){

#if !defined(_WIN32)
  struct stat buf;
  if( stat(zFilename, &buf)!=0 ) return;
  if( onoff ){
    if( (buf.st_mode & 0111)!=0111 ){
      chmod(zFilename, buf.st_mode | 0111);

    }
  }else{
    if( (buf.st_mode & 0111)!=0 ){
      chmod(zFilename, buf.st_mode & ~0111);

    }
  }
#endif /* _WIN32 */

}

/*
** Create the directory named in the argument, if it does not already
** exist.  If forceFlag is 1, delete any prior non-directory object 
** with the same name.
**







|
>

|
>


|



>




>



>







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
    fwrite(zBuf, 1, got, out);
  }
  fclose(in);
  fclose(out);
}

/*
** Set or clear the execute bit on a file.  Return true if a change
** occurred and false if this routine is a no-op.
*/
int file_setexe(const char *zFilename, int onoff){
  int rc = 0;
#if !defined(_WIN32)
  struct stat buf;
  if( stat(zFilename, &buf)!=0 ) return 0;
  if( onoff ){
    if( (buf.st_mode & 0111)!=0111 ){
      chmod(zFilename, buf.st_mode | 0111);
      rc = 1;
    }
  }else{
    if( (buf.st_mode & 0111)!=0 ){
      chmod(zFilename, buf.st_mode & ~0111);
      rc = 1;
    }
  }
#endif /* _WIN32 */
  return rc;
}

/*
** Create the directory named in the argument, if it does not already
** exist.  If forceFlag is 1, delete any prior non-directory object 
** with the same name.
**

Changes to src/merge.c.

156
157
158
159
160
161
162

163
164
165
166
167
168
169
170

171
172
173
174
175
176
177
178
179
...
192
193
194
195
196
197
198

199
200
201
202
203
204
205
206
207
...
218
219
220
221
222
223
224

225
226
227
228
229
230
231
232
233
...
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255

256
257
258
259
260
261
262
...
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
...
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
...
364
365
366
367
368
369
370

371
372
373
374
375
376
377
    "  idv INTEGER,"              /* VFILE entry for current version */
    "  idp INTEGER,"              /* VFILE entry for the pivot */
    "  idm INTEGER,"              /* VFILE entry for version merging in */
    "  chnged BOOLEAN,"           /* True if current version has been edited */
    "  ridv INTEGER,"             /* Record ID for current version */
    "  ridp INTEGER,"             /* Record ID for pivot */
    "  ridm INTEGER,"             /* Record ID for merge */

    "  fnp TEXT,"                 /* The filename in the pivot */
    "  fnm TEXT"                  /* the filename in the merged version */
    ");"
  );

  /* Add files found in V
  */
  db_multi_exec(

    "INSERT OR IGNORE INTO fv(fn,fnp,fnm,idv,idp,idm,ridv,ridp,ridm,chnged)"
    " SELECT pathname, pathname, pathname, id, 0, 0, rid, 0, 0, chnged "
    " FROM vfile WHERE vid=%d",
    vid
  );

  /*
  ** Compute name changes from P->V
  */
................................................................................
    fossil_free(aChng);
    db_multi_exec("UPDATE fv SET fnm=fnp WHERE fnp!=fn");
  }

  /* Add files found in P but not in V
  */
  db_multi_exec(

    "INSERT OR IGNORE INTO fv(fn,fnp,fnm,idv,idp,idm,ridv,ridp,ridm,chnged)"
    " SELECT pathname, pathname, pathname, 0, 0, 0, 0, 0, 0, 0 "
    "   FROM vfile"
    "  WHERE vid=%d AND pathname NOT IN (SELECT fnp FROM fv)",
    pid
  );

  /*
  ** Compute name changes from P->M
................................................................................
    }
    fossil_free(aChng);
  }

  /* Add files found in M but not in P or V.
  */
  db_multi_exec(

    "INSERT OR IGNORE INTO fv(fn,fnp,fnm,idv,idp,idm,ridv,ridp,ridm,chnged)"
    " SELECT pathname, pathname, pathname, 0, 0, 0, 0, 0, 0, 0 "
    "   FROM vfile"
    "  WHERE vid=%d"
    "    AND pathname NOT IN (SELECT fnp FROM fv UNION SELECT fnm FROM fv)",
    mid
  );

  /*
................................................................................
    " idm=coalesce((SELECT id FROM vfile WHERE vid=%d AND pathname=fnm),0),"
    " ridm=coalesce((SELECT rid FROM vfile WHERE vid=%d AND pathname=fnm),0)",
    pid, pid, mid, mid
  );

  if( debugFlag ){
    db_prepare(&q,
       "SELECT rowid, fn, fnp, fnm, chnged, ridv, ridp, ridm FROM fv"
    );
    while( db_step(&q)==SQLITE_ROW ){
       printf("%3d: ridv=%-4d ridp=%-4d ridm=%-4d chnged=%d\n",
          db_column_int(&q, 0),
          db_column_int(&q, 5),
          db_column_int(&q, 6),
          db_column_int(&q, 7),
          db_column_int(&q, 4));

       printf("     fn  = [%s]\n", db_column_text(&q, 1));
       printf("     fnp = [%s]\n", db_column_text(&q, 2));
       printf("     fnm = [%s]\n", db_column_text(&q, 3));
    }
    db_finalize(&q);
  }

................................................................................
  );
  while( db_step(&q)==SQLITE_ROW ){
    int idm = db_column_int(&q, 0);
    int rowid = db_column_int(&q, 1);
    int idv;
    const char *zName;
    db_multi_exec(
      "INSERT INTO vfile(vid,chnged,deleted,rid,mrid,pathname)"
      "  SELECT %d,3,0,rid,mrid,pathname FROM vfile WHERE id=%d",
      vid, idm
    );
    idv = db_last_insert_rowid();
    db_multi_exec("UPDATE fv SET idv=%d WHERE rowid=%d", idv, rowid);
    zName = db_column_text(&q, 2);
    printf("ADDED %s\n", zName);
    if( !nochangeFlag ){
................................................................................
  }
  db_finalize(&q);

  /*
  ** Do a three-way merge on files that have changes on both P->M and P->V.
  */
  db_prepare(&q,
    "SELECT ridm, idv, ridp, ridv, %s, fn FROM fv"
    " WHERE idp>0 AND idv>0 AND idm>0"
    "   AND ridm!=ridp AND (ridv!=ridp OR chnged)",
    glob_expr("fv.fn", zBinGlob)
  );
  while( db_step(&q)==SQLITE_ROW ){
    int ridm = db_column_int(&q, 0);
    int idv = db_column_int(&q, 1);
    int ridp = db_column_int(&q, 2);
    int ridv = db_column_int(&q, 3);
    int isBinary = db_column_int(&q, 4);
    const char *zName = db_column_text(&q, 5);

    int rc;
    char *zFullPath;
    Blob m, p, r;
    /* Do a 3-way merge of idp->idm into idp->idv.  The results go into idv. */
    if( detailFlag ){
      printf("MERGE %s  (pivot=%d v1=%d v2=%d)\n", zName, ridp, ridm, ridv);
    }else{
................................................................................
      blob_zero(&r);
    }else{
      rc = merge_3way(&p, zFullPath, &m, &r);
    }
    if( rc>=0 ){
      if( !nochangeFlag ){
        blob_write_to_file(&r, zFullPath);

      }
      db_multi_exec("UPDATE vfile SET mtime=0 WHERE id=%d", idv);
      if( rc>0 ){
        printf("***** %d merge conflicts in %s\n", rc, zName);
        nConflict++;
      }
    }else{







>








>
|
|







 







>
|
|







 







>
|
|







 







|


|




|
>







 







|
|







 







|











>







 







>







156
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
...
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
...
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
...
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
...
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
...
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
...
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
    "  idv INTEGER,"              /* VFILE entry for current version */
    "  idp INTEGER,"              /* VFILE entry for the pivot */
    "  idm INTEGER,"              /* VFILE entry for version merging in */
    "  chnged BOOLEAN,"           /* True if current version has been edited */
    "  ridv INTEGER,"             /* Record ID for current version */
    "  ridp INTEGER,"             /* Record ID for pivot */
    "  ridm INTEGER,"             /* Record ID for merge */
    "  isexe BOOLEAN,"            /* Execute permission enabled */
    "  fnp TEXT,"                 /* The filename in the pivot */
    "  fnm TEXT"                  /* the filename in the merged version */
    ");"
  );

  /* Add files found in V
  */
  db_multi_exec(
    "INSERT OR IGNORE"
    " INTO fv(fn,fnp,fnm,idv,idp,idm,ridv,ridp,ridm,isexe,chnged)"
    " SELECT pathname, pathname, pathname, id, 0, 0, rid, 0, 0, isexe, chnged "
    " FROM vfile WHERE vid=%d",
    vid
  );

  /*
  ** Compute name changes from P->V
  */
................................................................................
    fossil_free(aChng);
    db_multi_exec("UPDATE fv SET fnm=fnp WHERE fnp!=fn");
  }

  /* Add files found in P but not in V
  */
  db_multi_exec(
    "INSERT OR IGNORE"
    " INTO fv(fn,fnp,fnm,idv,idp,idm,ridv,ridp,ridm,isexe,chnged)"
    " SELECT pathname, pathname, pathname, 0, 0, 0, 0, 0, 0, isexe, 0 "
    "   FROM vfile"
    "  WHERE vid=%d AND pathname NOT IN (SELECT fnp FROM fv)",
    pid
  );

  /*
  ** Compute name changes from P->M
................................................................................
    }
    fossil_free(aChng);
  }

  /* Add files found in M but not in P or V.
  */
  db_multi_exec(
    "INSERT OR IGNORE"
    " INTO fv(fn,fnp,fnm,idv,idp,idm,ridv,ridp,ridm,isexe,chnged)"
    " SELECT pathname, pathname, pathname, 0, 0, 0, 0, 0, 0, isexe, 0 "
    "   FROM vfile"
    "  WHERE vid=%d"
    "    AND pathname NOT IN (SELECT fnp FROM fv UNION SELECT fnm FROM fv)",
    mid
  );

  /*
................................................................................
    " idm=coalesce((SELECT id FROM vfile WHERE vid=%d AND pathname=fnm),0),"
    " ridm=coalesce((SELECT rid FROM vfile WHERE vid=%d AND pathname=fnm),0)",
    pid, pid, mid, mid
  );

  if( debugFlag ){
    db_prepare(&q,
       "SELECT rowid, fn, fnp, fnm, chnged, ridv, ridp, ridm, isexe FROM fv"
    );
    while( db_step(&q)==SQLITE_ROW ){
       printf("%3d: ridv=%-4d ridp=%-4d ridm=%-4d chnged=%d isexe=%d\n",
          db_column_int(&q, 0),
          db_column_int(&q, 5),
          db_column_int(&q, 6),
          db_column_int(&q, 7),
          db_column_int(&q, 4),
          db_column_int(&q, 8));
       printf("     fn  = [%s]\n", db_column_text(&q, 1));
       printf("     fnp = [%s]\n", db_column_text(&q, 2));
       printf("     fnm = [%s]\n", db_column_text(&q, 3));
    }
    db_finalize(&q);
  }

................................................................................
  );
  while( db_step(&q)==SQLITE_ROW ){
    int idm = db_column_int(&q, 0);
    int rowid = db_column_int(&q, 1);
    int idv;
    const char *zName;
    db_multi_exec(
      "INSERT INTO vfile(vid,chnged,deleted,rid,mrid,isexe,pathname)"
      "  SELECT %d,3,0,rid,mrid,isexe,pathname FROM vfile WHERE id=%d",
      vid, idm
    );
    idv = db_last_insert_rowid();
    db_multi_exec("UPDATE fv SET idv=%d WHERE rowid=%d", idv, rowid);
    zName = db_column_text(&q, 2);
    printf("ADDED %s\n", zName);
    if( !nochangeFlag ){
................................................................................
  }
  db_finalize(&q);

  /*
  ** Do a three-way merge on files that have changes on both P->M and P->V.
  */
  db_prepare(&q,
    "SELECT ridm, idv, ridp, ridv, %s, fn, isexe FROM fv"
    " WHERE idp>0 AND idv>0 AND idm>0"
    "   AND ridm!=ridp AND (ridv!=ridp OR chnged)",
    glob_expr("fv.fn", zBinGlob)
  );
  while( db_step(&q)==SQLITE_ROW ){
    int ridm = db_column_int(&q, 0);
    int idv = db_column_int(&q, 1);
    int ridp = db_column_int(&q, 2);
    int ridv = db_column_int(&q, 3);
    int isBinary = db_column_int(&q, 4);
    const char *zName = db_column_text(&q, 5);
    int isExe = db_column_int(&q, 6);
    int rc;
    char *zFullPath;
    Blob m, p, r;
    /* Do a 3-way merge of idp->idm into idp->idv.  The results go into idv. */
    if( detailFlag ){
      printf("MERGE %s  (pivot=%d v1=%d v2=%d)\n", zName, ridp, ridm, ridv);
    }else{
................................................................................
      blob_zero(&r);
    }else{
      rc = merge_3way(&p, zFullPath, &m, &r);
    }
    if( rc>=0 ){
      if( !nochangeFlag ){
        blob_write_to_file(&r, zFullPath);
        file_setexe(zFullPath, isExe);
      }
      db_multi_exec("UPDATE vfile SET mtime=0 WHERE id=%d", idv);
      if( rc>0 ){
        printf("***** %d merge conflicts in %s\n", rc, zName);
        nConflict++;
      }
    }else{

Changes to src/stash.c.

90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
...
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
    const char *zOrig = db_column_text(&q, 4);
    char *zPath = mprintf("%s%s", g.zLocalRoot, zName);
    Blob content;

    db_bind_int(&ins, ":rid", rid);
    db_bind_int(&ins, ":isadd", rid==0);
    db_bind_int(&ins, ":isrm", deleted);
#ifdef _WIN32
    db_bind_int(&ins, ":isexe", db_column_int(&q, 1));
#endif
    db_bind_text(&ins, ":orig", zOrig);
    db_bind_text(&ins, ":new", zName);
    if( rid==0 ){
      /* A new file */
      blob_read_from_file(&content, zPath);
      db_bind_blob(&ins, ":content", &content);
    }else if( deleted ){
................................................................................
     "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);
    Blob delta;
    undo_save(zNew);
    blob_zero(&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 = merge_3way(&a, zOPath, &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);







<

<







 







>










>












>




>







90
91
92
93
94
95
96

97

98
99
100
101
102
103
104
...
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
    const char *zOrig = db_column_text(&q, 4);
    char *zPath = mprintf("%s%s", g.zLocalRoot, zName);
    Blob content;

    db_bind_int(&ins, ":rid", rid);
    db_bind_int(&ins, ":isadd", rid==0);
    db_bind_int(&ins, ":isrm", deleted);

    db_bind_int(&ins, ":isexe", db_column_int(&q, 1));

    db_bind_text(&ins, ":orig", zOrig);
    db_bind_text(&ins, ":new", zName);
    if( rid==0 ){
      /* A new file */
      blob_read_from_file(&content, zPath);
      db_bind_blob(&ins, ":content", &content);
    }else if( deleted ){
................................................................................
     "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);
    int isExec = db_column_int(&q, 2);
    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);
    Blob delta;
    undo_save(zNew);
    blob_zero(&delta);
    if( rid==0 ){
      db_ephemeral_blob(&q, 5, &delta);
      blob_write_to_file(&delta, zNPath);
      file_setexe(zNPath, isExec);
      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);
        file_setexe(zNPath, isExec);
        printf("UPDATE %s\n", zNew);
      }else{
        int rc = merge_3way(&a, zOPath, &b, &out);
        blob_write_to_file(&out, zNPath);
        file_setexe(zNPath, isExec);
        if( rc ){
          printf("CONFLICT %s\n", zNew);
          nConflict++;
        }else{
          printf("MERGE %s\n", zNew);
        }
        blob_reset(&out);

Changes to src/undo.c.

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
68
69
70

71
72
73
74
75
76
77
78
79
...
198
199
200
201
202
203
204

205
206
207
208
209
210
211
...
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
** true the redo a change.  If there is nothing to undo (or redo) then
** this routine is a noop.
*/
static void undo_one(const char *zPathname, int redoFlag){
  Stmt q;
  char *zFullname;
  db_prepare(&q,

    "SELECT content, existsflag FROM undo WHERE pathname=%Q AND redoflag=%d",
     zPathname, redoFlag
  );
  if( db_step(&q)==SQLITE_ROW ){
    int old_exists;
    int new_exists;


    Blob current;
    Blob new;
    zFullname = mprintf("%s/%s", g.zLocalRoot, zPathname);
    new_exists = file_size(zFullname)>=0;
    if( new_exists ){
      blob_read_from_file(&current, zFullname);

    }else{
      blob_zero(&current);

    }
    blob_zero(&new);
    old_exists = db_column_int(&q, 1);

    if( old_exists ){
      db_ephemeral_blob(&q, 0, &new);
    }
    if( old_exists ){
      if( new_exists ){
        printf("%s %s\n", redoFlag ? "REDO" : "UNDO", zPathname);
      }else{
        printf("NEW %s\n", zPathname);
      }
      blob_write_to_file(&new, zFullname);

    }else{
      printf("DELETE %s\n", zPathname);
      unlink(zFullname);
    }
    blob_reset(&new);
    free(zFullname);
    db_finalize(&q);
    db_prepare(&q, 
       "UPDATE undo SET content=:c, existsflag=%d, redoflag=NOT redoflag"

       " WHERE pathname=%Q",
       new_exists, zPathname
    );
    if( new_exists ){
      db_bind_blob(&q, ":c", &current);
    }
    db_step(&q);
    blob_reset(&current);
  }
................................................................................
  int cid;
  const char *zDb = db_name("localdb");
  static const char zSql[] = 
    @ CREATE TABLE %s.undo(
    @   pathname TEXT UNIQUE,             -- Name of the file
    @   redoflag BOOLEAN,                 -- 0 for undoable.  1 for redoable
    @   existsflag BOOLEAN,               -- True if the file exists

    @   content BLOB                      -- Saved content
    @ );
    @ CREATE TABLE %s.undo_vfile AS SELECT * FROM vfile;
    @ CREATE TABLE %s.undo_vmerge AS SELECT * FROM vmerge;
  ;
  if( undoDisable ) return;
  undo_reset();
................................................................................
  int existsFlag;
  Stmt q;

  if( !undoActive ) return;
  zFullname = mprintf("%s%s", g.zLocalRoot, zPathname);
  existsFlag = file_size(zFullname)>=0;
  db_prepare(&q,
    "INSERT OR IGNORE INTO undo(pathname,redoflag,existsflag,content)"
    " VALUES(%Q,0,%d,:c)",
    zPathname, existsFlag
  );
  if( existsFlag ){
    blob_read_from_file(&content, zFullname);
    db_bind_blob(&q, ":c", &content);
  }
  free(zFullname);
  db_step(&q);







>
|





>
>






>


>



>










>








|
>

|







 







>







 







|
|
|







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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
...
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
...
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
** true the redo a change.  If there is nothing to undo (or redo) then
** this routine is a noop.
*/
static void undo_one(const char *zPathname, int redoFlag){
  Stmt q;
  char *zFullname;
  db_prepare(&q,
    "SELECT content, existsflag, isExe FROM undo"
    " WHERE pathname=%Q AND redoflag=%d",
     zPathname, redoFlag
  );
  if( db_step(&q)==SQLITE_ROW ){
    int old_exists;
    int new_exists;
    int old_exe;
    int new_exe;
    Blob current;
    Blob new;
    zFullname = mprintf("%s/%s", g.zLocalRoot, zPathname);
    new_exists = file_size(zFullname)>=0;
    if( new_exists ){
      blob_read_from_file(&current, zFullname);
      new_exe = file_isexe(zFullname);
    }else{
      blob_zero(&current);
      new_exe = 0;
    }
    blob_zero(&new);
    old_exists = db_column_int(&q, 1);
    old_exe = db_column_int(&q, 2);
    if( old_exists ){
      db_ephemeral_blob(&q, 0, &new);
    }
    if( old_exists ){
      if( new_exists ){
        printf("%s %s\n", redoFlag ? "REDO" : "UNDO", zPathname);
      }else{
        printf("NEW %s\n", zPathname);
      }
      blob_write_to_file(&new, zFullname);
      file_setexe(zFullname, old_exe);
    }else{
      printf("DELETE %s\n", zPathname);
      unlink(zFullname);
    }
    blob_reset(&new);
    free(zFullname);
    db_finalize(&q);
    db_prepare(&q, 
       "UPDATE undo SET content=:c, existsflag=%d, isExe=%d,"
             " redoflag=NOT redoflag"
       " WHERE pathname=%Q",
       new_exists, new_exe, zPathname
    );
    if( new_exists ){
      db_bind_blob(&q, ":c", &current);
    }
    db_step(&q);
    blob_reset(&current);
  }
................................................................................
  int cid;
  const char *zDb = db_name("localdb");
  static const char zSql[] = 
    @ CREATE TABLE %s.undo(
    @   pathname TEXT UNIQUE,             -- Name of the file
    @   redoflag BOOLEAN,                 -- 0 for undoable.  1 for redoable
    @   existsflag BOOLEAN,               -- True if the file exists
    @   isExe BOOLEAN,                    -- True if the file is executable
    @   content BLOB                      -- Saved content
    @ );
    @ CREATE TABLE %s.undo_vfile AS SELECT * FROM vfile;
    @ CREATE TABLE %s.undo_vmerge AS SELECT * FROM vmerge;
  ;
  if( undoDisable ) return;
  undo_reset();
................................................................................
  int existsFlag;
  Stmt q;

  if( !undoActive ) return;
  zFullname = mprintf("%s%s", g.zLocalRoot, zPathname);
  existsFlag = file_size(zFullname)>=0;
  db_prepare(&q,
    "INSERT OR IGNORE INTO undo(pathname,redoflag,existsflag,isExe,content)"
    " VALUES(%Q,0,%d,%d,:c)",
    zPathname, existsFlag, file_isexe(zFullname)
  );
  if( existsFlag ){
    blob_read_from_file(&content, zFullname);
    db_bind_blob(&q, ":c", &content);
  }
  free(zFullname);
  db_step(&q);

Changes to src/update.c.

195
196
197
198
199
200
201

202
203
204
205
206
207
208
209
210

211
212
213
214
215
216
217
...
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
...
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260

261
262
263
264
265
266
267
...
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
...
314
315
316
317
318
319
320

321
322
323
324
325
326
327
...
372
373
374
375
376
377
378

379


380
381
382
383
384

385


386
387
388
389
390
391
392
...
552
553
554
555
556
557
558

559

560
561
562


563
564







565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581

582
583
584
585
586
    "CREATE TEMP TABLE fv("
    "  fn TEXT PRIMARY KEY,"      /* The filename relative to root */
    "  idv INTEGER,"              /* VFILE entry for current version */
    "  idt INTEGER,"              /* VFILE entry for target version */
    "  chnged BOOLEAN,"           /* True if current version has been edited */
    "  ridv INTEGER,"             /* Record ID for current version */
    "  ridt INTEGER,"             /* Record ID for target */

    "  fnt TEXT"                  /* Filename of same file on target version */
    ");"
  );

  /* Add files found in the current version
  */
  db_multi_exec(
    "INSERT OR IGNORE INTO fv(fn,fnt,idv,idt,ridv,ridt,chnged)"
    " SELECT pathname, pathname, id, 0, rid, 0, chnged FROM vfile WHERE vid=%d",

    vid
  );

  /* Compute file name changes on V->T.  Record name changes in files that
  ** have changed locally.
  */
  find_filename_changes(vid, tid, &nChng, &aChng);
................................................................................
    fossil_free(aChng);
  }

  /* Add files found in the target version T but missing from the current
  ** version V.
  */
  db_multi_exec(
    "INSERT OR IGNORE INTO fv(fn,fnt,idv,idt,ridv,ridt,chnged)"
    " SELECT pathname, pathname, 0, 0, 0, 0, 0 FROM vfile"
    "  WHERE vid=%d"
    "    AND pathname NOT IN (SELECT fnt FROM fv)",
    tid
  );

  /*
  ** Compute the file version ids for T
................................................................................
    " idt=coalesce((SELECT id FROM vfile WHERE vid=%d AND pathname=fnt),0),"
    " ridt=coalesce((SELECT rid FROM vfile WHERE vid=%d AND pathname=fnt),0)",
    tid, tid
  );

  if( debugFlag ){
    db_prepare(&q,
       "SELECT rowid, fn, fnt, chnged, ridv, ridt FROM fv"
    );
    while( db_step(&q)==SQLITE_ROW ){
       printf("%3d: ridv=%-4d ridt=%-4d chnged=%d\n",
          db_column_int(&q, 0),
          db_column_int(&q, 4),
          db_column_int(&q, 5),
          db_column_int(&q, 3));

       printf("     fnv = [%s]\n", db_column_text(&q, 1));
       printf("     fnt = [%s]\n", db_column_text(&q, 2));
    }
    db_finalize(&q);
  }

  /* If FILES appear on the command-line, remove from the "fv" table
................................................................................
  }

  /*
  ** Alter the content of the checkout so that it conforms with the
  ** target
  */
  db_prepare(&q, 
    "SELECT fn, idv, ridv, idt, ridt, chnged, fnt FROM fv ORDER BY 1"
  );
  db_prepare(&mtimeXfer,
    "UPDATE vfile SET mtime=(SELECT mtime FROM vfile WHERE id=:idv)"
    " WHERE id=:idt"
  );
  assert( g.zLocalRoot!=0 );
  assert( strlen(g.zLocalRoot)>1 );
................................................................................
    const char *zName = db_column_text(&q, 0);  /* The filename from root */
    int idv = db_column_int(&q, 1);             /* VFILE entry for current */
    int ridv = db_column_int(&q, 2);            /* RecordID for current */
    int idt = db_column_int(&q, 3);             /* VFILE entry for target */
    int ridt = db_column_int(&q, 4);            /* RecordID for target */
    int chnged = db_column_int(&q, 5);          /* Current is edited */
    const char *zNewName = db_column_text(&q,6);/* New filename */

    char *zFullPath;                            /* Full pathname of the file */
    char *zFullNewPath;                         /* Full pathname of dest */
    char nameChng;                              /* True if the name changed */

    zFullPath = mprintf("%s%s", g.zLocalRoot, zName);
    zFullNewPath = mprintf("%s%s", g.zLocalRoot, zNewName);
    nameChng = fossil_strcmp(zName, zNewName);
................................................................................
        printf("MERGE %s\n", zName);
      }
      undo_save(zName);
      content_get(ridt, &t);
      content_get(ridv, &v);
      rc = merge_3way(&v, zFullPath, &t, &r);
      if( rc>=0 ){

        if( !nochangeFlag ) blob_write_to_file(&r, zFullNewPath);


        if( rc>0 ){
          printf("***** %d merge conflicts in %s\n", rc, zNewName);
          nConflict++;
        }
      }else{

        if( !nochangeFlag ) blob_write_to_file(&t, zFullNewPath);


        printf("***** Cannot merge binary file %s\n", zNewName);
        nConflict++;
      }
      if( nameChng && !nochangeFlag ) unlink(zFullPath);
      blob_reset(&v);
      blob_reset(&t);
      blob_reset(&r);
................................................................................
  db_prepare(&q, "SELECT name FROM torevert");
  if( zRevision==0 ){
    int vid = db_lget_int("checkout", 0);
    zRevision = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid);
  }
  while( db_step(&q)==SQLITE_ROW ){
    int isExe = 0;

    zFile = db_column_text(&q, 0);

    errCode = historical_version_of_file(zRevision, zFile, &record, &isExe,2);
    if( errCode==2 ){
      fossil_warning("file not in repository: %s", zFile);


    }else{
      char *zFull = mprintf("%/%/", g.zLocalRoot, zFile);







      undo_save(zFile);
      blob_write_to_file(&record, zFull);
      file_setexe(zFull, isExe);
      printf("REVERTED: %s\n", zFile);
      if( zRevision==0 ){
        sqlite3_int64 mtime = file_mtime(zFull);
        db_multi_exec(
           "UPDATE vfile"
           "   SET mtime=%lld, chnged=0, deleted=0, isexe=%d,"
           "       pathname=coalesce(origname,pathname), origname=NULL"     
           " WHERE pathname=%Q",
           mtime, isExe, zFile
        );
      }
      free(zFull);
    }
    blob_reset(&record);

  }
  db_finalize(&q);
  undo_finish();
  db_end_transaction(0);
}







>







|
|
>







 







|
|







 







|


|



|
>







 







|







 







>







 







>
|
>
>





>
|
>
>







 







>

>


<
>
>
|
<
>
>
>
>
>
>
>




<
|
|
|
|
|
|
|
|
|
<
<

>





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
...
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
...
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
...
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
...
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
...
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
...
562
563
564
565
566
567
568
569
570
571
572
573

574
575
576

577
578
579
580
581
582
583
584
585
586
587

588
589
590
591
592
593
594
595
596


597
598
599
600
601
602
603
    "CREATE TEMP TABLE fv("
    "  fn TEXT PRIMARY KEY,"      /* The filename relative to root */
    "  idv INTEGER,"              /* VFILE entry for current version */
    "  idt INTEGER,"              /* VFILE entry for target version */
    "  chnged BOOLEAN,"           /* True if current version has been edited */
    "  ridv INTEGER,"             /* Record ID for current version */
    "  ridt INTEGER,"             /* Record ID for target */
    "  isexe BOOLEAN,"            /* Does target have execute permission? */
    "  fnt TEXT"                  /* Filename of same file on target version */
    ");"
  );

  /* Add files found in the current version
  */
  db_multi_exec(
    "INSERT OR IGNORE INTO fv(fn,fnt,idv,idt,ridv,ridt,isexe,chnged)"
    " SELECT pathname, pathname, id, 0, rid, 0, isexe, chnged"
    "   FROM vfile WHERE vid=%d",
    vid
  );

  /* Compute file name changes on V->T.  Record name changes in files that
  ** have changed locally.
  */
  find_filename_changes(vid, tid, &nChng, &aChng);
................................................................................
    fossil_free(aChng);
  }

  /* Add files found in the target version T but missing from the current
  ** version V.
  */
  db_multi_exec(
    "INSERT OR IGNORE INTO fv(fn,fnt,idv,idt,ridv,ridt,isexe,chnged)"
    " SELECT pathname, pathname, 0, 0, 0, 0, isexe, 0 FROM vfile"
    "  WHERE vid=%d"
    "    AND pathname NOT IN (SELECT fnt FROM fv)",
    tid
  );

  /*
  ** Compute the file version ids for T
................................................................................
    " idt=coalesce((SELECT id FROM vfile WHERE vid=%d AND pathname=fnt),0),"
    " ridt=coalesce((SELECT rid FROM vfile WHERE vid=%d AND pathname=fnt),0)",
    tid, tid
  );

  if( debugFlag ){
    db_prepare(&q,
       "SELECT rowid, fn, fnt, chnged, ridv, ridt, isexe FROM fv"
    );
    while( db_step(&q)==SQLITE_ROW ){
       printf("%3d: ridv=%-4d ridt=%-4d chnged=%d isexe=%d\n",
          db_column_int(&q, 0),
          db_column_int(&q, 4),
          db_column_int(&q, 5),
          db_column_int(&q, 3),
          db_column_int(&q, 6));
       printf("     fnv = [%s]\n", db_column_text(&q, 1));
       printf("     fnt = [%s]\n", db_column_text(&q, 2));
    }
    db_finalize(&q);
  }

  /* If FILES appear on the command-line, remove from the "fv" table
................................................................................
  }

  /*
  ** Alter the content of the checkout so that it conforms with the
  ** target
  */
  db_prepare(&q, 
    "SELECT fn, idv, ridv, idt, ridt, chnged, fnt, isexe FROM fv ORDER BY 1"
  );
  db_prepare(&mtimeXfer,
    "UPDATE vfile SET mtime=(SELECT mtime FROM vfile WHERE id=:idv)"
    " WHERE id=:idt"
  );
  assert( g.zLocalRoot!=0 );
  assert( strlen(g.zLocalRoot)>1 );
................................................................................
    const char *zName = db_column_text(&q, 0);  /* The filename from root */
    int idv = db_column_int(&q, 1);             /* VFILE entry for current */
    int ridv = db_column_int(&q, 2);            /* RecordID for current */
    int idt = db_column_int(&q, 3);             /* VFILE entry for target */
    int ridt = db_column_int(&q, 4);            /* RecordID for target */
    int chnged = db_column_int(&q, 5);          /* Current is edited */
    const char *zNewName = db_column_text(&q,6);/* New filename */
    int isexe = db_column_int(&q, 6);           /* EXE perm for new file */
    char *zFullPath;                            /* Full pathname of the file */
    char *zFullNewPath;                         /* Full pathname of dest */
    char nameChng;                              /* True if the name changed */

    zFullPath = mprintf("%s%s", g.zLocalRoot, zName);
    zFullNewPath = mprintf("%s%s", g.zLocalRoot, zNewName);
    nameChng = fossil_strcmp(zName, zNewName);
................................................................................
        printf("MERGE %s\n", zName);
      }
      undo_save(zName);
      content_get(ridt, &t);
      content_get(ridv, &v);
      rc = merge_3way(&v, zFullPath, &t, &r);
      if( rc>=0 ){
        if( !nochangeFlag ){
          blob_write_to_file(&r, zFullNewPath);
          file_setexe(zFullNewPath, isexe);
        }
        if( rc>0 ){
          printf("***** %d merge conflicts in %s\n", rc, zNewName);
          nConflict++;
        }
      }else{
        if( !nochangeFlag ){
          blob_write_to_file(&t, zFullNewPath);
          file_setexe(zFullNewPath, isexe);
        }
        printf("***** Cannot merge binary file %s\n", zNewName);
        nConflict++;
      }
      if( nameChng && !nochangeFlag ) unlink(zFullPath);
      blob_reset(&v);
      blob_reset(&t);
      blob_reset(&r);
................................................................................
  db_prepare(&q, "SELECT name FROM torevert");
  if( zRevision==0 ){
    int vid = db_lget_int("checkout", 0);
    zRevision = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid);
  }
  while( db_step(&q)==SQLITE_ROW ){
    int isExe = 0;
    char *zFull;
    zFile = db_column_text(&q, 0);
    zFull = mprintf("%/%/", g.zLocalRoot, zFile);
    errCode = historical_version_of_file(zRevision, zFile, &record, &isExe,2);
    if( errCode==2 ){

      if( db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q", zFile)==0 ){
        printf("UNMANAGE: %s\n", zFile);
      }else{

        undo_save(zFile);
        unlink(zFull);
        printf("DELETE: %s\n", zFile);
      }
      db_multi_exec("DELETE FROM vfile WHERE pathname=%Q", zFile);
    }else{
      sqlite3_int64 mtime;
      undo_save(zFile);
      blob_write_to_file(&record, zFull);
      file_setexe(zFull, isExe);
      printf("REVERTED: %s\n", zFile);

      mtime = file_mtime(zFull);
      db_multi_exec(
         "UPDATE vfile"
         "   SET mtime=%lld, chnged=0, deleted=0, isexe=%d, mrid=rid,"
         "       pathname=coalesce(origname,pathname), origname=NULL"     
         " WHERE pathname=%Q",
         mtime, isExe, zFile
      );
    }


    blob_reset(&record);
    free(zFull);
  }
  db_finalize(&q);
  undo_finish();
  db_end_transaction(0);
}

Changes to src/vfile.c.

83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100

101
102
103
104
105
106
107
...
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
...
248
249
250
251
252
253
254

255
256
257
258
259
260
261
  ManifestFile *pFile;

  db_begin_transaction();
  p = manifest_get(vid, CFTYPE_MANIFEST);
  if( p==0 ) return;
  db_multi_exec("DELETE FROM vfile WHERE vid=%d", vid);
  db_prepare(&ins,
    "INSERT INTO vfile(vid,rid,mrid,pathname) "
    " VALUES(:vid,:id,:id,:name)");
  db_bind_int(&ins, ":vid", vid);
  manifest_file_rewind(p);
  while( (pFile = manifest_file_next(p,0))!=0 ){
    if( pFile->zUuid==0 || uuid_is_shunned(pFile->zUuid) ) continue;
    rid = uuid_to_rid(pFile->zUuid, 0);
    if( rid==0 || content_size(rid, -1)<0 ){
      fossil_warning("content missing for %s", pFile->zName);
      continue;
    }

    db_bind_int(&ins, ":id", rid);
    db_bind_text(&ins, ":name", pFile->zName);
    db_step(&ins);
    db_reset(&ins);
  }
  db_finalize(&ins);
  manifest_destroy(p);
................................................................................
  int promptFlag         /* Prompt user to confirm overwrites */
){
  Stmt q;
  Blob content;
  int nRepos = strlen(g.zLocalRoot);

  if( vid>0 && id==0 ){
    db_prepare(&q, "SELECT id, %Q || pathname, mrid"
                   "  FROM vfile"
                   " WHERE vid=%d AND mrid>0",
                   g.zLocalRoot, vid);
  }else{
    assert( vid==0 && id>0 );
    db_prepare(&q, "SELECT id, %Q || pathname, mrid"
                   "  FROM vfile"
                   " WHERE id=%d AND mrid>0",
                   g.zLocalRoot, id);
  }
  while( db_step(&q)==SQLITE_ROW ){
    int id, rid;
    const char *zName;

    id = db_column_int(&q, 0);
    zName = db_column_text(&q, 1);
    rid = db_column_int(&q, 2);

    content_get(rid, &content);
    if( file_is_the_same(&content, zName) ){
      blob_reset(&content);




      continue;
    }
    if( promptFlag && file_size(zName)>=0 ){
      Blob ans;
      char *zMsg;
      char cReply;
      zMsg = mprintf("overwrite %s (a=always/y/N)? ", zName);
................................................................................
      if( cReply=='n' || cReply=='N' ){
        blob_reset(&content);
        continue;
      }
    }
    if( verbose ) printf("%s\n", &zName[nRepos]);
    blob_write_to_file(&content, zName);

    blob_reset(&content);
    db_multi_exec("UPDATE vfile SET mtime=%lld WHERE id=%d",
                  file_mtime(zName), id);
  }
  db_finalize(&q);
}








|
|









>







 







|





|





|





>



>
>
>
>







 







>







83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
...
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
...
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
  ManifestFile *pFile;

  db_begin_transaction();
  p = manifest_get(vid, CFTYPE_MANIFEST);
  if( p==0 ) return;
  db_multi_exec("DELETE FROM vfile WHERE vid=%d", vid);
  db_prepare(&ins,
    "INSERT INTO vfile(vid,isexe,rid,mrid,pathname) "
    " VALUES(:vid,:isexe,:id,:id,:name)");
  db_bind_int(&ins, ":vid", vid);
  manifest_file_rewind(p);
  while( (pFile = manifest_file_next(p,0))!=0 ){
    if( pFile->zUuid==0 || uuid_is_shunned(pFile->zUuid) ) continue;
    rid = uuid_to_rid(pFile->zUuid, 0);
    if( rid==0 || content_size(rid, -1)<0 ){
      fossil_warning("content missing for %s", pFile->zName);
      continue;
    }
    db_bind_int(&ins, ":isexe", manifest_file_mperm(pFile));
    db_bind_int(&ins, ":id", rid);
    db_bind_text(&ins, ":name", pFile->zName);
    db_step(&ins);
    db_reset(&ins);
  }
  db_finalize(&ins);
  manifest_destroy(p);
................................................................................
  int promptFlag         /* Prompt user to confirm overwrites */
){
  Stmt q;
  Blob content;
  int nRepos = strlen(g.zLocalRoot);

  if( vid>0 && id==0 ){
    db_prepare(&q, "SELECT id, %Q || pathname, mrid, isexe"
                   "  FROM vfile"
                   " WHERE vid=%d AND mrid>0",
                   g.zLocalRoot, vid);
  }else{
    assert( vid==0 && id>0 );
    db_prepare(&q, "SELECT id, %Q || pathname, mrid, isexe"
                   "  FROM vfile"
                   " WHERE id=%d AND mrid>0",
                   g.zLocalRoot, id);
  }
  while( db_step(&q)==SQLITE_ROW ){
    int id, rid, isExe;
    const char *zName;

    id = db_column_int(&q, 0);
    zName = db_column_text(&q, 1);
    rid = db_column_int(&q, 2);
    isExe = db_column_int(&q, 3);
    content_get(rid, &content);
    if( file_is_the_same(&content, zName) ){
      blob_reset(&content);
      if( file_setexe(zName, isExe) ){
        db_multi_exec("UPDATE vfile SET mtime=%lld WHERE id=%d",
                      file_mtime(zName), id);
      }
      continue;
    }
    if( promptFlag && file_size(zName)>=0 ){
      Blob ans;
      char *zMsg;
      char cReply;
      zMsg = mprintf("overwrite %s (a=always/y/N)? ", zName);
................................................................................
      if( cReply=='n' || cReply=='N' ){
        blob_reset(&content);
        continue;
      }
    }
    if( verbose ) printf("%s\n", &zName[nRepos]);
    blob_write_to_file(&content, zName);
    file_setexe(zName, isExe);
    blob_reset(&content);
    db_multi_exec("UPDATE vfile SET mtime=%lld WHERE id=%d",
                  file_mtime(zName), id);
  }
  db_finalize(&q);
}