Fossil SCM
Enhance the stash so that it stores hashes and no long depends on RID value. Do this is a way that is backwards compatible and transparent to the user. After running any "stash" command using this version of Fossil or later, the schema will automatically update and the stash should survive a subsequent RID renumbering event in the repository without damage.
Commit
ed06585f41ee2acef54b03da90445278b7a88f2241f29932855d071718d3b5cd
Parent
cc44fa8425e9ba8…
2 files changed
+5
+131
-31
M
src/db.c
+5
| --- src/db.c | ||
| +++ src/db.c | ||
| @@ -1662,10 +1662,14 @@ | ||
| 1662 | 1662 | ** sure that the repository database that was just opened has not |
| 1663 | 1663 | ** be replaced by a clone of the same project, with different RID |
| 1664 | 1664 | ** values. |
| 1665 | 1665 | */ |
| 1666 | 1666 | if( g.localOpen && !db_fingerprint_ok() ){ |
| 1667 | + /* Uncomment the following when we are ready for automatic recovery: */ | |
| 1668 | +#if 0 | |
| 1669 | + stash_rid_renumbering_event(); | |
| 1670 | +#else | |
| 1667 | 1671 | fossil_print( |
| 1668 | 1672 | "Oops. It looks like the repository database file located at\n" |
| 1669 | 1673 | " \"%s\"\n", zDbName |
| 1670 | 1674 | ); |
| 1671 | 1675 | fossil_print( |
| @@ -1682,10 +1686,11 @@ | ||
| 1682 | 1686 | "WILL BE IRREVOCABLY LOST.\n\n", |
| 1683 | 1687 | g.argv[0], |
| 1684 | 1688 | g.argv[0], zDbName |
| 1685 | 1689 | ); |
| 1686 | 1690 | fossil_fatal("bad fingerprint"); |
| 1691 | +#endif | |
| 1687 | 1692 | } |
| 1688 | 1693 | } |
| 1689 | 1694 | |
| 1690 | 1695 | /* |
| 1691 | 1696 | ** Return true if there have been any changes to the repository |
| 1692 | 1697 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -1662,10 +1662,14 @@ | |
| 1662 | ** sure that the repository database that was just opened has not |
| 1663 | ** be replaced by a clone of the same project, with different RID |
| 1664 | ** values. |
| 1665 | */ |
| 1666 | if( g.localOpen && !db_fingerprint_ok() ){ |
| 1667 | fossil_print( |
| 1668 | "Oops. It looks like the repository database file located at\n" |
| 1669 | " \"%s\"\n", zDbName |
| 1670 | ); |
| 1671 | fossil_print( |
| @@ -1682,10 +1686,11 @@ | |
| 1682 | "WILL BE IRREVOCABLY LOST.\n\n", |
| 1683 | g.argv[0], |
| 1684 | g.argv[0], zDbName |
| 1685 | ); |
| 1686 | fossil_fatal("bad fingerprint"); |
| 1687 | } |
| 1688 | } |
| 1689 | |
| 1690 | /* |
| 1691 | ** Return true if there have been any changes to the repository |
| 1692 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -1662,10 +1662,14 @@ | |
| 1662 | ** sure that the repository database that was just opened has not |
| 1663 | ** be replaced by a clone of the same project, with different RID |
| 1664 | ** values. |
| 1665 | */ |
| 1666 | if( g.localOpen && !db_fingerprint_ok() ){ |
| 1667 | /* Uncomment the following when we are ready for automatic recovery: */ |
| 1668 | #if 0 |
| 1669 | stash_rid_renumbering_event(); |
| 1670 | #else |
| 1671 | fossil_print( |
| 1672 | "Oops. It looks like the repository database file located at\n" |
| 1673 | " \"%s\"\n", zDbName |
| 1674 | ); |
| 1675 | fossil_print( |
| @@ -1682,10 +1686,11 @@ | |
| 1686 | "WILL BE IRREVOCABLY LOST.\n\n", |
| 1687 | g.argv[0], |
| 1688 | g.argv[0], zDbName |
| 1689 | ); |
| 1690 | fossil_fatal("bad fingerprint"); |
| 1691 | #endif |
| 1692 | } |
| 1693 | } |
| 1694 | |
| 1695 | /* |
| 1696 | ** Return true if there have been any changes to the repository |
| 1697 |
+131
-31
| --- src/stash.c | ||
| +++ src/stash.c | ||
| @@ -21,32 +21,145 @@ | ||
| 21 | 21 | #include <assert.h> |
| 22 | 22 | |
| 23 | 23 | |
| 24 | 24 | /* |
| 25 | 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 | +** | |
| 26 | 39 | */ |
| 27 | 40 | static const char zStashInit[] = |
| 28 | 41 | @ CREATE TABLE IF NOT EXISTS localdb.stash( |
| 29 | 42 | @ stashid INTEGER PRIMARY KEY, -- Unique stash identifier |
| 30 | -@ vid INTEGER, -- The baseline checkout for this stash | |
| 43 | +@ vid INTEGER, -- Legacy baseline RID value. Do not use. | |
| 44 | +@ hash TEXT, -- The SHA hash for the baseline | |
| 31 | 45 | @ comment TEXT, -- Comment for this stash. Or NULL |
| 32 | 46 | @ ctime TIMESTAMP -- When the stash was created |
| 33 | 47 | @ ); |
| 34 | 48 | @ CREATE TABLE IF NOT EXISTS localdb.stashfile( |
| 35 | 49 | @ stashid INTEGER REFERENCES stash, -- Stash that contains this file |
| 36 | -@ rid INTEGER, -- Baseline content in BLOB table or 0. | |
| 37 | 50 | @ isAdded BOOLEAN, -- True if this is an added file |
| 38 | 51 | @ isRemoved BOOLEAN, -- True if this file is deleted |
| 39 | 52 | @ isExec BOOLEAN, -- True if file is executable |
| 40 | 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 | |
| 41 | 56 | @ origname TEXT, -- Original filename |
| 42 | 57 | @ newname TEXT, -- New name for file at next check-in |
| 43 | -@ delta BLOB, -- Delta from baseline. Content if rid=0 | |
| 58 | +@ delta BLOB, -- Delta from baseline or raw content | |
| 44 | 59 | @ PRIMARY KEY(newname, stashid) |
| 45 | 60 | @ ); |
| 46 | 61 | @ INSERT OR IGNORE INTO vvar(name, value) VALUES('stash-next', 1); |
| 47 | 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 | +} | |
| 48 | 161 | |
| 49 | 162 | /* |
| 50 | 163 | ** Add zFName to the stash given by stashid. zFName might be the name of a |
| 51 | 164 | ** file or a directory. If a directory, add all changed files contained |
| 52 | 165 | ** within that directory. |
| @@ -77,13 +190,14 @@ | ||
| 77 | 190 | ); |
| 78 | 191 | } |
| 79 | 192 | db_prepare(&q, "%s", blob_sql_text(&sql)); |
| 80 | 193 | blob_reset(&sql); |
| 81 | 194 | db_prepare(&ins, |
| 82 | - "INSERT INTO stashfile(stashid, rid, isAdded, isRemoved, isExec, isLink," | |
| 83 | - "origname, newname, delta)" | |
| 84 | - "VALUES(%d,:rid,:isadd,:isrm,:isexe,:islink,:orig,:new,:content)", | |
| 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)", | |
| 85 | 199 | stashid |
| 86 | 200 | ); |
| 87 | 201 | while( db_step(&q)==SQLITE_ROW ){ |
| 88 | 202 | int deleted = db_column_int(&q, 0); |
| 89 | 203 | int rid = db_column_int(&q, 3); |
| @@ -171,13 +285,13 @@ | ||
| 171 | 285 | stashid = db_lget_int("stash-next", 1); |
| 172 | 286 | db_lset_int("stash-next", stashid+1); |
| 173 | 287 | vid = db_lget_int("checkout", 0); |
| 174 | 288 | vfile_check_signature(vid, 0); |
| 175 | 289 | db_multi_exec( |
| 176 | - "INSERT INTO stash(stashid,vid,comment,ctime)" | |
| 177 | - "VALUES(%d,%d,%Q,julianday('now'))", | |
| 178 | - stashid, vid, zComment | |
| 290 | + "INSERT INTO stash(stashid,vid,hash,comment,ctime)" | |
| 291 | + "VALUES(%d,%d,(SELECT uuid FROM blob WHERE rid=%d),%Q,julianday('now'))", | |
| 292 | + stashid, vid, vid, zComment | |
| 179 | 293 | ); |
| 180 | 294 | if( g.argc>3 ){ |
| 181 | 295 | int i; |
| 182 | 296 | for(i=3; i<g.argc; i++){ |
| 183 | 297 | stash_add_file_or_dir(stashid, vid, g.argv[i]); |
| @@ -193,12 +307,12 @@ | ||
| 193 | 307 | */ |
| 194 | 308 | static void stash_apply(int stashid, int nConflict){ |
| 195 | 309 | int vid; |
| 196 | 310 | Stmt q; |
| 197 | 311 | db_prepare(&q, |
| 198 | - "SELECT rid, isRemoved, isExec, isLink, origname, newname, delta" | |
| 199 | - " FROM stashfile WHERE stashid=%d", | |
| 312 | + "SELECT blob.rid, isRemoved, isExec, isLink, origname, newname, delta" | |
| 313 | + " FROM stashfile, blob WHERE stashid=%d AND blob.uuid=stashfile.hash", | |
| 200 | 314 | stashid |
| 201 | 315 | ); |
| 202 | 316 | vid = db_lget_int("checkout",0); |
| 203 | 317 | db_multi_exec("CREATE TEMP TABLE sfile(pathname TEXT PRIMARY KEY %s)", |
| 204 | 318 | filename_collation()); |
| @@ -296,12 +410,12 @@ | ||
| 296 | 410 | ){ |
| 297 | 411 | Stmt q; |
| 298 | 412 | Blob empty; |
| 299 | 413 | blob_zero(&empty); |
| 300 | 414 | db_prepare(&q, |
| 301 | - "SELECT rid, isRemoved, isExec, isLink, origname, newname, delta" | |
| 302 | - " FROM stashfile WHERE stashid=%d", | |
| 415 | + "SELECT blob.rid, isRemoved, isExec, isLink, origname, newname, delta" | |
| 416 | + " FROM stashfile, blob WHERE stashid=%d AND blob.uuid=stashfile.hash", | |
| 303 | 417 | stashid |
| 304 | 418 | ); |
| 305 | 419 | while( db_step(&q)==SQLITE_ROW ){ |
| 306 | 420 | int rid = db_column_int(&q, 0); |
| 307 | 421 | int isRemoved = db_column_int(&q, 1); |
| @@ -468,25 +582,11 @@ | ||
| 468 | 582 | int rc; |
| 469 | 583 | undo_capture_command_line(); |
| 470 | 584 | db_must_be_within_tree(); |
| 471 | 585 | db_open_config(0, 0); |
| 472 | 586 | db_begin_transaction(); |
| 473 | - db_multi_exec(zStashInit /*works-like:""*/); | |
| 474 | - rc = db_exists("SELECT 1 FROM sqlite_master" | |
| 475 | - " WHERE name='stashfile'" | |
| 476 | - " AND sql GLOB '* PRIMARY KEY(origname, stashid)*'"); | |
| 477 | - if( rc!=0 ){ | |
| 478 | - db_multi_exec( | |
| 479 | - "CREATE TABLE localdb.stashfile_tmp AS SELECT * FROM stashfile;" | |
| 480 | - "DROP TABLE stashfile;" | |
| 481 | - ); | |
| 482 | - db_multi_exec(zStashInit /*works-like:""*/); | |
| 483 | - db_multi_exec( | |
| 484 | - "INSERT INTO stashfile SELECT * FROM stashfile_tmp;" | |
| 485 | - "DROP TABLE stashfile_tmp;" | |
| 486 | - ); | |
| 487 | - } | |
| 587 | + stash_tables_exist_and_current(); | |
| 488 | 588 | if( g.argc<=2 ){ |
| 489 | 589 | zCmd = "save"; |
| 490 | 590 | }else{ |
| 491 | 591 | zCmd = g.argv[2]; |
| 492 | 592 | } |
| @@ -534,12 +634,11 @@ | ||
| 534 | 634 | if( !verboseFlag ){ |
| 535 | 635 | verboseFlag = find_option("detail","l",0)!=0; /* deprecated */ |
| 536 | 636 | } |
| 537 | 637 | verify_all_options(); |
| 538 | 638 | db_prepare(&q, |
| 539 | - "SELECT stashid, (SELECT uuid FROM blob WHERE rid=vid)," | |
| 540 | - " comment, datetime(ctime) FROM stash" | |
| 639 | + "SELECT stashid, hash, comment, datetime(ctime) FROM stash" | |
| 541 | 640 | " ORDER BY ctime" |
| 542 | 641 | ); |
| 543 | 642 | if( verboseFlag ){ |
| 544 | 643 | db_prepare(&q2, "SELECT isAdded, isRemoved, origname, newname" |
| 545 | 644 | " FROM stashfile WHERE stashid=$id"); |
| @@ -628,11 +727,12 @@ | ||
| 628 | 727 | int nConflict; |
| 629 | 728 | int vid; |
| 630 | 729 | if( g.argc>4 ) usage("apply STASHID"); |
| 631 | 730 | stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0); |
| 632 | 731 | undo_begin(); |
| 633 | - vid = db_int(0, "SELECT vid FROM stash WHERE stashid=%d", stashid); | |
| 732 | + vid = db_int(0, "SELECT blob.rid FROM stash,blob" | |
| 733 | + " WHERE stashid=%d AND blob.uuid=stash.hash", stashid); | |
| 634 | 734 | nConflict = update_to(vid); |
| 635 | 735 | stash_apply(stashid, nConflict); |
| 636 | 736 | db_multi_exec("UPDATE vfile SET mtime=0 WHERE pathname IN " |
| 637 | 737 | "(SELECT origname FROM stashfile WHERE stashid=%d)", |
| 638 | 738 | stashid); |
| 639 | 739 |
| --- src/stash.c | |
| +++ src/stash.c | |
| @@ -21,32 +21,145 @@ | |
| 21 | #include <assert.h> |
| 22 | |
| 23 | |
| 24 | /* |
| 25 | ** SQL code to implement the tables needed by the stash. |
| 26 | */ |
| 27 | static const char zStashInit[] = |
| 28 | @ CREATE TABLE IF NOT EXISTS localdb.stash( |
| 29 | @ stashid INTEGER PRIMARY KEY, -- Unique stash identifier |
| 30 | @ vid INTEGER, -- The baseline checkout for this stash |
| 31 | @ comment TEXT, -- Comment for this stash. Or NULL |
| 32 | @ ctime TIMESTAMP -- When the stash was created |
| 33 | @ ); |
| 34 | @ CREATE TABLE IF NOT EXISTS localdb.stashfile( |
| 35 | @ stashid INTEGER REFERENCES stash, -- Stash that contains this file |
| 36 | @ rid INTEGER, -- Baseline content in BLOB table or 0. |
| 37 | @ isAdded BOOLEAN, -- True if this is an added file |
| 38 | @ isRemoved BOOLEAN, -- True if this file is deleted |
| 39 | @ isExec BOOLEAN, -- True if file is executable |
| 40 | @ isLink BOOLEAN, -- True if file is a symlink |
| 41 | @ origname TEXT, -- Original filename |
| 42 | @ newname TEXT, -- New name for file at next check-in |
| 43 | @ delta BLOB, -- Delta from baseline. Content if rid=0 |
| 44 | @ PRIMARY KEY(newname, stashid) |
| 45 | @ ); |
| 46 | @ INSERT OR IGNORE INTO vvar(name, value) VALUES('stash-next', 1); |
| 47 | ; |
| 48 | |
| 49 | /* |
| 50 | ** Add zFName to the stash given by stashid. zFName might be the name of a |
| 51 | ** file or a directory. If a directory, add all changed files contained |
| 52 | ** within that directory. |
| @@ -77,13 +190,14 @@ | |
| 77 | ); |
| 78 | } |
| 79 | db_prepare(&q, "%s", blob_sql_text(&sql)); |
| 80 | blob_reset(&sql); |
| 81 | db_prepare(&ins, |
| 82 | "INSERT INTO stashfile(stashid, rid, isAdded, isRemoved, isExec, isLink," |
| 83 | "origname, newname, delta)" |
| 84 | "VALUES(%d,:rid,:isadd,:isrm,:isexe,:islink,:orig,:new,:content)", |
| 85 | stashid |
| 86 | ); |
| 87 | while( db_step(&q)==SQLITE_ROW ){ |
| 88 | int deleted = db_column_int(&q, 0); |
| 89 | int rid = db_column_int(&q, 3); |
| @@ -171,13 +285,13 @@ | |
| 171 | stashid = db_lget_int("stash-next", 1); |
| 172 | db_lset_int("stash-next", stashid+1); |
| 173 | vid = db_lget_int("checkout", 0); |
| 174 | vfile_check_signature(vid, 0); |
| 175 | db_multi_exec( |
| 176 | "INSERT INTO stash(stashid,vid,comment,ctime)" |
| 177 | "VALUES(%d,%d,%Q,julianday('now'))", |
| 178 | stashid, vid, zComment |
| 179 | ); |
| 180 | if( g.argc>3 ){ |
| 181 | int i; |
| 182 | for(i=3; i<g.argc; i++){ |
| 183 | stash_add_file_or_dir(stashid, vid, g.argv[i]); |
| @@ -193,12 +307,12 @@ | |
| 193 | */ |
| 194 | static void stash_apply(int stashid, int nConflict){ |
| 195 | int vid; |
| 196 | Stmt q; |
| 197 | db_prepare(&q, |
| 198 | "SELECT rid, isRemoved, isExec, isLink, origname, newname, delta" |
| 199 | " FROM stashfile WHERE stashid=%d", |
| 200 | stashid |
| 201 | ); |
| 202 | vid = db_lget_int("checkout",0); |
| 203 | db_multi_exec("CREATE TEMP TABLE sfile(pathname TEXT PRIMARY KEY %s)", |
| 204 | filename_collation()); |
| @@ -296,12 +410,12 @@ | |
| 296 | ){ |
| 297 | Stmt q; |
| 298 | Blob empty; |
| 299 | blob_zero(&empty); |
| 300 | db_prepare(&q, |
| 301 | "SELECT rid, isRemoved, isExec, isLink, origname, newname, delta" |
| 302 | " FROM stashfile WHERE stashid=%d", |
| 303 | stashid |
| 304 | ); |
| 305 | while( db_step(&q)==SQLITE_ROW ){ |
| 306 | int rid = db_column_int(&q, 0); |
| 307 | int isRemoved = db_column_int(&q, 1); |
| @@ -468,25 +582,11 @@ | |
| 468 | int rc; |
| 469 | undo_capture_command_line(); |
| 470 | db_must_be_within_tree(); |
| 471 | db_open_config(0, 0); |
| 472 | db_begin_transaction(); |
| 473 | db_multi_exec(zStashInit /*works-like:""*/); |
| 474 | rc = db_exists("SELECT 1 FROM sqlite_master" |
| 475 | " WHERE name='stashfile'" |
| 476 | " AND sql GLOB '* PRIMARY KEY(origname, stashid)*'"); |
| 477 | if( rc!=0 ){ |
| 478 | db_multi_exec( |
| 479 | "CREATE TABLE localdb.stashfile_tmp AS SELECT * FROM stashfile;" |
| 480 | "DROP TABLE stashfile;" |
| 481 | ); |
| 482 | db_multi_exec(zStashInit /*works-like:""*/); |
| 483 | db_multi_exec( |
| 484 | "INSERT INTO stashfile SELECT * FROM stashfile_tmp;" |
| 485 | "DROP TABLE stashfile_tmp;" |
| 486 | ); |
| 487 | } |
| 488 | if( g.argc<=2 ){ |
| 489 | zCmd = "save"; |
| 490 | }else{ |
| 491 | zCmd = g.argv[2]; |
| 492 | } |
| @@ -534,12 +634,11 @@ | |
| 534 | if( !verboseFlag ){ |
| 535 | verboseFlag = find_option("detail","l",0)!=0; /* deprecated */ |
| 536 | } |
| 537 | verify_all_options(); |
| 538 | db_prepare(&q, |
| 539 | "SELECT stashid, (SELECT uuid FROM blob WHERE rid=vid)," |
| 540 | " comment, datetime(ctime) FROM stash" |
| 541 | " ORDER BY ctime" |
| 542 | ); |
| 543 | if( verboseFlag ){ |
| 544 | db_prepare(&q2, "SELECT isAdded, isRemoved, origname, newname" |
| 545 | " FROM stashfile WHERE stashid=$id"); |
| @@ -628,11 +727,12 @@ | |
| 628 | int nConflict; |
| 629 | int vid; |
| 630 | if( g.argc>4 ) usage("apply STASHID"); |
| 631 | stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0); |
| 632 | undo_begin(); |
| 633 | vid = db_int(0, "SELECT vid FROM stash WHERE stashid=%d", stashid); |
| 634 | nConflict = update_to(vid); |
| 635 | stash_apply(stashid, nConflict); |
| 636 | db_multi_exec("UPDATE vfile SET mtime=0 WHERE pathname IN " |
| 637 | "(SELECT origname FROM stashfile WHERE stashid=%d)", |
| 638 | stashid); |
| 639 |
| --- src/stash.c | |
| +++ src/stash.c | |
| @@ -21,32 +21,145 @@ | |
| 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. |
| @@ -77,13 +190,14 @@ | |
| 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); |
| @@ -171,13 +285,13 @@ | |
| 285 | stashid = db_lget_int("stash-next", 1); |
| 286 | db_lset_int("stash-next", stashid+1); |
| 287 | vid = db_lget_int("checkout", 0); |
| 288 | vfile_check_signature(vid, 0); |
| 289 | db_multi_exec( |
| 290 | "INSERT INTO stash(stashid,vid,hash,comment,ctime)" |
| 291 | "VALUES(%d,%d,(SELECT uuid FROM blob WHERE rid=%d),%Q,julianday('now'))", |
| 292 | stashid, vid, vid, zComment |
| 293 | ); |
| 294 | if( g.argc>3 ){ |
| 295 | int i; |
| 296 | for(i=3; i<g.argc; i++){ |
| 297 | stash_add_file_or_dir(stashid, vid, g.argv[i]); |
| @@ -193,12 +307,12 @@ | |
| 307 | */ |
| 308 | static void stash_apply(int stashid, int nConflict){ |
| 309 | int vid; |
| 310 | Stmt q; |
| 311 | db_prepare(&q, |
| 312 | "SELECT blob.rid, isRemoved, isExec, isLink, origname, newname, delta" |
| 313 | " FROM stashfile, blob WHERE stashid=%d AND blob.uuid=stashfile.hash", |
| 314 | stashid |
| 315 | ); |
| 316 | vid = db_lget_int("checkout",0); |
| 317 | db_multi_exec("CREATE TEMP TABLE sfile(pathname TEXT PRIMARY KEY %s)", |
| 318 | filename_collation()); |
| @@ -296,12 +410,12 @@ | |
| 410 | ){ |
| 411 | Stmt q; |
| 412 | Blob empty; |
| 413 | blob_zero(&empty); |
| 414 | db_prepare(&q, |
| 415 | "SELECT blob.rid, isRemoved, isExec, isLink, origname, newname, delta" |
| 416 | " FROM stashfile, blob WHERE stashid=%d AND blob.uuid=stashfile.hash", |
| 417 | stashid |
| 418 | ); |
| 419 | while( db_step(&q)==SQLITE_ROW ){ |
| 420 | int rid = db_column_int(&q, 0); |
| 421 | int isRemoved = db_column_int(&q, 1); |
| @@ -468,25 +582,11 @@ | |
| 582 | int rc; |
| 583 | undo_capture_command_line(); |
| 584 | db_must_be_within_tree(); |
| 585 | db_open_config(0, 0); |
| 586 | db_begin_transaction(); |
| 587 | stash_tables_exist_and_current(); |
| 588 | if( g.argc<=2 ){ |
| 589 | zCmd = "save"; |
| 590 | }else{ |
| 591 | zCmd = g.argv[2]; |
| 592 | } |
| @@ -534,12 +634,11 @@ | |
| 634 | if( !verboseFlag ){ |
| 635 | verboseFlag = find_option("detail","l",0)!=0; /* deprecated */ |
| 636 | } |
| 637 | verify_all_options(); |
| 638 | db_prepare(&q, |
| 639 | "SELECT stashid, hash, comment, datetime(ctime) FROM stash" |
| 640 | " ORDER BY ctime" |
| 641 | ); |
| 642 | if( verboseFlag ){ |
| 643 | db_prepare(&q2, "SELECT isAdded, isRemoved, origname, newname" |
| 644 | " FROM stashfile WHERE stashid=$id"); |
| @@ -628,11 +727,12 @@ | |
| 727 | int nConflict; |
| 728 | int vid; |
| 729 | if( g.argc>4 ) usage("apply STASHID"); |
| 730 | stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0); |
| 731 | undo_begin(); |
| 732 | vid = db_int(0, "SELECT blob.rid FROM stash,blob" |
| 733 | " WHERE stashid=%d AND blob.uuid=stash.hash", stashid); |
| 734 | nConflict = update_to(vid); |
| 735 | stash_apply(stashid, nConflict); |
| 736 | db_multi_exec("UPDATE vfile SET mtime=0 WHERE pathname IN " |
| 737 | "(SELECT origname FROM stashfile WHERE stashid=%d)", |
| 738 | stashid); |
| 739 |