Fossil SCM

fossil-scm / src / undo.c
Blame History Raw 544 lines
1
/*
2
** Copyright (c) 2007 D. Richard Hipp
3
**
4
** This program is free software; you can redistribute it and/or
5
** modify it under the terms of the Simplified BSD License (also
6
** known as the "2-Clause License" or "FreeBSD License".)
7
8
** This program is distributed in the hope that it will be useful,
9
** but without any warranty; without even the implied warranty of
10
** merchantability or fitness for a particular purpose.
11
**
12
** Author contact information:
13
** [email protected]
14
** http://www.hwaci.com/drh/
15
**
16
*******************************************************************************
17
**
18
** This file implements the undo/redo functionality.
19
*/
20
#include "config.h"
21
#include "undo.h"
22
23
#if INTERFACE
24
/*
25
** Possible return values from the undo_maybe_save() routine.
26
*/
27
#define UNDO_NONE (0) /* Placeholder only used to initialize vars. */
28
#define UNDO_SAVED_OK (1) /* The specified file was saved successfully. */
29
#define UNDO_DISABLED (2) /* File not saved, subsystem is disabled. */
30
#define UNDO_INACTIVE (3) /* File not saved, subsystem is not active. */
31
#define UNDO_TOOBIG (4) /* File not saved, it exceeded a size limit. */
32
#endif
33
34
/*
35
** Undo the change to the file zPathname. zPathname is the pathname
36
** of the file relative to the root of the repository. If redoFlag is
37
** true the redo a change. If there is nothing to undo (or redo) then
38
** this routine is a noop.
39
*/
40
static void undo_one(const char *zPathname, int redoFlag){
41
Stmt q;
42
char *zFullname;
43
db_prepare(&q,
44
"SELECT content, existsflag, isExe, isLink FROM undo"
45
" WHERE pathname=%Q AND redoflag=%d",
46
zPathname, redoFlag
47
);
48
if( db_step(&q)==SQLITE_ROW ){
49
int old_exists;
50
int new_exists;
51
int old_exe;
52
int new_exe;
53
int new_link;
54
int old_link;
55
Blob current;
56
Blob new;
57
zFullname = mprintf("%s%s", g.zLocalRoot, zPathname);
58
old_link = db_column_int(&q, 3);
59
new_exists = file_size(zFullname, RepoFILE)>=0;
60
new_link = file_islink(0);
61
if( new_exists ){
62
blob_read_from_file(&current, zFullname, RepoFILE);
63
new_exe = file_isexe(0,0);
64
}else{
65
blob_zero(&current);
66
new_exe = 0;
67
}
68
blob_zero(&new);
69
old_exists = db_column_int(&q, 1);
70
old_exe = db_column_int(&q, 2);
71
if( old_exists ){
72
db_ephemeral_blob(&q, 0, &new);
73
}
74
if( file_unsafe_in_tree_path(zFullname) ){
75
/* do nothing with this unsafe file */
76
}else if( old_exists ){
77
if( new_exists ){
78
fossil_print("%s %s\n", redoFlag ? "REDO" : "UNDO", zPathname);
79
}else{
80
fossil_print("NEW %s\n", zPathname);
81
}
82
if( new_exists && (new_link || old_link) ){
83
file_delete(zFullname);
84
}
85
if( old_link ){
86
symlink_create(blob_str(&new), zFullname);
87
}else{
88
blob_write_to_file(&new, zFullname);
89
}
90
file_setexe(zFullname, old_exe);
91
}else{
92
fossil_print("DELETE %s\n", zPathname);
93
file_delete(zFullname);
94
}
95
blob_reset(&new);
96
free(zFullname);
97
db_finalize(&q);
98
db_prepare(&q,
99
"UPDATE undo SET content=:c, existsflag=%d, isExe=%d, isLink=%d,"
100
" redoflag=NOT redoflag"
101
" WHERE pathname=%Q",
102
new_exists, new_exe, new_link, zPathname
103
);
104
if( new_exists ){
105
db_bind_blob(&q, ":c", &current);
106
}
107
db_step(&q);
108
blob_reset(&current);
109
}
110
db_finalize(&q);
111
}
112
113
/*
114
** Undo or redo changes to the filesystem. Undo the changes in the
115
** same order that they were originally carried out - undo the oldest
116
** change first and undo the most recent change last.
117
*/
118
static void undo_all_filesystem(int redoFlag){
119
Stmt q;
120
db_prepare(&q,
121
"SELECT pathname FROM undo"
122
" WHERE redoflag=%d"
123
" ORDER BY rowid",
124
redoFlag
125
);
126
while( db_step(&q)==SQLITE_ROW ){
127
const char *zPathname = db_column_text(&q, 0);
128
undo_one(zPathname, redoFlag);
129
}
130
db_finalize(&q);
131
}
132
133
/*
134
** Undo or redo all undoable or redoable changes.
135
*/
136
static void undo_all(int redoFlag){
137
int ucid;
138
int ncid;
139
undo_all_filesystem(redoFlag);
140
db_multi_exec(
141
"CREATE TEMP TABLE undo_vfile_2 AS SELECT * FROM vfile;"
142
"DELETE FROM vfile;"
143
"INSERT INTO vfile SELECT * FROM undo_vfile;"
144
"DELETE FROM undo_vfile;"
145
"INSERT INTO undo_vfile SELECT * FROM undo_vfile_2;"
146
"DROP TABLE undo_vfile_2;"
147
"CREATE TEMP TABLE undo_vmerge_2 AS SELECT * FROM vmerge;"
148
"DELETE FROM vmerge;"
149
"INSERT INTO vmerge SELECT * FROM undo_vmerge;"
150
"DELETE FROM undo_vmerge;"
151
"INSERT INTO undo_vmerge SELECT * FROM undo_vmerge_2;"
152
"DROP TABLE undo_vmerge_2;"
153
);
154
if( db_table_exists("localdb", "undo_stash") ){
155
if( redoFlag ){
156
db_multi_exec(
157
"DELETE FROM stash WHERE stashid IN (SELECT stashid FROM undo_stash);"
158
"DELETE FROM stashfile"
159
" WHERE stashid NOT IN (SELECT stashid FROM stash);"
160
);
161
}else{
162
db_multi_exec(
163
"INSERT OR IGNORE INTO stash SELECT * FROM undo_stash;"
164
"INSERT OR IGNORE INTO stashfile SELECT * FROM undo_stashfile;"
165
);
166
}
167
}
168
ncid = db_lget_int("undo_checkout", 0);
169
ucid = db_lget_int("checkout", 0);
170
db_lset_int("undo_checkout", ucid);
171
db_set_checkout(ncid, 1);
172
}
173
174
/*
175
** Reset the undo memory.
176
*/
177
void undo_reset(void){
178
static const char zSql[] =
179
@ DROP TABLE IF EXISTS undo;
180
@ DROP TABLE IF EXISTS undo_vfile;
181
@ DROP TABLE IF EXISTS undo_vmerge;
182
@ DROP TABLE IF EXISTS undo_stash;
183
@ DROP TABLE IF EXISTS undo_stashfile;
184
;
185
db_exec_sql(zSql);
186
db_lset_int("undo_available", 0);
187
db_lset_int("undo_checkout", 0);
188
}
189
190
/*
191
** The following variable stores the original command-line of the
192
** command that is a candidate to be undone.
193
*/
194
static char *undoCmd = 0;
195
196
/*
197
** This flag is true if we are in the process of collecting file changes
198
** for undo. When this flag is false, undo_save() is a no-op.
199
**
200
** The undoDisable flag, if set, prevents undo from being activated.
201
*/
202
static int undoActive = 0;
203
static int undoDisable = 0;
204
205
206
/*
207
** Capture the current command-line and store it as part of the undo
208
** state. This routine is called before options are extracted from the
209
** command-line so that we can record the complete command-line.
210
*/
211
void undo_capture_command_line(void){
212
Blob cmdline;
213
int i;
214
if( undoCmd!=0 || undoDisable ) return;
215
blob_zero(&cmdline);
216
for(i=1; i<g.argc; i++){
217
if( i>1 ) blob_append(&cmdline, " ", 1);
218
blob_append(&cmdline, g.argv[i], -1);
219
}
220
undoCmd = blob_str(&cmdline);
221
}
222
223
/*
224
** Begin capturing a snapshot that can be undone.
225
*/
226
void undo_begin(void){
227
int cid;
228
static const char zSql[] =
229
@ CREATE TABLE localdb.undo(
230
@ pathname TEXT UNIQUE, -- Name of the file
231
@ redoflag BOOLEAN, -- 0 for undoable. 1 for redoable
232
@ existsflag BOOLEAN, -- True if the file exists
233
@ isExe BOOLEAN, -- True if the file is executable
234
@ isLink BOOLEAN, -- True if the file is symlink
235
@ content BLOB -- Saved content
236
@ );
237
@ CREATE TABLE localdb.undo_vfile AS SELECT * FROM vfile;
238
@ CREATE TABLE localdb.undo_vmerge AS SELECT * FROM vmerge;
239
;
240
if( undoDisable ) return;
241
undo_reset();
242
db_exec_sql(zSql);
243
cid = db_lget_int("checkout", 0);
244
db_lset_int("undo_checkout", cid);
245
db_lset_int("undo_available", 1);
246
db_lset("undo_cmdline", undoCmd);
247
undoActive = 1;
248
}
249
250
/*
251
** Permanently disable undo
252
*/
253
void undo_disable(void){
254
undoDisable = 1;
255
}
256
257
/*
258
** This flag is true if one or more files have changed and have been
259
** recorded in the undo log but the undo log has not yet been committed.
260
**
261
** If a fatal error occurs and this flag is set, that means we should
262
** rollback all the filesystem changes.
263
*/
264
static int undoNeedRollback = 0;
265
266
/*
267
** Save the current content of the file zPathname so that it
268
** will be undoable. The name is relative to the root of the
269
** tree.
270
*/
271
void undo_save(const char *zPathname){
272
if( undoDisable ) return;
273
if( undo_maybe_save(zPathname, -1)!=UNDO_SAVED_OK ){
274
fossil_fatal("failed to save undo information for path: %s",
275
zPathname);
276
}
277
}
278
279
/*
280
** Possibly save the current content of the file zPathname so
281
** that it will be undoable. The name is relative to the root
282
** of the tree. The limit argument may be used to specify the
283
** maximum size for the file to be saved. If the size of the
284
** specified file exceeds this size limit (in bytes), it will
285
** not be saved and an appropriate code will be returned.
286
**
287
** WARNING: Please do NOT call this function with a limit
288
** value less than zero, call the undo_save()
289
** function instead.
290
**
291
** The return value of this function will always be one of the
292
** following codes:
293
**
294
** UNDO_SAVED_OK: The specified file was saved successfully.
295
**
296
** UNDO_DISABLED: The specified file was NOT saved, because the
297
** "undo subsystem" is disabled. This error may
298
** indicate that a call to undo_disable() was
299
** issued.
300
**
301
** UNDO_INACTIVE: The specified file was NOT saved, because the
302
** "undo subsystem" is not active. This error
303
** may indicate that a call to undo_begin() is
304
** missing.
305
**
306
** UNDO_TOOBIG: The specified file was NOT saved, because it
307
** exceeded the specified size limit. It is
308
** impossible for this value to be returned if
309
** the specified size limit is less than zero
310
** (i.e. unlimited).
311
*/
312
int undo_maybe_save(const char *zPathname, i64 limit){
313
char *zFullname;
314
i64 size;
315
int result;
316
317
if( undoDisable ) return UNDO_DISABLED;
318
if( !undoActive ) return UNDO_INACTIVE;
319
zFullname = mprintf("%s%s", g.zLocalRoot, zPathname);
320
size = file_size(zFullname, RepoFILE);
321
if( limit<0 || size<=limit ){
322
int existsFlag = (size>=0);
323
int isLink = file_islink(zFullname);
324
Stmt q;
325
Blob content;
326
db_prepare(&q,
327
"INSERT OR IGNORE INTO"
328
" undo(pathname,redoflag,existsflag,isExe,isLink,content)"
329
" VALUES(%Q,0,%d,%d,%d,:c)",
330
zPathname, existsFlag, file_isexe(zFullname,RepoFILE), isLink
331
);
332
if( existsFlag ){
333
blob_read_from_file(&content, zFullname, RepoFILE);
334
db_bind_blob(&q, ":c", &content);
335
}
336
db_step(&q);
337
db_finalize(&q);
338
if( existsFlag ){
339
blob_reset(&content);
340
}
341
undoNeedRollback = 1;
342
result = UNDO_SAVED_OK;
343
}else{
344
result = UNDO_TOOBIG;
345
}
346
free(zFullname);
347
return result;
348
}
349
350
/*
351
** Returns an error message for the undo_maybe_save() return code.
352
** Currently, this function assumes that the caller is using the
353
** returned error message in a context prefixed with "because".
354
*/
355
const char *undo_save_message(int rc){
356
static char zRc[32];
357
358
switch( rc ){
359
case UNDO_NONE: return "undo is disabled for this operation";
360
case UNDO_SAVED_OK: return "the save operation was successful";
361
case UNDO_DISABLED: return "the undo subsystem is disabled";
362
case UNDO_INACTIVE: return "the undo subsystem is inactive";
363
case UNDO_TOOBIG: return "the file is too big";
364
default: {
365
sqlite3_snprintf(sizeof(zRc), zRc, "of error code %d", rc);
366
}
367
}
368
return zRc;
369
}
370
371
/*
372
** Make the current state of stashid undoable.
373
*/
374
void undo_save_stash(int stashid){
375
db_multi_exec(
376
"CREATE TABLE IF NOT EXISTS localdb.undo_stash"
377
" AS SELECT * FROM stash WHERE 0;"
378
"INSERT INTO undo_stash"
379
" SELECT * FROM stash WHERE stashid=%d;",
380
stashid
381
);
382
db_multi_exec(
383
"CREATE TABLE IF NOT EXISTS localdb.undo_stashfile"
384
" AS SELECT * FROM stashfile WHERE 0;"
385
"INSERT INTO undo_stashfile"
386
" SELECT * FROM stashfile WHERE stashid=%d;",
387
stashid
388
);
389
}
390
391
/*
392
** Complete the undo process is one is currently in process.
393
*/
394
void undo_finish(void){
395
if( undoActive ){
396
if( undoNeedRollback ){
397
fossil_print(" \"fossil undo\" is available to undo changes"
398
" to the working checkout.\n");
399
}
400
undoActive = 0;
401
undoNeedRollback = 0;
402
}
403
}
404
405
/*
406
** This routine is called when the process aborts due to an error.
407
** If an undo was being accumulated but was not finished, attempt
408
** to rollback all of the filesystem changes.
409
**
410
** This rollback occurs, for example, if an "update" or "merge" operation
411
** could not run to completion because a file that needed to be written
412
** was locked or had permissions turned off.
413
*/
414
void undo_rollback(void){
415
if( !undoNeedRollback ) return;
416
assert( undoActive );
417
undoNeedRollback = 0;
418
undoActive = 0;
419
fossil_print("Rolling back prior filesystem changes...\n");
420
undo_all_filesystem(0);
421
}
422
423
/*
424
** COMMAND: undo
425
** COMMAND: redo*
426
**
427
** Usage: %fossil undo ?OPTIONS? ?FILENAME...?
428
** or: %fossil redo ?OPTIONS? ?FILENAME...?
429
**
430
** The undo command reverts the changes caused by the previous command
431
** if the previous command is one of the following:
432
** * fossil update
433
** * fossil merge
434
** * fossil revert
435
** * fossil stash pop
436
** * fossil stash apply
437
** * fossil stash drop
438
** * fossil stash goto
439
** * fossil clean (*see note below*)
440
**
441
** Note: The "fossil clean" command only saves state for files less than
442
** 10MiB in size and so if fossil clean deleted files larger than that,
443
** then "fossil undo" will not recover the larger files.
444
**
445
** If FILENAME is specified then restore the content of the named
446
** file(s) but otherwise leave the update or merge or revert in effect.
447
** The redo command undoes the effect of the most recent undo.
448
**
449
** If the -n|--dry-run option is present, no changes are made and instead
450
** the undo or redo command explains what actions the undo or redo would
451
** have done had the -n|--dry-run been omitted.
452
**
453
** If the most recent command is not one of those listed as undoable,
454
** then the undo command might try to restore the state to be what it was
455
** prior to the last undoable command, or it might be a no-op. If in
456
** doubt about what the undo command will do, first run it with the -n
457
** option.
458
**
459
** A single level of undo/redo is supported. The undo/redo stack
460
** is cleared by the commit and check-out commands. Other commands may
461
** or may not clear the undo stack.
462
**
463
** Future versions of Fossil might add new commands to the set of commands
464
** that are undoable.
465
**
466
** Options:
467
** -n|--dry-run Do not make changes, but show what would be done
468
**
469
** See also: [[commit]], [[status]]
470
*/
471
void undo_cmd(void){
472
int isRedo = g.argv[1][0]=='r';
473
int undo_available;
474
int dryRunFlag = find_option("dry-run", "n", 0)!=0;
475
const char *zCmd = isRedo ? "redo" : "undo";
476
477
if( !dryRunFlag ){
478
dryRunFlag = find_option("explain", 0, 0)!=0;
479
}
480
db_must_be_within_tree();
481
verify_all_options();
482
db_begin_transaction();
483
undo_available = db_lget_int("undo_available", 0);
484
if( dryRunFlag ){
485
if( undo_available==0 ){
486
fossil_print("No undo or redo is available\n");
487
}else{
488
Stmt q;
489
int nChng = 0;
490
const char *zArticle = undo_available==1 ? "An" : "A";
491
zCmd = undo_available==1 ? "undo" : "redo";
492
fossil_print("%s %s is available for the following command:\n\n"
493
" %s %s\n\n",
494
zArticle, zCmd, g.argv[0], db_lget("undo_cmdline", "???"));
495
db_prepare(&q,
496
"SELECT existsflag, pathname FROM undo ORDER BY pathname"
497
);
498
while( db_step(&q)==SQLITE_ROW ){
499
if( nChng==0 ){
500
fossil_print("The following file changes would occur if the "
501
"command above is %sne:\n\n", zCmd);
502
}
503
nChng++;
504
fossil_print("%s %s\n",
505
db_column_int(&q,0) ? "UPDATE" : "DELETE",
506
db_column_text(&q, 1)
507
);
508
}
509
db_finalize(&q);
510
if( nChng==0 ){
511
fossil_print("No file changes would occur with this undo/redo.\n");
512
}
513
}
514
}else{
515
int vid1 = db_lget_int("checkout", 0);
516
int vid2;
517
if( g.argc==2 ){
518
if( undo_available!=(1+isRedo) ){
519
fossil_fatal("nothing to %s", zCmd);
520
}
521
undo_all(isRedo);
522
db_lset_int("undo_available", 2-isRedo);
523
}else if( g.argc>=3 ){
524
int i;
525
if( undo_available==0 ){
526
fossil_fatal("nothing to %s", zCmd);
527
}
528
for(i=2; i<g.argc; i++){
529
const char *zFile = g.argv[i];
530
Blob path;
531
file_tree_name(zFile, &path, 0, 1);
532
undo_one(blob_str(&path), isRedo);
533
blob_reset(&path);
534
}
535
}
536
vid2 = db_lget_int("checkout", 0);
537
if( vid1!=vid2 ){
538
fossil_print("--------------------\n");
539
show_common_info(vid2, "updated-to:", 1, 0);
540
}
541
}
542
db_end_transaction(0);
543
}
544

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button