Index: src/add.c ================================================================== --- src/add.c +++ src/add.c @@ -187,12 +187,12 @@ zPath, filename_collation()); }else{ char *zFullname = mprintf("%s%s", g.zLocalRoot, zPath); int isExe = file_isexe(zFullname, RepoFILE); db_multi_exec( - "INSERT INTO vfile(vid,deleted,rid,mrid,pathname,isexe,islink)" - "VALUES(%d,0,0,0,%Q,%d,%d)", + "INSERT INTO vfile(vid,deleted,rid,mrid,pathname,isexe,islink,mhash)" + "VALUES(%d,0,0,0,%Q,%d,%d,NULL)", vid, zPath, isExe, file_islink(0)); fossil_free(zFullname); } if( db_changes() ){ fossil_print("ADDED %s\n", zPath); Index: src/bisect.c ================================================================== --- src/bisect.c +++ src/bisect.c @@ -286,10 +286,21 @@ (db_column_int(&q, 4) && zGoodBad[0]!='C') ? " CURRENT" : ""); } db_finalize(&q); } + +/* +** Reset the bisect subsystem. +*/ +void bisect_reset(void){ + db_multi_exec( + "DELETE FROM vvar WHERE name IN " + " ('bisect-good', 'bisect-bad', 'bisect-log')" + ); +} + /* ** COMMAND: bisect ** ** Usage: %fossil bisect SUBCOMMAND ... ** @@ -489,14 +500,11 @@ } }else{ usage("options ?NAME? ?VALUE?"); } }else if( strncmp(zCmd, "reset", n)==0 ){ - db_multi_exec( - "DELETE FROM vvar WHERE name IN " - " ('bisect-good', 'bisect-bad', 'bisect-log')" - ); + bisect_reset(); }else if( strcmp(zCmd, "ui")==0 ){ char *newArgv[8]; newArgv[0] = g.argv[0]; newArgv[1] = "ui"; newArgv[2] = "--page"; Index: src/checkin.c ================================================================== --- src/checkin.c +++ src/checkin.c @@ -307,12 +307,11 @@ db_finalize(&q); /* If C_MERGE, put merge contributors at the end of the report. */ skipFiles: if( flags & C_MERGE ){ - db_prepare(&q, "SELECT uuid, id FROM vmerge JOIN blob ON merge=rid" - " WHERE id<=0"); + db_prepare(&q, "SELECT mhash, id FROM vmerge WHERE id<=0" ); while( db_step(&q)==SQLITE_ROW ){ if( flags & C_COMMENT ){ blob_append(report, "# ", 2); } if( flags & C_CLASSIFY ){ @@ -1635,14 +1634,13 @@ blob_appendf(pOut, "\n"); } free(zDate); db_prepare(&q, - "SELECT CASE vmerge.id WHEN -1 THEN '+' ELSE '-' END || blob.uuid, merge" - " FROM vmerge, blob" + "SELECT CASE vmerge.id WHEN -1 THEN '+' ELSE '-' END || mhash, merge" + " FROM vmerge" " WHERE (vmerge.id=-1 OR vmerge.id=-2)" - " AND blob.rid=vmerge.merge" " ORDER BY 1"); while( db_step(&q)==SQLITE_ROW ){ const char *zCherrypickUuid = db_column_text(&q, 0); int mid = db_column_int(&q, 1); if( mid != vid ){ @@ -1667,11 +1665,11 @@ blob_appendf(pOut, "T +bgcolor * %F\n", zColor); } if( p->closeFlag ){ blob_appendf(pOut, "T +closed *\n"); } - db_prepare(&q, "SELECT uuid,merge FROM vmerge JOIN blob ON merge=rid" + db_prepare(&q, "SELECT mhash,merge FROM vmerge" " WHERE id %s ORDER BY 1", p->integrateFlag ? "IN(0,-4)" : "=(-4)"); while( db_step(&q)==SQLITE_ROW ){ const char *zIntegrateUuid = db_column_text(&q, 0); int rid = db_column_int(&q, 1); @@ -2400,11 +2398,12 @@ nrid = content_put(&content); blob_reset(&content); if( rid>0 ){ content_deltify(rid, &nrid, 1, 0); } - db_multi_exec("UPDATE vfile SET mrid=%d, rid=%d WHERE id=%d", nrid,nrid,id); + db_multi_exec("UPDATE vfile SET mrid=%d, rid=%d, mhash=NULL WHERE id=%d", + nrid,nrid,id); db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid); } db_finalize(&q); if( nConflict && !allowConflict ){ fossil_fatal("abort due to unresolved merge conflicts; " @@ -2508,12 +2507,11 @@ } assert( blob_is_reset(&manifest) ); content_deltify(vid, &nvid, 1, 0); zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nvid); - db_prepare(&q, "SELECT uuid,merge FROM vmerge JOIN blob ON merge=rid" - " WHERE id=-4"); + db_prepare(&q, "SELECT mhash,merge FROM vmerge WHERE id=-4"); while( db_step(&q)==SQLITE_ROW ){ const char *zIntegrateUuid = db_column_text(&q, 0); if( is_a_leaf(db_column_int(&q, 1)) ){ fossil_print("Closed: %s\n", zIntegrateUuid); }else{ @@ -2535,11 +2533,11 @@ /* Update the vfile and vmerge tables */ db_multi_exec( "DELETE FROM vfile WHERE (vid!=%d OR deleted) AND is_selected(id);" "DELETE FROM vmerge;" "UPDATE vfile SET vid=%d;" - "UPDATE vfile SET rid=mrid, chnged=0, deleted=0, origname=NULL" + "UPDATE vfile SET rid=mrid, mhash=NULL, chnged=0, deleted=0, origname=NULL" " WHERE is_selected(id);" , vid, nvid ); db_set_checkout(nvid); Index: src/db.c ================================================================== --- src/db.c +++ src/db.c @@ -1473,33 +1473,44 @@ ** If zDbName is a valid local database file, open it and return ** true. If it is not a valid local database file, return 0. */ static int isValidLocalDb(const char *zDbName){ i64 lsize; - char *zVFileDef; if( file_access(zDbName, F_OK) ) return 0; lsize = file_size(zDbName, ExtFILE); if( lsize%1024!=0 || lsize<4096 ) return 0; db_open_or_attach(zDbName, "localdb"); - zVFileDef = db_text(0, "SELECT sql FROM localdb.sqlite_master" - " WHERE name=='vfile'"); - if( zVFileDef==0 ) return 0; + + /* Check to see if the checkout database has the lastest schema changes. + ** The most recent schema change (2019-01-19) is the addition of the + ** vmerge.mhash and vfile.mhash fields. If the schema has the vmerge.mhash + ** column, assume everything else is up-to-date. + */ + if( db_table_has_column("localdb","vmerge","mhash") ){ + return 1; /* This is a checkout database with the latest schema */ + } + + /* If there is no vfile table, then assume we have picked up something + ** that is not even close to being a valid checkout database */ + if( !db_table_exists("localdb","vfile") ){ + return 0; /* Not a DB */ + } /* If the "isexe" column is missing from the vfile table, then ** add it now. This code added on 2010-03-06. After all users have ** upgraded, this code can be safely deleted. */ - if( sqlite3_strglob("* isexe *", zVFileDef)!=0 ){ + if( !db_table_has_column("localdb","vfile","isexe") ){ db_multi_exec("ALTER TABLE vfile ADD COLUMN isexe BOOLEAN DEFAULT 0"); } /* If "islink"/"isLink" columns are missing from tables, then ** add them now. This code added on 2011-01-17 and 2011-08-27. ** After all users have upgraded, this code can be safely deleted. */ - if( sqlite3_strglob("* islink *", zVFileDef)!=0 ){ + if( !db_table_has_column("localdb","vfile","isLink") ){ db_multi_exec("ALTER TABLE vfile ADD COLUMN islink BOOLEAN DEFAULT 0"); if( db_local_table_exists_but_lacks_column("stashfile", "isLink") ){ db_multi_exec("ALTER TABLE stashfile ADD COLUMN isLink BOOL DEFAULT 0"); } if( db_local_table_exists_but_lacks_column("undo", "isLink") ){ @@ -1507,11 +1518,16 @@ } if( db_local_table_exists_but_lacks_column("undo_vfile", "islink") ){ db_multi_exec("ALTER TABLE undo_vfile ADD COLUMN islink BOOL DEFAULT 0"); } } - fossil_free(zVFileDef); + + /* The design of the checkout database changed on 2019-01-19, adding the mhash + ** column to vfile and vmerge and changing the UNIQUE index on vmerge into + ** a PRIMARY KEY that includes the new mhash column. However, we must have + ** the repository database at hand in order to do the migration, so that + ** step is deferred. */ return 1; } /* ** Locate the root directory of the local repository tree. The root @@ -1656,41 +1672,74 @@ /* Make a change to the CHECK constraint on the BLOB table for ** version 2.0 and later. */ rebuild_schema_update_2_0(); /* Do the Fossil-2.0 schema updates */ - /* 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" - "we are working on enhancing Fossil to be able to deal with\n" - "that automatically, but we are not there yet. Sorry.\n\n" - ); - fossil_print( - "As an interim workaround, try:\n" - " %s close --force\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 + /* Additional checks that occur when opening the checkout database */ + if( g.localOpen ){ + + /* If the repository database that was just opened has been + ** eplaced by a clone of the same project, with different RID + ** values, then renumber the RID values stored in various tables + ** of the checkout database, so that the repository and checkout + ** databases align. + */ + if( !db_fingerprint_ok() ){ + if( find_option("no-rid-adjust",0,0)!=0 ){ + /* The --no-rid-adjust command-line option bypasses the RID value + ** updates. Intended for use during debugging, especially to be + ** able to run "fossil sql" after a database swap. */ + fossil_print( + "WARNING: repository change detected, but no adjust made.\n" + ); + }else if( find_option("rid-renumber-dryrun",0,0)!=0 ){ + /* the --rid-renumber-dryrun option shows how RID values would be + ** renumbered, but does not actually perform the renumbering. + ** This is a debugging-only option. */ + vfile_rid_renumbering_event(1); + exit(0); + }else{ + char *z; + stash_rid_renumbering_event(); + vfile_rid_renumbering_event(0); + undo_reset(); + bisect_reset(); + z = db_fingerprint(0); + db_lset("fingerprint", z); + fossil_free(z); + fossil_print( + "WARNING: The repository database has been replaced by a clone.\n" + "Bisect history and undo have been lost.\n" + ); + } + } + + /* Make sure the checkout database schema migration of 2019-01-20 + ** has occurred. + ** + ** The 2019-01-19 migration is the addition of the vmerge.mhash and + ** vfile.mhash columns and making the vmerge.mhash column part of the + ** PRIMARY KEY for vmerge. + */ + if( !db_table_has_column("localdb", "vfile", "mhash") ){ + db_multi_exec("ALTER TABLE vfile ADD COLUMN mhash;"); + db_multi_exec( + "UPDATE vfile" + " SET mhash=(SELECT uuid FROM blob WHERE blob.rid=vfile.mrid)" + " WHERE mrid!=rid;" + ); + if( !db_table_has_column("localdb", "vmerge", "mhash") ){ + db_multi_exec("ALTER TABLE vmerge RENAME TO old_vmerge;"); + db_multi_exec(zLocalSchemaVmerge /*works-like:""*/); + db_multi_exec( + "INSERT OR IGNORE INTO vmerge(id,merge,mhash)" + " SELECT id, merge, blob.uuid FROM old_vmerge, blob" + " WHERE old_vmerge.merge=blob.rid;" + "DROP TABLE old_vmerge;" + ); + } + } } } /* ** Return true if there have been any changes to the repository @@ -2880,11 +2929,11 @@ #if defined(_WIN32) || defined(__CYGWIN__) # define LOCALDB_NAME "./_FOSSIL_" #else # define LOCALDB_NAME "./.fslckout" #endif - db_init_database(LOCALDB_NAME, zLocalSchema, + db_init_database(LOCALDB_NAME, zLocalSchema, zLocalSchemaVmerge, #ifdef FOSSIL_LOCAL_WAL "COMMIT; PRAGMA journal_mode=WAL; BEGIN;", #endif (char*)0); db_delete_on_failure(LOCALDB_NAME); Index: src/foci.c ================================================================== --- src/foci.c +++ src/foci.c @@ -121,12 +121,13 @@ ** (1) checkinID=?. visit only the single manifest specified. ** (2) symName=? visit only the single manifest specified. */ static int fociBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ int i; - pIdxInfo->estimatedCost = 10000.0; + pIdxInfo->estimatedCost = 1000000000.0; for(i=0; inConstraint; i++){ + if( !pIdxInfo->aConstraint[i].usable ) continue; if( pIdxInfo->aConstraint[i].op==SQLITE_INDEX_CONSTRAINT_EQ && (pIdxInfo->aConstraint[i].iColumn==FOCI_CHECKINID || pIdxInfo->aConstraint[i].iColumn==FOCI_SYMNAME) ){ if( pIdxInfo->aConstraint[i].iColumn==FOCI_CHECKINID ){ Index: src/json_status.c ================================================================== --- src/json_status.c +++ src/json_status.c @@ -154,12 +154,11 @@ #if 0 /* TODO: add "merged with" status. First need (A) to decide on a structure and (B) to set up some tests for the multi-merge case.*/ - db_prepare(&q, "SELECT uuid, id FROM vmerge JOIN blob ON merge=rid" - " WHERE id<=0"); + db_prepare(&q, "SELECT mhash, id FROM vmerge WHERE id<=0"); while( db_step(&q)==SQLITE_ROW ){ const char *zLabel = "MERGED_WITH"; switch( db_column_int(&q, 1) ){ case -1: zLabel = "CHERRYPICK "; break; case -2: zLabel = "BACKOUT "; break; Index: src/merge.c ================================================================== --- src/merge.c +++ src/merge.c @@ -164,10 +164,20 @@ free(zN); free(zV); } free(aChng); } + +/* Make an entry in the vmerge table for the given id, and rid. +*/ +static void vmerge_insert(int id, int rid){ + db_multi_exec( + "INSERT OR IGNORE INTO vmerge(id,merge,mhash)" + "VALUES(%d,%d,(SELECT uuid FROM blob WHERE rid=%d))", + id, rid, rid + ); +} /* ** COMMAND: merge ** ** Usage: %fossil merge ?OPTIONS? ?VERSION? @@ -594,12 +604,14 @@ /* Copy content from idm over into idv. Overwrite idv. */ fossil_print("UPDATE %s\n", zName); if( !dryRunFlag ){ undo_save(zName); db_multi_exec( - "UPDATE vfile SET mtime=0, mrid=%d, chnged=%d, islink=%d " - " WHERE id=%d", ridm, integrateFlag?4:2, islinkm, idv + "UPDATE vfile SET mtime=0, mrid=%d, chnged=%d, islink=%d," + " mhash=CASE WHEN rid<>%d" + " THEN (SELECT uuid FROM blob WHERE blob.rid=%d) END" + " WHERE id=%d", ridm, integrateFlag?4:2, islinkm, ridm, ridm, idv ); vfile_to_disk(0, idv, 0, 0); } } db_finalize(&q); @@ -664,12 +676,11 @@ } blob_reset(&p); blob_reset(&m); blob_reset(&r); } - db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(%d,%d)", - idv,ridm); + vmerge_insert(idv, ridm); } db_finalize(&q); /* ** Drop files that are in P and V but not in M @@ -786,12 +797,16 @@ while( db_step(&q)==SQLITE_ROW ){ int idm = db_column_int(&q, 0); const char *zName; char *zFullName; db_multi_exec( - "REPLACE INTO vfile(vid,chnged,deleted,rid,mrid,isexe,islink,pathname)" - " SELECT %d,%d,0,rid,mrid,isexe,islink,pathname FROM vfile WHERE id=%d", + "REPLACE INTO vfile(vid,chnged,deleted,rid,mrid," + "isexe,islink,pathname,mhash)" + " SELECT %d,%d,0,rid,mrid,isexe,islink,pathname," + "CASE WHEN rid<>mrid" + " THEN (SELECT uuid FROM blob WHERE blob.rid=vfile.mrid) END " + "FROM vfile WHERE id=%d", vid, integrateFlag?5:3, idm ); zName = db_column_text(&q, 1); zFullName = mprintf("%s%s", g.zLocalRoot, zName); if( file_isfile_or_link(zFullName) @@ -826,24 +841,24 @@ /* ** Clean up the mid and pid VFILE entries. Then commit the changes. */ db_multi_exec("DELETE FROM vfile WHERE vid!=%d", vid); if( pickFlag ){ - db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(-1,%d)",mid); + vmerge_insert(-1, mid); /* For a cherry-pick merge, make the default check-in comment the same ** as the check-in comment on the check-in that is being merged in. */ db_multi_exec( "REPLACE INTO vvar(name,value)" " SELECT 'ci-comment', coalesce(ecomment,comment) FROM event" " WHERE type='ci' AND objid=%d", mid ); }else if( backoutFlag ){ - db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(-2,%d)",pid); + vmerge_insert(-2, pid); }else if( integrateFlag ){ - db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(-4,%d)",mid); + vmerge_insert(-4, mid); }else{ - db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(0,%d)", mid); + vmerge_insert(0, mid); } if( !dryRunFlag ) undo_finish(); db_end_transaction(dryRunFlag); } Index: src/schema.c ================================================================== --- src/schema.c +++ src/schema.c @@ -2,11 +2,11 @@ ** Copyright (c) 2007 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the Simplified BSD License (also ** known as the "2-Clause License" or "FreeBSD License".) - +** ** This program is distributed in the hope that it will be useful, ** but without any warranty; without even the implied warranty of ** merchantability or fitness for a particular purpose. ** ** Author contact information: @@ -352,11 +352,11 @@ @ -- @ CREATE TABLE unsent( @ rid INTEGER PRIMARY KEY -- Record ID of the phantom @ ); @ -@ -- Each baseline or manifest can have one or more tags. A tag +@ -- Each artifact can have one or more tags. A tag @ -- is defined by a row in the next table. @ -- @ -- Wiki pages are tagged with "wiki-NAME" where NAME is the name of @ -- the wiki page. Tickets changes are tagged with "ticket-UUID" where @ -- UUID is the indentifier of the ticket. Tags used to assign symbolic @@ -377,11 +377,11 @@ @ INSERT INTO tag VALUES(8, 'branch'); -- TAG_BRANCH @ INSERT INTO tag VALUES(9, 'closed'); -- TAG_CLOSED @ INSERT INTO tag VALUES(10,'parent'); -- TAG_PARENT @ INSERT INTO tag VALUES(11,'note'); -- TAG_NOTE @ -@ -- Assignments of tags to baselines. Note that we allow tags to +@ -- Assignments of tags to artifacts. Note that we allow tags to @ -- have values assigned to them. So we are not really dealing with @ -- tags here. These are really properties. But we are going to @ -- keep calling them tags because in many cases the value is ignored. @ -- @ CREATE TABLE tagxref( @@ -490,11 +490,11 @@ #endif /* ** The schema for the local FOSSIL database file found at the root ** of every check-out. This database contains the complete state of -** the checkout. +** the checkout. See also the addendum in zLocalSchemaVmerge[]. */ const char zLocalSchema[] = @ -- The VVAR table holds miscellanous information about the local database @ -- in the form of name-value pairs. This is similar to the VAR table @ -- table in the repository except that this table holds information that @@ -515,51 +515,71 @@ @ -- current checkout. @ -- @ -- The file.rid field is 0 for files or folders that have been @ -- added but not yet committed. @ -- -@ -- Vfile.chnged is 0 for unmodified files, 1 for files that have -@ -- been edited or which have been subjected to a 3-way merge. -@ -- Vfile.chnged is 2 if the file has been replaced from a different -@ -- version by the merge and 3 if the file has been added by a merge. -@ -- Vfile.chnged is 4|5 is the same as 2|3, but the operation has been -@ -- done by an --integrate merge. The difference between vfile.chnged==3|5 -@ -- and a regular add is that with vfile.chnged==3|5 we know that the -@ -- current version of the file is already in the repository. +@ -- Vfile.chnged meaning: +@ -- 0 File is unmodified +@ -- 1 Manually edited and/or modified as part of a merge command +@ -- 2 Replaced by a merge command +@ -- 3 Added by a merge command +@ -- 4,5 Same as 2,3 except merge using --integrate @ -- @ CREATE TABLE vfile( @ id INTEGER PRIMARY KEY, -- ID of the checked out file -@ vid INTEGER REFERENCES blob, -- The baseline this file is part of. +@ vid INTEGER REFERENCES blob, -- The checkin this file is part of. @ chnged INT DEFAULT 0, -- 0:unchng 1:edit 2:m-chng 3:m-add 4:i-chng 5:i-add @ deleted BOOLEAN DEFAULT 0, -- True if deleted @ isexe BOOLEAN, -- True if file should be executable @ islink BOOLEAN, -- True if file should be symlink @ rid INTEGER, -- Originally from this repository record @ mrid INTEGER, -- Based on this record due to a merge @ mtime INTEGER, -- Mtime of file on disk. sec since 1970 @ pathname TEXT, -- Full pathname relative to root @ origname TEXT, -- Original pathname. NULL if unchanged +@ mhash TEXT, -- Hash of mrid iff mrid!=rid @ UNIQUE(pathname,vid) @ ); @ +@ -- Identifier for this file type. +@ -- The integer is the same as 'FSLC'. +@ PRAGMA application_id=252006674; +; + +/* Additional local database initialization following the schema +** enhancement of 2019-01-19, in which the mhash column was added +** to vmerge and vfile. +*/ +const char zLocalSchemaVmerge[] = @ -- This table holds a record of uncommitted merges in the local @ -- file tree. If a VFILE entry with id has merged with another @ -- record, there is an entry in this table with (id,merge) where @ -- merge is the RECORD table entry that the file merged against. @ -- An id of 0 or <-3 here means the version record itself. When @ -- id==(-1) that is a cherrypick merge, id==(-2) that is a @ -- backout merge and id==(-4) is a integrate merge. +@ -- @ @ CREATE TABLE vmerge( @ id INTEGER REFERENCES vfile, -- VFILE entry that has been merged @ merge INTEGER, -- Merged with this record -@ UNIQUE(id, merge) +@ mhash TEXT -- SHA1/SHA3 hash for merge object @ ); +@ CREATE UNIQUE INDEX vmergex1 ON vmerge(id,mhash); +@ +@ -- The following trigger will prevent older versions of Fossil that +@ -- do not know about the new vmerge.mhash column from updating the +@ -- vmerge table. This must be done with a trigger, since legacy Fossil +@ -- uses INSERT OR IGNORE to update vmerge, and the OR IGNORE will cause +@ -- a NOT NULL constraint to be silently ignored. @ -@ -- Identifier for this file type. -@ -- The integer is the same as 'FSLC'. -@ PRAGMA application_id=252006674; +@ CREATE TRIGGER vmerge_ck1 AFTER INSERT ON vmerge +@ WHEN new.mhash IS NULL BEGIN +@ SELECT raise(FAIL, +@ 'trying to update a newer checkout with an older version of Fossil'); +@ END; +@ ; /* ** The following table holds information about forum posts. It ** is created on-demand whenever the manifest parser encounters Index: src/update.c ================================================================== --- src/update.c +++ src/update.c @@ -529,12 +529,11 @@ /* Report on conflicts */ if( !dryRunFlag ){ Stmt q; int nMerge = 0; - db_prepare(&q, "SELECT uuid, id FROM vmerge JOIN blob ON merge=rid" - " WHERE id<=0"); + db_prepare(&q, "SELECT mhash, id FROM vmerge WHERE id<=0"); while( db_step(&q)==SQLITE_ROW ){ const char *zLabel = "merge"; switch( db_column_int(&q, 1) ){ case -1: zLabel = "cherrypick merge"; break; case -2: zLabel = "backout merge"; break; @@ -865,11 +864,12 @@ file_setexe(zFull, rvPerm==PERM_EXE); fossil_print("REVERT %s\n", zFile); mtime = file_mtime(zFull, RepoFILE); db_multi_exec( "UPDATE vfile" - " SET mtime=%lld, chnged=%d, deleted=0, isexe=%d, islink=%d,mrid=rid" + " SET mtime=%lld, chnged=%d, deleted=0, isexe=%d, islink=%d," + " mrid=rid, mhash=NULL" " WHERE pathname=%Q OR origname=%Q", mtime, rvChnged, rvPerm==PERM_EXE, rvPerm==PERM_LNK, zFile, zFile ); } blob_reset(&record); Index: src/vfile.c ================================================================== --- src/vfile.c +++ src/vfile.c @@ -88,12 +88,12 @@ if( p==0 ) { db_end_transaction(1); return 0; } db_prepare(&ins, - "INSERT INTO vfile(vid,isexe,islink,rid,mrid,pathname) " - " VALUES(:vid,:isexe,:islink,:id,:id,:name)"); + "INSERT INTO vfile(vid,isexe,islink,rid,mrid,pathname,mhash) " + " VALUES(:vid,:isexe,:islink,:id,:id,:name,NULL)"); db_prepare(&ridq, "SELECT rid,size FROM blob WHERE uuid=:uuid"); db_bind_int(&ins, ":vid", vid); manifest_file_rewind(p); nMissing = 0; while( (pFile = manifest_file_next(p,0))!=0 ){ @@ -242,11 +242,11 @@ const char *zUuid = db_column_text(&q, 5); int nUuid = db_column_bytes(&q, 5); assert( origSize==currentSize ); if( !hname_verify_file_hash(zName, zUuid, nUuid) ) chnged = 1; } - if( (cksigFlags & CKSIG_SETMTIME) && (chnged==0 || chnged==2 || chnged==4) ){ + if( (cksigFlags & CKSIG_SETMTIME) && (chnged==0 || chnged==2 || chnged==4)){ i64 desiredMtime; if( mtime_of_manifest_file(vid,rid,&desiredMtime)==0 ){ if( currentMtime!=desiredMtime ){ file_set_mtime(zName, desiredMtime); currentMtime = file_mtime(zName, RepoFILE); @@ -966,5 +966,132 @@ blob_reset(&hash); vfile_aggregate_checksum_manifest(vid, &hash, &hash2); printf("manifest: %s\n", blob_str(&hash)); printf("recorded: %s\n", blob_str(&hash2)); } + +/* +** This routine recomputes certain columns of the vfile and vmerge tables +** when the associated repository is swapped out for a clone of the same +** project, and the blob.rid value change. The following columns are +** updated: +** +** vmerge.merge +** vfile.vid +** vfile.rid +** vfile.mrid +** +** Also: +** +** vvar.value WHERE name='checkout' +*/ +void vfile_rid_renumbering_event(int dryRun){ + int oldVid; + int newVid; + char *zUnresolved; + + oldVid = db_lget_int("checkout", 0); + newVid = db_int(0, "SELECT blob.rid FROM blob, vvar" + " WHERE blob.uuid=vvar.value" + " AND vvar.name='checkout-hash'"); + + /* The idMap table will make old RID values into new ones */ + db_multi_exec( + "CREATE TEMP TABLE idMap(oldrid INTEGER PRIMARY KEY, newrid INT);\n" + ); + + /* Add the RID value for the current check-out */ + db_multi_exec( + "INSERT INTO idMap(oldrid, newrid) VALUES(%d,%d)", + oldVid, newVid + ); + + /* Add the RID values for any other check-ins that have been merged into + ** the current check-out. */ + db_multi_exec( + "INSERT OR IGNORE INTO idMap(oldrid, newrid)" + " SELECT vmerge.merge, blob.rid FROM vmerge, blob" + " WHERE blob.uuid=vmerge.mhash;" + ); + + /* Add RID values for files in the current check-out */ + db_multi_exec( + "CREATE TEMP TABLE hashoffile(name TEXT PRIMARY KEY, hash TEXT)" + "WITHOUT ROWID;" + + "INSERT INTO hashoffile(name,hash)" + " SELECT filename, uuid FROM vvar, files_of_checkin(vvar.value)" + " WHERE vvar.name='checkout-hash';" + + "INSERT OR IGNORE INTO idMap(oldrid, newrid)" + " SELECT vfile.rid, blob.rid FROM vfile, hashoffile, blob" + " WHERE hashoffile.name=coalesce(vfile.origname,vfile.pathname)" + " AND blob.uuid=hashoffile.hash;" + ); + + /* Add RID values for merged-in files */ + db_multi_exec( + "INSERT OR IGNORE INTO idMap(oldrid, newrid)" + " SELECT vfile.mrid, blob.rid FROM vfile, blob" + " WHERE blob.uuid=vfile.mhash;" + ); + + if( dryRun ){ + Stmt q; + db_prepare(&q, "SELECT oldrid, newrid, blob.uuid" + " FROM idMap, blob WHERE blob.rid=idMap.newrid"); + while( db_step(&q)==SQLITE_ROW ){ + fossil_print("%8d -> %8d %.25s\n", + db_column_int(&q,0), + db_column_int(&q,1), + db_column_text(&q,2)); + } + db_finalize(&q); + } + + /* Verify that all RID values in the VFILE table and VMERGE table have + ** been resolved. */ + zUnresolved = db_text("", + "WITH allrid(x) AS (" + " SELECT rid FROM vfile" + " UNION SELECT mrid FROM vfile" + " UNION SELECT merge FROM vmerge" + " UNION SELECT %d" + ")" + "SELECT group_concat(x,' ') FROM allrid" + " WHERE x NOT IN (SELECT oldrid FROM idMap);", + oldVid + ); + if( zUnresolved[0] ){ + fossil_fatal("Unresolved RID values: %s\n", zUnresolved); + } + + /* Make the changes to the VFILE and VMERGE tables */ + if( !dryRun ){ + db_multi_exec( + "UPDATE vfile" + " SET rid=(SELECT newrid FROM idMap WHERE oldrid=vfile.rid)" + " WHERE vid=%d AND rid>0;", oldVid); + + db_multi_exec( + "UPDATE vfile" + " SET mrid=(SELECT newrid FROM idMap WHERE oldrid=vfile.mrid)" + " WHERE vid=%d AND mrid>0;", oldVid); + + db_multi_exec( + "UPDATE vfile" + " SET vid=%d" + " WHERE vid=%d", newVid, oldVid); + + db_multi_exec( + "UPDATE vmerge" + " SET merge=(SELECT newrid FROM idMap WHERE oldrid=vmerge.merge);"); + + db_lset_int("checkout",newVid); + } + + /* Clear out the TEMP tables we constructed */ + db_multi_exec( + "DROP TABLE idMap;" + "DROP TABLE hashoffile;" + ); +}