Fossil SCM

fossil-scm / src / stash.c
Blame History Raw 797 lines
1
/*
2
** Copyright (c) 2010 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
**
15
*******************************************************************************
16
**
17
** This file contains code used to implement the "stash" command.
18
*/
19
#include "config.h"
20
#include "stash.h"
21
#include <assert.h>
22
23
24
/*
25
** SQL code to implement the tables needed by the stash.
26
**
27
** Historical schema changes:
28
**
29
** 2019-01-19: stash.hash and stashfile.hash columns added. The
30
** corresponding stash.vid and stashfile.rid columns are
31
** retained for compatibility with older versions of
32
** fossil but are no longer used.
33
**
34
** 2016-10-16: Change the PRIMARY KEY on stashfile from (origname,stashid)
35
** to (newname,stashid).
36
**
37
** 2011-09-01: stashfile.isLink column added
38
**
39
*/
40
static const char zStashInit[] =
41
@ CREATE TABLE IF NOT EXISTS localdb.stash(
42
@ stashid INTEGER PRIMARY KEY, -- Unique stash identifier
43
@ vid INTEGER, -- Legacy baseline RID value. Do not use.
44
@ hash TEXT, -- The SHA hash for the baseline
45
@ comment TEXT, -- Comment for this stash. Or NULL
46
@ ctime TIMESTAMP -- When the stash was created
47
@ );
48
@ CREATE TABLE IF NOT EXISTS localdb.stashfile(
49
@ stashid INTEGER REFERENCES stash, -- Stash that contains this file
50
@ isAdded BOOLEAN, -- True if this is an added file
51
@ isRemoved BOOLEAN, -- True if this file is deleted
52
@ isExec BOOLEAN, -- True if file is executable
53
@ isLink BOOLEAN, -- True if file is a symlink
54
@ rid INTEGER, -- Legacy baseline RID value. Do not use
55
@ hash TEXT, -- Hash for baseline or NULL
56
@ origname TEXT, -- Original filename
57
@ newname TEXT, -- New name for file at next check-in
58
@ delta BLOB, -- Delta from baseline or raw content
59
@ PRIMARY KEY(newname, stashid)
60
@ );
61
@ INSERT OR IGNORE INTO vvar(name, value) VALUES('stash-next', 1);
62
;
63
64
/*
65
** Make sure the stash and stashfile tables exist and have been
66
** upgraded to their latest format. Create and upgrade the tables
67
** as necessary.
68
*/
69
static void stash_tables_exist_and_current(void){
70
if( db_table_has_column("localdb","stashfile","hash") ){
71
/* The schema is up-to-date. But it could be that an older version
72
** of Fossil that does no know about the stash.hash and stashfile.hash
73
** columns has run since the schema was updated, and added entries that
74
** have NULL hash columns. Check for this case, and fill in any missing
75
** hash values.
76
*/
77
if( db_int(0, "SELECT hash IS NULL FROM stash"
78
" ORDER BY stashid DESC LIMIT 1")
79
){
80
db_multi_exec(
81
"UPDATE stash"
82
" SET hash=(SELECT uuid FROM blob WHERE blob.rid=stash.vid)"
83
" WHERE hash IS NULL;"
84
"UPDATE stashfile"
85
" SET hash=(SELECT uuid FROM blob WHERE blob.rid=stashfile.rid)"
86
" WHERE hash IS NULL AND rid>0;"
87
);
88
}
89
return;
90
}
91
92
if( !db_table_exists("localdb","stashfile")
93
|| !db_table_exists("localdb","stash")
94
){
95
/* Tables do not exist. Create them from scratch. */
96
db_multi_exec("DROP TABLE IF EXISTS localdb.stash;");
97
db_multi_exec("DROP TABLE IF EXISTS localdb.stashfile;");
98
db_multi_exec(zStashInit /*works-like:""*/);
99
return;
100
}
101
102
/* The tables exists but are not necessarily current. Upgrade them
103
** to the latest format.
104
**
105
** We can assume the 2011-09-01 format that includes the stashfile.isLink
106
** column. The only upgrades we need to worry about the PRIMARY KEY
107
** change on 2016-10-16 and the addition of the "hash" columns on
108
** 2019-01-19.
109
*/
110
db_multi_exec(
111
"ALTER TABLE localdb.stash RENAME TO old_stash;"
112
"ALTER TABLE localdb.stashfile RENAME TO old_stashfile;"
113
);
114
db_multi_exec(zStashInit /*works-like:""*/);
115
db_multi_exec(
116
"INSERT INTO localdb.stash(stashid,vid,hash,comment,ctime)"
117
" SELECT stashid, vid,"
118
" (SELECT uuid FROM blob WHERE blob.rid=old_stash.vid),"
119
" comment, ctime FROM old_stash;"
120
"DROP TABLE old_stash;"
121
);
122
db_multi_exec(
123
"INSERT INTO localdb.stashfile(stashid,isAdded,isRemoved,isExec,"
124
"isLink,rid,hash,origname,newname,delta)"
125
" SELECT stashid, isAdded, isRemoved, isExec, isLink, rid,"
126
" (SELECT uuid FROM blob WHERE blob.rid=old_stashfile.rid),"
127
" origname, newname, delta FROM old_stashfile;"
128
"DROP TABLE old_stashfile;"
129
);
130
}
131
132
/*
133
** Update the stash.vid and stashfile.rid values after a RID renumbering
134
** event.
135
*/
136
void stash_rid_renumbering_event(void){
137
if( !db_table_has_column("localdb","stash","hash") ){
138
/* If the stash schema was the older style that lacked hash value, then
139
** recovery is not possible. Save off the old data, then reset the stash
140
** to empty. */
141
if( db_table_exists("localdb","stash") ){
142
db_multi_exec("ALTER TABLE stash RENAME TO broken_stash;");
143
fossil_print("Unrecoverable stash content stored in \"broken_stash\"\n");
144
}
145
if( db_table_exists("localdb","stashfile") ){
146
db_multi_exec("ALTER TABLE stashfile RENAME TO broken_stashfile;");
147
fossil_print("Unrecoverable stashfile content stored"
148
" in \"broken_stashfile\"\n");
149
}
150
}else{
151
/* Reset stash.vid and stash.rid values based on hashes */
152
db_multi_exec(
153
"UPDATE stash"
154
" SET vid=(SELECT rid FROM blob WHERE blob.uuid=stash.hash);"
155
"UPDATE stashfile"
156
" SET rid=(SELECT rid FROM blob WHERE blob.uuid=stashfile.hash)"
157
" WHERE hash IS NOT NULL;"
158
);
159
}
160
}
161
162
/*
163
** Add zFName to the stash given by stashid. zFName might be the name of a
164
** file or a directory. If a directory, add all changed files contained
165
** within that directory.
166
*/
167
static void stash_add_file_or_dir(int stashid, int vid, const char *zFName){
168
char *zFile; /* Normalized filename */
169
char *zTreename; /* Name of the file in the tree */
170
Blob fname; /* Filename relative to root */
171
Blob sql; /* Query statement text */
172
Stmt q; /* Query against the vfile table */
173
Stmt ins; /* Insert statement */
174
175
zFile = mprintf("%/", zFName);
176
file_tree_name(zFile, &fname, 0, 1);
177
zTreename = blob_str(&fname);
178
blob_zero(&sql);
179
blob_append_sql(&sql,
180
"SELECT deleted, isexe, islink, mrid, pathname, coalesce(origname,pathname)"
181
" FROM vfile"
182
" WHERE vid=%d AND (chnged OR deleted OR origname NOT NULL OR mrid==0)",
183
vid
184
);
185
if( fossil_strcmp(zTreename,".")!=0 ){
186
blob_append_sql(&sql,
187
" AND (pathname GLOB '%q/*' OR origname GLOB '%q/*'"
188
" OR pathname=%Q OR origname=%Q)",
189
zTreename, zTreename, zTreename, zTreename
190
);
191
}
192
db_prepare(&q, "%s", blob_sql_text(&sql));
193
blob_reset(&sql);
194
db_prepare(&ins,
195
"INSERT INTO stashfile(stashid, isAdded, isRemoved, isExec, isLink, rid, "
196
"hash, origname, newname, delta)"
197
"VALUES(%d,:isadd,:isrm,:isexe,:islink,:rid,"
198
"(SELECT uuid FROM blob WHERE rid=:rid),:orig,:new,:content)",
199
stashid
200
);
201
while( db_step(&q)==SQLITE_ROW ){
202
int deleted = db_column_int(&q, 0);
203
int rid = db_column_int(&q, 3);
204
const char *zName = db_column_text(&q, 4);
205
const char *zOrig = db_column_text(&q, 5);
206
char *zPath = mprintf("%s%s", g.zLocalRoot, zName);
207
Blob content;
208
209
db_bind_int(&ins, ":rid", rid);
210
db_bind_int(&ins, ":isadd", rid==0);
211
db_bind_int(&ins, ":isrm", deleted);
212
db_bind_int(&ins, ":isexe", db_column_int(&q, 1));
213
db_bind_int(&ins, ":islink", db_column_int(&q, 2));
214
db_bind_text(&ins, ":orig", zOrig);
215
db_bind_text(&ins, ":new", zName);
216
217
if( rid==0 ){
218
/* A new file */
219
blob_read_from_file(&content, zPath, RepoFILE);
220
db_bind_blob(&ins, ":content", &content);
221
}else if( deleted ){
222
blob_zero(&content);
223
db_bind_null(&ins, ":content");
224
}else{
225
/* A modified file */
226
Blob orig;
227
Blob disk;
228
229
blob_read_from_file(&disk, zPath, RepoFILE);
230
content_get(rid, &orig);
231
blob_delta_create(&orig, &disk, &content);
232
blob_reset(&orig);
233
blob_reset(&disk);
234
db_bind_blob(&ins, ":content", &content);
235
}
236
db_bind_int(&ins, ":islink", file_islink(zPath));
237
db_step(&ins);
238
db_reset(&ins);
239
fossil_free(zPath);
240
blob_reset(&content);
241
}
242
db_finalize(&ins);
243
db_finalize(&q);
244
fossil_free(zFile);
245
blob_reset(&fname);
246
}
247
248
/*
249
** Create a new stash based on the uncommitted changes currently in
250
** the working directory.
251
**
252
** If the "-m" or "--comment" command-line option is present, gather
253
** its argument as the stash comment.
254
**
255
** If files are named on the command-line, then only stash the named
256
** files.
257
*/
258
static int stash_create(void){
259
const char *zComment; /* Comment to add to the stash */
260
int stashid; /* ID of the new stash */
261
int vid; /* Current check-out */
262
263
zComment = find_option("comment", "m", 1);
264
(void)fossil_text_editor();
265
verify_all_options();
266
if( zComment==0 ){
267
Blob prompt; /* Prompt for stash comment */
268
Blob comment; /* User comment reply */
269
#if defined(_WIN32) || defined(__CYGWIN__)
270
int bomSize;
271
const unsigned char *bom = get_utf8_bom(&bomSize);
272
blob_init(&prompt, (const char *) bom, bomSize);
273
#else
274
blob_zero(&prompt);
275
#endif
276
blob_append(&prompt,
277
"\n"
278
"# Enter a description of what is being stashed. Lines beginning\n"
279
"# with \"#\" are ignored. Stash comments are plain text except\n"
280
"# newlines are not preserved.\n",
281
-1);
282
prompt_for_user_comment(&comment, &prompt);
283
blob_reset(&prompt);
284
zComment = blob_str(&comment);
285
}
286
stashid = db_lget_int("stash-next", 1);
287
db_lset_int("stash-next", stashid+1);
288
vid = db_lget_int("checkout", 0);
289
vfile_check_signature(vid, 0);
290
db_multi_exec(
291
"INSERT INTO stash(stashid,vid,hash,comment,ctime)"
292
"VALUES(%d,%d,(SELECT uuid FROM blob WHERE rid=%d),%Q,julianday('now'))",
293
stashid, vid, vid, zComment
294
);
295
if( g.argc>3 ){
296
int i;
297
for(i=3; i<g.argc; i++){
298
stash_add_file_or_dir(stashid, vid, g.argv[i]);
299
}
300
}else{
301
stash_add_file_or_dir(stashid, vid, g.zLocalRoot);
302
}
303
return stashid;
304
}
305
306
/*
307
** Apply a stash to the current check-out.
308
*/
309
static void stash_apply(int stashid, int nConflict){
310
int vid;
311
Stmt q;
312
db_prepare(&q,
313
"SELECT blob.rid, isRemoved, isExec, isLink, origname, newname, delta"
314
" FROM stashfile, blob WHERE stashid=%d AND blob.uuid=stashfile.hash"
315
" UNION ALL SELECT 0, isRemoved, isExec, isLink, origname, newname, delta"
316
" FROM stashfile WHERE stashid=%d AND stashfile.hash IS NULL",
317
stashid, stashid
318
);
319
vid = db_lget_int("checkout",0);
320
db_multi_exec("CREATE TEMP TABLE sfile(pathname TEXT PRIMARY KEY %s)",
321
filename_collation());
322
while( db_step(&q)==SQLITE_ROW ){
323
int rid = db_column_int(&q, 0);
324
int isRemoved = db_column_int(&q, 1);
325
int isExec = db_column_int(&q, 2);
326
int isLink = db_column_int(&q, 3);
327
const char *zOrig = db_column_text(&q, 4);
328
const char *zNew = db_column_text(&q, 5);
329
char *zOPath = mprintf("%s%s", g.zLocalRoot, zOrig);
330
char *zNPath = mprintf("%s%s", g.zLocalRoot, zNew);
331
Blob delta;
332
undo_save(zNew);
333
blob_zero(&delta);
334
if( rid==0 ){
335
db_multi_exec("INSERT OR IGNORE INTO sfile(pathname) VALUES(%Q)", zNew);
336
db_ephemeral_blob(&q, 6, &delta);
337
blob_write_to_file(&delta, zNPath);
338
file_setexe(zNPath, isExec);
339
}else if( isRemoved ){
340
fossil_print("DELETE %s\n", zOrig);
341
file_delete(zOPath);
342
}else if( file_unsafe_in_tree_path(zNPath) ){
343
/* Ignore the unsafe path */
344
}else{
345
Blob a, b, out, disk;
346
int isNewLink = file_islink(zOPath);
347
db_ephemeral_blob(&q, 6, &delta);
348
blob_read_from_file(&disk, zOPath, RepoFILE);
349
content_get(rid, &a);
350
blob_delta_apply(&a, &delta, &b);
351
if( isLink == isNewLink && blob_compare(&disk, &a)==0 ){
352
if( isLink || isNewLink ){
353
file_delete(zNPath);
354
}
355
if( isLink ){
356
symlink_create(blob_str(&b), zNPath);
357
}else{
358
blob_write_to_file(&b, zNPath);
359
}
360
file_setexe(zNPath, isExec);
361
fossil_print("UPDATE %s\n", zNew);
362
}else{
363
int rc;
364
if( isLink || isNewLink ){
365
rc = -1;
366
blob_zero(&b); /* because we reset it later */
367
fossil_print("***** Cannot merge symlink %s\n", zNew);
368
}else{
369
rc = merge_3way(&a, zOPath, &b, &out, MERGE_KEEP_FILES);
370
blob_write_to_file(&out, zNPath);
371
blob_reset(&out);
372
file_setexe(zNPath, isExec);
373
}
374
if( rc ){
375
fossil_print("CONFLICT %s\n", zNew);
376
nConflict++;
377
}else{
378
fossil_print("MERGE %s\n", zNew);
379
}
380
}
381
blob_reset(&a);
382
blob_reset(&b);
383
blob_reset(&disk);
384
}
385
blob_reset(&delta);
386
if( fossil_strcmp(zOrig,zNew)!=0 ){
387
undo_save(zOrig);
388
file_delete(zOPath);
389
db_multi_exec(
390
"UPDATE vfile SET pathname='%q', origname='%q'"
391
" WHERE pathname='%q' %s AND vid=%d",
392
zNew, zOrig, zOrig, filename_collation(), vid
393
);
394
}
395
}
396
stash_add_files_in_sfile(vid);
397
db_finalize(&q);
398
if( nConflict ){
399
fossil_print(
400
"WARNING: %d merge conflicts - see messages above for details.\n",
401
nConflict);
402
}
403
}
404
405
/*
406
** Show the diffs associate with a single stash.
407
*/
408
static void stash_diff(
409
int stashid, /* The stash entry to diff */
410
int fBaseline, /* Diff against original baseline check-in if true */
411
DiffConfig *pCfg /* Diff formatting options */
412
){
413
Stmt q;
414
Blob empty;
415
int bWebpage = (pCfg->diffFlags & (DIFF_WEBPAGE|DIFF_JSON|DIFF_TCL))!=0;
416
blob_zero(&empty);
417
diff_begin(pCfg);
418
db_prepare(&q,
419
"SELECT blob.rid, isRemoved, isExec, isLink, origname, newname, delta"
420
" FROM stashfile, blob WHERE stashid=%d AND blob.uuid=stashfile.hash"
421
" UNION ALL SELECT 0, isRemoved, isExec, isLink, origname, newname, delta"
422
" FROM stashfile WHERE stashid=%d AND stashfile.hash IS NULL",
423
stashid, stashid
424
);
425
while( db_step(&q)==SQLITE_ROW ){
426
int rid = db_column_int(&q, 0);
427
int isRemoved = db_column_int(&q, 1);
428
int isLink = db_column_int(&q, 3);
429
const char *zOrig = db_column_text(&q, 4);
430
const char *zNew = db_column_text(&q, 5);
431
char *zOPath = mprintf("%s%s", g.zLocalRoot, zOrig);
432
Blob a, b;
433
pCfg->diffFlags &= (~DIFF_FILE_MASK);
434
if( rid==0 ){
435
db_ephemeral_blob(&q, 6, &a);
436
if( !bWebpage ) fossil_print("ADDED %s\n", zNew);
437
pCfg->diffFlags |= DIFF_FILE_ADDED;
438
diff_print_index(zNew, pCfg, 0);
439
diff_file_mem(&empty, &a, zNew, pCfg);
440
}else if( isRemoved ){
441
if( !bWebpage) fossil_print("DELETE %s\n", zOrig);
442
pCfg->diffFlags |= DIFF_FILE_DELETED;
443
diff_print_index(zNew, pCfg, 0);
444
if( fBaseline ){
445
content_get(rid, &a);
446
diff_file_mem(&a, &empty, zOrig, pCfg);
447
}
448
}else{
449
Blob delta;
450
int isOrigLink = file_islink(zOPath);
451
db_ephemeral_blob(&q, 6, &delta);
452
if( !bWebpage ) fossil_print("CHANGED %s\n", zNew);
453
if( !isOrigLink != !isLink ){
454
diff_print_index(zNew, pCfg, 0);
455
diff_print_filenames(zOrig, zNew, pCfg, 0);
456
printf(DIFF_CANNOT_COMPUTE_SYMLINK);
457
}else{
458
content_get(rid, &a);
459
blob_delta_apply(&a, &delta, &b);
460
if( fBaseline ){
461
diff_file_mem(&a, &b, zNew, pCfg);
462
}else{
463
pCfg->diffFlags ^= DIFF_INVERT;
464
diff_file(&b, zOPath, zNew, pCfg, 0);
465
pCfg->diffFlags ^= DIFF_INVERT;
466
}
467
blob_reset(&a);
468
blob_reset(&b);
469
}
470
blob_reset(&delta);
471
}
472
}
473
db_finalize(&q);
474
diff_end(pCfg, 0);
475
}
476
477
/*
478
** Drop the indicated stash
479
*/
480
static void stash_drop(int stashid){
481
db_multi_exec(
482
"DELETE FROM stash WHERE stashid=%d;"
483
"DELETE FROM stashfile WHERE stashid=%d;",
484
stashid, stashid
485
);
486
}
487
488
/*
489
** If zStashId is non-NULL then interpret is as a stash number and
490
** return that number. Or throw a fatal error if it is not a valid
491
** stash number. If it is NULL, return the most recent stash or
492
** throw an error if the stash is empty.
493
*/
494
static int stash_get_id(const char *zStashId){
495
int stashid;
496
if( zStashId==0 ){
497
stashid = db_int(0, "SELECT max(stashid) FROM stash");
498
if( stashid==0 ) fossil_fatal("empty stash");
499
}else{
500
stashid = atoi(zStashId);
501
if( !db_exists("SELECT 1 FROM stash WHERE stashid=%d", stashid) ){
502
fossil_fatal("no such stash: %s", zStashId);
503
}
504
}
505
return stashid;
506
}
507
508
/*
509
** COMMAND: stash
510
**
511
** Usage: %fossil stash SUBCOMMAND ARGS...
512
**
513
** > fossil stash
514
** > fossil stash save ?FILES...?
515
** > fossil stash snapshot ?FILES...?
516
**
517
** Save the current changes in the working tree as a new stash.
518
** Then revert the changes back to the last check-in. If FILES
519
** are listed, then only stash and revert the named files. The
520
** "save" verb can be omitted if and only if there are no other
521
** arguments. The "snapshot" verb works the same as "save" but
522
** omits the revert, keeping the check-out unchanged.
523
**
524
** Options:
525
** --editor NAME Use the NAME editor to enter comment
526
** -m|--comment COMMENT Comment text for the new stash
527
**
528
**
529
** > fossil stash list|ls ?-v|--verbose? ?-W|--width NUM?
530
**
531
** List all changes sets currently stashed. Show information about
532
** individual files in each changeset if -v or --verbose is used.
533
**
534
** > fossil stash show|cat ?STASHID? ?DIFF-OPTIONS?
535
** > fossil stash gshow|gcat ?STASHID? ?DIFF-OPTIONS?
536
**
537
** Show the contents of a stash as a diff against its baseline.
538
** With gshow and gcat, gdiff-command is used instead of internal
539
** diff logic.
540
**
541
** > fossil stash pop
542
** > fossil stash apply ?STASHID?
543
**
544
** Apply STASHID or the most recently created stash to the current
545
** working check-out. The "pop" command deletes that changeset from
546
** the stash after applying it but the "apply" command retains the
547
** changeset.
548
**
549
** > fossil stash goto ?STASHID?
550
**
551
** Update to the baseline check-out for STASHID then apply the
552
** changes of STASHID. Keep STASHID so that it can be reused
553
** This command is undoable.
554
**
555
** > fossil stash drop|rm ?STASHIDs...? ?-a|--all?
556
**
557
** Forget everything about the given STASHIDs. Forget the whole
558
** stash if the -a|--all flag is used. Individual drops are
559
** undoable but -a|--all is not.
560
**
561
** > fossil stash diff ?STASHID? ?DIFF-OPTIONS?
562
** > fossil stash gdiff ?STASHID? ?DIFF-OPTIONS?
563
**
564
** Show diffs of the current working directory and what that
565
** directory would be if STASHID were applied. With gdiff,
566
** gdiff-command is used instead of internal diff logic.
567
**
568
** > fossil stash rename STASHID NEW-NAME
569
**
570
** Change the description of the given STASHID entry to NEW-NAME.
571
*/
572
void stash_cmd(void){
573
const char *zCmd;
574
int nCmd;
575
int stashid = 0;
576
undo_capture_command_line();
577
db_must_be_within_tree();
578
db_open_config(0, 0);
579
db_begin_transaction();
580
stash_tables_exist_and_current();
581
if( g.argc<=2 ){
582
zCmd = "save";
583
}else{
584
zCmd = g.argv[2];
585
}
586
nCmd = strlen(zCmd);
587
if( strncmp(zCmd, "save", nCmd)==0 ){
588
if( unsaved_changes(0)==0 ){
589
fossil_fatal("nothing to stash");
590
}
591
stashid = stash_create();
592
undo_disable();
593
if( g.argc>=2 ){
594
int nFile = db_int(0, "SELECT count(*) FROM stashfile WHERE stashid=%d",
595
stashid);
596
char **newArgv;
597
int i = 2;
598
Stmt q;
599
if( nFile==0 ){
600
fossil_fatal("No modified files match the provided pattern.");
601
}
602
newArgv = fossil_malloc( sizeof(char*)*(nFile+2) );
603
db_prepare(&q,"SELECT origname FROM stashfile WHERE stashid=%d", stashid);
604
while( db_step(&q)==SQLITE_ROW ){
605
newArgv[i++] = mprintf("%s%s", g.zLocalRoot, db_column_text(&q, 0));
606
}
607
db_finalize(&q);
608
newArgv[0] = g.argv[0];
609
newArgv[1] = 0;
610
g.argv = newArgv;
611
g.argc = nFile+2;
612
}
613
/* Make sure the stash has committed before running the revert, so that
614
** we have a copy of the changes before deleting them. */
615
db_commit_transaction();
616
g.argv[1] = "revert";
617
revert_cmd();
618
fossil_print("stash %d saved\n", stashid);
619
return;
620
}else
621
if( strncmp(zCmd, "snapshot", nCmd)==0 ){
622
stash_create();
623
}else
624
if( strncmp(zCmd, "list", nCmd)==0 || strncmp(zCmd, "ls", nCmd)==0 ){
625
Stmt q, q2;
626
int n = 0, width;
627
int verboseFlag = find_option("verbose","v",0)!=0;
628
const char *zWidth = find_option("width","W",1);
629
630
if( zWidth ){
631
width = atoi(zWidth);
632
if( (width!=0) && (width<=46) ){
633
fossil_fatal("-W|--width value must be >46 or 0");
634
}
635
}else{
636
width = -1;
637
}
638
if( !verboseFlag ){
639
verboseFlag = find_option("detail","l",0)!=0; /* deprecated */
640
}
641
verify_all_options();
642
db_prepare(&q,
643
"SELECT stashid, hash, comment, datetime(ctime) FROM stash"
644
" ORDER BY ctime"
645
);
646
if( verboseFlag ){
647
db_prepare(&q2, "SELECT isAdded, isRemoved, origname, newname"
648
" FROM stashfile WHERE stashid=$id");
649
}
650
while( db_step(&q)==SQLITE_ROW ){
651
int stashid = db_column_int(&q, 0);
652
const char *zCom;
653
n++;
654
fossil_print("%5d: [%.14s] on %s\n",
655
stashid,
656
db_column_text(&q, 1),
657
db_column_text(&q, 3)
658
);
659
zCom = db_column_text(&q, 2);
660
if( zCom && zCom[0] ){
661
fossil_print(" ");
662
comment_print(zCom, 0, 7, width, get_comment_format());
663
}
664
if( verboseFlag ){
665
db_bind_int(&q2, "$id", stashid);
666
while( db_step(&q2)==SQLITE_ROW ){
667
int isAdded = db_column_int(&q2, 0);
668
int isRemoved = db_column_int(&q2, 1);
669
const char *zOrig = db_column_text(&q2, 2);
670
const char *zNew = db_column_text(&q2, 3);
671
if( isAdded ){
672
fossil_print(" ADD %s\n", zNew);
673
}else if( isRemoved ){
674
fossil_print(" REMOVE %s\n", zOrig);
675
}else if( fossil_strcmp(zOrig,zNew)!=0 ){
676
fossil_print(" RENAME %s -> %s\n", zOrig, zNew);
677
}else{
678
fossil_print(" EDIT %s\n", zOrig);
679
}
680
}
681
db_reset(&q2);
682
}
683
}
684
db_finalize(&q);
685
if( verboseFlag ) db_finalize(&q2);
686
if( n==0 ) fossil_print("empty stash\n");
687
}else
688
if( strncmp(zCmd, "drop", nCmd)==0 || strncmp(zCmd, "rm", nCmd)==0 ){
689
int allFlag = find_option("all", "a", 0)!=0;
690
if( allFlag ){
691
Blob ans;
692
char cReply;
693
prompt_user("This action is not undoable. Continue (y/N)? ", &ans);
694
cReply = blob_str(&ans)[0];
695
if( cReply=='y' || cReply=='Y' ){
696
db_multi_exec("DELETE FROM stash; DELETE FROM stashfile;");
697
}
698
}else if( g.argc>=4 ){
699
int i;
700
undo_begin();
701
for(i=3; i<g.argc; i++){
702
stashid = stash_get_id(g.argv[i]);
703
undo_save_stash(stashid);
704
stash_drop(stashid);
705
}
706
undo_finish();
707
}else{
708
undo_begin();
709
undo_save_stash(0);
710
stash_drop(stashid);
711
undo_finish();
712
}
713
}else
714
if( strncmp(zCmd, "pop", nCmd)==0 || strncmp(zCmd, "apply", nCmd)==0 ){
715
char *zCom = 0, *zDate = 0, *zHash = 0;
716
int popped = *zCmd=='p';
717
if( popped ){
718
if( g.argc>3 ) usage("pop");
719
stashid = stash_get_id(0);
720
}else{
721
if( g.argc>4 ) usage("apply STASHID");
722
stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0);
723
}
724
zCom = db_text(0, "SELECT comment FROM stash WHERE stashid=%d", stashid);
725
zDate = db_text(0, "SELECT datetime(ctime) FROM stash WHERE stashid=%d",
726
stashid);
727
zHash = db_text(0, "SELECT hash FROM stash WHERE stashid=%d", stashid);
728
undo_begin();
729
stash_apply(stashid, 0);
730
if( popped ) undo_save_stash(stashid);
731
fossil_print("%s stash:\n%5d: [%.14s] from %s\n",
732
popped ? "Popped" : "Applied", stashid, zHash, zDate);
733
if( zCom && *zCom ){
734
fossil_print(" ");
735
comment_print(zCom, 0, 7, -1, get_comment_format());
736
}
737
fossil_free(zCom);
738
fossil_free(zDate);
739
fossil_free(zHash);
740
undo_finish();
741
if( popped ) stash_drop(stashid);
742
}else
743
if( strncmp(zCmd, "goto", nCmd)==0 ){
744
int nConflict;
745
int vid;
746
if( g.argc>4 ) usage("apply STASHID");
747
stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0);
748
undo_begin();
749
vid = db_int(0, "SELECT blob.rid FROM stash,blob"
750
" WHERE stashid=%d AND blob.uuid=stash.hash", stashid);
751
nConflict = update_to(vid);
752
stash_apply(stashid, nConflict);
753
db_multi_exec("UPDATE vfile SET mtime=0 WHERE pathname IN "
754
"(SELECT origname FROM stashfile WHERE stashid=%d)",
755
stashid);
756
undo_finish();
757
}else
758
if( strncmp(zCmd, "diff", nCmd)==0
759
|| strncmp(zCmd, "gdiff", nCmd)==0
760
|| strncmp(zCmd, "show", nCmd)==0
761
|| strncmp(zCmd, "gshow", nCmd)==0
762
|| strncmp(zCmd, "cat", nCmd)==0
763
|| strncmp(zCmd, "gcat", nCmd)==0
764
){
765
int fBaseline = 0;
766
DiffConfig DCfg;
767
768
if( strstr(zCmd,"show")!=0 || strstr(zCmd,"cat")!=0 ){
769
fBaseline = 1;
770
}
771
if( find_option("tk",0,0)!=0 || gdiff_using_tk(zCmd[0]=='g') ){
772
db_close(0);
773
diff_tk(fBaseline ? "stash show" : "stash diff", 3);
774
return;
775
}
776
diff_options(&DCfg, zCmd[0]=='g', 0);
777
stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0);
778
stash_diff(stashid, fBaseline, &DCfg);
779
}else
780
if( strncmp(zCmd, "rename", nCmd)==0 ){
781
if( g.argc!=5 ) usage("rename STASHID NAME");
782
stashid = stash_get_id(g.argv[3]);
783
db_multi_exec("UPDATE STASH SET COMMENT=%Q WHERE stashid=%d",
784
g.argv[4], stashid);
785
}
786
else if( strncmp(zCmd, "help", nCmd)==0 ){
787
g.argv[1] = "help";
788
g.argv[2] = "stash";
789
g.argc = 3;
790
help_cmd();
791
}else
792
{
793
usage("SUBCOMMAND ARGS...");
794
}
795
db_end_transaction(0);
796
}
797

Keyboard Shortcuts

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