Fossil

Check-in [8a6aa0a1]
Login

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

Overview
Comment: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.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | experimental
Files: files | file ages | folders
SHA1: 8a6aa0a13ff9b9d3c0f7f7ebfcadd068103016d4
User & Date: drh 2010-12-18 20:39:23
Context
2010-12-18
20:54
Fix a couple of out-of-order variable declarations. check-in: 4a8b4210 user: drh tags: experimental
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
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/checkin.c.

47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
...
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
  );
  while( db_step(&q)==SQLITE_ROW ){
    const char *zPathname = db_column_text(&q,0);
    int isDeleted = db_column_int(&q, 1);
    int isChnged = db_column_int(&q,2);
    int isNew = db_column_int(&q,3)==0;
    int isRenamed = db_column_int(&q,4);
    char *zFullName = mprintf("%s/%s", g.zLocalRoot, zPathname);
    blob_append(report, zPrefix, nPrefix);
    if( isDeleted ){
      blob_appendf(report, "DELETED    %s\n", zPathname);
    }else if( !file_isfile(zFullName) ){
      if( access(zFullName, 0)==0 ){
        blob_appendf(report, "NOT_A_FILE %s\n", zPathname);
        if( missingIsFatal ){
................................................................................
  );
  while( db_step(&q)==SQLITE_ROW ){
    const char *zPathname = db_column_text(&q,0);
    int isDeleted = db_column_int(&q, 1);
    int isNew = db_column_int(&q,2)==0;
    int chnged = db_column_int(&q,3);
    int renamed = db_column_int(&q,4);
    char *zFullName = mprintf("%s/%s", g.zLocalRoot, zPathname);
    if( isBrief ){
      printf("%s\n", zPathname);
    }else if( isNew ){
      printf("ADDED      %s\n", zPathname);
    }else if( isDeleted ){
      printf("DELETED    %s\n", zPathname);
    }else if( !file_isfile(zFullName) ){







|







 







|







47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
...
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
  );
  while( db_step(&q)==SQLITE_ROW ){
    const char *zPathname = db_column_text(&q,0);
    int isDeleted = db_column_int(&q, 1);
    int isChnged = db_column_int(&q,2);
    int isNew = db_column_int(&q,3)==0;
    int isRenamed = db_column_int(&q,4);
    char *zFullName = mprintf("%s%s", g.zLocalRoot, zPathname);
    blob_append(report, zPrefix, nPrefix);
    if( isDeleted ){
      blob_appendf(report, "DELETED    %s\n", zPathname);
    }else if( !file_isfile(zFullName) ){
      if( access(zFullName, 0)==0 ){
        blob_appendf(report, "NOT_A_FILE %s\n", zPathname);
        if( missingIsFatal ){
................................................................................
  );
  while( db_step(&q)==SQLITE_ROW ){
    const char *zPathname = db_column_text(&q,0);
    int isDeleted = db_column_int(&q, 1);
    int isNew = db_column_int(&q,2)==0;
    int chnged = db_column_int(&q,3);
    int renamed = db_column_int(&q,4);
    char *zFullName = mprintf("%s%s", g.zLocalRoot, zPathname);
    if( isBrief ){
      printf("%s\n", zPathname);
    }else if( isNew ){
      printf("ADDED      %s\n", zPathname);
    }else if( isDeleted ){
      printf("DELETED    %s\n", zPathname);
    }else if( !file_isfile(zFullName) ){

Changes to src/db.c.

865
866
867
868
869
870
871











872
873
874
875
876
877
878
    return;
  }
rep_not_found:
  if( (bFlags & OPEN_OK_NOT_FOUND)==0 ){
    fossil_fatal("use --repository or -R to specify the repository database");
  }
}












/*
** Verify that the repository schema is correct.  If it is not correct,
** issue a fatal error and die.
*/
void db_verify_schema(void){
  if( db_exists("SELECT 1 FROM config"







>
>
>
>
>
>
>
>
>
>
>







865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
    return;
  }
rep_not_found:
  if( (bFlags & OPEN_OK_NOT_FOUND)==0 ){
    fossil_fatal("use --repository or -R to specify the repository database");
  }
}

/*
** Return the name of the database "localdb", "configdb", or "repository".
*/
const char *db_name(const char *zDb){
  assert( strcmp(zDb,"localdb")==0
       || strcmp(zDb,"configdb")==0
       || strcmp(zDb,"repository")==0 );
  if( strcmp(zDb, g.zMainDbType)==0 ) zDb = "main";
  return zDb;
}

/*
** Verify that the repository schema is correct.  If it is not correct,
** issue a fatal error and die.
*/
void db_verify_schema(void){
  if( db_exists("SELECT 1 FROM config"

Changes to src/stash.c.

84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
...
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
...
155
156
157
158
159
160
161
162

163
164
165
166
167
168
169
...
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
...
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
...
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
...
273
274
275
276
277
278
279

280
281
282
283
284



285
286
287
288
289

290
291
292
293
294
295
296
297
298
299
300
301
302
303


304
305
306
307
308
309
310
311
312
313
314
...
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
     stashid
  );
  while( db_step(&q)==SQLITE_ROW ){
    int deleted = db_column_int(&q, 0);
    int rid = db_column_int(&q, 2);
    const char *zName = db_column_text(&q, 3);
    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, 2));
................................................................................
**
** If the "-m" or "--comment" command-line option is present, gather
** its argument as the stash comment.
**
** If files are named on the command-line, then only stash the named
** files.
*/
static void stash_create(void){
  const char *zComment;              /* Comment to add to the stash */
  int stashid;                       /* ID of the new stash */
  int vid;                           /* Current checkout */

  zComment = find_option("comment", "m", 1);
  verify_all_options();
  stashid = db_lget_int("stash-next", 1);
................................................................................
  if( g.argc>3 ){
    int i;
    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;
................................................................................
     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);
................................................................................
        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");
  }
................................................................................

/*
** COMMAND: stash
**
** Usage: %fossil COMMAND ARGS...
**
**    fossil stash
**    fossil stash save [-m COMMENT] [FILES...]
**
**         Save the current changes in the working tree as a new stash.
**         Then revert the changes back to the last check-in.  If FILES
**         are listed, then only stash and revert the named files.  The
**         "save" verb can be omitted if and only if there are no other
**         arguments.
**
................................................................................
**         List all changes sets currently stashed.
**
**    fossil stash pop
**
**         Apply the most recently create stash to the current working
**         check-out.  Then delete that stash.  This is equivalent to
**         doing an "apply" and a "drop" against the most recent stash.

**
**    fossil stash apply STASHID
**
**         Apply the identified stash to the current working check-out.
**         But unlike "pop", keep the stash so that it can be used again.



**
**    fossil stash goto STASHID
**
**         Update to the baseline checkout for STASHID then apply the
**         changes of STASHID.  Keep STASHID so that it can be reused

**
**    fossil drop STASHID
**
**         Forget everything about STASHID.
**
**    fossil stash snapshot [-m COMMENT] [FILES...]
**
**         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);
................................................................................
      }
    }
    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);
}







|







 







|







 







|
>







 







|
|
|







 







|







 







|







 







>

|


<
>
>
>

|



>

|

|

|





|


>
>



|







 







|
>
>

>



|


>





|





>
>
>

|

|
>
|
>
>
>







84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
...
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
...
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
...
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
...
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
...
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
...
274
275
276
277
278
279
280
281
282
283
284
285

286
287
288
289
290
291
292
293
294
295
296
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
...
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
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
     stashid
  );
  while( db_step(&q)==SQLITE_ROW ){
    int deleted = db_column_int(&q, 0);
    int rid = db_column_int(&q, 2);
    const char *zName = db_column_text(&q, 3);
    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, 2));
................................................................................
**
** If the "-m" or "--comment" command-line option is present, gather
** its argument as the stash comment.
**
** If files are named on the command-line, then only stash the named
** files.
*/
static int stash_create(void){
  const char *zComment;              /* Comment to add to the stash */
  int stashid;                       /* ID of the new stash */
  int vid;                           /* Current checkout */

  zComment = find_option("comment", "m", 1);
  verify_all_options();
  stashid = db_lget_int("stash-next", 1);
................................................................................
  if( g.argc>3 ){
    int i;
    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, ".");
  }
  return stashid;
}

/*
** Apply a stash to the current check-out.
*/
static void stash_apply(int stashid, int nConflict){
  Stmt q;
................................................................................
     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(zNew);
    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);
................................................................................
        blob_reset(&out);
      }
      blob_reset(&a);
      blob_reset(&b);
      blob_reset(&delta);
    }
    if( strcmp(zOrig,zNew)!=0 ){
      undo_save(zOrig);
      unlink(zOPath);
    }
  }
  db_finalize(&q);
  if( nConflict ){
    printf("WARNING: merge conflicts - see messages above for details.\n");
  }
................................................................................

/*
** COMMAND: stash
**
** Usage: %fossil COMMAND ARGS...
**
**    fossil stash
**    fossil stash save ?-m COMMENT? ?FILES...?
**
**         Save the current changes in the working tree as a new stash.
**         Then revert the changes back to the last check-in.  If FILES
**         are listed, then only stash and revert the named files.  The
**         "save" verb can be omitted if and only if there are no other
**         arguments.
**
................................................................................
**         List all changes sets currently stashed.
**
**    fossil stash pop
**
**         Apply the most recently create stash to the current working
**         check-out.  Then delete that stash.  This is equivalent to
**         doing an "apply" and a "drop" against the most recent stash.
**         This command is undoable.
**
**    fossil stash apply ?STASHID?
**
**         Apply the identified stash to the current working check-out.

**         If no STASHID is specifed, use the most recent stash.  Unlike
**         the "pop" command, the stash is retained so that it can be used
**         again.  This command is undoable.
**
**    fossil stash goto ?STASHID?
**
**         Update to the baseline checkout for STASHID then apply the
**         changes of STASHID.  Keep STASHID so that it can be reused
**         This command is undoable.
**
**    fossil drop ?STASHID?
**
**         Forget everything about STASHID.  This command is undoable.
**
**    fossil stash snapshot ?-m COMMENT? ?FILES...?
**
**         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;
  const char *zCmd;
  int nCmd;
  int stashid;

  undo_capture_command_line();
  db_must_be_within_tree();
  db_begin_transaction();
  zDb = db_name("localdb");
  db_multi_exec(zStashInit, zDb, zDb);
  if( g.argc<=2 ){
    zCmd = "save";
  }else{
    zCmd = g.argv[2];
  }
  nCmd = strlen(zCmd);
................................................................................
      }
    }
    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");
    stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0);
    undo_begin();
    undo_save_stash(stashid);
    stash_drop(stashid);
    undo_finish();
  }else
  if( memcmp(zCmd, "pop", nCmd)==0 ){
    if( g.argc>3 ) usage("stash pop");
    stashid = stash_get_id(0);
    undo_begin();
    stash_apply(stashid, 0);
    undo_save_stash(stashid);
    undo_finish();
    stash_drop(stashid);
  }else
  if( memcmp(zCmd, "apply", nCmd)==0 ){
    if( g.argc>4 ) usage("stash apply STASHID");
    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 ){
    int nConflict;
    int vid;

    if( g.argc>4 ) usage("stash apply STASHID");
    stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0);
    undo_begin();
    vid = db_int(0, "SELECT vid FROM stash WHERE stashid=%d", stashid);
    nConflict = update_to(vid);
    stash_apply(stashid, nConflict);
    db_multi_exec("UPDATE vfile SET mtime=0 WHERE pathname IN "
                  "(SELECT origname FROM stashfile WHERE stashid=%d)",
                  stashid);
    undo_finish();
  }else
  {
    usage("apply|drop|goto|list|pop|save|snapshot ARGS...");
  }
  db_end_transaction(0);
}

Changes to src/stat.c.

107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128

  @ <tr><th>Fossil&nbsp;Version:</th><td>
  @ %h(MANIFEST_DATE) %h(MANIFEST_VERSION) (%h(COMPILER_NAME))
  @ </td></tr>
  @ <tr><th>SQLite&nbsp;Version:</th><td>
  sqlite3_snprintf(sizeof(zBuf), zBuf, "%.19s [%.10s] (%s)",
                   SQLITE_SOURCE_ID, &SQLITE_SOURCE_ID[20], SQLITE_VERSION);
  zDb = "repository";
  if( strcmp(g.zMainDbType, zDb)==0 ) zDb = "main";
  @ %s(zBuf)
  @ </td></tr>
  @ <tr><th>Database&nbsp;Stats:</th><td>
  @ %d(db_int(0, "PRAGMA %s.page_count", zDb)) pages,
  @ %d(db_int(0, "PRAGMA %s.page_size", zDb)) bytes/page,
  @ %d(db_int(0, "PRAGMA %s.freelist_count", zDb)) free pages,
  @ %s(db_text(0, "PRAGMA %s.encoding", zDb)),
  @ %s(db_text(0, "PRAGMA %s.journal_mode", zDb)) mode
  @ </td></tr>

  @ </table>
  style_footer();
}







|
<













107
108
109
110
111
112
113
114

115
116
117
118
119
120
121
122
123
124
125
126
127

  @ <tr><th>Fossil&nbsp;Version:</th><td>
  @ %h(MANIFEST_DATE) %h(MANIFEST_VERSION) (%h(COMPILER_NAME))
  @ </td></tr>
  @ <tr><th>SQLite&nbsp;Version:</th><td>
  sqlite3_snprintf(sizeof(zBuf), zBuf, "%.19s [%.10s] (%s)",
                   SQLITE_SOURCE_ID, &SQLITE_SOURCE_ID[20], SQLITE_VERSION);
  zDb = db_name("repository");

  @ %s(zBuf)
  @ </td></tr>
  @ <tr><th>Database&nbsp;Stats:</th><td>
  @ %d(db_int(0, "PRAGMA %s.page_count", zDb)) pages,
  @ %d(db_int(0, "PRAGMA %s.page_size", zDb)) bytes/page,
  @ %d(db_int(0, "PRAGMA %s.freelist_count", zDb)) free pages,
  @ %s(db_text(0, "PRAGMA %s.encoding", zDb)),
  @ %s(db_text(0, "PRAGMA %s.journal_mode", zDb)) mode
  @ </td></tr>

  @ </table>
  style_footer();
}

Changes to src/undo.c.

102
103
104
105
106
107
108

109
110
111
112
113
114
115
...
117
118
119
120
121
122
123














124
125
126
127
128
129
130
...
131
132
133
134
135
136
137


138
139
140
141
142
143
144
...
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
...
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
...
244
245
246
247
248
249
250



















251
252
253
254
255
256
257
...
284
285
286
287
288
289
290
291







292
293
294
295
296
297
298
299
300
301
302
303

/*
** Undo or redo all undoable or redoable changes.
*/
static void undo_all(int redoFlag){
  int ucid;
  int ncid;

  undo_all_filesystem(redoFlag);
  db_multi_exec(
    "CREATE TEMP TABLE undo_vfile_2 AS SELECT * FROM vfile;"
    "DELETE FROM vfile;"
    "INSERT INTO vfile SELECT * FROM undo_vfile;"
    "DELETE FROM undo_vfile;"
    "INSERT INTO undo_vfile SELECT * FROM undo_vfile_2;"
................................................................................
    "CREATE TEMP TABLE undo_vmerge_2 AS SELECT * FROM vmerge;"
    "DELETE FROM vmerge;"
    "INSERT INTO vmerge SELECT * FROM undo_vmerge;"
    "DELETE FROM undo_vmerge;"
    "INSERT INTO undo_vmerge SELECT * FROM undo_vmerge_2;"
    "DROP TABLE undo_vmerge_2;"
  );














  ncid = db_lget_int("undo_checkout", 0);
  ucid = db_lget_int("checkout", 0);
  db_lset_int("undo_checkout", ucid);
  db_lset_int("checkout", ncid);
}

/*
................................................................................
** Reset the undo memory.
*/
void undo_reset(void){
  static const char zSql[] =
    @ DROP TABLE IF EXISTS undo;
    @ DROP TABLE IF EXISTS undo_vfile;
    @ DROP TABLE IF EXISTS undo_vmerge;


    ;
  db_multi_exec(zSql);
  db_lset_int("undo_available", 0);
  db_lset_int("undo_checkout", 0);
}

/*
................................................................................
}

/*
** Begin capturing a snapshot that can be undone.
*/
void undo_begin(void){
  int cid;
  const char *zDb = "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();
  if( strcmp(g.zMainDbType,zDb)==0 ) zDb = "main";
  db_multi_exec(zSql, zDb, zDb, zDb, zDb);
  cid = db_lget_int("checkout", 0);
  db_lset_int("undo_checkout", cid);
  db_lset_int("undo_available", 1);
  db_lset("undo_cmdline", undoCmd);
  undoActive = 1;
}

................................................................................
void undo_save(const char *zPathname){
  char *zFullname;
  Blob content;
  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 ){
................................................................................
  db_step(&q);
  db_finalize(&q);
  if( existsFlag ){
    blob_reset(&content);
  }
  undoNeedRollback = 1;
}




















/*
** Complete the undo process is one is currently in process.
*/
void undo_finish(void){
  if( undoActive ){
    if( undoNeedRollback ){
................................................................................
/*
** COMMAND: undo
** COMMAND: redo
**
** Usage: %fossil undo ?--explain? ?FILENAME...?
**    or: %fossil redo ?--explain? ?FILENAME...?
**
** Undo the most recent update or merge or revert operation.  If FILENAME is







** specified then restore the content of the named file(s) but otherwise
** leave the update or merge or revert in effect.  The redo command undoes
** the effect of the most recent undo.
**
** If the --explain option is present, not changes are made and instead
** the undo or redo command explains what actions the undo or redo would
** have done had the --explain been omitted.
**
** A single level of undo/redo is supported.  The undo/redo stack
** is cleared by the commit and checkout commands.
*/
void undo_cmd(void){







>







 







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







 







>
>







 







|












<
|







 







|







 







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







 







|
>
>
>
>
>
>
>
|
|
|

|







102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
...
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
...
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
...
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
...
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
...
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
...
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

/*
** Undo or redo all undoable or redoable changes.
*/
static void undo_all(int redoFlag){
  int ucid;
  int ncid;
  const char *zDb = db_name("localdb");
  undo_all_filesystem(redoFlag);
  db_multi_exec(
    "CREATE TEMP TABLE undo_vfile_2 AS SELECT * FROM vfile;"
    "DELETE FROM vfile;"
    "INSERT INTO vfile SELECT * FROM undo_vfile;"
    "DELETE FROM undo_vfile;"
    "INSERT INTO undo_vfile SELECT * FROM undo_vfile_2;"
................................................................................
    "CREATE TEMP TABLE undo_vmerge_2 AS SELECT * FROM vmerge;"
    "DELETE FROM vmerge;"
    "INSERT INTO vmerge SELECT * FROM undo_vmerge;"
    "DELETE FROM undo_vmerge;"
    "INSERT INTO undo_vmerge SELECT * FROM undo_vmerge_2;"
    "DROP TABLE undo_vmerge_2;"
  );
  if(db_exists("SELECT 1 FROM %s.sqlite_master WHERE name='undo_stash'", zDb) ){
    if( redoFlag ){
      db_multi_exec(
        "DELETE FROM stash WHERE stashid IN (SELECT stashid FROM undo_stash);"
        "DELETE FROM stashfile"
        " WHERE stashid NOT IN (SELECT stashid FROM stash);"
      );
    }else{
      db_multi_exec(
        "INSERT OR IGNORE INTO stash SELECT * FROM undo_stash;"
        "INSERT OR IGNORE INTO stashfile SELECT * FROM undo_stashfile;"
      );
    }
  }
  ncid = db_lget_int("undo_checkout", 0);
  ucid = db_lget_int("checkout", 0);
  db_lset_int("undo_checkout", ucid);
  db_lset_int("checkout", ncid);
}

/*
................................................................................
** Reset the undo memory.
*/
void undo_reset(void){
  static const char zSql[] =
    @ DROP TABLE IF EXISTS undo;
    @ DROP TABLE IF EXISTS undo_vfile;
    @ DROP TABLE IF EXISTS undo_vmerge;
    @ DROP TABLE IF EXISTS undo_stash;
    @ DROP TABLE IF EXISTS undo_stashfile;
    ;
  db_multi_exec(zSql);
  db_lset_int("undo_available", 0);
  db_lset_int("undo_checkout", 0);
}

/*
................................................................................
}

/*
** Begin capturing a snapshot that can be undone.
*/
void undo_begin(void){
  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();

  db_multi_exec(zSql, zDb, zDb, zDb);
  cid = db_lget_int("checkout", 0);
  db_lset_int("undo_checkout", cid);
  db_lset_int("undo_available", 1);
  db_lset("undo_cmdline", undoCmd);
  undoActive = 1;
}

................................................................................
void undo_save(const char *zPathname){
  char *zFullname;
  Blob content;
  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 ){
................................................................................
  db_step(&q);
  db_finalize(&q);
  if( existsFlag ){
    blob_reset(&content);
  }
  undoNeedRollback = 1;
}

/*
** Make the current state of stashid undoable.
*/
void undo_save_stash(int stashid){
  const char *zDb = db_name("localdb");
  db_multi_exec(
    "DROP TABLE IF EXISTS undo_stash;"
    "CREATE TABLE %s.undo_stash AS"
    " SELECT * FROM stash WHERE stashid=%d;",
    zDb, stashid
  );
  db_multi_exec(
    "DROP TABLE IF EXISTS undo_stashfile;"
    "CREATE TABLE %s.undo_stashfile AS"
    " SELECT * FROM stashfile WHERE stashid=%d;",
    zDb, stashid
  );
}

/*
** Complete the undo process is one is currently in process.
*/
void undo_finish(void){
  if( undoActive ){
    if( undoNeedRollback ){
................................................................................
/*
** COMMAND: undo
** COMMAND: redo
**
** Usage: %fossil undo ?--explain? ?FILENAME...?
**    or: %fossil redo ?--explain? ?FILENAME...?
**
** Undo the changes to the working checkout caused by the most recent
** of the following operations:
**
**    (1) fossil update             (5) fossil stash apply
**    (2) fossil merge              (6) fossil stash drop
**    (3) fossil revert             (7) fossil stash goto
**    (4) fossil stash pop
**
** If FILENAME is specified then restore the content of the named
** file(s) but otherwise leave the update or merge or revert in effect. 
** The redo command undoes the effect of the most recent undo.
**
** If the --explain option is present, no changes are made and instead
** the undo or redo command explains what actions the undo or redo would
** have done had the --explain been omitted.
**
** A single level of undo/redo is supported.  The undo/redo stack
** is cleared by the commit and checkout commands.
*/
void undo_cmd(void){

Changes to src/update.c.

553
554
555
556
557
558
559
560
561
562
563










564
565
566
567
568
569
570
571
572
        errCode = 0;
      }
    }

    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);
      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);
}







|



>
>
>
>
>
>
>
>
>
>





<



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
        errCode = 0;
      }
    }

    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);
      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,"
           "       pathname=coalesce(origname,pathname), origname=NULL"     
           " WHERE pathname=%Q",
           mtime, zFile
        );
      }
      free(zFull);
    }
    blob_reset(&record);
  }
  db_finalize(&q);

  undo_finish();
  db_end_transaction(0);
}