Fossil

Check-in [ed06585f]
Login

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

Overview
Comment: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.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: ed06585f41ee2acef54b03da90445278b7a88f2241f29932855d071718d3b5cd
User & Date: drh 2019-01-19 18:29:47
Context
2019-01-19
21:31
Change the schema of the vmerge table so that it remembers the hash of merged artifacts, not the RID. Schema updates are automatic, but once the update occurs, you are committed to using the newer version of Fossil moving forward. check-in: 8977dfb0 user: drh tags: rid-renumbering
21:03
Remove unused variable check-in: 302ce439 user: andygoth tags: trunk
18:29
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. check-in: ed06585f user: drh tags: trunk
2019-01-17
22:43
Added a warning about loss of stash and undo when performing the 'interim workaround' for an RID mismatch. check-in: cc44fa84 user: stephan tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/db.c.

1660
1661
1662
1663
1664
1665
1666




1667
1668
1669
1670
1671
1672
1673
....
1680
1681
1682
1683
1684
1685
1686

1687
1688
1689
1690
1691
1692
1693

  /* If the checkout database was opened first, then check to make
  ** sure that the repository database that was just opened has not
  ** be replaced by a clone of the same project, with different RID
  ** values.
  */
  if( g.localOpen && !db_fingerprint_ok() ){




    fossil_print(
      "Oops. It looks like the repository database file located at\n"
      "    \"%s\"\n", zDbName
    );
    fossil_print(
      "has been swapped with a clone that may have different\n"
      "integer keys for the various artifacts. As of 2019-01-11,\n"
................................................................................
      "  %s open \"%s\" --keep\n"
      "Noting that any STASH and UNDO information "
      "WILL BE IRREVOCABLY LOST.\n\n",
      g.argv[0],
      g.argv[0], zDbName
    );
    fossil_fatal("bad fingerprint");

  }
}

/*
** Return true if there have been any changes to the repository
** database since it was opened.
**







>
>
>
>







 







>







1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
....
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698

  /* If the checkout database was opened first, then check to make
  ** sure that the repository database that was just opened has not
  ** be replaced by a clone of the same project, with different RID
  ** values.
  */
  if( g.localOpen && !db_fingerprint_ok() ){
    /* Uncomment the following when we are ready for automatic recovery: */
#if 0
    stash_rid_renumbering_event();
#else
    fossil_print(
      "Oops. It looks like the repository database file located at\n"
      "    \"%s\"\n", zDbName
    );
    fossil_print(
      "has been swapped with a clone that may have different\n"
      "integer keys for the various artifacts. As of 2019-01-11,\n"
................................................................................
      "  %s open \"%s\" --keep\n"
      "Noting that any STASH and UNDO information "
      "WILL BE IRREVOCABLY LOST.\n\n",
      g.argv[0],
      g.argv[0], zDbName
    );
    fossil_fatal("bad fingerprint");
#endif
  }
}

/*
** Return true if there have been any changes to the repository
** database since it was opened.
**

Changes to src/stash.c.

19
20
21
22
23
24
25













26
27
28
29
30

31
32
33
34
35
36
37
38
39
40


41
42
43
44
45
46
47


































































































48
49
50
51
52
53
54
..
75
76
77
78
79
80
81
82
83
84

85
86
87
88
89
90
91
...
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
...
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
...
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
...
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
...
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
...
626
627
628
629
630
631
632
633

634
635
636
637
638
639
640
#include "config.h"
#include "stash.h"
#include <assert.h>


/*
** SQL code to implement the tables needed by the stash.













*/
static const char zStashInit[] =
@ CREATE TABLE IF NOT EXISTS localdb.stash(
@   stashid INTEGER PRIMARY KEY,     -- Unique stash identifier
@   vid INTEGER,                     -- The baseline checkout for this stash

@   comment TEXT,                    -- Comment for this stash.  Or NULL
@   ctime TIMESTAMP                  -- When the stash was created
@ );
@ CREATE TABLE IF NOT EXISTS localdb.stashfile(
@   stashid INTEGER REFERENCES stash,  -- Stash that contains this file
@   rid INTEGER,                       -- Baseline content in BLOB table or 0.
@   isAdded BOOLEAN,                   -- True if this is an added file
@   isRemoved BOOLEAN,                 -- True if this file is deleted
@   isExec BOOLEAN,                    -- True if file is executable
@   isLink BOOLEAN,                    -- True if file is a symlink


@   origname TEXT,                     -- Original filename
@   newname TEXT,                      -- New name for file at next check-in
@   delta BLOB,                        -- Delta from baseline. Content if rid=0
@   PRIMARY KEY(newname, stashid)
@ );
@ INSERT OR IGNORE INTO vvar(name, value) VALUES('stash-next', 1);
;



































































































/*
** Add zFName to the stash given by stashid.  zFName might be the name of a
** file or a directory.  If a directory, add all changed files contained
** within that directory.
*/
static void stash_add_file_or_dir(int stashid, int vid, const char *zFName){
................................................................................
            "  OR pathname=%Q OR origname=%Q)",
      zTreename, zTreename, zTreename, zTreename
    );
  }
  db_prepare(&q, "%s", blob_sql_text(&sql));
  blob_reset(&sql);
  db_prepare(&ins,
     "INSERT INTO stashfile(stashid, rid, isAdded, isRemoved, isExec, isLink,"
                           "origname, newname, delta)"
     "VALUES(%d,:rid,:isadd,:isrm,:isexe,:islink,:orig,:new,:content)",

     stashid
  );
  while( db_step(&q)==SQLITE_ROW ){
    int deleted = db_column_int(&q, 0);
    int rid = db_column_int(&q, 3);
    const char *zName = db_column_text(&q, 4);
    const char *zOrig = db_column_text(&q, 5);
................................................................................
    zComment = blob_str(&comment);
  }
  stashid = db_lget_int("stash-next", 1);
  db_lset_int("stash-next", stashid+1);
  vid = db_lget_int("checkout", 0);
  vfile_check_signature(vid, 0);
  db_multi_exec(
    "INSERT INTO stash(stashid,vid,comment,ctime)"
    "VALUES(%d,%d,%Q,julianday('now'))",
    stashid, vid, zComment
  );
  if( g.argc>3 ){
    int i;
    for(i=3; i<g.argc; i++){
      stash_add_file_or_dir(stashid, vid, g.argv[i]);
    }
  }else{
................................................................................
/*
** Apply a stash to the current checkout.
*/
static void stash_apply(int stashid, int nConflict){
  int vid;
  Stmt q;
  db_prepare(&q,
     "SELECT rid, isRemoved, isExec, isLink, origname, newname, delta"
     "  FROM stashfile WHERE stashid=%d",
     stashid
  );
  vid = db_lget_int("checkout",0);
  db_multi_exec("CREATE TEMP TABLE sfile(pathname TEXT PRIMARY KEY %s)",
                filename_collation());
  while( db_step(&q)==SQLITE_ROW ){
    int rid = db_column_int(&q, 0);
................................................................................
  int fIncludeBinary,      /* Do diffs against binary files */
  u64 diffFlags            /* Other diff flags */
){
  Stmt q;
  Blob empty;
  blob_zero(&empty);
  db_prepare(&q,
     "SELECT rid, isRemoved, isExec, isLink, origname, newname, delta"
     "  FROM stashfile WHERE stashid=%d",
     stashid
  );
  while( db_step(&q)==SQLITE_ROW ){
    int rid = db_column_int(&q, 0);
    int isRemoved = db_column_int(&q, 1);
    int isLink = db_column_int(&q, 3);
    int isBin1, isBin2;
................................................................................
  int nCmd;
  int stashid = 0;
  int rc;
  undo_capture_command_line();
  db_must_be_within_tree();
  db_open_config(0, 0);
  db_begin_transaction();
  db_multi_exec(zStashInit /*works-like:""*/);
  rc = db_exists("SELECT 1 FROM sqlite_master"
                 " WHERE name='stashfile'"
                 "   AND sql GLOB '* PRIMARY KEY(origname, stashid)*'");
  if( rc!=0 ){
    db_multi_exec(
      "CREATE TABLE localdb.stashfile_tmp AS SELECT * FROM stashfile;"
      "DROP TABLE stashfile;"
    );
    db_multi_exec(zStashInit /*works-like:""*/);
    db_multi_exec(
      "INSERT INTO stashfile SELECT * FROM stashfile_tmp;"
      "DROP TABLE stashfile_tmp;"
    );
  }
  if( g.argc<=2 ){
    zCmd = "save";
  }else{
    zCmd = g.argv[2];
  }
  nCmd = strlen(zCmd);
  if( memcmp(zCmd, "save", nCmd)==0 ){
................................................................................
      width = -1;
    }
    if( !verboseFlag ){
      verboseFlag = find_option("detail","l",0)!=0; /* deprecated */
    }
    verify_all_options();
    db_prepare(&q,
       "SELECT stashid, (SELECT uuid FROM blob WHERE rid=vid),"
       "       comment, datetime(ctime) FROM stash"
       " ORDER BY ctime"
    );
    if( verboseFlag ){
      db_prepare(&q2, "SELECT isAdded, isRemoved, origname, newname"
                      "  FROM stashfile WHERE stashid=$id");
    }
    while( db_step(&q)==SQLITE_ROW ){
................................................................................
  }else
  if( memcmp(zCmd, "goto", nCmd)==0 ){
    int nConflict;
    int vid;
    if( g.argc>4 ) usage("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







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




|
>





<




>
>


|




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







 







|
|
|
>







 







|
|
|







 







|
|







 







|
|







 







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







 







<
|







 







|
>







19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
...
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
...
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
...
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
...
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
...
580
581
582
583
584
585
586
587














588
589
590
591
592
593
594
...
632
633
634
635
636
637
638

639
640
641
642
643
644
645
646
...
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
#include "config.h"
#include "stash.h"
#include <assert.h>


/*
** SQL code to implement the tables needed by the stash.
**
** Historical schema changes:
**
**   2019-01-19:   stash.hash and stashfile.hash columns added.  The
**                 corresponding stash.vid and stashfile.rid columns are
**                 retained for compatibility with older versions of
**                 fossil but are no longer used.
**
**   2016-10-16:   Change the PRIMARY KEY on stashfile from (origname,stashid)
**                 to (newname,stashid).
**
**   2011-09-01:   stashfile.isLink column added
**
*/
static const char zStashInit[] =
@ CREATE TABLE IF NOT EXISTS localdb.stash(
@   stashid INTEGER PRIMARY KEY,     -- Unique stash identifier
@   vid INTEGER,                     -- Legacy baseline RID value. Do not use.
@   hash TEXT,                       -- The SHA hash for the baseline
@   comment TEXT,                    -- Comment for this stash.  Or NULL
@   ctime TIMESTAMP                  -- When the stash was created
@ );
@ CREATE TABLE IF NOT EXISTS localdb.stashfile(
@   stashid INTEGER REFERENCES stash,  -- Stash that contains this file

@   isAdded BOOLEAN,                   -- True if this is an added file
@   isRemoved BOOLEAN,                 -- True if this file is deleted
@   isExec BOOLEAN,                    -- True if file is executable
@   isLink BOOLEAN,                    -- True if file is a symlink
@   rid INTEGER,                       -- Legacy baseline RID value. Do not use
@   hash TEXT,                         -- Hash for baseline or NULL
@   origname TEXT,                     -- Original filename
@   newname TEXT,                      -- New name for file at next check-in
@   delta BLOB,                        -- Delta from baseline or raw content
@   PRIMARY KEY(newname, stashid)
@ );
@ INSERT OR IGNORE INTO vvar(name, value) VALUES('stash-next', 1);
;

/*
** Make sure the stash and stashfile tables exist and have been
** upgraded to their latest format.  Create and upgrade the tables
** as necessary.
*/
static void stash_tables_exist_and_current(void){
  if( db_table_has_column("localdb","stashfile","hash") ){
    /* The schema is up-to-date.  But it could be that an older version
    ** of Fossil that does no know about the stash.hash and stashfile.hash
    ** columns has run since the schema was updated, and added entries that
    ** have NULL hash columns.  Check for this case, and fill in any missing
    ** hash values.
    */
    if( db_int(0, "SELECT hash IS NULL FROM stash"
                  " ORDER BY stashid DESC LIMIT 1")
    ){
      db_multi_exec(
        "UPDATE stash"
        "   SET hash=(SELECT uuid FROM blob WHERE blob.rid=stash.vid)"
        " WHERE hash IS NULL;"
        "UPDATE stashfile"
        "   SET hash=(SELECT uuid FROM blob WHERE blob.rid=stashfile.rid)"
        " WHERE hash IS NULL AND rid>0;"
      );
    }
    return;
  }

  if( !db_table_exists("localdb","stashfile")
   || !db_table_exists("localdb","stash")
  ){
    /* Tables do not exist.  Create them from scratch. */
    db_multi_exec("DROP TABLE IF EXISTS localdb.stash;");
    db_multi_exec("DROP TABLE IF EXISTS localdb.stashfile;");
    db_multi_exec(zStashInit /*works-like:""*/);
    return;
  }

  /* The tables exists but are not necessarily current.  Upgrade them
  ** to the latest format.
  **
  ** We can assume the 2011-09-01 format that includes the stashfile.isLink
  ** column.  The only upgrades we need to worry about the PRIMARY KEY
  ** change on 2016-10-16 and the addition of the "hash" columns on
  ** 2019-01-19.
  */
  db_multi_exec(
    "ALTER TABLE localdb.stash RENAME TO old_stash;"
    "ALTER TABLE localdb.stashfile RENAME TO old_stashfile;"
  );
  db_multi_exec(zStashInit /*works-like:""*/);
  db_multi_exec(
    "INSERT INTO localdb.stash(stashid,vid,hash,comment,ctime)"
    " SELECT stashid, vid,"
    "   (SELECT uuid FROM blob WHERE blob.rid=old_stash.vid),"
    "   comment, ctime FROM old_stash;"
    "DROP TABLE old_stash;"
  );
  db_multi_exec(
    "INSERT INTO localdb.stashfile(stashid,isAdded,isRemoved,isExec,"
                                  "isLink,rid,hash,origname,newname,delta)"
    " SELECT stashid, isAdded, isRemoved, isExec, isLink, rid,"
    "   (SELECT uuid FROM blob WHERE blob.rid=old_stashfile.rid),"
    "   origname, newname, delta FROM old_stashfile;"
    "DROP TABLE old_stashfile;"
  );
}

/*
** Update the stash.vid and stashfile.rid values after a RID renumbering
** event.
*/
void stash_rid_renumbering_event(void){
  if( !db_table_has_column("localdb","stash","hash") ){
    /* If the stash schema was the older style that lacked hash value, then
    ** recovery is not possible.  Save off the old data, then reset the stash
    ** to empty. */
    if( db_table_exists("localdb","stash") ){
      db_multi_exec("ALTER TABLE stash RENAME TO broken_stash;");
      fossil_print("Unrecoverable stash content stored in \"broken_stash\"\n");
    }
    if( db_table_exists("localdb","stashfile") ){
      db_multi_exec("ALTER TABLE stashfile RENAME TO broken_stashfile;");
      fossil_print("Unrecoverable stashfile content stored"
                   " in \"broken_stashfile\"\n");
    }
  }else{
    /* Reset stash.vid and stash.rid values based on hashes */
    db_multi_exec(
      "UPDATE stash"
      "   SET vid=(SELECT rid FROM blob WHERE blob.uuid=stash.hash);"
      "UPDATE stashfile"
      "   SET rid=(SELECT rid FROM blob WHERE blob.uuid=stashfile.hash)"
      " WHERE hash IS NOT NULL;"
    );
  }
}

/*
** Add zFName to the stash given by stashid.  zFName might be the name of a
** file or a directory.  If a directory, add all changed files contained
** within that directory.
*/
static void stash_add_file_or_dir(int stashid, int vid, const char *zFName){
................................................................................
            "  OR pathname=%Q OR origname=%Q)",
      zTreename, zTreename, zTreename, zTreename
    );
  }
  db_prepare(&q, "%s", blob_sql_text(&sql));
  blob_reset(&sql);
  db_prepare(&ins,
     "INSERT INTO stashfile(stashid, isAdded, isRemoved, isExec, isLink, rid, "
                           "hash, origname, newname, delta)"
     "VALUES(%d,:isadd,:isrm,:isexe,:islink,:rid,"
     "(SELECT uuid FROM blob WHERE rid=:rid),:orig,:new,:content)",
     stashid
  );
  while( db_step(&q)==SQLITE_ROW ){
    int deleted = db_column_int(&q, 0);
    int rid = db_column_int(&q, 3);
    const char *zName = db_column_text(&q, 4);
    const char *zOrig = db_column_text(&q, 5);
................................................................................
    zComment = blob_str(&comment);
  }
  stashid = db_lget_int("stash-next", 1);
  db_lset_int("stash-next", stashid+1);
  vid = db_lget_int("checkout", 0);
  vfile_check_signature(vid, 0);
  db_multi_exec(
    "INSERT INTO stash(stashid,vid,hash,comment,ctime)"
    "VALUES(%d,%d,(SELECT uuid FROM blob WHERE rid=%d),%Q,julianday('now'))",
    stashid, vid, vid, zComment
  );
  if( g.argc>3 ){
    int i;
    for(i=3; i<g.argc; i++){
      stash_add_file_or_dir(stashid, vid, g.argv[i]);
    }
  }else{
................................................................................
/*
** Apply a stash to the current checkout.
*/
static void stash_apply(int stashid, int nConflict){
  int vid;
  Stmt q;
  db_prepare(&q,
     "SELECT blob.rid, isRemoved, isExec, isLink, origname, newname, delta"
     "  FROM stashfile, blob WHERE stashid=%d AND blob.uuid=stashfile.hash",
     stashid
  );
  vid = db_lget_int("checkout",0);
  db_multi_exec("CREATE TEMP TABLE sfile(pathname TEXT PRIMARY KEY %s)",
                filename_collation());
  while( db_step(&q)==SQLITE_ROW ){
    int rid = db_column_int(&q, 0);
................................................................................
  int fIncludeBinary,      /* Do diffs against binary files */
  u64 diffFlags            /* Other diff flags */
){
  Stmt q;
  Blob empty;
  blob_zero(&empty);
  db_prepare(&q,
     "SELECT blob.rid, isRemoved, isExec, isLink, origname, newname, delta"
     "  FROM stashfile, blob WHERE stashid=%d AND blob.uuid=stashfile.hash",
     stashid
  );
  while( db_step(&q)==SQLITE_ROW ){
    int rid = db_column_int(&q, 0);
    int isRemoved = db_column_int(&q, 1);
    int isLink = db_column_int(&q, 3);
    int isBin1, isBin2;
................................................................................
  int nCmd;
  int stashid = 0;
  int rc;
  undo_capture_command_line();
  db_must_be_within_tree();
  db_open_config(0, 0);
  db_begin_transaction();
  stash_tables_exist_and_current();














  if( g.argc<=2 ){
    zCmd = "save";
  }else{
    zCmd = g.argv[2];
  }
  nCmd = strlen(zCmd);
  if( memcmp(zCmd, "save", nCmd)==0 ){
................................................................................
      width = -1;
    }
    if( !verboseFlag ){
      verboseFlag = find_option("detail","l",0)!=0; /* deprecated */
    }
    verify_all_options();
    db_prepare(&q,

       "SELECT stashid, hash, comment, datetime(ctime) FROM stash"
       " ORDER BY ctime"
    );
    if( verboseFlag ){
      db_prepare(&q2, "SELECT isAdded, isRemoved, origname, newname"
                      "  FROM stashfile WHERE stashid=$id");
    }
    while( db_step(&q)==SQLITE_ROW ){
................................................................................
  }else
  if( memcmp(zCmd, "goto", nCmd)==0 ){
    int nConflict;
    int vid;
    if( g.argc>4 ) usage("apply STASHID");
    stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0);
    undo_begin();
    vid = db_int(0, "SELECT blob.rid FROM stash,blob"
                    " WHERE stashid=%d AND blob.uuid=stash.hash", 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