Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Changes In Branch config-sync Excluding Merge-Ins
This is equivalent to a diff from e17fc71319 to 940faaa37e
2011-04-27
| ||
02:10 | Merge in the config-sync changes. This is a major schema change and definitely requires a "fossil rebuild". Note that the schema upgrade is irreversible and so you should be certain you want to continue with the new schema before you upgrade. check-in: 1654456ef5 user: drh tags: trunk | |
02:05 | Fix an issue with the --legacy option to configuration pull. Closed-Leaf check-in: 940faaa37e user: drh tags: config-sync | |
01:40 | Fix an out-of-order local variable declaration. Ticket [a659e233cd79a0d]. check-in: 7b45d101dd user: drh tags: config-sync | |
2011-04-26
| ||
00:45 | Begin implementing the protocol changes for configuration sync. check-in: f99e3fa9e6 user: drh tags: config-sync | |
2011-04-25
| ||
22:23 | Change the definition of a "Leaf" to be any node that has no children of any kind (merge or non-merge) in the same branch. A "rebuild" or a "fossil leaves --recompute" is required to recompute the LEAF table after upgrading to this version. check-in: e17fc71319 user: drh tags: trunk | |
20:26 | Add hyperlink to the annotation of a file in the object description header. check-in: 030a048697 user: drh tags: trunk | |
Changes to src/checkin.c.
274 274 void clean_cmd(void){ 275 275 int allFlag; 276 276 int dotfilesFlag; 277 277 const char *zIgnoreFlag; 278 278 Blob path, repo; 279 279 Stmt q; 280 280 int n; 281 + Glob *pIgnore; 282 + 281 283 allFlag = find_option("force","f",0)!=0; 282 284 dotfilesFlag = find_option("dotfiles",0,0)!=0; 283 285 zIgnoreFlag = find_option("ignore",0,1); 284 - Glob *pIgnore; 285 - 286 286 db_must_be_within_tree(); 287 287 if( zIgnoreFlag==0 ){ 288 288 zIgnoreFlag = db_get("ignore-glob", 0); 289 289 } 290 290 db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY)"); 291 291 n = strlen(g.zLocalRoot); 292 292 blob_init(&path, g.zLocalRoot, n-1);
Changes to src/clone.c.
62 62 url_parse(g.argv[2]); 63 63 if( g.urlIsFile ){ 64 64 file_copy(g.urlName, g.argv[3]); 65 65 db_close(1); 66 66 db_open_repository(g.argv[3]); 67 67 db_record_repository_filename(g.argv[3]); 68 68 db_multi_exec( 69 - "REPLACE INTO config(name,value)" 70 - " VALUES('server-code', lower(hex(randomblob(20))));" 71 - "REPLACE INTO config(name,value)" 72 - " VALUES('last-sync-url', '%q');", 69 + "REPLACE INTO config(name,value,mtime)" 70 + " VALUES('server-code', lower(hex(randomblob(20))),now());" 71 + "REPLACE INTO config(name,value,mtime)" 72 + " VALUES('last-sync-url', '%q',now());", 73 73 g.urlCanonical 74 74 ); 75 75 db_multi_exec( 76 76 "DELETE FROM blob WHERE rid IN private;" 77 77 "DELETE FROM delta wHERE rid IN private;" 78 78 "DELETE FROM private;" 79 79 ); ................................................................................ 90 90 db_record_repository_filename(g.argv[3]); 91 91 db_initial_setup(0, zDefaultUser, 0); 92 92 user_select(); 93 93 db_set("content-schema", CONTENT_SCHEMA, 0); 94 94 db_set("aux-schema", AUX_SCHEMA, 0); 95 95 db_set("last-sync-url", g.argv[2], 0); 96 96 db_multi_exec( 97 - "REPLACE INTO config(name,value)" 98 - " VALUES('server-code', lower(hex(randomblob(20))));" 97 + "REPLACE INTO config(name,value,mtime)" 98 + " VALUES('server-code', lower(hex(randomblob(20))), now());" 99 99 ); 100 100 url_enable_proxy(0); 101 101 url_get_password_if_needed(); 102 102 g.xlinkClusterOnly = 1; 103 103 nErr = client_sync(0,0,1,bPrivate,CONFIGSET_ALL,0); 104 104 g.xlinkClusterOnly = 0; 105 105 verify_cancel();
Changes to src/configure.c.
1 1 /* 2 2 ** Copyright (c) 2008 D. Richard Hipp 3 3 ** 4 4 ** This program is free software; you can redistribute it and/or 5 5 ** modify it under the terms of the Simplified BSD License (also 6 6 ** known as the "2-Clause License" or "FreeBSD License".) 7 - 7 +** 8 8 ** This program is distributed in the hope that it will be useful, 9 9 ** but without any warranty; without even the implied warranty of 10 10 ** merchantability or fitness for a particular purpose. 11 11 ** 12 12 ** Author contact information: 13 13 ** drh@hwaci.com 14 14 ** http://www.hwaci.com/drh/ ................................................................................ 25 25 #include <assert.h> 26 26 27 27 #if INTERFACE 28 28 /* 29 29 ** Configuration transfers occur in groups. These are the allowed 30 30 ** groupings: 31 31 */ 32 -#define CONFIGSET_SKIN 0x000001 /* WWW interface appearance */ 33 -#define CONFIGSET_TKT 0x000002 /* Ticket configuration */ 34 -#define CONFIGSET_PROJ 0x000004 /* Project name */ 35 -#define CONFIGSET_SHUN 0x000008 /* Shun settings */ 36 -#define CONFIGSET_USER 0x000010 /* The USER table */ 37 -#define CONFIGSET_ADDR 0x000020 /* The CONCEALED table */ 32 +#define CONFIGSET_SKIN 0x000001 /* WWW interface appearance */ 33 +#define CONFIGSET_TKT 0x000002 /* Ticket configuration */ 34 +#define CONFIGSET_PROJ 0x000004 /* Project name */ 35 +#define CONFIGSET_SHUN 0x000008 /* Shun settings */ 36 +#define CONFIGSET_USER 0x000010 /* The USER table */ 37 +#define CONFIGSET_ADDR 0x000020 /* The CONCEALED table */ 38 38 39 -#define CONFIGSET_ALL 0xffffff /* Everything */ 39 +#define CONFIGSET_ALL 0x0000ff /* Everything */ 40 + 41 +#define CONFIGSET_OVERWRITE 0x100000 /* Causes overwrite instead of merge */ 42 +#define CONFIGSET_OLDFORMAT 0x200000 /* Use the legacy format */ 40 43 41 44 #endif /* INTERFACE */ 42 45 43 46 /* 44 47 ** Names of the configuration sets 45 48 */ 46 49 static struct { 47 50 const char *zName; /* Name of the configuration set */ 48 51 int groupMask; /* Mask for that configuration set */ 49 52 const char *zHelp; /* What it does */ 50 53 } aGroupName[] = { 51 - { "email", CONFIGSET_ADDR, "Concealed email addresses in tickets" }, 52 - { "project", CONFIGSET_PROJ, "Project name and description" }, 53 - { "skin", CONFIGSET_SKIN, "Web interface apparance settings" }, 54 - { "shun", CONFIGSET_SHUN, "List of shunned artifacts" }, 55 - { "ticket", CONFIGSET_TKT, "Ticket setup", }, 56 - { "user", CONFIGSET_USER, "Users and privilege settings" }, 57 - { "all", CONFIGSET_ALL, "All of the above" }, 54 + { "/email", CONFIGSET_ADDR, "Concealed email addresses in tickets" }, 55 + { "/project", CONFIGSET_PROJ, "Project name and description" }, 56 + { "/skin", CONFIGSET_SKIN, "Web interface apparance settings" }, 57 + { "/shun", CONFIGSET_SHUN, "List of shunned artifacts" }, 58 + { "/ticket", CONFIGSET_TKT, "Ticket setup", }, 59 + { "/user", CONFIGSET_USER, "Users and privilege settings" }, 60 + { "/all", CONFIGSET_ALL, "All of the above" }, 58 61 }; 59 62 60 63 61 64 /* 62 65 ** The following is a list of settings that we are willing to 63 66 ** transfer. 64 67 ** ................................................................................ 104 107 ** Return name of first configuration property matching the given mask. 105 108 */ 106 109 const char *configure_first_name(int iMask){ 107 110 iConfig = 0; 108 111 return configure_next_name(iMask); 109 112 } 110 113 const char *configure_next_name(int iMask){ 111 - while( iConfig<count(aConfig) ){ 112 - if( aConfig[iConfig].groupMask & iMask ){ 113 - return aConfig[iConfig++].zName; 114 - }else{ 115 - iConfig++; 114 + if( iMask & CONFIGSET_OLDFORMAT ){ 115 + while( iConfig<count(aConfig) ){ 116 + if( aConfig[iConfig].groupMask & iMask ){ 117 + return aConfig[iConfig++].zName; 118 + }else{ 119 + iConfig++; 120 + } 121 + } 122 + }else{ 123 + if( iConfig==0 && (iMask & CONFIGSET_ALL)==CONFIGSET_ALL ){ 124 + iConfig = count(aGroupName); 125 + return "/all"; 126 + } 127 + while( iConfig<count(aGroupName)-1 ){ 128 + if( aGroupName[iConfig].groupMask & iMask ){ 129 + return aGroupName[iConfig++].zName; 130 + }else{ 131 + iConfig++; 132 + } 116 133 } 117 134 } 118 135 return 0; 119 136 } 120 137 121 138 /* 122 139 ** Return the mask for the named configuration parameter if it can be ................................................................................ 125 142 ** "Safe" in the previous paragraph means the permission is created to 126 143 ** export the property. In other words, the requesting side has presented 127 144 ** login credentials and has sufficient capabilities to access the requested 128 145 ** information. 129 146 */ 130 147 int configure_is_exportable(const char *zName){ 131 148 int i; 149 + int n = strlen(zName); 150 + if( n>2 && zName[0]=='\'' && zName[n-1]=='\'' ){ 151 + zName++; 152 + n -= 2; 153 + } 132 154 for(i=0; i<count(aConfig); i++){ 133 - if( fossil_strcmp(zName, aConfig[i].zName)==0 ){ 155 + if( memcmp(zName, aConfig[i].zName, n)==0 && aConfig[i].zName[n]==0 ){ 134 156 int m = aConfig[i].groupMask; 135 157 if( !g.okAdmin ){ 136 158 m &= ~CONFIGSET_USER; 137 159 } 138 160 if( !g.okRdAddr ){ 139 161 m &= ~CONFIGSET_ADDR; 140 162 } ................................................................................ 198 220 db_finalize(&q); 199 221 } 200 222 } 201 223 202 224 /* 203 225 ** Two SQL functions: 204 226 ** 205 -** flag_test(int) 206 -** flag_clear(int) 227 +** config_is_reset(int) 228 +** config_reset(int) 207 229 ** 208 -** The flag_test() function takes the integer valued argument and 209 -** ANDs it against the static variable "flag_value" below. The 210 -** function returns TRUE or false depending on the result. The 211 -** flag_clear() function masks off the bits from "flag_value" that 230 +** The config_is_reset() function takes the integer valued argument and 231 +** ANDs it against the static variable "configHasBeenReset" below. The 232 +** function returns TRUE or FALSE depending on the result depending on 233 +** whether or not the corresponding configuration table has been reset. The 234 +** config_reset() function adds the bits to "configHasBeenReset" that 212 235 ** are given in the argument. 213 236 ** 214 237 ** These functions are used below in the WHEN clause of a trigger to 215 238 ** get the trigger to fire exactly once. 216 239 */ 217 -static int flag_value = 0xffff; 218 -static void flag_test_function( 240 +static int configHasBeenReset = 0; 241 +static void config_is_reset_function( 219 242 sqlite3_context *context, 220 243 int argc, 221 244 sqlite3_value **argv 222 245 ){ 223 246 int m = sqlite3_value_int(argv[0]); 224 - sqlite3_result_int(context, (flag_value&m)!=0 ); 247 + sqlite3_result_int(context, (configHasBeenReset&m)!=0 ); 225 248 } 226 -static void flag_clear_function( 249 +static void config_reset_function( 227 250 sqlite3_context *context, 228 251 int argc, 229 252 sqlite3_value **argv 230 253 ){ 231 254 int m = sqlite3_value_int(argv[0]); 232 - flag_value &= ~m; 255 + configHasBeenReset |= m; 233 256 } 234 257 235 258 /* 236 259 ** Create the temporary _xfer_reportfmt and _xfer_user tables that are 237 260 ** necessary in order to evalute the SQL text generated by the 238 261 ** configure_render_special_name() routine. 239 262 ** ................................................................................ 259 282 @ cap TEXT, -- Capabilities of this user 260 283 @ cookie TEXT, -- WWW login cookie 261 284 @ ipaddr TEXT, -- IP address for which cookie is valid 262 285 @ cexpire DATETIME, -- Time when cookie expires 263 286 @ info TEXT, -- contact information 264 287 @ photo BLOB -- JPEG image of this user 265 288 @ ); 266 - @ INSERT INTO _xfer_reportfmt SELECT * FROM reportfmt; 267 - @ INSERT INTO _xfer_user SELECT * FROM user; 289 + @ INSERT INTO _xfer_reportfmt 290 + @ SELECT rn,owner,title,cols,sqlcode FROM reportfmt; 291 + @ INSERT INTO _xfer_user 292 + @ SELECT uid,login,pw,cap,cookie,ipaddr,cexpire,info,photo FROM user; 268 293 ; 269 294 db_multi_exec(zSQL1); 270 295 271 296 /* When the replace flag is set, add triggers that run the first time 272 297 ** that new data is seen. The triggers run only once and delete all the 273 298 ** existing data. 274 299 */ 275 300 if( replaceFlag ){ 276 301 static const char zSQL2[] = 277 302 @ CREATE TRIGGER _xfer_r1 BEFORE INSERT ON _xfer_reportfmt 278 - @ WHEN flag_test(1) BEGIN 303 + @ WHEN NOT config_is_reset(2) BEGIN 279 304 @ DELETE FROM _xfer_reportfmt; 280 - @ SELECT flag_clear(1); 305 + @ SELECT config_reset(2); 281 306 @ END; 282 307 @ CREATE TRIGGER _xfer_r2 BEFORE INSERT ON _xfer_user 283 - @ WHEN flag_test(2) BEGIN 308 + @ WHEN NOT config_is_reset(16) BEGIN 284 309 @ DELETE FROM _xfer_user; 285 - @ SELECT flag_clear(2); 310 + @ SELECT config_reset(16); 286 311 @ END; 287 312 @ CREATE TEMP TRIGGER _xfer_r3 BEFORE INSERT ON shun 288 - @ WHEN flag_test(4) BEGIN 313 + @ WHEN NOT config_is_reset(8) BEGIN 289 314 @ DELETE FROM shun; 290 - @ SELECT flag_clear(4); 315 + @ SELECT config_reset(8); 291 316 @ END; 292 317 ; 293 - sqlite3_create_function(g.db, "flag_test", 1, SQLITE_UTF8, 0, 294 - flag_test_function, 0, 0); 295 - sqlite3_create_function(g.db, "flag_clear", 1, SQLITE_UTF8, 0, 296 - flag_clear_function, 0, 0); 297 - flag_value = 0xffff; 318 + sqlite3_create_function(g.db, "config_is_reset", 1, SQLITE_UTF8, 0, 319 + config_is_reset_function, 0, 0); 320 + sqlite3_create_function(g.db, "config_reset", 1, SQLITE_UTF8, 0, 321 + config_reset_function, 0, 0); 322 + configHasBeenReset = 0; 298 323 db_multi_exec(zSQL2); 299 324 } 300 325 } 326 + 327 +/* 328 +** After receiving configuration data, call this routine to transfer 329 +** the results into the main database. 330 +*/ 331 +void configure_finalize_receive(void){ 332 + static const char zSQL[] = 333 + @ DELETE FROM user; 334 + @ INSERT INTO user SELECT * FROM _xfer_user; 335 + @ DELETE FROM reportfmt; 336 + @ INSERT INTO reportfmt SELECT * FROM _xfer_reportfmt; 337 + @ DROP TABLE _xfer_user; 338 + @ DROP TABLE _xfer_reportfmt; 339 + ; 340 + db_multi_exec(zSQL); 341 +} 342 + 343 +/* 344 +** Return true if z[] is not a "safe" SQL token. A safe token is one of: 345 +** 346 +** * A string literal 347 +** * A blob literal 348 +** * An integer literal (no floating point) 349 +** * NULL 350 +*/ 351 +static int safeSql(const char *z){ 352 + int i; 353 + if( z==0 || z[0]==0 ) return 0; 354 + if( (z[0]=='x' || z[0]=='X') && z[1]=='\'' ) z++; 355 + if( z[0]=='\'' ){ 356 + for(i=1; z[i]; i++){ 357 + if( z[i]=='\'' ){ 358 + i++; 359 + if( z[i]=='\'' ){ continue; } 360 + return z[i]==0; 361 + } 362 + } 363 + return 0; 364 + }else{ 365 + char c; 366 + for(i=0; (c = z[i])!=0; i++){ 367 + if( !fossil_isalnum(c) ) return 0; 368 + } 369 + } 370 + return 1; 371 +} 372 + 373 +/* 374 +** Return true if z[] consists of nothing but digits 375 +*/ 376 +static int safeInt(const char *z){ 377 + int i; 378 + if( z==0 || z[0]==0 ) return 0; 379 + for(i=0; fossil_isdigit(z[i]); i++){} 380 + return z[i]==0; 381 +} 301 382 302 383 /* 303 384 ** Process a single "config" card received from the other side of a 304 385 ** sync session. 305 386 ** 306 387 ** Mask consists of one or more CONFIGSET_* values ORed together, to 307 388 ** designate what types of configuration we are allowed to receive. ................................................................................ 342 423 ** SQL that is evaluated. Note that the raw SQL in CONTENT might not 343 424 ** insert directly into the target table but might instead use a proxy 344 425 ** table like _fer_reportfmt or _xfer_user. Such tables must be created 345 426 ** ahead of time using configure_prepare_to_receive(). Then after multiple 346 427 ** calls to this routine, configure_finalize_receive() to transfer the 347 428 ** information received into the true target table. 348 429 */ 349 -void configure_receive(const char *zName, Blob *pContent, int mask){ 350 - if( (configure_is_exportable(zName) & mask)==0 ) return; 351 - if( strcmp(zName, "logo-image")==0 ){ 352 - Stmt ins; 353 - db_prepare(&ins, 354 - "REPLACE INTO config(name, value) VALUES(:name, :value)" 355 - ); 356 - db_bind_text(&ins, ":name", zName); 357 - db_bind_blob(&ins, ":value", pContent); 358 - db_step(&ins); 359 - db_finalize(&ins); 360 - }else if( zName[0]=='@' ){ 361 - /* Notice that we are evaluating arbitrary SQL received from the 362 - ** client. But this can only happen if the client has authenticated 363 - ** as an administrator, so presumably we trust the client at this 364 - ** point. 365 - */ 366 - db_multi_exec("%s", blob_str(pContent)); 430 +void configure_receive(const char *zName, Blob *pContent, int groupMask){ 431 + if( zName[0]=='/' ){ 432 + /* The new format */ 433 + char *azToken[12]; 434 + int nToken = 0; 435 + int ii, jj; 436 + int thisMask; 437 + Blob name, value, sql; 438 + static const struct receiveType { 439 + const char *zName; 440 + const char *zPrimKey; 441 + int nField; 442 + const char *azField[4]; 443 + } aType[] = { 444 + { "/config", "name", 1, { "value", 0, 0, 0 } }, 445 + { "@user", "login", 4, { "pw", "cap", "info", "photo" } }, 446 + { "@shun", "uuid", 1, { "scom", 0, 0, 0 } }, 447 + { "@reportfmt", "title", 3, { "owner", "cols", "sqlcode", 0 } }, 448 + { "@concealed", "hash", 1, { "content", 0, 0, 0 } }, 449 + }; 450 + for(ii=0; ii<count(aType); ii++){ 451 + if( fossil_strcmp(&aType[ii].zName[1],&zName[1])==0 ) break; 452 + } 453 + if( ii>=count(aType) ) return; 454 + while( blob_token(pContent, &name) && blob_sqltoken(pContent, &value) ){ 455 + char *z = blob_terminate(&name); 456 + if( !safeSql(z) ) return; 457 + if( nToken>0 ){ 458 + for(jj=0; jj<aType[ii].nField; jj++){ 459 + if( fossil_strcmp(aType[ii].azField[jj], z)==0 ) break; 460 + } 461 + if( jj>=aType[ii].nField ) continue; 462 + }else{ 463 + if( !safeInt(z) ) return; 464 + } 465 + azToken[nToken++] = z; 466 + azToken[nToken++] = z = blob_terminate(&value); 467 + if( !safeSql(z) ) return; 468 + if( nToken>=count(azToken) ) break; 469 + } 470 + if( nToken<2 ) return; 471 + if( aType[ii].zName[0]=='/' ){ 472 + thisMask = configure_is_exportable(azToken[1]); 473 + }else{ 474 + thisMask = configure_is_exportable(aType[ii].zName); 475 + } 476 + if( (thisMask & groupMask)==0 ) return; 477 + 478 + blob_zero(&sql); 479 + if( groupMask & CONFIGSET_OVERWRITE ){ 480 + if( (thisMask & configHasBeenReset)==0 && aType[ii].zName[0]!='/' ){ 481 + db_multi_exec("DELETE FROM %s", &aType[ii].zName[1]); 482 + configHasBeenReset |= thisMask; 483 + } 484 + blob_append(&sql, "REPLACE INTO ", -1); 485 + }else{ 486 + blob_append(&sql, "INSERT OR IGNORE INTO ", -1); 487 + } 488 + blob_appendf(&sql, "%s(%s, mtime", &zName[1], aType[ii].zPrimKey); 489 + for(jj=2; jj<nToken; jj+=2){ 490 + blob_appendf(&sql, ",%s", azToken[jj]); 491 + } 492 + blob_appendf(&sql,") VALUES(%s,%s", azToken[1], azToken[0]); 493 + for(jj=2; jj<nToken; jj+=2){ 494 + blob_appendf(&sql, ",%s", azToken[jj+1]); 495 + } 496 + db_multi_exec("%s)", blob_str(&sql)); 497 + if( db_changes()==0 ){ 498 + blob_reset(&sql); 499 + blob_appendf(&sql, "UPDATE %s SET mtime=%s", &zName[1], azToken[0]); 500 + for(jj=2; jj<nToken; jj+=2){ 501 + blob_appendf(&sql, ", %s=%s", azToken[jj], azToken[jj+1]); 502 + } 503 + blob_appendf(&sql, " WHERE %s=%s AND mtime<%s", 504 + aType[ii].zPrimKey, azToken[1], azToken[0]); 505 + db_multi_exec("%s", blob_str(&sql)); 506 + } 507 + blob_reset(&sql); 367 508 }else{ 368 - db_multi_exec( 369 - "REPLACE INTO config(name,value) VALUES(%Q,%Q)", 370 - zName, blob_str(pContent) 371 - ); 509 + /* Otherwise, the old format */ 510 + if( (configure_is_exportable(zName) & groupMask)==0 ) return; 511 + if( strcmp(zName, "logo-image")==0 ){ 512 + Stmt ins; 513 + db_prepare(&ins, 514 + "REPLACE INTO config(name, value, mtime) VALUES(:name, :value, now())" 515 + ); 516 + db_bind_text(&ins, ":name", zName); 517 + db_bind_blob(&ins, ":value", pContent); 518 + db_step(&ins); 519 + db_finalize(&ins); 520 + }else if( zName[0]=='@' ){ 521 + /* Notice that we are evaluating arbitrary SQL received from the 522 + ** client. But this can only happen if the client has authenticated 523 + ** as an administrator, so presumably we trust the client at this 524 + ** point. 525 + */ 526 + db_multi_exec("%s", blob_str(pContent)); 527 + }else{ 528 + db_multi_exec( 529 + "REPLACE INTO config(name,value,mtime) VALUES(%Q,%Q,now())", 530 + zName, blob_str(pContent) 531 + ); 532 + } 533 + } 534 +} 535 + 536 +/* 537 +** Process a file full of "config" cards. 538 +*/ 539 +void configure_receive_all(Blob *pIn, int groupMask){ 540 + Blob line; 541 + int nToken; 542 + int size; 543 + Blob aToken[4]; 544 + 545 + configHasBeenReset = 0; 546 + while( blob_line(pIn, &line) ){ 547 + if( blob_buffer(&line)[0]=='#' ) continue; 548 + nToken = blob_tokenize(&line, aToken, count(aToken)); 549 + if( blob_eq(&aToken[0],"config") 550 + && nToken==3 551 + && blob_is_int(&aToken[2], &size) 552 + ){ 553 + const char *zName = blob_str(&aToken[1]); 554 + Blob content; 555 + blob_zero(&content); 556 + blob_extract(pIn, size, &content); 557 + g.okAdmin = g.okRdAddr = 1; 558 + configure_receive(zName, &content, groupMask); 559 + blob_reset(&content); 560 + blob_seek(pIn, 1, BLOB_SEEK_CUR); 561 + } 372 562 } 373 563 } 374 - 564 + 375 565 376 566 /* 377 -** After receiving configuration data, call this routine to transfer 378 -** the results into the main database. 567 +** Send "config" cards using the new format for all elements of a group 568 +** that have recently changed. 569 +** 570 +** Output goes into pOut. The groupMask identifies the group(s) to be sent. 571 +** Send only entries whose timestamp is later than or equal to iStart. 572 +** 573 +** Return the number of cards sent. 379 574 */ 380 -void configure_finalize_receive(void){ 381 - static const char zSQL[] = 382 - @ DELETE FROM user; 383 - @ INSERT INTO user SELECT * FROM _xfer_user; 384 - @ DELETE FROM reportfmt; 385 - @ INSERT INTO reportfmt SELECT * FROM _xfer_reportfmt; 386 - @ DROP TABLE _xfer_user; 387 - @ DROP TABLE _xfer_reportfmt; 388 - ; 389 - db_multi_exec(zSQL); 575 +int configure_send_group( 576 + Blob *pOut, /* Write output here */ 577 + int groupMask, /* Mask of groups to be send */ 578 + sqlite3_int64 iStart /* Only write values changed since this time */ 579 +){ 580 + Stmt q; 581 + Blob rec; 582 + int ii; 583 + int nCard = 0; 584 + 585 + blob_zero(&rec); 586 + if( groupMask & CONFIGSET_SHUN ){ 587 + db_prepare(&q, "SELECT mtime, quote(uuid), quote(scom) FROM shun" 588 + " WHERE mtime>=%lld", iStart); 589 + while( db_step(&q)==SQLITE_ROW ){ 590 + blob_appendf(&rec,"%s %s scom %s", 591 + db_column_text(&q, 0), 592 + db_column_text(&q, 1), 593 + db_column_text(&q, 2) 594 + ); 595 + blob_appendf(pOut, "config /shun %d\n%s\n", 596 + blob_size(&rec), blob_str(&rec)); 597 + nCard++; 598 + blob_reset(&rec); 599 + } 600 + db_finalize(&q); 601 + } 602 + if( groupMask & CONFIGSET_USER ){ 603 + db_prepare(&q, "SELECT mtime, quote(login), quote(pw), quote(cap)," 604 + " quote(info), quote(photo) FROM user" 605 + " WHERE mtime>=%lld", iStart); 606 + while( db_step(&q)==SQLITE_ROW ){ 607 + blob_appendf(&rec,"%s %s pw %s cap %s info %s photo %s", 608 + db_column_text(&q, 0), 609 + db_column_text(&q, 1), 610 + db_column_text(&q, 2), 611 + db_column_text(&q, 3), 612 + db_column_text(&q, 4), 613 + db_column_text(&q, 5) 614 + ); 615 + blob_appendf(pOut, "config /user %d\n%s\n", 616 + blob_size(&rec), blob_str(&rec)); 617 + nCard++; 618 + blob_reset(&rec); 619 + } 620 + db_finalize(&q); 621 + } 622 + if( groupMask & CONFIGSET_TKT ){ 623 + db_prepare(&q, "SELECT mtime, quote(title), quote(owner), quote(cols)," 624 + " quote(sqlcode) FROM reportfmt" 625 + " WHERE mtime>=%lld", iStart); 626 + while( db_step(&q)==SQLITE_ROW ){ 627 + blob_appendf(&rec,"%s %s owner %s cols %s sqlcode %s", 628 + db_column_text(&q, 0), 629 + db_column_text(&q, 1), 630 + db_column_text(&q, 2), 631 + db_column_text(&q, 3), 632 + db_column_text(&q, 4) 633 + ); 634 + blob_appendf(pOut, "config /reportfmt %d\n%s\n", 635 + blob_size(&rec), blob_str(&rec)); 636 + nCard++; 637 + blob_reset(&rec); 638 + } 639 + db_finalize(&q); 640 + } 641 + if( groupMask & CONFIGSET_ADDR ){ 642 + db_prepare(&q, "SELECT mtime, quote(hash), quote(content) FROM concealed" 643 + " WHERE mtime>=%lld", iStart); 644 + while( db_step(&q)==SQLITE_ROW ){ 645 + blob_appendf(&rec,"%s %s content %s", 646 + db_column_text(&q, 0), 647 + db_column_text(&q, 1), 648 + db_column_text(&q, 2) 649 + ); 650 + blob_appendf(pOut, "config /concealed %d\n%s\n", 651 + blob_size(&rec), blob_str(&rec)); 652 + nCard++; 653 + blob_reset(&rec); 654 + } 655 + db_finalize(&q); 656 + } 657 + db_prepare(&q, "SELECT mtime, quote(name), quote(value) FROM config" 658 + " WHERE name=:name AND mtime>=%lld", iStart); 659 + for(ii=0; ii<count(aConfig); ii++){ 660 + if( (aConfig[ii].groupMask & groupMask)!=0 && aConfig[ii].zName[0]!='@' ){ 661 + db_bind_text(&q, ":name", aConfig[ii].zName); 662 + while( db_step(&q)==SQLITE_ROW ){ 663 + blob_appendf(&rec,"%s %s value %s", 664 + db_column_text(&q, 0), 665 + db_column_text(&q, 1), 666 + db_column_text(&q, 2) 667 + ); 668 + blob_appendf(pOut, "config /config %d\n%s\n", 669 + blob_size(&rec), blob_str(&rec)); 670 + nCard++; 671 + blob_reset(&rec); 672 + } 673 + db_reset(&q); 674 + } 675 + } 676 + db_finalize(&q); 677 + return nCard; 390 678 } 391 679 392 680 /* 393 681 ** Identify a configuration group by name. Return its mask. 394 682 ** Throw an error if no match. 395 683 */ 396 -static int find_area(const char *z){ 684 +int configure_name_to_mask(const char *z, int notFoundIsFatal){ 397 685 int i; 398 686 int n = strlen(z); 399 687 for(i=0; i<count(aGroupName); i++){ 400 - if( strncmp(z, aGroupName[i].zName, n)==0 ){ 688 + if( strncmp(z, &aGroupName[i].zName[1], n)==0 ){ 401 689 return aGroupName[i].groupMask; 402 690 } 403 691 } 404 - printf("Available configuration areas:\n"); 405 - for(i=0; i<count(aGroupName); i++){ 406 - printf(" %-10s %s\n", aGroupName[i].zName, aGroupName[i].zHelp); 692 + if( notFoundIsFatal ){ 693 + printf("Available configuration areas:\n"); 694 + for(i=0; i<count(aGroupName); i++){ 695 + printf(" %-10s %s\n", &aGroupName[i].zName[1], aGroupName[i].zHelp); 696 + } 697 + fossil_fatal("no such configuration area: \"%s\"", z); 407 698 } 408 - fossil_fatal("no such configuration area: \"%s\"", z); 409 699 return 0; 410 700 } 411 701 412 702 /* 413 703 ** Write SQL text into file zFilename that will restore the configuration 414 704 ** area identified by mask to its current state from any other state. 415 705 */ 416 706 static void export_config( 417 - int mask, /* Mask indicating which configuration to export */ 707 + int groupMask, /* Mask indicating which configuration to export */ 418 708 const char *zMask, /* Name of the configuration */ 709 + sqlite3_int64 iStart, /* Start date */ 419 710 const char *zFilename /* Write into this file */ 420 711 ){ 421 - int i; 422 712 Blob out; 423 713 blob_zero(&out); 424 714 blob_appendf(&out, 425 - "-- The \"%s\" configuration exported from\n" 426 - "-- repository \"%s\"\n" 427 - "-- on %s\n", 715 + "# The \"%s\" configuration exported from\n" 716 + "# repository \"%s\"\n" 717 + "# on %s\n", 428 718 zMask, g.zRepositoryName, 429 719 db_text(0, "SELECT datetime('now')") 430 720 ); 431 - for(i=0; i<count(aConfig); i++){ 432 - if( (aConfig[i].groupMask & mask)!=0 ){ 433 - const char *zName = aConfig[i].zName; 434 - if( zName[0]!='@' ){ 435 - char *zValue = db_text(0, 436 - "SELECT quote(value) FROM config WHERE name=%Q", zName); 437 - if( zValue ){ 438 - blob_appendf(&out,"REPLACE INTO config VALUES(%Q,%s);\n", 439 - zName, zValue); 440 - } 441 - free(zValue); 442 - }else{ 443 - configure_render_special_name(zName, &out); 444 - } 445 - } 446 - } 721 + configure_send_group(&out, groupMask, iStart); 447 722 blob_write_to_file(&out, zFilename); 448 723 blob_reset(&out); 449 724 } 450 725 451 726 452 727 /* 453 728 ** COMMAND: configuration ................................................................................ 473 748 ** the current configuration. Existing values take priority over 474 749 ** values read from FILENAME. 475 750 ** 476 751 ** %fossil configuration pull AREA ?URL? 477 752 ** 478 753 ** Pull and install the configuration from a different server 479 754 ** identified by URL. If no URL is specified, then the default 480 -** server is used. 755 +** server is used. Use the --legacy option for the older protocol 756 +** (when talking to servers compiled prior to 2011-04-27.) Use 757 +** the --overwrite flag to completely replace local settings with 758 +** content received from URL. 481 759 ** 482 760 ** %fossil configuration push AREA ?URL? 483 761 ** 484 762 ** Push the local configuration into the remote server identified 485 763 ** by URL. Admin privilege is required on the remote server for 486 -** this to work. 764 +** this to work. When the same record exists both locally and on 765 +** the remote end, the one that was most recently changed wins. 766 +** Use the --legacy flag when talking to holder servers. 487 767 ** 488 768 ** %fossil configuration reset AREA 489 769 ** 490 770 ** Restore the configuration to the default. AREA as above. 491 771 ** 492 -** WARNING: Do not import, merge, or pull configurations from an untrusted 493 -** source. The inbound configuration is not checked for safety and can 494 -** introduce security vulnerabilities. 772 +** %fossil configuration sync AREA ?URL? 773 +** 774 +** Synchronize configuration changes in the local repository with 775 +** the remote repository at URL. 495 776 */ 496 777 void configuration_cmd(void){ 497 778 int n; 498 779 const char *zMethod; 499 780 if( g.argc<3 ){ 500 781 usage("export|import|merge|pull|reset ..."); 501 782 } 502 783 db_find_and_open_repository(0, 0); 503 784 zMethod = g.argv[2]; 504 785 n = strlen(zMethod); 505 786 if( strncmp(zMethod, "export", n)==0 ){ 506 787 int mask; 788 + const char *zSince = find_option("since",0,1); 789 + sqlite3_int64 iStart; 507 790 if( g.argc!=5 ){ 508 791 usage("export AREA FILENAME"); 509 792 } 510 - mask = find_area(g.argv[3]); 511 - export_config(mask, g.argv[3], g.argv[4]); 793 + mask = configure_name_to_mask(g.argv[3], 1); 794 + if( zSince ){ 795 + iStart = db_multi_exec( 796 + "SELECT coalesce(strftime('%%s',%Q),strftime('%%s','now',%Q))+0", 797 + zSince, zSince 798 + ); 799 + }else{ 800 + iStart = 0; 801 + } 802 + export_config(mask, g.argv[3], iStart, g.argv[4]); 512 803 }else 513 804 if( strncmp(zMethod, "import", n)==0 514 805 || strncmp(zMethod, "merge", n)==0 ){ 515 806 Blob in; 807 + int groupMask; 516 808 if( g.argc!=4 ) usage(mprintf("%s FILENAME",zMethod)); 517 809 blob_read_from_file(&in, g.argv[3]); 518 810 db_begin_transaction(); 519 - configure_prepare_to_receive(zMethod[0]=='i'); 520 - db_multi_exec("%s", blob_str(&in)); 521 - configure_finalize_receive(); 811 + if( zMethod[0]=='i' ){ 812 + groupMask = CONFIGSET_ALL | CONFIGSET_OVERWRITE; 813 + }else{ 814 + groupMask = CONFIGSET_ALL; 815 + } 816 + configure_receive_all(&in, groupMask); 522 817 db_end_transaction(0); 523 818 }else 524 - if( strncmp(zMethod, "pull", n)==0 || strncmp(zMethod, "push", n)==0 ){ 819 + if( strncmp(zMethod, "pull", n)==0 820 + || strncmp(zMethod, "push", n)==0 821 + || strncmp(zMethod, "sync", n)==0 822 + ){ 525 823 int mask; 526 824 const char *zServer; 527 825 const char *zPw; 826 + int legacyFlag = 0; 827 + int overwriteFlag = 0; 828 + if( zMethod[0]!='s' ) legacyFlag = find_option("legacy",0,0)!=0; 829 + if( strncmp(zMethod,"pull",n)==0 ){ 830 + overwriteFlag = find_option("overwrite",0,0)!=0; 831 + } 528 832 url_proxy_options(); 529 833 if( g.argc!=4 && g.argc!=5 ){ 530 834 usage("pull AREA ?URL?"); 531 835 } 532 - mask = find_area(g.argv[3]); 836 + mask = configure_name_to_mask(g.argv[3], 1); 533 837 if( g.argc==5 ){ 534 838 zServer = g.argv[4]; 535 839 zPw = 0; 536 840 g.dontKeepUrl = 1; 537 841 }else{ 538 842 zServer = db_get("last-sync-url", 0); 539 843 if( zServer==0 ){ ................................................................................ 541 845 } 542 846 zPw = unobscure(db_get("last-sync-pw", 0)); 543 847 } 544 848 url_parse(zServer); 545 849 if( g.urlPasswd==0 && zPw ) g.urlPasswd = mprintf("%s", zPw); 546 850 user_select(); 547 851 url_enable_proxy("via proxy: "); 852 + if( legacyFlag ) mask |= CONFIGSET_OLDFORMAT; 853 + if( overwriteFlag ) mask |= CONFIGSET_OVERWRITE; 548 854 if( strncmp(zMethod, "push", n)==0 ){ 549 855 client_sync(0,0,0,0,0,mask); 550 - }else{ 856 + }else if( strncmp(zMethod, "pull", n)==0 ){ 551 857 client_sync(0,0,0,0,mask,0); 858 + }else{ 859 + client_sync(0,0,0,0,mask,mask); 552 860 } 553 861 }else 554 862 if( strncmp(zMethod, "reset", n)==0 ){ 555 863 int mask, i; 556 864 char *zBackup; 557 865 if( g.argc!=4 ) usage("reset AREA"); 558 - mask = find_area(g.argv[3]); 866 + mask = configure_name_to_mask(g.argv[3], 1); 559 867 zBackup = db_text(0, 560 868 "SELECT strftime('config-backup-%%Y%%m%%d%%H%%M%%f','now')"); 561 869 db_begin_transaction(); 562 - export_config(mask, g.argv[3], zBackup); 870 + export_config(mask, g.argv[3], 0, zBackup); 563 871 for(i=0; i<count(aConfig); i++){ 564 872 const char *zName = aConfig[i].zName; 565 873 if( (aConfig[i].groupMask & mask)==0 ) continue; 566 874 if( zName[0]!='@' ){ 567 875 db_multi_exec("DELETE FROM config WHERE name=%Q", zName); 568 876 }else if( fossil_strcmp(zName,"@user")==0 ){ 569 877 db_multi_exec("DELETE FROM user");
Changes to src/db.c.
32 32 #if ! defined(_WIN32) 33 33 # include <pwd.h> 34 34 #endif 35 35 #include <sqlite3.h> 36 36 #include <sys/types.h> 37 37 #include <sys/stat.h> 38 38 #include <unistd.h> 39 +#include <time.h> 39 40 #include "db.h" 40 41 41 42 #if INTERFACE 42 43 /* 43 44 ** An single SQL statement is represented as an instance of the following 44 45 ** structure. 45 46 */ ................................................................................ 604 605 db_err(sqlite3_errmsg(db)); 605 606 } 606 607 } 607 608 va_end(ap); 608 609 sqlite3_exec(db, "COMMIT", 0, 0, 0); 609 610 sqlite3_close(db); 610 611 } 612 + 613 +/* 614 +** Function to return the number of seconds since 1970. This is 615 +** the same as strftime('%s','now') but is more compact. 616 +*/ 617 +static void db_now_function( 618 + sqlite3_context *context, 619 + int argc, 620 + sqlite3_value **argv 621 +){ 622 + sqlite3_result_int64(context, time(0)); 623 +} 624 + 611 625 612 626 /* 613 627 ** Open a database file. Return a pointer to the new database 614 628 ** connection. An error results in process abort. 615 629 */ 616 630 static sqlite3 *openDatabase(const char *zDbName){ 617 631 int rc; ................................................................................ 628 642 zVfs 629 643 ); 630 644 if( rc!=SQLITE_OK ){ 631 645 db_err(sqlite3_errmsg(db)); 632 646 } 633 647 sqlite3_busy_timeout(db, 5000); 634 648 sqlite3_wal_autocheckpoint(db, 1); /* Set to checkpoint frequently */ 649 + sqlite3_create_function(db, "now", 0, SQLITE_ANY, 0, db_now_function, 0, 0); 635 650 return db; 636 651 } 637 652 638 653 639 654 /* 640 655 ** zDbName is the name of a database file. If no other database 641 656 ** file is open, then open this one. If another database file is ................................................................................ 1103 1118 Blob hash; 1104 1119 Blob manifest; 1105 1120 1106 1121 db_set("content-schema", CONTENT_SCHEMA, 0); 1107 1122 db_set("aux-schema", AUX_SCHEMA, 0); 1108 1123 if( makeServerCodes ){ 1109 1124 db_multi_exec( 1110 - "INSERT INTO config(name,value)" 1111 - " VALUES('server-code', lower(hex(randomblob(20))));" 1112 - "INSERT INTO config(name,value)" 1113 - " VALUES('project-code', lower(hex(randomblob(20))));" 1125 + "INSERT INTO config(name,value,mtime)" 1126 + " VALUES('server-code', lower(hex(randomblob(20))),now());" 1127 + "INSERT INTO config(name,value,mtime)" 1128 + " VALUES('project-code', lower(hex(randomblob(20))),now());" 1114 1129 ); 1115 1130 } 1116 1131 if( !db_is_global("autosync") ) db_set_int("autosync", 1, 0); 1117 1132 if( !db_is_global("localauth") ) db_set_int("localauth", 0, 0); 1118 1133 db_create_default_users(0, zDefaultUser); 1119 1134 user_select(); 1120 1135 ................................................................................ 1293 1308 zHash[n] = 0; 1294 1309 }else{ 1295 1310 sha1sum_step_text(zContent, n); 1296 1311 sha1sum_finish(&out); 1297 1312 sqlite3_snprintf(sizeof(zHash), zHash, "%s", blob_str(&out)); 1298 1313 blob_reset(&out); 1299 1314 db_multi_exec( 1300 - "INSERT OR IGNORE INTO concealed VALUES(%Q,%#Q)", 1315 + "INSERT OR IGNORE INTO concealed(hash,content,mtime)" 1316 + " VALUES(%Q,%#Q,now())", 1301 1317 zHash, n, zContent 1302 1318 ); 1303 1319 } 1304 1320 return zHash; 1305 1321 } 1306 1322 1307 1323 /* ................................................................................ 1404 1420 db_begin_transaction(); 1405 1421 if( globalFlag ){ 1406 1422 db_swap_connections(); 1407 1423 db_multi_exec("REPLACE INTO global_config(name,value) VALUES(%Q,%Q)", 1408 1424 zName, zValue); 1409 1425 db_swap_connections(); 1410 1426 }else{ 1411 - db_multi_exec("REPLACE INTO config(name,value) VALUES(%Q,%Q)", 1427 + db_multi_exec("REPLACE INTO config(name,value,mtime) VALUES(%Q,%Q,now())", 1412 1428 zName, zValue); 1413 1429 } 1414 1430 if( globalFlag && g.repositoryOpen ){ 1415 1431 db_multi_exec("DELETE FROM config WHERE name=%Q", zName); 1416 1432 } 1417 1433 db_end_transaction(0); 1418 1434 } ................................................................................ 1463 1479 void db_set_int(const char *zName, int value, int globalFlag){ 1464 1480 if( globalFlag ){ 1465 1481 db_swap_connections(); 1466 1482 db_multi_exec("REPLACE INTO global_config(name,value) VALUES(%Q,%d)", 1467 1483 zName, value); 1468 1484 db_swap_connections(); 1469 1485 }else{ 1470 - db_multi_exec("REPLACE INTO config(name,value) VALUES(%Q,%d)", 1486 + db_multi_exec("REPLACE INTO config(name,value,mtime) VALUES(%Q,%d,now())", 1471 1487 zName, value); 1472 1488 } 1473 1489 if( globalFlag && g.repositoryOpen ){ 1474 1490 db_multi_exec("DELETE FROM config WHERE name=%Q", zName); 1475 1491 } 1476 1492 } 1477 1493 int db_get_boolean(const char *zName, int dflt){
Changes to src/login.c.
1255 1255 ); 1256 1256 db_end_transaction(0); 1257 1257 db_multi_exec("DETACH other"); 1258 1258 1259 1259 /* Propagate the changes to all other members of the login-group */ 1260 1260 zSql = mprintf( 1261 1261 "BEGIN;" 1262 - "REPLACE INTO config(name, value) VALUES('peer-name-%q', %Q);" 1263 - "REPLACE INTO config(name, value) VALUES('peer-repo-%q', %Q);" 1262 + "REPLACE INTO config(name,value,mtime) VALUES('peer-name-%q',%Q,now());" 1263 + "REPLACE INTO config(name,value,mtime) VALUES('peer-repo-%q',%Q,now());" 1264 1264 "COMMIT;", 1265 1265 zSelfProjCode, zSelfLabel, zSelfProjCode, zSelfRepo 1266 1266 ); 1267 1267 login_group_sql(zSql, "<li> ", "</li>", pzErrMsg); 1268 1268 fossil_free(zSql); 1269 1269 } 1270 1270
Changes to src/rebuild.c.
20 20 #include "config.h" 21 21 #include "rebuild.h" 22 22 #include <assert.h> 23 23 #include <dirent.h> 24 24 #include <errno.h> 25 25 26 26 /* 27 -** Schema changes 27 +** Make changes to the stable part of the schema (the part that is not 28 +** simply deleted and reconstructed on a rebuild) to bring the schema 29 +** up to the latest. 28 30 */ 29 -static const char zSchemaUpdates[] = 31 +static const char zSchemaUpdates1[] = 30 32 @ -- Index on the delta table 31 33 @ -- 32 34 @ CREATE INDEX IF NOT EXISTS delta_i1 ON delta(srcid); 33 35 @ 34 36 @ -- Artifacts that should not be processed are identified in the 35 37 @ -- "shun" table. Artifacts that are control-file forgeries or 36 38 @ -- spam or artifacts whose contents violate administrative policy ................................................................................ 37 39 @ -- can be shunned in order to prevent them from contaminating 38 40 @ -- the repository. 39 41 @ -- 40 42 @ -- Shunned artifacts do not exist in the blob table. Hence they 41 43 @ -- have not artifact ID (rid) and we thus must store their full 42 44 @ -- UUID. 43 45 @ -- 44 -@ CREATE TABLE IF NOT EXISTS shun(uuid UNIQUE); 46 +@ CREATE TABLE IF NOT EXISTS shun( 47 +@ uuid UNIQUE, -- UUID of artifact to be shunned. Canonical form 48 +@ mtime INTEGER, -- When added. Seconds since 1970 49 +@ scom TEXT -- Optional text explaining why the shun occurred 50 +@ ); 45 51 @ 46 52 @ -- Artifacts that should not be pushed are stored in the "private" 47 53 @ -- table. 48 54 @ -- 49 55 @ CREATE TABLE IF NOT EXISTS private(rid INTEGER PRIMARY KEY); 50 56 @ 51 -@ -- An entry in this table describes a database query that generates a 52 -@ -- table of tickets. 53 -@ -- 54 -@ CREATE TABLE IF NOT EXISTS reportfmt( 55 -@ rn integer primary key, -- Report number 56 -@ owner text, -- Owner of this report format (not used) 57 -@ title text, -- Title of this report 58 -@ cols text, -- A color-key specification 59 -@ sqlcode text -- An SQL SELECT statement for this report 60 -@ ); 61 -@ 62 57 @ -- Some ticket content (such as the originators email address or contact 63 58 @ -- information) needs to be obscured to protect privacy. This is achieved 64 59 @ -- by storing an SHA1 hash of the content. For display, the hash is 65 60 @ -- mapped back into the original text using this table. 66 61 @ -- 67 62 @ -- This table contains sensitive information and should not be shared 68 63 @ -- with unauthorized users. 69 64 @ -- 70 65 @ CREATE TABLE IF NOT EXISTS concealed( 71 -@ hash TEXT PRIMARY KEY, 72 -@ content TEXT 66 +@ hash TEXT PRIMARY KEY, -- The SHA1 hash of content 67 +@ mtime INTEGER, -- Time created. Seconds since 1970 68 +@ content TEXT -- Content intended to be concealed 69 +@ ); 70 +; 71 +static const char zSchemaUpdates2[] = 72 +@ -- An entry in this table describes a database query that generates a 73 +@ -- table of tickets. 74 +@ -- 75 +@ CREATE TABLE IF NOT EXISTS reportfmt( 76 +@ rn INTEGER PRIMARY KEY, -- Report number 77 +@ owner TEXT, -- Owner of this report format (not used) 78 +@ title TEXT UNIQUE, -- Title of this report 79 +@ mtime INTEGER, -- Time last modified. Seconds since 1970 80 +@ cols TEXT, -- A color-key specification 81 +@ sqlcode TEXT -- An SQL SELECT statement for this report 73 82 @ ); 74 83 ; 84 + 85 +static void rebuild_update_schema(void){ 86 + int rc; 87 + db_multi_exec(zSchemaUpdates1); 88 + db_multi_exec(zSchemaUpdates2); 89 + 90 + rc = db_exists("SELECT 1 FROM sqlite_master" 91 + " WHERE name='user' AND sql GLOB '* mtime *'"); 92 + if( rc==0 ){ 93 + db_multi_exec( 94 + "CREATE TEMP TABLE temp_user AS SELECT * FROM user;" 95 + "DROP TABLE user;" 96 + "CREATE TABLE user(\n" 97 + " uid INTEGER PRIMARY KEY,\n" 98 + " login TEXT UNIQUE,\n" 99 + " pw TEXT,\n" 100 + " cap TEXT,\n" 101 + " cookie TEXT,\n" 102 + " ipaddr TEXT,\n" 103 + " cexpire DATETIME,\n" 104 + " info TEXT,\n" 105 + " mtime DATE,\n" 106 + " photo BLOB\n" 107 + ");" 108 + "INSERT OR IGNORE INTO user" 109 + " SELECT uid, login, pw, cap, cookie," 110 + " ipaddr, cexpire, info, now(), photo FROM temp_user;" 111 + "DROP TABLE temp_user;" 112 + ); 113 + } 114 + 115 + rc = db_exists("SELECT 1 FROM sqlite_master" 116 + " WHERE name='config' AND sql GLOB '* mtime *'"); 117 + if( rc==0 ){ 118 + db_multi_exec( 119 + "ALTER TABLE config ADD COLUMN mtime INTEGER;" 120 + "UPDATE config SET mtime=now();" 121 + ); 122 + } 123 + 124 + rc = db_exists("SELECT 1 FROM sqlite_master" 125 + " WHERE name='shun' AND sql GLOB '* mtime *'"); 126 + if( rc==0 ){ 127 + db_multi_exec( 128 + "ALTER TABLE shun ADD COLUMN mtime INTEGER;" 129 + "ALTER TABLE shun ADD COLUMN scom TEXT;" 130 + "UPDATE shun SET mtime=now();" 131 + ); 132 + } 133 + 134 + rc = db_exists("SELECT 1 FROM sqlite_master" 135 + " WHERE name='reportfmt' AND sql GLOB '* mtime *'"); 136 + if( rc==0 ){ 137 + db_multi_exec( 138 + "CREATE TEMP TABLE old_fmt AS SELECT * FROM reportfmt;" 139 + "DROP TABLE reportfmt;" 140 + ); 141 + db_multi_exec(zSchemaUpdates2); 142 + db_multi_exec( 143 + "INSERT OR IGNORE INTO reportfmt(rn,owner,title,cols,sqlcode,mtime)" 144 + " SELECT rn, owner, title, cols, sqlcode, now() FROM old_fmt;" 145 + "INSERT OR IGNORE INTO reportfmt(rn,owner,title,cols,sqlcode,mtime)" 146 + " SELECT rn, owner, title || ' (' || rn || ')', cols, sqlcode, now()" 147 + " FROM old_fmt;" 148 + ); 149 + } 150 + 151 + rc = db_exists("SELECT 1 FROM sqlite_master" 152 + " WHERE name='concealed' AND sql GLOB '* mtime *'"); 153 + if( rc==0 ){ 154 + db_multi_exec( 155 + "ALTER TABLE concealed ADD COLUMN mtime INTEGER;" 156 + "UPDATE concealed SET mtime=now();" 157 + ); 158 + } 159 +} 75 160 76 161 /* 77 162 ** Variables used to store state information about an on-going "rebuild" 78 163 ** or "deconstruct". 79 164 */ 80 165 static int totalSize; /* Total number of artifacts to process */ 81 166 static int processCnt; /* Number processed so far */ ................................................................................ 254 339 255 340 bag_init(&bagDone); 256 341 ttyOutput = doOut; 257 342 processCnt = 0; 258 343 if (!g.fQuiet) { 259 344 percent_complete(0); 260 345 } 261 - db_multi_exec(zSchemaUpdates); 346 + rebuild_update_schema(); 262 347 for(;;){ 263 348 zTable = db_text(0, 264 349 "SELECT name FROM sqlite_master /*scan*/" 265 350 " WHERE type='table'" 266 351 " AND name NOT IN ('blob','delta','rcvfrom','user'," 267 352 "'config','shun','private','reportfmt'," 268 353 "'concealed','accesslog')" ................................................................................ 457 542 db_close(1); 458 543 db_open_repository(g.zRepositoryName); 459 544 } 460 545 db_begin_transaction(); 461 546 ttyOutput = 1; 462 547 errCnt = rebuild_db(randomizeFlag, 1, doClustering); 463 548 db_multi_exec( 464 - "REPLACE INTO config(name,value) VALUES('content-schema','%s');" 465 - "REPLACE INTO config(name,value) VALUES('aux-schema','%s');", 549 + "REPLACE INTO config(name,value,mtime) VALUES('content-schema','%s',now());" 550 + "REPLACE INTO config(name,value,mtime) VALUES('aux-schema','%s',now());", 466 551 CONTENT_SCHEMA, AUX_SCHEMA 467 552 ); 468 553 if( errCnt && !forceFlag ){ 469 554 printf("%d errors. Rolling back changes. Use --force to force a commit.\n", 470 555 errCnt); 471 556 db_end_transaction(1); 472 557 }else{
Changes to src/report.c.
358 358 if( zSQL[0]==0 ){ 359 359 zErr = "Please supply an SQL query statement"; 360 360 }else if( (zTitle = trim_string(zTitle))[0]==0 ){ 361 361 zErr = "Please supply a title"; 362 362 }else{ 363 363 zErr = verify_sql_statement(zSQL); 364 364 } 365 + if( zErr==0 366 + && db_exists("SELECT 1 FROM reportfmt WHERE title=%Q and rn<>%d", 367 + zTitle, rn) 368 + ){ 369 + zErr = mprintf("There is already another report named \"%h\"", zTitle); 370 + } 365 371 if( zErr==0 ){ 366 372 login_verify_csrf_secret(); 367 373 if( rn>0 ){ 368 374 db_multi_exec("UPDATE reportfmt SET title=%Q, sqlcode=%Q," 369 - " owner=%Q, cols=%Q WHERE rn=%d", 375 + " owner=%Q, cols=%Q, mtime=now() WHERE rn=%d", 370 376 zTitle, zSQL, zOwner, zClrKey, rn); 371 377 }else{ 372 - db_multi_exec("INSERT INTO reportfmt(title,sqlcode,owner,cols) " 373 - "VALUES(%Q,%Q,%Q,%Q)", 378 + db_multi_exec("INSERT INTO reportfmt(title,sqlcode,owner,cols,mtime) " 379 + "VALUES(%Q,%Q,%Q,%Q,now())", 374 380 zTitle, zSQL, zOwner, zClrKey); 375 381 rn = db_last_insert_rowid(); 376 382 } 377 383 cgi_redirect(mprintf("rptview?rn=%d", rn)); 378 384 return; 379 385 } 380 386 }else if( rn==0 ){
Changes to src/schema.c.
37 37 /* 38 38 ** The content tables have a content version number which rarely 39 39 ** changes. The aux tables have an arbitrary version number (typically 40 40 ** a date) which can change frequently. When the content schema changes, 41 41 ** we have to execute special procedures to update the schema. When 42 42 ** the aux schema changes, all we need to do is rebuild the database. 43 43 */ 44 -#define CONTENT_SCHEMA "1" 45 -#define AUX_SCHEMA "2011-02-25 14:52" 44 +#define CONTENT_SCHEMA "2" 45 +#define AUX_SCHEMA "2011-04-25 19:50" 46 46 47 47 #endif /* INTERFACE */ 48 48 49 49 50 50 /* 51 51 ** The schema for a repository database. 52 52 ** 53 53 ** Schema1[] contains parts of the schema that are fixed and unchanging 54 54 ** across versions. Schema2[] contains parts of the schema that can 55 55 ** change from one version to the next. The information in Schema2[] 56 -** can be reconstructed from the information in Schema1[]. 56 +** is reconstructed from the information in Schema1[] by the "rebuild" 57 +** operation. 57 58 */ 58 59 const char zRepositorySchema1[] = 59 60 @ -- The BLOB and DELTA tables contain all records held in the repository. 60 61 @ -- 61 -@ -- The BLOB.CONTENT column is always compressed using libz. This 62 +@ -- The BLOB.CONTENT column is always compressed using zlib. This 62 63 @ -- column might hold the full text of the record or it might hold 63 64 @ -- a delta that is able to reconstruct the record from some other 64 65 @ -- record. If BLOB.CONTENT holds a delta, then a DELTA table entry 65 66 @ -- will exist for the record and that entry will point to another 66 67 @ -- entry that holds the source of the delta. Deltas can be chained. 68 +@ -- 69 +@ -- The blob and delta tables collectively hold the "global state" of 70 +@ -- a Fossil repository. 67 71 @ -- 68 72 @ CREATE TABLE blob( 69 73 @ rid INTEGER PRIMARY KEY, -- Record ID 70 74 @ rcvid INTEGER, -- Origin of this record 71 75 @ size INTEGER, -- Size of content. -1 for a phantom. 72 76 @ uuid TEXT UNIQUE NOT NULL, -- SHA1 hash of the content 73 77 @ content BLOB, -- Compressed content of this record ................................................................................ 75 79 @ ); 76 80 @ CREATE TABLE delta( 77 81 @ rid INTEGER PRIMARY KEY, -- Record ID 78 82 @ srcid INTEGER NOT NULL REFERENCES blob -- Record holding source document 79 83 @ ); 80 84 @ CREATE INDEX delta_i1 ON delta(srcid); 81 85 @ 86 +@ ------------------------------------------------------------------------- 87 +@ -- The BLOB and DELTA tables above hold the "global state" of a Fossil 88 +@ -- project; the stuff that is normally exchanged during "sync". The 89 +@ -- "local state" of a repository is contained in the remaining tables of 90 +@ -- the zRepositorySchema1 string. 91 +@ ------------------------------------------------------------------------- 92 +@ 82 93 @ -- Whenever new blobs are received into the repository, an entry 83 94 @ -- in this table records the source of the blob. 84 95 @ -- 85 96 @ CREATE TABLE rcvfrom( 86 97 @ rcvid INTEGER PRIMARY KEY, -- Received-From ID 87 98 @ uid INTEGER REFERENCES user, -- User login 88 -@ mtime DATETIME, -- Time or receipt 99 +@ mtime DATETIME, -- Time of receipt. Julian day. 89 100 @ nonce TEXT UNIQUE, -- Nonce used for login 90 101 @ ipaddr TEXT -- Remote IP address. NULL for direct. 91 102 @ ); 92 103 @ 93 104 @ -- Information about users 94 105 @ -- 95 106 @ -- The user.pw field can be either cleartext of the password, or ................................................................................ 97 108 @ -- characters long we assume it is a SHA1 hash. Otherwise, it is 98 109 @ -- cleartext. The sha1_shared_secret() routine computes the password 99 110 @ -- hash based on the project-code, the user login, and the cleartext 100 111 @ -- password. 101 112 @ -- 102 113 @ CREATE TABLE user( 103 114 @ uid INTEGER PRIMARY KEY, -- User ID 104 -@ login TEXT, -- login name of the user 115 +@ login TEXT UNIQUE, -- login name of the user 105 116 @ pw TEXT, -- password 106 117 @ cap TEXT, -- Capabilities of this user 107 118 @ cookie TEXT, -- WWW login cookie 108 119 @ ipaddr TEXT, -- IP address for which cookie is valid 109 120 @ cexpire DATETIME, -- Time when cookie expires 110 121 @ info TEXT, -- contact information 122 +@ mtime DATE, -- last change. seconds since 1970 111 123 @ photo BLOB -- JPEG image of this user 112 124 @ ); 113 125 @ 114 126 @ -- The VAR table holds miscellanous information about the repository. 115 127 @ -- in the form of name-value pairs. 116 128 @ -- 117 129 @ CREATE TABLE config( 118 130 @ name TEXT PRIMARY KEY NOT NULL, -- Primary name of the entry 119 131 @ value CLOB, -- Content of the named parameter 132 +@ mtime DATE, -- last modified. seconds since 1970 120 133 @ CHECK( typeof(name)='text' AND length(name)>=1 ) 121 134 @ ); 122 135 @ 123 136 @ -- Artifacts that should not be processed are identified in the 124 137 @ -- "shun" table. Artifacts that are control-file forgeries or 125 138 @ -- spam or artifacts whose contents violate administrative policy 126 139 @ -- can be shunned in order to prevent them from contaminating 127 140 @ -- the repository. 128 141 @ -- 129 142 @ -- Shunned artifacts do not exist in the blob table. Hence they 130 143 @ -- have not artifact ID (rid) and we thus must store their full 131 144 @ -- UUID. 132 145 @ -- 133 -@ CREATE TABLE shun(uuid UNIQUE); 146 +@ CREATE TABLE shun( 147 +@ uuid UNIQUE, -- UUID of artifact to be shunned. Canonical form 148 +@ mtime DATE, -- When added. seconds since 1970 149 +@ scom TEXT -- Optional text explaining why the shun occurred 150 +@ ); 134 151 @ 135 152 @ -- Artifacts that should not be pushed are stored in the "private" 136 153 @ -- table. Private artifacts are omitted from the "unclustered" and 137 154 @ -- "unsent" tables. 138 155 @ -- 139 156 @ CREATE TABLE private(rid INTEGER PRIMARY KEY); 140 157 @ 141 158 @ -- An entry in this table describes a database query that generates a 142 159 @ -- table of tickets. 143 160 @ -- 144 161 @ CREATE TABLE reportfmt( 145 -@ rn integer primary key, -- Report number 146 -@ owner text, -- Owner of this report format (not used) 147 -@ title text, -- Title of this report 148 -@ cols text, -- A color-key specification 149 -@ sqlcode text -- An SQL SELECT statement for this report 162 +@ rn INTEGER PRIMARY KEY, -- Report number 163 +@ owner TEXT, -- Owner of this report format (not used) 164 +@ title TEXT UNIQUE, -- Title of this report 165 +@ mtime DATE, -- Last modified. seconds since 1970 166 +@ cols TEXT, -- A color-key specification 167 +@ sqlcode TEXT -- An SQL SELECT statement for this report 150 168 @ ); 151 -@ INSERT INTO reportfmt(title,cols,sqlcode) VALUES('All Tickets','#ffffff Key: 169 +@ INSERT INTO reportfmt(title,mtime,cols,sqlcode) 170 +@ VALUES('All Tickets',julianday('1970-01-01'),'#ffffff Key: 152 171 @ #f2dcdc Active 153 172 @ #e8e8e8 Review 154 173 @ #cfe8bd Fixed 155 174 @ #bde5d6 Tested 156 175 @ #cacae5 Deferred 157 176 @ #c8c8c8 Closed','SELECT 158 177 @ CASE WHEN status IN (''Open'',''Verified'') THEN ''#f2dcdc'' ................................................................................ 174 193 @ -- by storing an SHA1 hash of the content. For display, the hash is 175 194 @ -- mapped back into the original text using this table. 176 195 @ -- 177 196 @ -- This table contains sensitive information and should not be shared 178 197 @ -- with unauthorized users. 179 198 @ -- 180 199 @ CREATE TABLE concealed( 181 -@ hash TEXT PRIMARY KEY, 182 -@ content TEXT 200 +@ hash TEXT PRIMARY KEY, -- The SHA1 hash of content 201 +@ mtime DATE, -- Time created. Seconds since 1970 202 +@ content TEXT -- Content intended to be concealed 183 203 @ ); 184 204 ; 185 205 186 206 const char zRepositorySchema2[] = 187 207 @ -- Filenames 188 208 @ -- 189 209 @ CREATE TABLE filename( ................................................................................ 212 232 @ 213 233 @ -- Parent/child linkages between checkins 214 234 @ -- 215 235 @ CREATE TABLE plink( 216 236 @ pid INTEGER REFERENCES blob, -- Parent manifest 217 237 @ cid INTEGER REFERENCES blob, -- Child manifest 218 238 @ isprim BOOLEAN, -- pid is the primary parent of cid 219 -@ mtime DATETIME, -- the date/time stamp on cid 239 +@ mtime DATETIME, -- the date/time stamp on cid. Julian day. 220 240 @ UNIQUE(pid, cid) 221 241 @ ); 222 242 @ CREATE INDEX plink_i2 ON plink(cid,pid); 223 243 @ 224 244 @ -- A "leaf" checkin is a checkin that has no children in the same 225 245 @ -- branch. The set of all leaves is easily computed with a join, 226 246 @ -- between the plink and tagxref tables, but it is a slower join for ................................................................................ 230 250 @ -- 231 251 @ CREATE TABLE leaf(rid INTEGER PRIMARY KEY); 232 252 @ 233 253 @ -- Events used to generate a timeline 234 254 @ -- 235 255 @ CREATE TABLE event( 236 256 @ type TEXT, -- Type of event: 'ci', 'w', 'e', 't' 237 -@ mtime DATETIME, -- Date and time when the event occurs 257 +@ mtime DATETIME, -- Time of occurrence. Julian day. 238 258 @ objid INTEGER PRIMARY KEY, -- Associated record ID 239 259 @ tagid INTEGER, -- Associated ticket or wiki name tag 240 260 @ uid INTEGER REFERENCES user, -- User who caused the event 241 261 @ bgcolor TEXT, -- Color set by 'bgcolor' property 242 262 @ euser TEXT, -- User set by 'user' property 243 263 @ user TEXT, -- Name of the user 244 264 @ ecomment TEXT, -- Comment set by 'comment' property ................................................................................ 317 337 @ -- 318 338 @ CREATE TABLE tagxref( 319 339 @ tagid INTEGER REFERENCES tag, -- The tag that added or removed 320 340 @ tagtype INTEGER, -- 0:-,cancel 1:+,single 2:*,propagate 321 341 @ srcid INTEGER REFERENCES blob, -- Artifact of tag. 0 for propagated tags 322 342 @ origid INTEGER REFERENCES blob, -- check-in holding propagated tag 323 343 @ value TEXT, -- Value of the tag. Might be NULL. 324 -@ mtime TIMESTAMP, -- Time of addition or removal 344 +@ mtime TIMESTAMP, -- Time of addition or removal. Julian day 325 345 @ rid INTEGER REFERENCE blob, -- Artifact tag is applied to 326 346 @ UNIQUE(rid, tagid) 327 347 @ ); 328 348 @ CREATE INDEX tagxref_i1 ON tagxref(tagid, mtime); 329 349 @ 330 350 @ -- When a hyperlink occurs from one artifact to another (for example 331 351 @ -- when a check-in comment refers to a ticket) an entry is made in ................................................................................ 332 352 @ -- the following table for that hyperlink. This table is used to 333 353 @ -- facilitate the display of "back links". 334 354 @ -- 335 355 @ CREATE TABLE backlink( 336 356 @ target TEXT, -- Where the hyperlink points to 337 357 @ srctype INT, -- 0: check-in 1: ticket 2: wiki 338 358 @ srcid INT, -- rid for checkin or wiki. tkt_id for ticket. 339 -@ mtime TIMESTAMP, -- time that the hyperlink was added 359 +@ mtime TIMESTAMP, -- time that the hyperlink was added. Julian day. 340 360 @ UNIQUE(target, srctype, srcid) 341 361 @ ); 342 362 @ CREATE INDEX backlink_src ON backlink(srcid, srctype); 343 363 @ 344 364 @ -- Each attachment is an entry in the following table. Only 345 365 @ -- the most recent attachment (identified by the D card) is saved. 346 366 @ -- 347 367 @ CREATE TABLE attachment( 348 368 @ attachid INTEGER PRIMARY KEY, -- Local id for this attachment 349 369 @ isLatest BOOLEAN DEFAULT 0, -- True if this is the one to use 350 -@ mtime TIMESTAMP, -- Time when attachment last changed 370 +@ mtime TIMESTAMP, -- Last changed. Julian day. 351 371 @ src TEXT, -- UUID of the attachment. NULL to delete 352 372 @ target TEXT, -- Object attached to. Wikiname or Tkt UUID 353 373 @ filename TEXT, -- Filename for the attachment 354 374 @ comment TEXT, -- Comment associated with this attachment 355 375 @ user TEXT -- Name of user adding attachment 356 376 @ ); 357 377 @ CREATE INDEX attachment_idx1 ON attachment(target, filename, mtime); ................................................................................ 441 461 @ id INTEGER PRIMARY KEY, -- ID of the checked out file 442 462 @ vid INTEGER REFERENCES blob, -- The baseline this file is part of. 443 463 @ chnged INT DEFAULT 0, -- 0:unchnged 1:edited 2:m-chng 3:m-add 444 464 @ deleted BOOLEAN DEFAULT 0, -- True if deleted 445 465 @ isexe BOOLEAN, -- True if file should be executable 446 466 @ rid INTEGER, -- Originally from this repository record 447 467 @ mrid INTEGER, -- Based on this record due to a merge 448 -@ mtime INTEGER, -- Modification time of file on disk 468 +@ mtime INTEGER, -- Mtime of file on disk. sec since 1970 449 469 @ pathname TEXT, -- Full pathname relative to root 450 470 @ origname TEXT, -- Original pathname. NULL if unchanged 451 471 @ UNIQUE(pathname,vid) 452 472 @ ); 453 473 @ 454 474 @ -- This table holds a record of uncommitted merges in the local 455 475 @ -- file tree. If a VFILE entry with id has merged with another
Changes to src/setup.c.
352 352 @ 353 353 @ <p><a href="setup_uedit?id=%d(uid)">[Bummer]</a></p> 354 354 style_footer(); 355 355 return; 356 356 } 357 357 login_verify_csrf_secret(); 358 358 db_multi_exec( 359 - "REPLACE INTO user(uid,login,info,pw,cap) " 360 - "VALUES(nullif(%d,0),%Q,%Q,%Q,'%s')", 359 + "REPLACE INTO user(uid,login,info,pw,cap,mtime) " 360 + "VALUES(nullif(%d,0),%Q,%Q,%Q,'%s',now())", 361 361 uid, P("login"), P("info"), zPw, zCap 362 362 ); 363 363 if( atoi(PD("all","0"))>0 ){ 364 364 Blob sql; 365 365 char *zErr = 0; 366 366 blob_zero(&sql); 367 367 if( zOldLogin==0 ){ ................................................................................ 373 373 zOldLogin = zLogin; 374 374 } 375 375 blob_appendf(&sql, 376 376 "UPDATE user SET login=%Q," 377 377 " pw=coalesce(shared_secret(%Q,%Q," 378 378 "(SELECT value FROM config WHERE name='project-code')),pw)," 379 379 " info=%Q," 380 - " cap=%Q" 380 + " cap=%Q," 381 + " mtime=now()" 381 382 " WHERE login=%Q;", 382 383 zLogin, P("pw"), zLogin, P("info"), zCap, 383 384 zOldLogin 384 385 ); 385 386 login_group_sql(blob_str(&sql), "<li> ", " </li>\n", &zErr); 386 387 blob_reset(&sql); 387 388 if( zErr ){ ................................................................................ 1284 1285 } 1285 1286 db_begin_transaction(); 1286 1287 if( P("set")!=0 && zMime && zMime[0] && szImg>0 ){ 1287 1288 Blob img; 1288 1289 Stmt ins; 1289 1290 blob_init(&img, aImg, szImg); 1290 1291 db_prepare(&ins, 1291 - "REPLACE INTO config(name, value)" 1292 - " VALUES('logo-image',:bytes)" 1292 + "REPLACE INTO config(name,value,mtime)" 1293 + " VALUES('logo-image',:bytes,now())" 1293 1294 ); 1294 1295 db_bind_blob(&ins, ":bytes", &img); 1295 1296 db_step(&ins); 1296 1297 db_finalize(&ins); 1297 1298 db_multi_exec( 1298 - "REPLACE INTO config(name, value) VALUES('logo-mimetype',%Q)", 1299 + "REPLACE INTO config(name,value,mtime) VALUES('logo-mimetype',%Q,now())", 1299 1300 zMime 1300 1301 ); 1301 1302 db_end_transaction(0); 1302 1303 cgi_redirect("setup_logo"); 1303 1304 }else if( P("clr")!=0 ){ 1304 1305 db_multi_exec( 1305 1306 "DELETE FROM config WHERE name GLOB 'logo-*'"
Changes to src/shun.c.
79 79 @ be shunned. But it does not exist in the repository. It 80 80 @ may be necessary to rebuild the repository using the 81 81 @ <b>fossil rebuild</b> command-line before the artifact content 82 82 @ can pulled in from other respositories.</p> 83 83 } 84 84 } 85 85 if( zUuid && P("add") ){ 86 + int rid, tagid; 86 87 login_verify_csrf_secret(); 87 - db_multi_exec("INSERT OR IGNORE INTO shun VALUES('%s')", zUuid); 88 + db_multi_exec( 89 + "INSERT OR IGNORE INTO shun(uuid,mtime)" 90 + " VALUES('%s', now())", zUuid); 88 91 @ <p class="shunned">Artifact 89 92 @ <a href="%s(g.zTop)/artifact/%s(zUuid)">%s(zUuid)</a> has been 90 93 @ shunned. It will no longer be pushed. 91 94 @ It will be removed from the repository the next time the respository 92 95 @ is rebuilt using the <b>fossil rebuild</b> command-line</p> 96 + db_multi_exec("DELETE FROM attachment WHERE src=%Q", zUuid); 97 + rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zUuid); 98 + if( rid ){ 99 + db_multi_exec("DELETE FROM event WHERE objid=%d", rid); 100 + } 101 + tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='tkt-%q'", zUuid); 102 + if( tagid ){ 103 + db_multi_exec("DELETE FROM ticket WHERE tkt_uuid=%Q", zUuid); 104 + db_multi_exec("DELETE FROM tag WHERE tagid=%d", tagid); 105 + db_multi_exec("DELETE FROM tagxref WHERE tagid=%d", tagid); 106 + } 93 107 } 94 108 @ <p>A shunned artifact will not be pushed nor accepted in a pull and the 95 109 @ artifact content will be purged from the repository the next time the 96 110 @ repository is rebuilt. A list of shunned artifacts can be seen at the 97 111 @ bottom of this page.</p> 98 112 @ 99 113 @ <a name="addshun"></a>
Changes to src/skins.c.
23 23 24 24 /* @-comment: // */ 25 25 /* 26 26 ** A black-and-white theme with the project title in a bar across the top 27 27 ** and no logo image. 28 28 */ 29 29 static const char zBuiltinSkin1[] = 30 -@ REPLACE INTO config VALUES('css','/* General settings for the entire page */ 30 +@ REPLACE INTO config(name,mtime,value) 31 +@ VALUES('css',now(),'/* General settings for the entire page */ 31 32 @ body { 32 33 @ margin: 0ex 1ex; 33 34 @ padding: 0px; 34 35 @ background-color: white; 35 36 @ font-family: sans-serif; 36 37 @ } 37 38 @ ................................................................................ 150 151 @ 151 152 @ /* The label/value pairs on (for example) the vinfo page */ 152 153 @ table.label-value th { 153 154 @ vertical-align: top; 154 155 @ text-align: right; 155 156 @ padding: 0.2ex 2ex; 156 157 @ }'); 157 -@ REPLACE INTO config VALUES('header','<html> 158 +@ REPLACE INTO config(name,mtime,value) VALUES('header',now(),'<html> 158 159 @ <head> 159 160 @ <title>$<project_name>: $<title></title> 160 161 @ <link rel="alternate" type="application/rss+xml" title="RSS Feed" 161 162 @ href="$home/timeline.rss"> 162 163 @ <link rel="stylesheet" href="$home/style.css?blackwhite" type="text/css" 163 164 @ media="screen"> 164 165 @ </head> ................................................................................ 202 203 @ if {[info exists login]} { 203 204 @ html "<a href=''$home/login''>Logout</a> " 204 205 @ } else { 205 206 @ html "<a href=''$home/login''>Login</a> " 206 207 @ } 207 208 @ </th1></div> 208 209 @ '); 209 -@ REPLACE INTO config VALUES('footer','<div class="footer"> 210 +@ REPLACE INTO config(name,mtime,value) 211 +@ VALUES('footer',now(),'<div class="footer"> 210 212 @ Fossil version $manifest_version $manifest_date 211 213 @ </div> 212 214 @ </body></html> 213 215 @ '); 214 216 ; 215 217 216 218 /* 217 219 ** A tan theme with the project title above the user identification 218 220 ** and no logo image. 219 221 */ 220 222 static const char zBuiltinSkin2[] = 221 -@ REPLACE INTO config VALUES('css','/* General settings for the entire page */ 223 +@ REPLACE INTO config(name,mtime,value) 224 +@ VALUES('css',now(),'/* General settings for the entire page */ 222 225 @ body { 223 226 @ margin: 0ex 0ex; 224 227 @ padding: 0px; 225 228 @ background-color: #fef3bc; 226 229 @ font-family: sans-serif; 227 230 @ } 228 231 @ ................................................................................ 352 355 @ /* The label/value pairs on (for example) the ci page */ 353 356 @ table.label-value th { 354 357 @ vertical-align: top; 355 358 @ text-align: right; 356 359 @ padding: 0.2ex 2ex; 357 360 @ } 358 361 @ '); 359 -@ REPLACE INTO config VALUES('header','<html> 362 +@ REPLACE INTO config(name,mtime,value) VALUES('header',now(),'<html> 360 363 @ <head> 361 364 @ <title>$<project_name>: $<title></title> 362 365 @ <link rel="alternate" type="application/rss+xml" title="RSS Feed" 363 366 @ href="$home/timeline.rss"> 364 367 @ <link rel="stylesheet" href="$home/style.css?tan" type="text/css" 365 368 @ media="screen"> 366 369 @ </head> ................................................................................ 403 406 @ if {[info exists login]} { 404 407 @ html "<a href=''$home/login''>Logout</a> " 405 408 @ } else { 406 409 @ html "<a href=''$home/login''>Login</a> " 407 410 @ } 408 411 @ </th1></div> 409 412 @ '); 410 -@ REPLACE INTO config VALUES('footer','<div class="footer"> 413 +@ REPLACE INTO config(name,mtime,value) 414 +@ VALUES('footer',now(),'<div class="footer"> 411 415 @ Fossil version $manifest_version $manifest_date 412 416 @ </div> 413 417 @ </body></html> 414 418 @ '); 415 419 ; 416 420 417 421 /* 418 422 ** Black letters on a white or cream background with the main menu 419 423 ** stuck on the left-hand side. 420 424 */ 421 425 static const char zBuiltinSkin3[] = 422 -@ REPLACE INTO config VALUES('css','/* General settings for the entire page */ 426 +@ REPLACE INTO config(name,mtime,value) 427 +@ VALUES('css',now(),'/* General settings for the entire page */ 423 428 @ body { 424 429 @ margin:0px 0px 0px 0px; 425 430 @ padding:0px; 426 431 @ font-family:verdana, arial, helvetica, "sans serif"; 427 432 @ color:#333; 428 433 @ background-color:white; 429 434 @ } ................................................................................ 584 589 @ 585 590 @ /* The label/value pairs on (for example) the ci page */ 586 591 @ table.label-value th { 587 592 @ vertical-align: top; 588 593 @ text-align: right; 589 594 @ padding: 0.2ex 2ex; 590 595 @ }'); 591 -@ REPLACE INTO config VALUES('header','<html> 596 +@ REPLACE INTO config(name,mtime,value) VALUES('header',now(),'<html> 592 597 @ <head> 593 598 @ <title>$<project_name>: $<title></title> 594 599 @ <link rel="alternate" type="application/rss+xml" title="RSS Feed" 595 600 @ href="$home/timeline.rss"> 596 601 @ <link rel="stylesheet" href="$home/style.css?black2" type="text/css" 597 602 @ media="screen"> 598 603 @ </head> ................................................................................ 638 643 @ html "<li><a href=''$home/login''>Logout</a></li>" 639 644 @ } else { 640 645 @ html "<li><a href=''$home/login''>Login</a></li>" 641 646 @ } 642 647 @ </th1></ul></div> 643 648 @ <div id="container"> 644 649 @ '); 645 -@ REPLACE INTO config VALUES('footer','</div> 650 +@ REPLACE INTO config(name,mtime,value) VALUES('footer',now(),'</div> 646 651 @ <div class="footer"> 647 652 @ Fossil version $manifest_version $manifest_date 648 653 @ </div> 649 654 @ </body></html> 650 655 @ '); 651 656 ; 652 657 653 658 654 659 /* 655 660 ** Gradients and rounded corners. 656 661 */ 657 662 static const char zBuiltinSkin4[] = 658 -@ REPLACE INTO config VALUES('css','/* General settings for the entire page */ 663 +@ REPLACE INTO config(name,mtime,value) 664 +@ VALUES('css',now(),'/* General settings for the entire page */ 659 665 @ html { 660 666 @ min-height: 100%; 661 667 @ } 662 668 @ body { 663 669 @ margin: 0ex 1ex; 664 670 @ padding: 0px; 665 671 @ background-color: white; ................................................................................ 878 884 @ table.report tr td { 879 885 @ padding: 3px 5px; 880 886 @ } 881 887 @ 882 888 @ textarea { 883 889 @ font-size: 1em; 884 890 @ }'); 885 -@ REPLACE INTO config VALUES('header','<html> 891 +@ REPLACE INTO config(name,mtime,value) VALUES('header',now(),'<html> 886 892 @ <head> 887 893 @ <title>$<project_name>: $<title></title> 888 894 @ <link rel="alternate" type="application/rss+xml" title="RSS Feed" 889 895 @ href="$home/timeline.rss"> 890 896 @ <link rel="stylesheet" href="$home/style.css?black2" type="text/css" 891 897 @ media="screen"> 892 898 @ </head> ................................................................................ 932 938 @ html "<a href=''$home/login''>Logout</a>" 933 939 @ } else { 934 940 @ html "<a href=''$home/login''>Login</a>" 935 941 @ } 936 942 @ </th1></ul></div> 937 943 @ <div id="container"> 938 944 @ '); 939 -@ REPLACE INTO config VALUES('footer','</div> 945 +@ REPLACE INTO config(name,mtime,value) VALUES('footer',now(),'</div> 940 946 @ <div class="footer"> 941 947 @ Fossil version $manifest_version $manifest_date 942 948 @ </div> 943 949 @ </body></html> 944 950 @ '); 945 951 ; 946 952 ................................................................................ 982 988 ** useDefault==0 or a string for the default skin if useDefault==1. 983 989 ** 984 990 ** Memory to hold the returned string is obtained from malloc. 985 991 */ 986 992 static char *getSkin(int useDefault){ 987 993 Blob val; 988 994 blob_zero(&val); 989 - blob_appendf(&val, "REPLACE INTO config VALUES('css',%Q);\n", 995 + blob_appendf(&val, 996 + "REPLACE INTO config(name,value,mtime) VALUES('css',%Q,now());\n", 990 997 useDefault ? zDefaultCSS : db_get("css", (char*)zDefaultCSS) 991 998 ); 992 - blob_appendf(&val, "REPLACE INTO config VALUES('header',%Q);\n", 999 + blob_appendf(&val, 1000 + "REPLACE INTO config(name,value,mtime) VALUES('header',%Q,now());\n", 993 1001 useDefault ? zDefaultHeader : db_get("header", (char*)zDefaultHeader) 994 1002 ); 995 - blob_appendf(&val, "REPLACE INTO config VALUES('footer',%Q);\n", 1003 + blob_appendf(&val, 1004 + "REPLACE INTO config(name,value,mtime) VALUES('footer',%Q,now());\n", 996 1005 useDefault ? zDefaultFooter : db_get("footer", (char*)zDefaultFooter) 997 1006 ); 998 1007 return blob_str(&val); 999 1008 } 1000 1009 1001 1010 /* 1002 1011 ** Construct the default skin string and fill in the corresponding ................................................................................ 1046 1055 1047 1056 if( P("save")!=0 && (zName = skinVarName(P("save"),0))!=0 ){ 1048 1057 if( db_exists("SELECT 1 FROM config WHERE name=%Q", zName) 1049 1058 || strcmp(zName, "Default")==0 ){ 1050 1059 zErr = mprintf("Skin name \"%h\" already exists. " 1051 1060 "Choose a different name.", P("sn")); 1052 1061 }else{ 1053 - db_multi_exec("INSERT INTO config VALUES(%Q,%Q)", 1062 + db_multi_exec("INSERT INTO config(name,value,mtime) VALUES(%Q,%Q,now())", 1054 1063 zName, zCurrent 1055 1064 ); 1056 1065 } 1057 1066 } 1058 1067 1059 1068 /* The user pressed the "Use This Skin" button. */ 1060 1069 if( P("load") && (z = P("sn"))!=0 && z[0] ){ ................................................................................ 1067 1076 } 1068 1077 if( !seen ){ 1069 1078 seen = db_exists("SELECT 1 FROM config WHERE name GLOB 'skin:*'" 1070 1079 " AND value=%Q", zCurrent); 1071 1080 } 1072 1081 if( !seen ){ 1073 1082 db_multi_exec( 1074 - "INSERT INTO config VALUES(" 1083 + "INSERT INTO config(name,value,mtime) VALUES(" 1075 1084 " strftime('skin:Backup On %%Y-%%m-%%d %%H:%%M:%%S')," 1076 - " %Q)", zCurrent 1085 + " %Q,now())", zCurrent 1077 1086 ); 1078 1087 } 1079 1088 seen = 0; 1080 1089 for(i=0; i<sizeof(aBuiltinSkin)/sizeof(aBuiltinSkin[0]); i++){ 1081 1090 if( strcmp(aBuiltinSkin[i].zName, z)==0 ){ 1082 1091 seen = 1; 1083 1092 zCurrent = aBuiltinSkin[i].zValue;
Changes to src/user.c.
202 202 if( g.argc>=6 ){ 203 203 blob_init(&passwd, g.argv[5], -1); 204 204 }else{ 205 205 prompt_for_password("password: ", &passwd, 1); 206 206 } 207 207 zPw = sha1_shared_secret(blob_str(&passwd), blob_str(&login), 0); 208 208 db_multi_exec( 209 - "INSERT INTO user(login,pw,cap,info)" 210 - "VALUES(%B,%Q,%B,%B)", 209 + "INSERT INTO user(login,pw,cap,info,mtime)" 210 + "VALUES(%B,%Q,%B,%B,now())", 211 211 &login, zPw, &caps, &contact 212 212 ); 213 213 free(zPw); 214 214 }else if( n>=2 && strncmp(g.argv[2],"default",n)==0 ){ 215 215 user_select(); 216 216 if( g.argc==3 ){ 217 217 printf("%s\n", g.zLogin); ................................................................................ 247 247 zPrompt = mprintf("New password for %s: ", g.argv[3]); 248 248 prompt_for_password(zPrompt, &pw, 1); 249 249 } 250 250 if( blob_size(&pw)==0 ){ 251 251 printf("password unchanged\n"); 252 252 }else{ 253 253 char *zSecret = sha1_shared_secret(blob_str(&pw), g.argv[3], 0); 254 - db_multi_exec("UPDATE user SET pw=%Q WHERE uid=%d", zSecret, uid); 254 + db_multi_exec("UPDATE user SET pw=%Q, mtime=now() WHERE uid=%d", 255 + zSecret, uid); 255 256 free(zSecret); 256 257 } 257 258 }else if( n>=2 && strncmp(g.argv[2],"capabilities",2)==0 ){ 258 259 int uid; 259 260 if( g.argc!=4 && g.argc!=5 ){ 260 261 usage("user capabilities USERNAME ?PERMISSIONS?"); 261 262 } 262 263 uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", g.argv[3]); 263 264 if( uid==0 ){ 264 265 fossil_fatal("no such user: %s", g.argv[3]); 265 266 } 266 267 if( g.argc==5 ){ 267 268 db_multi_exec( 268 - "UPDATE user SET cap=%Q WHERE uid=%d", g.argv[4], 269 - uid 269 + "UPDATE user SET cap=%Q, mtime=now() WHERE uid=%d", 270 + g.argv[4], uid 270 271 ); 271 272 } 272 273 printf("%s\n", db_text(0, "SELECT cap FROM user WHERE uid=%d", uid)); 273 274 }else{ 274 275 fossil_panic("user subcommand should be one of: " 275 276 "capabilities default list new password"); 276 277 } ................................................................................ 338 339 g.zLogin = mprintf("%s", db_column_text(&s, 1)); 339 340 } 340 341 db_finalize(&s); 341 342 } 342 343 343 344 if( g.userUid==0 ){ 344 345 db_multi_exec( 345 - "INSERT INTO user(login, pw, cap, info)" 346 - "VALUES('anonymous', '', 'cfghjkmnoqw', '')" 346 + "INSERT INTO user(login, pw, cap, info, mtime)" 347 + "VALUES('anonymous', '', 'cfghjkmnoqw', '', now())" 347 348 ); 348 349 g.userUid = db_last_insert_rowid(); 349 350 g.zLogin = "anonymous"; 350 351 } 351 352 } 352 353 353 354 ................................................................................ 362 363 */ 363 364 void user_hash_passwords_cmd(void){ 364 365 if( g.argc!=3 ) usage("REPOSITORY"); 365 366 db_open_repository(g.argv[2]); 366 367 sqlite3_create_function(g.db, "shared_secret", 2, SQLITE_UTF8, 0, 367 368 sha1_shared_secret_sql_function, 0, 0); 368 369 db_multi_exec( 369 - "UPDATE user SET pw=shared_secret(pw,login)" 370 + "UPDATE user SET pw=shared_secret(pw,login), mtime=now()" 370 371 " WHERE length(pw)>0 AND length(pw)!=40" 371 372 ); 372 373 } 373 374 374 375 /* 375 376 ** WEBPAGE: access_log 376 377 **
Changes to src/xfer.c.
452 452 db_static_prepare(&q1, 453 453 "SELECT uuid, size, content," 454 454 " (SELECT uuid FROM delta, blob" 455 455 " WHERE delta.rid=:rid AND delta.srcid=blob.rid)" 456 456 " FROM blob" 457 457 " WHERE rid=:rid" 458 458 " AND size>=0" 459 - " AND uuid NOT IN shun" 459 + " AND NOT EXISTS(SELECT 1 FROM shun WHERE shun.uuid=blob.uuid)" 460 460 ); 461 461 db_bind_int(&q1, ":rid", rid); 462 462 rc = db_step(&q1); 463 463 if( rc==SQLITE_ROW ){ 464 464 zUuid = db_column_text(&q1, 0); 465 465 szU = db_column_int(&q1, 1); 466 466 szC = db_column_bytes(&q1, 2); ................................................................................ 736 736 while( db_step(&q)==SQLITE_ROW ){ 737 737 blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0)); 738 738 } 739 739 db_finalize(&q); 740 740 } 741 741 742 742 /* 743 -** Send a single config card for configuration item zName 743 +** Send a single old-style config card for configuration item zName. 744 +** 745 +** This routine and the functionality it implements is scheduled for 746 +** removal on 2012-05-01. 744 747 */ 745 -static void send_config_card(Xfer *pXfer, const char *zName){ 748 +static void send_legacy_config_card(Xfer *pXfer, const char *zName){ 746 749 if( zName[0]!='@' ){ 747 750 Blob val; 748 751 blob_zero(&val); 749 752 db_blob(&val, "SELECT value FROM config WHERE name=%Q", zName); 750 753 if( blob_size(&val)>0 ){ 751 754 blob_appendf(pXfer->pOut, "config %s %d\n", zName, blob_size(&val)); 752 755 blob_append(pXfer->pOut, blob_buffer(&val), blob_size(&val)); ................................................................................ 758 761 blob_zero(&content); 759 762 configure_render_special_name(zName, &content); 760 763 blob_appendf(pXfer->pOut, "config %s %d\n%s\n", zName, 761 764 blob_size(&content), blob_str(&content)); 762 765 blob_reset(&content); 763 766 } 764 767 } 765 - 766 768 767 769 /* 768 770 ** Called when there is an attempt to transfer private content to and 769 771 ** from a server without authorization. 770 772 */ 771 773 static void server_private_xfer_not_authorized(void){ 772 774 @ error not\sauthorized\sto\ssync\sprivate\scontent ................................................................................ 1005 1007 ** Check for a valid login. This has to happen before anything else. 1006 1008 ** The client can send multiple logins. Permissions are cumulative. 1007 1009 */ 1008 1010 if( blob_eq(&xfer.aToken[0], "login") 1009 1011 && xfer.nToken==4 1010 1012 ){ 1011 1013 if( disableLogin ){ 1012 - g.okRead = g.okWrite = g.okPrivate = 1; 1014 + g.okRead = g.okWrite = g.okPrivate = g.okAdmin = 1; 1013 1015 }else{ 1014 1016 if( check_tail_hash(&xfer.aToken[2], xfer.pIn) 1015 1017 || check_login(&xfer.aToken[1], &xfer.aToken[2], &xfer.aToken[3]) 1016 1018 ){ 1017 1019 cgi_reset_content(); 1018 1020 @ error login\sfailed 1019 1021 nErr++; ................................................................................ 1027 1029 ** Request a configuration value 1028 1030 */ 1029 1031 if( blob_eq(&xfer.aToken[0], "reqconfig") 1030 1032 && xfer.nToken==2 1031 1033 ){ 1032 1034 if( g.okRead ){ 1033 1035 char *zName = blob_str(&xfer.aToken[1]); 1034 - if( configure_is_exportable(zName) ){ 1035 - send_config_card(&xfer, zName); 1036 + if( zName[0]=='/' ){ 1037 + /* New style configuration transfer */ 1038 + int groupMask = configure_name_to_mask(&zName[1], 0); 1039 + if( !g.okAdmin ) groupMask &= ~CONFIGSET_USER; 1040 + if( !g.okRdAddr ) groupMask &= ~CONFIGSET_ADDR; 1041 + configure_send_group(xfer.pOut, groupMask, 0); 1042 + }else if( configure_is_exportable(zName) ){ 1043 + /* Old style configuration transfer */ 1044 + send_legacy_config_card(&xfer, zName); 1036 1045 } 1037 1046 } 1038 1047 }else 1039 1048 1040 1049 /* config NAME SIZE \n CONTENT 1041 1050 ** 1042 1051 ** Receive a configuration value from the client. This is only ................................................................................ 1050 1059 blob_extract(xfer.pIn, size, &content); 1051 1060 if( !g.okAdmin ){ 1052 1061 cgi_reset_content(); 1053 1062 @ error not\sauthorized\sto\spush\sconfiguration 1054 1063 nErr++; 1055 1064 break; 1056 1065 } 1057 - if( !recvConfig ){ 1066 + if( !recvConfig && zName[0]=='@' ){ 1058 1067 configure_prepare_to_receive(0); 1059 1068 recvConfig = 1; 1060 1069 } 1061 1070 configure_receive(zName, &content, CONFIGSET_ALL); 1062 1071 blob_reset(&content); 1063 1072 blob_seek(xfer.pIn, 1, BLOB_SEEK_CUR); 1064 1073 }else ................................................................................ 1183 1192 ** server in gdb: 1184 1193 ** 1185 1194 ** gdb fossil 1186 1195 ** r test-xfer out.txt 1187 1196 */ 1188 1197 void cmd_test_xfer(void){ 1189 1198 int notUsed; 1199 + db_find_and_open_repository(0,0); 1190 1200 if( g.argc!=2 && g.argc!=3 ){ 1191 1201 usage("?MESSAGEFILE?"); 1192 1202 } 1193 - db_must_be_within_tree(); 1194 1203 blob_zero(&g.cgiIn); 1195 1204 blob_read_from_file(&g.cgiIn, g.argc==2 ? "-" : g.argv[2]); 1196 1205 disableLogin = 1; 1197 1206 page_xfer(); 1198 1207 printf("%s\n", cgi_extract_content(¬Used)); 1199 1208 } 1200 1209 ................................................................................ 1329 1338 const char *zName; 1330 1339 zName = configure_first_name(configRcvMask); 1331 1340 while( zName ){ 1332 1341 blob_appendf(&send, "reqconfig %s\n", zName); 1333 1342 zName = configure_next_name(configRcvMask); 1334 1343 nCardSent++; 1335 1344 } 1336 - if( configRcvMask & (CONFIGSET_USER|CONFIGSET_TKT) ){ 1337 - configure_prepare_to_receive(0); 1345 + if( (configRcvMask & (CONFIGSET_USER|CONFIGSET_TKT))!=0 1346 + && (configRcvMask & CONFIGSET_OLDFORMAT)!=0 1347 + ){ 1348 + int overwrite = (configRcvMask & CONFIGSET_OVERWRITE)!=0; 1349 + configure_prepare_to_receive(overwrite); 1338 1350 } 1339 1351 origConfigRcvMask = configRcvMask; 1340 1352 configRcvMask = 0; 1341 1353 } 1342 1354 1343 1355 /* Send configuration parameters being pushed */ 1344 1356 if( configSendMask ){ 1345 - const char *zName; 1346 - zName = configure_first_name(configSendMask); 1347 - while( zName ){ 1348 - send_config_card(&xfer, zName); 1349 - zName = configure_next_name(configSendMask); 1350 - nCardSent++; 1357 + if( configSendMask & CONFIGSET_OLDFORMAT ){ 1358 + const char *zName; 1359 + zName = configure_first_name(configSendMask); 1360 + while( zName ){ 1361 + send_legacy_config_card(&xfer, zName); 1362 + zName = configure_next_name(configSendMask); 1363 + nCardSent++; 1364 + } 1365 + }else{ 1366 + nCardSent += configure_send_group(xfer.pOut, configSendMask, 0); 1351 1367 } 1352 1368 configSendMask = 0; 1353 1369 } 1354 1370 1355 1371 /* Append randomness to the end of the message. This makes all 1356 1372 ** messages unique so that that the login-card nonce will always 1357 1373 ** be unique. ................................................................................ 1645 1661 fossil_warning("%b", &xfer.err); 1646 1662 nErr++; 1647 1663 break; 1648 1664 } 1649 1665 blobarray_reset(xfer.aToken, xfer.nToken); 1650 1666 blob_reset(&xfer.line); 1651 1667 } 1652 - if( origConfigRcvMask & (CONFIGSET_TKT|CONFIGSET_USER) ){ 1668 + if( (configRcvMask & (CONFIGSET_USER|CONFIGSET_TKT))!=0 1669 + && (configRcvMask & CONFIGSET_OLDFORMAT)!=0 1670 + ){ 1653 1671 configure_finalize_receive(); 1654 1672 } 1655 1673 origConfigRcvMask = 0; 1656 1674 if( nCardRcvd>0 ){ 1657 1675 fossil_print(zValueFormat, "Received:", 1658 1676 blob_size(&recv), nCardRcvd, 1659 1677 xfer.nFileRcvd, xfer.nDeltaRcvd + xfer.nDanglingFile);