Fossil SCM

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.

drh 2011-04-27 02:10 trunk merge
Commit 1654456ef5aa8d38c809414a5345bd7f7e76d3c7
+2 -2
--- src/checkin.c
+++ src/checkin.c
@@ -276,15 +276,15 @@
276276
int dotfilesFlag;
277277
const char *zIgnoreFlag;
278278
Blob path, repo;
279279
Stmt q;
280280
int n;
281
+ Glob *pIgnore;
282
+
281283
allFlag = find_option("force","f",0)!=0;
282284
dotfilesFlag = find_option("dotfiles",0,0)!=0;
283285
zIgnoreFlag = find_option("ignore",0,1);
284
- Glob *pIgnore;
285
-
286286
db_must_be_within_tree();
287287
if( zIgnoreFlag==0 ){
288288
zIgnoreFlag = db_get("ignore-glob", 0);
289289
}
290290
db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY)");
291291
--- src/checkin.c
+++ src/checkin.c
@@ -276,15 +276,15 @@
276 int dotfilesFlag;
277 const char *zIgnoreFlag;
278 Blob path, repo;
279 Stmt q;
280 int n;
 
 
281 allFlag = find_option("force","f",0)!=0;
282 dotfilesFlag = find_option("dotfiles",0,0)!=0;
283 zIgnoreFlag = find_option("ignore",0,1);
284 Glob *pIgnore;
285
286 db_must_be_within_tree();
287 if( zIgnoreFlag==0 ){
288 zIgnoreFlag = db_get("ignore-glob", 0);
289 }
290 db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY)");
291
--- src/checkin.c
+++ src/checkin.c
@@ -276,15 +276,15 @@
276 int dotfilesFlag;
277 const char *zIgnoreFlag;
278 Blob path, repo;
279 Stmt q;
280 int n;
281 Glob *pIgnore;
282
283 allFlag = find_option("force","f",0)!=0;
284 dotfilesFlag = find_option("dotfiles",0,0)!=0;
285 zIgnoreFlag = find_option("ignore",0,1);
 
 
286 db_must_be_within_tree();
287 if( zIgnoreFlag==0 ){
288 zIgnoreFlag = db_get("ignore-glob", 0);
289 }
290 db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY)");
291
+6 -6
--- src/clone.c
+++ src/clone.c
@@ -64,14 +64,14 @@
6464
file_copy(g.urlName, g.argv[3]);
6565
db_close(1);
6666
db_open_repository(g.argv[3]);
6767
db_record_repository_filename(g.argv[3]);
6868
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());",
7373
g.urlCanonical
7474
);
7575
db_multi_exec(
7676
"DELETE FROM blob WHERE rid IN private;"
7777
"DELETE FROM delta wHERE rid IN private;"
@@ -92,12 +92,12 @@
9292
user_select();
9393
db_set("content-schema", CONTENT_SCHEMA, 0);
9494
db_set("aux-schema", AUX_SCHEMA, 0);
9595
db_set("last-sync-url", g.argv[2], 0);
9696
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());"
9999
);
100100
url_enable_proxy(0);
101101
url_get_password_if_needed();
102102
g.xlinkClusterOnly = 1;
103103
nErr = client_sync(0,0,1,bPrivate,CONFIGSET_ALL,0);
104104
--- src/clone.c
+++ src/clone.c
@@ -64,14 +64,14 @@
64 file_copy(g.urlName, g.argv[3]);
65 db_close(1);
66 db_open_repository(g.argv[3]);
67 db_record_repository_filename(g.argv[3]);
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');",
73 g.urlCanonical
74 );
75 db_multi_exec(
76 "DELETE FROM blob WHERE rid IN private;"
77 "DELETE FROM delta wHERE rid IN private;"
@@ -92,12 +92,12 @@
92 user_select();
93 db_set("content-schema", CONTENT_SCHEMA, 0);
94 db_set("aux-schema", AUX_SCHEMA, 0);
95 db_set("last-sync-url", g.argv[2], 0);
96 db_multi_exec(
97 "REPLACE INTO config(name,value)"
98 " VALUES('server-code', lower(hex(randomblob(20))));"
99 );
100 url_enable_proxy(0);
101 url_get_password_if_needed();
102 g.xlinkClusterOnly = 1;
103 nErr = client_sync(0,0,1,bPrivate,CONFIGSET_ALL,0);
104
--- src/clone.c
+++ src/clone.c
@@ -64,14 +64,14 @@
64 file_copy(g.urlName, g.argv[3]);
65 db_close(1);
66 db_open_repository(g.argv[3]);
67 db_record_repository_filename(g.argv[3]);
68 db_multi_exec(
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 g.urlCanonical
74 );
75 db_multi_exec(
76 "DELETE FROM blob WHERE rid IN private;"
77 "DELETE FROM delta wHERE rid IN private;"
@@ -92,12 +92,12 @@
92 user_select();
93 db_set("content-schema", CONTENT_SCHEMA, 0);
94 db_set("aux-schema", AUX_SCHEMA, 0);
95 db_set("last-sync-url", g.argv[2], 0);
96 db_multi_exec(
97 "REPLACE INTO config(name,value,mtime)"
98 " VALUES('server-code', lower(hex(randomblob(20))), now());"
99 );
100 url_enable_proxy(0);
101 url_get_password_if_needed();
102 g.xlinkClusterOnly = 1;
103 nErr = client_sync(0,0,1,bPrivate,CONFIGSET_ALL,0);
104
+437 -129
--- src/configure.c
+++ src/configure.c
@@ -2,11 +2,11 @@
22
** Copyright (c) 2008 D. Richard Hipp
33
**
44
** This program is free software; you can redistribute it and/or
55
** modify it under the terms of the Simplified BSD License (also
66
** known as the "2-Clause License" or "FreeBSD License".)
7
-
7
+**
88
** This program is distributed in the hope that it will be useful,
99
** but without any warranty; without even the implied warranty of
1010
** merchantability or fitness for a particular purpose.
1111
**
1212
** Author contact information:
@@ -27,18 +27,21 @@
2727
#if INTERFACE
2828
/*
2929
** Configuration transfers occur in groups. These are the allowed
3030
** groupings:
3131
*/
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
-
39
-#define CONFIGSET_ALL 0xffffff /* Everything */
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
+
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 */
4043
4144
#endif /* INTERFACE */
4245
4346
/*
4447
** Names of the configuration sets
@@ -46,17 +49,17 @@
4649
static struct {
4750
const char *zName; /* Name of the configuration set */
4851
int groupMask; /* Mask for that configuration set */
4952
const char *zHelp; /* What it does */
5053
} 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" },
5861
};
5962
6063
6164
/*
6265
** The following is a list of settings that we are willing to
@@ -106,15 +109,29 @@
106109
const char *configure_first_name(int iMask){
107110
iConfig = 0;
108111
return configure_next_name(iMask);
109112
}
110113
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
+ }
116133
}
117134
}
118135
return 0;
119136
}
120137
@@ -127,12 +144,17 @@
127144
** login credentials and has sufficient capabilities to access the requested
128145
** information.
129146
*/
130147
int configure_is_exportable(const char *zName){
131148
int i;
149
+ int n = strlen(zName);
150
+ if( n>2 && zName[0]=='\'' && zName[n-1]=='\'' ){
151
+ zName++;
152
+ n -= 2;
153
+ }
132154
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 ){
134156
int m = aConfig[i].groupMask;
135157
if( !g.okAdmin ){
136158
m &= ~CONFIGSET_USER;
137159
}
138160
if( !g.okRdAddr ){
@@ -200,38 +222,39 @@
200222
}
201223
202224
/*
203225
** Two SQL functions:
204226
**
205
-** flag_test(int)
206
-** flag_clear(int)
227
+** config_is_reset(int)
228
+** config_reset(int)
207229
**
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
212235
** are given in the argument.
213236
**
214237
** These functions are used below in the WHEN clause of a trigger to
215238
** get the trigger to fire exactly once.
216239
*/
217
-static int flag_value = 0xffff;
218
-static void flag_test_function(
240
+static int configHasBeenReset = 0;
241
+static void config_is_reset_function(
219242
sqlite3_context *context,
220243
int argc,
221244
sqlite3_value **argv
222245
){
223246
int m = sqlite3_value_int(argv[0]);
224
- sqlite3_result_int(context, (flag_value&m)!=0 );
247
+ sqlite3_result_int(context, (configHasBeenReset&m)!=0 );
225248
}
226
-static void flag_clear_function(
249
+static void config_reset_function(
227250
sqlite3_context *context,
228251
int argc,
229252
sqlite3_value **argv
230253
){
231254
int m = sqlite3_value_int(argv[0]);
232
- flag_value &= ~m;
255
+ configHasBeenReset |= m;
233256
}
234257
235258
/*
236259
** Create the temporary _xfer_reportfmt and _xfer_user tables that are
237260
** necessary in order to evalute the SQL text generated by the
@@ -261,12 +284,14 @@
261284
@ ipaddr TEXT, -- IP address for which cookie is valid
262285
@ cexpire DATETIME, -- Time when cookie expires
263286
@ info TEXT, -- contact information
264287
@ photo BLOB -- JPEG image of this user
265288
@ );
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;
268293
;
269294
db_multi_exec(zSQL1);
270295
271296
/* When the replace flag is set, add triggers that run the first time
272297
** that new data is seen. The triggers run only once and delete all the
@@ -273,33 +298,89 @@
273298
** existing data.
274299
*/
275300
if( replaceFlag ){
276301
static const char zSQL2[] =
277302
@ CREATE TRIGGER _xfer_r1 BEFORE INSERT ON _xfer_reportfmt
278
- @ WHEN flag_test(1) BEGIN
303
+ @ WHEN NOT config_is_reset(2) BEGIN
279304
@ DELETE FROM _xfer_reportfmt;
280
- @ SELECT flag_clear(1);
305
+ @ SELECT config_reset(2);
281306
@ END;
282307
@ CREATE TRIGGER _xfer_r2 BEFORE INSERT ON _xfer_user
283
- @ WHEN flag_test(2) BEGIN
308
+ @ WHEN NOT config_is_reset(16) BEGIN
284309
@ DELETE FROM _xfer_user;
285
- @ SELECT flag_clear(2);
310
+ @ SELECT config_reset(16);
286311
@ END;
287312
@ CREATE TEMP TRIGGER _xfer_r3 BEFORE INSERT ON shun
288
- @ WHEN flag_test(4) BEGIN
313
+ @ WHEN NOT config_is_reset(8) BEGIN
289314
@ DELETE FROM shun;
290
- @ SELECT flag_clear(4);
315
+ @ SELECT config_reset(8);
291316
@ END;
292317
;
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;
298323
db_multi_exec(zSQL2);
299324
}
300325
}
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
+}
301382
302383
/*
303384
** Process a single "config" card received from the other side of a
304385
** sync session.
305386
**
@@ -344,108 +425,302 @@
344425
** table like _fer_reportfmt or _xfer_user. Such tables must be created
345426
** ahead of time using configure_prepare_to_receive(). Then after multiple
346427
** calls to this routine, configure_finalize_receive() to transfer the
347428
** information received into the true target table.
348429
*/
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));
367
- }else{
368
- db_multi_exec(
369
- "REPLACE INTO config(name,value) VALUES(%Q,%Q)",
370
- zName, blob_str(pContent)
371
- );
372
- }
373
-}
374
-
375
-
376
-/*
377
-** After receiving configuration data, call this routine to transfer
378
-** the results into the main database.
379
-*/
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);
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);
508
+ }else{
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
+ }
562
+ }
563
+}
564
+
565
+
566
+/*
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.
574
+*/
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;
390678
}
391679
392680
/*
393681
** Identify a configuration group by name. Return its mask.
394682
** Throw an error if no match.
395683
*/
396
-static int find_area(const char *z){
684
+int configure_name_to_mask(const char *z, int notFoundIsFatal){
397685
int i;
398686
int n = strlen(z);
399687
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 ){
401689
return aGroupName[i].groupMask;
402690
}
403691
}
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);
407698
}
408
- fossil_fatal("no such configuration area: \"%s\"", z);
409699
return 0;
410700
}
411701
412702
/*
413703
** Write SQL text into file zFilename that will restore the configuration
414704
** area identified by mask to its current state from any other state.
415705
*/
416706
static void export_config(
417
- int mask, /* Mask indicating which configuration to export */
707
+ int groupMask, /* Mask indicating which configuration to export */
418708
const char *zMask, /* Name of the configuration */
709
+ sqlite3_int64 iStart, /* Start date */
419710
const char *zFilename /* Write into this file */
420711
){
421
- int i;
422712
Blob out;
423713
blob_zero(&out);
424714
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",
428718
zMask, g.zRepositoryName,
429719
db_text(0, "SELECT datetime('now')")
430720
);
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);
447722
blob_write_to_file(&out, zFilename);
448723
blob_reset(&out);
449724
}
450725
451726
@@ -475,25 +750,31 @@
475750
**
476751
** %fossil configuration pull AREA ?URL?
477752
**
478753
** Pull and install the configuration from a different server
479754
** 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.
481759
**
482760
** %fossil configuration push AREA ?URL?
483761
**
484762
** Push the local configuration into the remote server identified
485763
** 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.
487767
**
488768
** %fossil configuration reset AREA
489769
**
490770
** Restore the configuration to the default. AREA as above.
491771
**
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.
495776
*/
496777
void configuration_cmd(void){
497778
int n;
498779
const char *zMethod;
499780
if( g.argc<3 ){
@@ -502,36 +783,59 @@
502783
db_find_and_open_repository(0, 0);
503784
zMethod = g.argv[2];
504785
n = strlen(zMethod);
505786
if( strncmp(zMethod, "export", n)==0 ){
506787
int mask;
788
+ const char *zSince = find_option("since",0,1);
789
+ sqlite3_int64 iStart;
507790
if( g.argc!=5 ){
508791
usage("export AREA FILENAME");
509792
}
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]);
512803
}else
513804
if( strncmp(zMethod, "import", n)==0
514805
|| strncmp(zMethod, "merge", n)==0 ){
515806
Blob in;
807
+ int groupMask;
516808
if( g.argc!=4 ) usage(mprintf("%s FILENAME",zMethod));
517809
blob_read_from_file(&in, g.argv[3]);
518810
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);
522817
db_end_transaction(0);
523818
}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
+ ){
525823
int mask;
526824
const char *zServer;
527825
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
+ }
528832
url_proxy_options();
529833
if( g.argc!=4 && g.argc!=5 ){
530834
usage("pull AREA ?URL?");
531835
}
532
- mask = find_area(g.argv[3]);
836
+ mask = configure_name_to_mask(g.argv[3], 1);
533837
if( g.argc==5 ){
534838
zServer = g.argv[4];
535839
zPw = 0;
536840
g.dontKeepUrl = 1;
537841
}else{
@@ -543,25 +847,29 @@
543847
}
544848
url_parse(zServer);
545849
if( g.urlPasswd==0 && zPw ) g.urlPasswd = mprintf("%s", zPw);
546850
user_select();
547851
url_enable_proxy("via proxy: ");
852
+ if( legacyFlag ) mask |= CONFIGSET_OLDFORMAT;
853
+ if( overwriteFlag ) mask |= CONFIGSET_OVERWRITE;
548854
if( strncmp(zMethod, "push", n)==0 ){
549855
client_sync(0,0,0,0,0,mask);
550
- }else{
856
+ }else if( strncmp(zMethod, "pull", n)==0 ){
551857
client_sync(0,0,0,0,mask,0);
858
+ }else{
859
+ client_sync(0,0,0,0,mask,mask);
552860
}
553861
}else
554862
if( strncmp(zMethod, "reset", n)==0 ){
555863
int mask, i;
556864
char *zBackup;
557865
if( g.argc!=4 ) usage("reset AREA");
558
- mask = find_area(g.argv[3]);
866
+ mask = configure_name_to_mask(g.argv[3], 1);
559867
zBackup = db_text(0,
560868
"SELECT strftime('config-backup-%%Y%%m%%d%%H%%M%%f','now')");
561869
db_begin_transaction();
562
- export_config(mask, g.argv[3], zBackup);
870
+ export_config(mask, g.argv[3], 0, zBackup);
563871
for(i=0; i<count(aConfig); i++){
564872
const char *zName = aConfig[i].zName;
565873
if( (aConfig[i].groupMask & mask)==0 ) continue;
566874
if( zName[0]!='@' ){
567875
db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
568876
--- src/configure.c
+++ src/configure.c
@@ -2,11 +2,11 @@
2 ** Copyright (c) 2008 D. Richard Hipp
3 **
4 ** This program is free software; you can redistribute it and/or
5 ** modify it under the terms of the Simplified BSD License (also
6 ** known as the "2-Clause License" or "FreeBSD License".)
7
8 ** This program is distributed in the hope that it will be useful,
9 ** but without any warranty; without even the implied warranty of
10 ** merchantability or fitness for a particular purpose.
11 **
12 ** Author contact information:
@@ -27,18 +27,21 @@
27 #if INTERFACE
28 /*
29 ** Configuration transfers occur in groups. These are the allowed
30 ** groupings:
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 */
38
39 #define CONFIGSET_ALL 0xffffff /* Everything */
 
 
 
40
41 #endif /* INTERFACE */
42
43 /*
44 ** Names of the configuration sets
@@ -46,17 +49,17 @@
46 static struct {
47 const char *zName; /* Name of the configuration set */
48 int groupMask; /* Mask for that configuration set */
49 const char *zHelp; /* What it does */
50 } 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" },
58 };
59
60
61 /*
62 ** The following is a list of settings that we are willing to
@@ -106,15 +109,29 @@
106 const char *configure_first_name(int iMask){
107 iConfig = 0;
108 return configure_next_name(iMask);
109 }
110 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++;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116 }
117 }
118 return 0;
119 }
120
@@ -127,12 +144,17 @@
127 ** login credentials and has sufficient capabilities to access the requested
128 ** information.
129 */
130 int configure_is_exportable(const char *zName){
131 int i;
 
 
 
 
 
132 for(i=0; i<count(aConfig); i++){
133 if( fossil_strcmp(zName, aConfig[i].zName)==0 ){
134 int m = aConfig[i].groupMask;
135 if( !g.okAdmin ){
136 m &= ~CONFIGSET_USER;
137 }
138 if( !g.okRdAddr ){
@@ -200,38 +222,39 @@
200 }
201
202 /*
203 ** Two SQL functions:
204 **
205 ** flag_test(int)
206 ** flag_clear(int)
207 **
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
 
212 ** are given in the argument.
213 **
214 ** These functions are used below in the WHEN clause of a trigger to
215 ** get the trigger to fire exactly once.
216 */
217 static int flag_value = 0xffff;
218 static void flag_test_function(
219 sqlite3_context *context,
220 int argc,
221 sqlite3_value **argv
222 ){
223 int m = sqlite3_value_int(argv[0]);
224 sqlite3_result_int(context, (flag_value&m)!=0 );
225 }
226 static void flag_clear_function(
227 sqlite3_context *context,
228 int argc,
229 sqlite3_value **argv
230 ){
231 int m = sqlite3_value_int(argv[0]);
232 flag_value &= ~m;
233 }
234
235 /*
236 ** Create the temporary _xfer_reportfmt and _xfer_user tables that are
237 ** necessary in order to evalute the SQL text generated by the
@@ -261,12 +284,14 @@
261 @ ipaddr TEXT, -- IP address for which cookie is valid
262 @ cexpire DATETIME, -- Time when cookie expires
263 @ info TEXT, -- contact information
264 @ photo BLOB -- JPEG image of this user
265 @ );
266 @ INSERT INTO _xfer_reportfmt SELECT * FROM reportfmt;
267 @ INSERT INTO _xfer_user SELECT * FROM user;
 
 
268 ;
269 db_multi_exec(zSQL1);
270
271 /* When the replace flag is set, add triggers that run the first time
272 ** that new data is seen. The triggers run only once and delete all the
@@ -273,33 +298,89 @@
273 ** existing data.
274 */
275 if( replaceFlag ){
276 static const char zSQL2[] =
277 @ CREATE TRIGGER _xfer_r1 BEFORE INSERT ON _xfer_reportfmt
278 @ WHEN flag_test(1) BEGIN
279 @ DELETE FROM _xfer_reportfmt;
280 @ SELECT flag_clear(1);
281 @ END;
282 @ CREATE TRIGGER _xfer_r2 BEFORE INSERT ON _xfer_user
283 @ WHEN flag_test(2) BEGIN
284 @ DELETE FROM _xfer_user;
285 @ SELECT flag_clear(2);
286 @ END;
287 @ CREATE TEMP TRIGGER _xfer_r3 BEFORE INSERT ON shun
288 @ WHEN flag_test(4) BEGIN
289 @ DELETE FROM shun;
290 @ SELECT flag_clear(4);
291 @ END;
292 ;
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;
298 db_multi_exec(zSQL2);
299 }
300 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
301
302 /*
303 ** Process a single "config" card received from the other side of a
304 ** sync session.
305 **
@@ -344,108 +425,302 @@
344 ** table like _fer_reportfmt or _xfer_user. Such tables must be created
345 ** ahead of time using configure_prepare_to_receive(). Then after multiple
346 ** calls to this routine, configure_finalize_receive() to transfer the
347 ** information received into the true target table.
348 */
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));
367 }else{
368 db_multi_exec(
369 "REPLACE INTO config(name,value) VALUES(%Q,%Q)",
370 zName, blob_str(pContent)
371 );
372 }
373 }
374
375
376 /*
377 ** After receiving configuration data, call this routine to transfer
378 ** the results into the main database.
379 */
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);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
390 }
391
392 /*
393 ** Identify a configuration group by name. Return its mask.
394 ** Throw an error if no match.
395 */
396 static int find_area(const char *z){
397 int i;
398 int n = strlen(z);
399 for(i=0; i<count(aGroupName); i++){
400 if( strncmp(z, aGroupName[i].zName, n)==0 ){
401 return aGroupName[i].groupMask;
402 }
403 }
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);
 
 
 
407 }
408 fossil_fatal("no such configuration area: \"%s\"", z);
409 return 0;
410 }
411
412 /*
413 ** Write SQL text into file zFilename that will restore the configuration
414 ** area identified by mask to its current state from any other state.
415 */
416 static void export_config(
417 int mask, /* Mask indicating which configuration to export */
418 const char *zMask, /* Name of the configuration */
 
419 const char *zFilename /* Write into this file */
420 ){
421 int i;
422 Blob out;
423 blob_zero(&out);
424 blob_appendf(&out,
425 "-- The \"%s\" configuration exported from\n"
426 "-- repository \"%s\"\n"
427 "-- on %s\n",
428 zMask, g.zRepositoryName,
429 db_text(0, "SELECT datetime('now')")
430 );
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 }
447 blob_write_to_file(&out, zFilename);
448 blob_reset(&out);
449 }
450
451
@@ -475,25 +750,31 @@
475 **
476 ** %fossil configuration pull AREA ?URL?
477 **
478 ** Pull and install the configuration from a different server
479 ** identified by URL. If no URL is specified, then the default
480 ** server is used.
 
 
 
481 **
482 ** %fossil configuration push AREA ?URL?
483 **
484 ** Push the local configuration into the remote server identified
485 ** by URL. Admin privilege is required on the remote server for
486 ** this to work.
 
 
487 **
488 ** %fossil configuration reset AREA
489 **
490 ** Restore the configuration to the default. AREA as above.
491 **
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.
 
495 */
496 void configuration_cmd(void){
497 int n;
498 const char *zMethod;
499 if( g.argc<3 ){
@@ -502,36 +783,59 @@
502 db_find_and_open_repository(0, 0);
503 zMethod = g.argv[2];
504 n = strlen(zMethod);
505 if( strncmp(zMethod, "export", n)==0 ){
506 int mask;
 
 
507 if( g.argc!=5 ){
508 usage("export AREA FILENAME");
509 }
510 mask = find_area(g.argv[3]);
511 export_config(mask, g.argv[3], g.argv[4]);
 
 
 
 
 
 
 
 
512 }else
513 if( strncmp(zMethod, "import", n)==0
514 || strncmp(zMethod, "merge", n)==0 ){
515 Blob in;
 
516 if( g.argc!=4 ) usage(mprintf("%s FILENAME",zMethod));
517 blob_read_from_file(&in, g.argv[3]);
518 db_begin_transaction();
519 configure_prepare_to_receive(zMethod[0]=='i');
520 db_multi_exec("%s", blob_str(&in));
521 configure_finalize_receive();
 
 
 
522 db_end_transaction(0);
523 }else
524 if( strncmp(zMethod, "pull", n)==0 || strncmp(zMethod, "push", n)==0 ){
 
 
 
525 int mask;
526 const char *zServer;
527 const char *zPw;
 
 
 
 
 
 
528 url_proxy_options();
529 if( g.argc!=4 && g.argc!=5 ){
530 usage("pull AREA ?URL?");
531 }
532 mask = find_area(g.argv[3]);
533 if( g.argc==5 ){
534 zServer = g.argv[4];
535 zPw = 0;
536 g.dontKeepUrl = 1;
537 }else{
@@ -543,25 +847,29 @@
543 }
544 url_parse(zServer);
545 if( g.urlPasswd==0 && zPw ) g.urlPasswd = mprintf("%s", zPw);
546 user_select();
547 url_enable_proxy("via proxy: ");
 
 
548 if( strncmp(zMethod, "push", n)==0 ){
549 client_sync(0,0,0,0,0,mask);
550 }else{
551 client_sync(0,0,0,0,mask,0);
 
 
552 }
553 }else
554 if( strncmp(zMethod, "reset", n)==0 ){
555 int mask, i;
556 char *zBackup;
557 if( g.argc!=4 ) usage("reset AREA");
558 mask = find_area(g.argv[3]);
559 zBackup = db_text(0,
560 "SELECT strftime('config-backup-%%Y%%m%%d%%H%%M%%f','now')");
561 db_begin_transaction();
562 export_config(mask, g.argv[3], zBackup);
563 for(i=0; i<count(aConfig); i++){
564 const char *zName = aConfig[i].zName;
565 if( (aConfig[i].groupMask & mask)==0 ) continue;
566 if( zName[0]!='@' ){
567 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
568
--- src/configure.c
+++ src/configure.c
@@ -2,11 +2,11 @@
2 ** Copyright (c) 2008 D. Richard Hipp
3 **
4 ** This program is free software; you can redistribute it and/or
5 ** modify it under the terms of the Simplified BSD License (also
6 ** known as the "2-Clause License" or "FreeBSD License".)
7 **
8 ** This program is distributed in the hope that it will be useful,
9 ** but without any warranty; without even the implied warranty of
10 ** merchantability or fitness for a particular purpose.
11 **
12 ** Author contact information:
@@ -27,18 +27,21 @@
27 #if INTERFACE
28 /*
29 ** Configuration transfers occur in groups. These are the allowed
30 ** groupings:
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 */
38
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 */
43
44 #endif /* INTERFACE */
45
46 /*
47 ** Names of the configuration sets
@@ -46,17 +49,17 @@
49 static struct {
50 const char *zName; /* Name of the configuration set */
51 int groupMask; /* Mask for that configuration set */
52 const char *zHelp; /* What it does */
53 } aGroupName[] = {
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" },
61 };
62
63
64 /*
65 ** The following is a list of settings that we are willing to
@@ -106,15 +109,29 @@
109 const char *configure_first_name(int iMask){
110 iConfig = 0;
111 return configure_next_name(iMask);
112 }
113 const char *configure_next_name(int iMask){
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 }
133 }
134 }
135 return 0;
136 }
137
@@ -127,12 +144,17 @@
144 ** login credentials and has sufficient capabilities to access the requested
145 ** information.
146 */
147 int configure_is_exportable(const char *zName){
148 int i;
149 int n = strlen(zName);
150 if( n>2 && zName[0]=='\'' && zName[n-1]=='\'' ){
151 zName++;
152 n -= 2;
153 }
154 for(i=0; i<count(aConfig); i++){
155 if( memcmp(zName, aConfig[i].zName, n)==0 && aConfig[i].zName[n]==0 ){
156 int m = aConfig[i].groupMask;
157 if( !g.okAdmin ){
158 m &= ~CONFIGSET_USER;
159 }
160 if( !g.okRdAddr ){
@@ -200,38 +222,39 @@
222 }
223
224 /*
225 ** Two SQL functions:
226 **
227 ** config_is_reset(int)
228 ** config_reset(int)
229 **
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
235 ** are given in the argument.
236 **
237 ** These functions are used below in the WHEN clause of a trigger to
238 ** get the trigger to fire exactly once.
239 */
240 static int configHasBeenReset = 0;
241 static void config_is_reset_function(
242 sqlite3_context *context,
243 int argc,
244 sqlite3_value **argv
245 ){
246 int m = sqlite3_value_int(argv[0]);
247 sqlite3_result_int(context, (configHasBeenReset&m)!=0 );
248 }
249 static void config_reset_function(
250 sqlite3_context *context,
251 int argc,
252 sqlite3_value **argv
253 ){
254 int m = sqlite3_value_int(argv[0]);
255 configHasBeenReset |= m;
256 }
257
258 /*
259 ** Create the temporary _xfer_reportfmt and _xfer_user tables that are
260 ** necessary in order to evalute the SQL text generated by the
@@ -261,12 +284,14 @@
284 @ ipaddr TEXT, -- IP address for which cookie is valid
285 @ cexpire DATETIME, -- Time when cookie expires
286 @ info TEXT, -- contact information
287 @ photo BLOB -- JPEG image of this user
288 @ );
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;
293 ;
294 db_multi_exec(zSQL1);
295
296 /* When the replace flag is set, add triggers that run the first time
297 ** that new data is seen. The triggers run only once and delete all the
@@ -273,33 +298,89 @@
298 ** existing data.
299 */
300 if( replaceFlag ){
301 static const char zSQL2[] =
302 @ CREATE TRIGGER _xfer_r1 BEFORE INSERT ON _xfer_reportfmt
303 @ WHEN NOT config_is_reset(2) BEGIN
304 @ DELETE FROM _xfer_reportfmt;
305 @ SELECT config_reset(2);
306 @ END;
307 @ CREATE TRIGGER _xfer_r2 BEFORE INSERT ON _xfer_user
308 @ WHEN NOT config_is_reset(16) BEGIN
309 @ DELETE FROM _xfer_user;
310 @ SELECT config_reset(16);
311 @ END;
312 @ CREATE TEMP TRIGGER _xfer_r3 BEFORE INSERT ON shun
313 @ WHEN NOT config_is_reset(8) BEGIN
314 @ DELETE FROM shun;
315 @ SELECT config_reset(8);
316 @ END;
317 ;
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;
323 db_multi_exec(zSQL2);
324 }
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 }
382
383 /*
384 ** Process a single "config" card received from the other side of a
385 ** sync session.
386 **
@@ -344,108 +425,302 @@
425 ** table like _fer_reportfmt or _xfer_user. Such tables must be created
426 ** ahead of time using configure_prepare_to_receive(). Then after multiple
427 ** calls to this routine, configure_finalize_receive() to transfer the
428 ** information received into the true target table.
429 */
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);
508 }else{
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 }
562 }
563 }
564
565
566 /*
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.
574 */
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;
678 }
679
680 /*
681 ** Identify a configuration group by name. Return its mask.
682 ** Throw an error if no match.
683 */
684 int configure_name_to_mask(const char *z, int notFoundIsFatal){
685 int i;
686 int n = strlen(z);
687 for(i=0; i<count(aGroupName); i++){
688 if( strncmp(z, &aGroupName[i].zName[1], n)==0 ){
689 return aGroupName[i].groupMask;
690 }
691 }
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);
698 }
 
699 return 0;
700 }
701
702 /*
703 ** Write SQL text into file zFilename that will restore the configuration
704 ** area identified by mask to its current state from any other state.
705 */
706 static void export_config(
707 int groupMask, /* Mask indicating which configuration to export */
708 const char *zMask, /* Name of the configuration */
709 sqlite3_int64 iStart, /* Start date */
710 const char *zFilename /* Write into this file */
711 ){
 
712 Blob out;
713 blob_zero(&out);
714 blob_appendf(&out,
715 "# The \"%s\" configuration exported from\n"
716 "# repository \"%s\"\n"
717 "# on %s\n",
718 zMask, g.zRepositoryName,
719 db_text(0, "SELECT datetime('now')")
720 );
721 configure_send_group(&out, groupMask, iStart);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
722 blob_write_to_file(&out, zFilename);
723 blob_reset(&out);
724 }
725
726
@@ -475,25 +750,31 @@
750 **
751 ** %fossil configuration pull AREA ?URL?
752 **
753 ** Pull and install the configuration from a different server
754 ** identified by URL. If no URL is specified, then the default
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.
759 **
760 ** %fossil configuration push AREA ?URL?
761 **
762 ** Push the local configuration into the remote server identified
763 ** by URL. Admin privilege is required on the remote server for
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.
767 **
768 ** %fossil configuration reset AREA
769 **
770 ** Restore the configuration to the default. AREA as above.
771 **
772 ** %fossil configuration sync AREA ?URL?
773 **
774 ** Synchronize configuration changes in the local repository with
775 ** the remote repository at URL.
776 */
777 void configuration_cmd(void){
778 int n;
779 const char *zMethod;
780 if( g.argc<3 ){
@@ -502,36 +783,59 @@
783 db_find_and_open_repository(0, 0);
784 zMethod = g.argv[2];
785 n = strlen(zMethod);
786 if( strncmp(zMethod, "export", n)==0 ){
787 int mask;
788 const char *zSince = find_option("since",0,1);
789 sqlite3_int64 iStart;
790 if( g.argc!=5 ){
791 usage("export AREA FILENAME");
792 }
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]);
803 }else
804 if( strncmp(zMethod, "import", n)==0
805 || strncmp(zMethod, "merge", n)==0 ){
806 Blob in;
807 int groupMask;
808 if( g.argc!=4 ) usage(mprintf("%s FILENAME",zMethod));
809 blob_read_from_file(&in, g.argv[3]);
810 db_begin_transaction();
811 if( zMethod[0]=='i' ){
812 groupMask = CONFIGSET_ALL | CONFIGSET_OVERWRITE;
813 }else{
814 groupMask = CONFIGSET_ALL;
815 }
816 configure_receive_all(&in, groupMask);
817 db_end_transaction(0);
818 }else
819 if( strncmp(zMethod, "pull", n)==0
820 || strncmp(zMethod, "push", n)==0
821 || strncmp(zMethod, "sync", n)==0
822 ){
823 int mask;
824 const char *zServer;
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 }
832 url_proxy_options();
833 if( g.argc!=4 && g.argc!=5 ){
834 usage("pull AREA ?URL?");
835 }
836 mask = configure_name_to_mask(g.argv[3], 1);
837 if( g.argc==5 ){
838 zServer = g.argv[4];
839 zPw = 0;
840 g.dontKeepUrl = 1;
841 }else{
@@ -543,25 +847,29 @@
847 }
848 url_parse(zServer);
849 if( g.urlPasswd==0 && zPw ) g.urlPasswd = mprintf("%s", zPw);
850 user_select();
851 url_enable_proxy("via proxy: ");
852 if( legacyFlag ) mask |= CONFIGSET_OLDFORMAT;
853 if( overwriteFlag ) mask |= CONFIGSET_OVERWRITE;
854 if( strncmp(zMethod, "push", n)==0 ){
855 client_sync(0,0,0,0,0,mask);
856 }else if( strncmp(zMethod, "pull", n)==0 ){
857 client_sync(0,0,0,0,mask,0);
858 }else{
859 client_sync(0,0,0,0,mask,mask);
860 }
861 }else
862 if( strncmp(zMethod, "reset", n)==0 ){
863 int mask, i;
864 char *zBackup;
865 if( g.argc!=4 ) usage("reset AREA");
866 mask = configure_name_to_mask(g.argv[3], 1);
867 zBackup = db_text(0,
868 "SELECT strftime('config-backup-%%Y%%m%%d%%H%%M%%f','now')");
869 db_begin_transaction();
870 export_config(mask, g.argv[3], 0, zBackup);
871 for(i=0; i<count(aConfig); i++){
872 const char *zName = aConfig[i].zName;
873 if( (aConfig[i].groupMask & mask)==0 ) continue;
874 if( zName[0]!='@' ){
875 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
876
+23 -7
--- src/db.c
+++ src/db.c
@@ -34,10 +34,11 @@
3434
#endif
3535
#include <sqlite3.h>
3636
#include <sys/types.h>
3737
#include <sys/stat.h>
3838
#include <unistd.h>
39
+#include <time.h>
3940
#include "db.h"
4041
4142
#if INTERFACE
4243
/*
4344
** An single SQL statement is represented as an instance of the following
@@ -606,10 +607,23 @@
606607
}
607608
va_end(ap);
608609
sqlite3_exec(db, "COMMIT", 0, 0, 0);
609610
sqlite3_close(db);
610611
}
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
+
611625
612626
/*
613627
** Open a database file. Return a pointer to the new database
614628
** connection. An error results in process abort.
615629
*/
@@ -630,10 +644,11 @@
630644
if( rc!=SQLITE_OK ){
631645
db_err(sqlite3_errmsg(db));
632646
}
633647
sqlite3_busy_timeout(db, 5000);
634648
sqlite3_wal_autocheckpoint(db, 1); /* Set to checkpoint frequently */
649
+ sqlite3_create_function(db, "now", 0, SQLITE_ANY, 0, db_now_function, 0, 0);
635650
return db;
636651
}
637652
638653
639654
/*
@@ -1105,14 +1120,14 @@
11051120
11061121
db_set("content-schema", CONTENT_SCHEMA, 0);
11071122
db_set("aux-schema", AUX_SCHEMA, 0);
11081123
if( makeServerCodes ){
11091124
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());"
11141129
);
11151130
}
11161131
if( !db_is_global("autosync") ) db_set_int("autosync", 1, 0);
11171132
if( !db_is_global("localauth") ) db_set_int("localauth", 0, 0);
11181133
db_create_default_users(0, zDefaultUser);
@@ -1295,11 +1310,12 @@
12951310
sha1sum_step_text(zContent, n);
12961311
sha1sum_finish(&out);
12971312
sqlite3_snprintf(sizeof(zHash), zHash, "%s", blob_str(&out));
12981313
blob_reset(&out);
12991314
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())",
13011317
zHash, n, zContent
13021318
);
13031319
}
13041320
return zHash;
13051321
}
@@ -1406,11 +1422,11 @@
14061422
db_swap_connections();
14071423
db_multi_exec("REPLACE INTO global_config(name,value) VALUES(%Q,%Q)",
14081424
zName, zValue);
14091425
db_swap_connections();
14101426
}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())",
14121428
zName, zValue);
14131429
}
14141430
if( globalFlag && g.repositoryOpen ){
14151431
db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
14161432
}
@@ -1465,11 +1481,11 @@
14651481
db_swap_connections();
14661482
db_multi_exec("REPLACE INTO global_config(name,value) VALUES(%Q,%d)",
14671483
zName, value);
14681484
db_swap_connections();
14691485
}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())",
14711487
zName, value);
14721488
}
14731489
if( globalFlag && g.repositoryOpen ){
14741490
db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
14751491
}
14761492
--- src/db.c
+++ src/db.c
@@ -34,10 +34,11 @@
34 #endif
35 #include <sqlite3.h>
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <unistd.h>
 
39 #include "db.h"
40
41 #if INTERFACE
42 /*
43 ** An single SQL statement is represented as an instance of the following
@@ -606,10 +607,23 @@
606 }
607 va_end(ap);
608 sqlite3_exec(db, "COMMIT", 0, 0, 0);
609 sqlite3_close(db);
610 }
 
 
 
 
 
 
 
 
 
 
 
 
 
611
612 /*
613 ** Open a database file. Return a pointer to the new database
614 ** connection. An error results in process abort.
615 */
@@ -630,10 +644,11 @@
630 if( rc!=SQLITE_OK ){
631 db_err(sqlite3_errmsg(db));
632 }
633 sqlite3_busy_timeout(db, 5000);
634 sqlite3_wal_autocheckpoint(db, 1); /* Set to checkpoint frequently */
 
635 return db;
636 }
637
638
639 /*
@@ -1105,14 +1120,14 @@
1105
1106 db_set("content-schema", CONTENT_SCHEMA, 0);
1107 db_set("aux-schema", AUX_SCHEMA, 0);
1108 if( makeServerCodes ){
1109 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))));"
1114 );
1115 }
1116 if( !db_is_global("autosync") ) db_set_int("autosync", 1, 0);
1117 if( !db_is_global("localauth") ) db_set_int("localauth", 0, 0);
1118 db_create_default_users(0, zDefaultUser);
@@ -1295,11 +1310,12 @@
1295 sha1sum_step_text(zContent, n);
1296 sha1sum_finish(&out);
1297 sqlite3_snprintf(sizeof(zHash), zHash, "%s", blob_str(&out));
1298 blob_reset(&out);
1299 db_multi_exec(
1300 "INSERT OR IGNORE INTO concealed VALUES(%Q,%#Q)",
 
1301 zHash, n, zContent
1302 );
1303 }
1304 return zHash;
1305 }
@@ -1406,11 +1422,11 @@
1406 db_swap_connections();
1407 db_multi_exec("REPLACE INTO global_config(name,value) VALUES(%Q,%Q)",
1408 zName, zValue);
1409 db_swap_connections();
1410 }else{
1411 db_multi_exec("REPLACE INTO config(name,value) VALUES(%Q,%Q)",
1412 zName, zValue);
1413 }
1414 if( globalFlag && g.repositoryOpen ){
1415 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
1416 }
@@ -1465,11 +1481,11 @@
1465 db_swap_connections();
1466 db_multi_exec("REPLACE INTO global_config(name,value) VALUES(%Q,%d)",
1467 zName, value);
1468 db_swap_connections();
1469 }else{
1470 db_multi_exec("REPLACE INTO config(name,value) VALUES(%Q,%d)",
1471 zName, value);
1472 }
1473 if( globalFlag && g.repositoryOpen ){
1474 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
1475 }
1476
--- src/db.c
+++ src/db.c
@@ -34,10 +34,11 @@
34 #endif
35 #include <sqlite3.h>
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <unistd.h>
39 #include <time.h>
40 #include "db.h"
41
42 #if INTERFACE
43 /*
44 ** An single SQL statement is represented as an instance of the following
@@ -606,10 +607,23 @@
607 }
608 va_end(ap);
609 sqlite3_exec(db, "COMMIT", 0, 0, 0);
610 sqlite3_close(db);
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
625
626 /*
627 ** Open a database file. Return a pointer to the new database
628 ** connection. An error results in process abort.
629 */
@@ -630,10 +644,11 @@
644 if( rc!=SQLITE_OK ){
645 db_err(sqlite3_errmsg(db));
646 }
647 sqlite3_busy_timeout(db, 5000);
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);
650 return db;
651 }
652
653
654 /*
@@ -1105,14 +1120,14 @@
1120
1121 db_set("content-schema", CONTENT_SCHEMA, 0);
1122 db_set("aux-schema", AUX_SCHEMA, 0);
1123 if( makeServerCodes ){
1124 db_multi_exec(
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());"
1129 );
1130 }
1131 if( !db_is_global("autosync") ) db_set_int("autosync", 1, 0);
1132 if( !db_is_global("localauth") ) db_set_int("localauth", 0, 0);
1133 db_create_default_users(0, zDefaultUser);
@@ -1295,11 +1310,12 @@
1310 sha1sum_step_text(zContent, n);
1311 sha1sum_finish(&out);
1312 sqlite3_snprintf(sizeof(zHash), zHash, "%s", blob_str(&out));
1313 blob_reset(&out);
1314 db_multi_exec(
1315 "INSERT OR IGNORE INTO concealed(hash,content,mtime)"
1316 " VALUES(%Q,%#Q,now())",
1317 zHash, n, zContent
1318 );
1319 }
1320 return zHash;
1321 }
@@ -1406,11 +1422,11 @@
1422 db_swap_connections();
1423 db_multi_exec("REPLACE INTO global_config(name,value) VALUES(%Q,%Q)",
1424 zName, zValue);
1425 db_swap_connections();
1426 }else{
1427 db_multi_exec("REPLACE INTO config(name,value,mtime) VALUES(%Q,%Q,now())",
1428 zName, zValue);
1429 }
1430 if( globalFlag && g.repositoryOpen ){
1431 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
1432 }
@@ -1465,11 +1481,11 @@
1481 db_swap_connections();
1482 db_multi_exec("REPLACE INTO global_config(name,value) VALUES(%Q,%d)",
1483 zName, value);
1484 db_swap_connections();
1485 }else{
1486 db_multi_exec("REPLACE INTO config(name,value,mtime) VALUES(%Q,%d,now())",
1487 zName, value);
1488 }
1489 if( globalFlag && g.repositoryOpen ){
1490 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
1491 }
1492
+2 -2
--- src/login.c
+++ src/login.c
@@ -1257,12 +1257,12 @@
12571257
db_multi_exec("DETACH other");
12581258
12591259
/* Propagate the changes to all other members of the login-group */
12601260
zSql = mprintf(
12611261
"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());"
12641264
"COMMIT;",
12651265
zSelfProjCode, zSelfLabel, zSelfProjCode, zSelfRepo
12661266
);
12671267
login_group_sql(zSql, "<li> ", "</li>", pzErrMsg);
12681268
fossil_free(zSql);
12691269
--- src/login.c
+++ src/login.c
@@ -1257,12 +1257,12 @@
1257 db_multi_exec("DETACH other");
1258
1259 /* Propagate the changes to all other members of the login-group */
1260 zSql = mprintf(
1261 "BEGIN;"
1262 "REPLACE INTO config(name, value) VALUES('peer-name-%q', %Q);"
1263 "REPLACE INTO config(name, value) VALUES('peer-repo-%q', %Q);"
1264 "COMMIT;",
1265 zSelfProjCode, zSelfLabel, zSelfProjCode, zSelfRepo
1266 );
1267 login_group_sql(zSql, "<li> ", "</li>", pzErrMsg);
1268 fossil_free(zSql);
1269
--- src/login.c
+++ src/login.c
@@ -1257,12 +1257,12 @@
1257 db_multi_exec("DETACH other");
1258
1259 /* Propagate the changes to all other members of the login-group */
1260 zSql = mprintf(
1261 "BEGIN;"
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 "COMMIT;",
1265 zSelfProjCode, zSelfLabel, zSelfProjCode, zSelfRepo
1266 );
1267 login_group_sql(zSql, "<li> ", "</li>", pzErrMsg);
1268 fossil_free(zSql);
1269
+104 -19
--- src/rebuild.c
+++ src/rebuild.c
@@ -22,13 +22,15 @@
2222
#include <assert.h>
2323
#include <dirent.h>
2424
#include <errno.h>
2525
2626
/*
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.
2830
*/
29
-static const char zSchemaUpdates[] =
31
+static const char zSchemaUpdates1[] =
3032
@ -- Index on the delta table
3133
@ --
3234
@ CREATE INDEX IF NOT EXISTS delta_i1 ON delta(srcid);
3335
@
3436
@ -- Artifacts that should not be processed are identified in the
@@ -39,41 +41,124 @@
3941
@ --
4042
@ -- Shunned artifacts do not exist in the blob table. Hence they
4143
@ -- have not artifact ID (rid) and we thus must store their full
4244
@ -- UUID.
4345
@ --
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
+@ );
4551
@
4652
@ -- Artifacts that should not be pushed are stored in the "private"
4753
@ -- table.
4854
@ --
4955
@ CREATE TABLE IF NOT EXISTS private(rid INTEGER PRIMARY KEY);
5056
@
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
-@
6257
@ -- Some ticket content (such as the originators email address or contact
6358
@ -- information) needs to be obscured to protect privacy. This is achieved
6459
@ -- by storing an SHA1 hash of the content. For display, the hash is
6560
@ -- mapped back into the original text using this table.
6661
@ --
6762
@ -- This table contains sensitive information and should not be shared
6863
@ -- with unauthorized users.
6964
@ --
7065
@ 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
7382
@ );
7483
;
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
+}
75160
76161
/*
77162
** Variables used to store state information about an on-going "rebuild"
78163
** or "deconstruct".
79164
*/
@@ -256,11 +341,11 @@
256341
ttyOutput = doOut;
257342
processCnt = 0;
258343
if (!g.fQuiet) {
259344
percent_complete(0);
260345
}
261
- db_multi_exec(zSchemaUpdates);
346
+ rebuild_update_schema();
262347
for(;;){
263348
zTable = db_text(0,
264349
"SELECT name FROM sqlite_master /*scan*/"
265350
" WHERE type='table'"
266351
" AND name NOT IN ('blob','delta','rcvfrom','user',"
@@ -459,12 +544,12 @@
459544
}
460545
db_begin_transaction();
461546
ttyOutput = 1;
462547
errCnt = rebuild_db(randomizeFlag, 1, doClustering);
463548
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());",
466551
CONTENT_SCHEMA, AUX_SCHEMA
467552
);
468553
if( errCnt && !forceFlag ){
469554
printf("%d errors. Rolling back changes. Use --force to force a commit.\n",
470555
errCnt);
471556
--- src/rebuild.c
+++ src/rebuild.c
@@ -22,13 +22,15 @@
22 #include <assert.h>
23 #include <dirent.h>
24 #include <errno.h>
25
26 /*
27 ** Schema changes
 
 
28 */
29 static const char zSchemaUpdates[] =
30 @ -- Index on the delta table
31 @ --
32 @ CREATE INDEX IF NOT EXISTS delta_i1 ON delta(srcid);
33 @
34 @ -- Artifacts that should not be processed are identified in the
@@ -39,41 +41,124 @@
39 @ --
40 @ -- Shunned artifacts do not exist in the blob table. Hence they
41 @ -- have not artifact ID (rid) and we thus must store their full
42 @ -- UUID.
43 @ --
44 @ CREATE TABLE IF NOT EXISTS shun(uuid UNIQUE);
 
 
 
 
45 @
46 @ -- Artifacts that should not be pushed are stored in the "private"
47 @ -- table.
48 @ --
49 @ CREATE TABLE IF NOT EXISTS private(rid INTEGER PRIMARY KEY);
50 @
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 @ -- Some ticket content (such as the originators email address or contact
63 @ -- information) needs to be obscured to protect privacy. This is achieved
64 @ -- by storing an SHA1 hash of the content. For display, the hash is
65 @ -- mapped back into the original text using this table.
66 @ --
67 @ -- This table contains sensitive information and should not be shared
68 @ -- with unauthorized users.
69 @ --
70 @ CREATE TABLE IF NOT EXISTS concealed(
71 @ hash TEXT PRIMARY KEY,
72 @ content TEXT
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73 @ );
74 ;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
76 /*
77 ** Variables used to store state information about an on-going "rebuild"
78 ** or "deconstruct".
79 */
@@ -256,11 +341,11 @@
256 ttyOutput = doOut;
257 processCnt = 0;
258 if (!g.fQuiet) {
259 percent_complete(0);
260 }
261 db_multi_exec(zSchemaUpdates);
262 for(;;){
263 zTable = db_text(0,
264 "SELECT name FROM sqlite_master /*scan*/"
265 " WHERE type='table'"
266 " AND name NOT IN ('blob','delta','rcvfrom','user',"
@@ -459,12 +544,12 @@
459 }
460 db_begin_transaction();
461 ttyOutput = 1;
462 errCnt = rebuild_db(randomizeFlag, 1, doClustering);
463 db_multi_exec(
464 "REPLACE INTO config(name,value) VALUES('content-schema','%s');"
465 "REPLACE INTO config(name,value) VALUES('aux-schema','%s');",
466 CONTENT_SCHEMA, AUX_SCHEMA
467 );
468 if( errCnt && !forceFlag ){
469 printf("%d errors. Rolling back changes. Use --force to force a commit.\n",
470 errCnt);
471
--- src/rebuild.c
+++ src/rebuild.c
@@ -22,13 +22,15 @@
22 #include <assert.h>
23 #include <dirent.h>
24 #include <errno.h>
25
26 /*
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.
30 */
31 static const char zSchemaUpdates1[] =
32 @ -- Index on the delta table
33 @ --
34 @ CREATE INDEX IF NOT EXISTS delta_i1 ON delta(srcid);
35 @
36 @ -- Artifacts that should not be processed are identified in the
@@ -39,41 +41,124 @@
41 @ --
42 @ -- Shunned artifacts do not exist in the blob table. Hence they
43 @ -- have not artifact ID (rid) and we thus must store their full
44 @ -- UUID.
45 @ --
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 @ );
51 @
52 @ -- Artifacts that should not be pushed are stored in the "private"
53 @ -- table.
54 @ --
55 @ CREATE TABLE IF NOT EXISTS private(rid INTEGER PRIMARY KEY);
56 @
 
 
 
 
 
 
 
 
 
 
 
57 @ -- Some ticket content (such as the originators email address or contact
58 @ -- information) needs to be obscured to protect privacy. This is achieved
59 @ -- by storing an SHA1 hash of the content. For display, the hash is
60 @ -- mapped back into the original text using this table.
61 @ --
62 @ -- This table contains sensitive information and should not be shared
63 @ -- with unauthorized users.
64 @ --
65 @ CREATE TABLE IF NOT EXISTS concealed(
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
82 @ );
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 }
160
161 /*
162 ** Variables used to store state information about an on-going "rebuild"
163 ** or "deconstruct".
164 */
@@ -256,11 +341,11 @@
341 ttyOutput = doOut;
342 processCnt = 0;
343 if (!g.fQuiet) {
344 percent_complete(0);
345 }
346 rebuild_update_schema();
347 for(;;){
348 zTable = db_text(0,
349 "SELECT name FROM sqlite_master /*scan*/"
350 " WHERE type='table'"
351 " AND name NOT IN ('blob','delta','rcvfrom','user',"
@@ -459,12 +544,12 @@
544 }
545 db_begin_transaction();
546 ttyOutput = 1;
547 errCnt = rebuild_db(randomizeFlag, 1, doClustering);
548 db_multi_exec(
549 "REPLACE INTO config(name,value,mtime) VALUES('content-schema','%s',now());"
550 "REPLACE INTO config(name,value,mtime) VALUES('aux-schema','%s',now());",
551 CONTENT_SCHEMA, AUX_SCHEMA
552 );
553 if( errCnt && !forceFlag ){
554 printf("%d errors. Rolling back changes. Use --force to force a commit.\n",
555 errCnt);
556
+9 -3
--- src/report.c
+++ src/report.c
@@ -360,19 +360,25 @@
360360
}else if( (zTitle = trim_string(zTitle))[0]==0 ){
361361
zErr = "Please supply a title";
362362
}else{
363363
zErr = verify_sql_statement(zSQL);
364364
}
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
+ }
365371
if( zErr==0 ){
366372
login_verify_csrf_secret();
367373
if( rn>0 ){
368374
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",
370376
zTitle, zSQL, zOwner, zClrKey, rn);
371377
}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())",
374380
zTitle, zSQL, zOwner, zClrKey);
375381
rn = db_last_insert_rowid();
376382
}
377383
cgi_redirect(mprintf("rptview?rn=%d", rn));
378384
return;
379385
--- src/report.c
+++ src/report.c
@@ -360,19 +360,25 @@
360 }else if( (zTitle = trim_string(zTitle))[0]==0 ){
361 zErr = "Please supply a title";
362 }else{
363 zErr = verify_sql_statement(zSQL);
364 }
 
 
 
 
 
 
365 if( zErr==0 ){
366 login_verify_csrf_secret();
367 if( rn>0 ){
368 db_multi_exec("UPDATE reportfmt SET title=%Q, sqlcode=%Q,"
369 " owner=%Q, cols=%Q WHERE rn=%d",
370 zTitle, zSQL, zOwner, zClrKey, rn);
371 }else{
372 db_multi_exec("INSERT INTO reportfmt(title,sqlcode,owner,cols) "
373 "VALUES(%Q,%Q,%Q,%Q)",
374 zTitle, zSQL, zOwner, zClrKey);
375 rn = db_last_insert_rowid();
376 }
377 cgi_redirect(mprintf("rptview?rn=%d", rn));
378 return;
379
--- src/report.c
+++ src/report.c
@@ -360,19 +360,25 @@
360 }else if( (zTitle = trim_string(zTitle))[0]==0 ){
361 zErr = "Please supply a title";
362 }else{
363 zErr = verify_sql_statement(zSQL);
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 }
371 if( zErr==0 ){
372 login_verify_csrf_secret();
373 if( rn>0 ){
374 db_multi_exec("UPDATE reportfmt SET title=%Q, sqlcode=%Q,"
375 " owner=%Q, cols=%Q, mtime=now() WHERE rn=%d",
376 zTitle, zSQL, zOwner, zClrKey, rn);
377 }else{
378 db_multi_exec("INSERT INTO reportfmt(title,sqlcode,owner,cols,mtime) "
379 "VALUES(%Q,%Q,%Q,%Q,now())",
380 zTitle, zSQL, zOwner, zClrKey);
381 rn = db_last_insert_rowid();
382 }
383 cgi_redirect(mprintf("rptview?rn=%d", rn));
384 return;
385
+41 -21
--- src/schema.c
+++ src/schema.c
@@ -39,12 +39,12 @@
3939
** changes. The aux tables have an arbitrary version number (typically
4040
** a date) which can change frequently. When the content schema changes,
4141
** we have to execute special procedures to update the schema. When
4242
** the aux schema changes, all we need to do is rebuild the database.
4343
*/
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"
4646
4747
#endif /* INTERFACE */
4848
4949
5050
/*
@@ -51,21 +51,25 @@
5151
** The schema for a repository database.
5252
**
5353
** Schema1[] contains parts of the schema that are fixed and unchanging
5454
** across versions. Schema2[] contains parts of the schema that can
5555
** 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.
5758
*/
5859
const char zRepositorySchema1[] =
5960
@ -- The BLOB and DELTA tables contain all records held in the repository.
6061
@ --
61
-@ -- The BLOB.CONTENT column is always compressed using libz. This
62
+@ -- The BLOB.CONTENT column is always compressed using zlib. This
6263
@ -- column might hold the full text of the record or it might hold
6364
@ -- a delta that is able to reconstruct the record from some other
6465
@ -- record. If BLOB.CONTENT holds a delta, then a DELTA table entry
6566
@ -- will exist for the record and that entry will point to another
6667
@ -- 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.
6771
@ --
6872
@ CREATE TABLE blob(
6973
@ rid INTEGER PRIMARY KEY, -- Record ID
7074
@ rcvid INTEGER, -- Origin of this record
7175
@ size INTEGER, -- Size of content. -1 for a phantom.
@@ -77,17 +81,24 @@
7781
@ rid INTEGER PRIMARY KEY, -- Record ID
7882
@ srcid INTEGER NOT NULL REFERENCES blob -- Record holding source document
7983
@ );
8084
@ CREATE INDEX delta_i1 ON delta(srcid);
8185
@
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
+@
8293
@ -- Whenever new blobs are received into the repository, an entry
8394
@ -- in this table records the source of the blob.
8495
@ --
8596
@ CREATE TABLE rcvfrom(
8697
@ rcvid INTEGER PRIMARY KEY, -- Received-From ID
8798
@ uid INTEGER REFERENCES user, -- User login
88
-@ mtime DATETIME, -- Time or receipt
99
+@ mtime DATETIME, -- Time of receipt. Julian day.
89100
@ nonce TEXT UNIQUE, -- Nonce used for login
90101
@ ipaddr TEXT -- Remote IP address. NULL for direct.
91102
@ );
92103
@
93104
@ -- Information about users
@@ -99,26 +110,28 @@
99110
@ -- hash based on the project-code, the user login, and the cleartext
100111
@ -- password.
101112
@ --
102113
@ CREATE TABLE user(
103114
@ uid INTEGER PRIMARY KEY, -- User ID
104
-@ login TEXT, -- login name of the user
115
+@ login TEXT UNIQUE, -- login name of the user
105116
@ pw TEXT, -- password
106117
@ cap TEXT, -- Capabilities of this user
107118
@ cookie TEXT, -- WWW login cookie
108119
@ ipaddr TEXT, -- IP address for which cookie is valid
109120
@ cexpire DATETIME, -- Time when cookie expires
110121
@ info TEXT, -- contact information
122
+@ mtime DATE, -- last change. seconds since 1970
111123
@ photo BLOB -- JPEG image of this user
112124
@ );
113125
@
114126
@ -- The VAR table holds miscellanous information about the repository.
115127
@ -- in the form of name-value pairs.
116128
@ --
117129
@ CREATE TABLE config(
118130
@ name TEXT PRIMARY KEY NOT NULL, -- Primary name of the entry
119131
@ value CLOB, -- Content of the named parameter
132
+@ mtime DATE, -- last modified. seconds since 1970
120133
@ CHECK( typeof(name)='text' AND length(name)>=1 )
121134
@ );
122135
@
123136
@ -- Artifacts that should not be processed are identified in the
124137
@ -- "shun" table. Artifacts that are control-file forgeries or
@@ -128,11 +141,15 @@
128141
@ --
129142
@ -- Shunned artifacts do not exist in the blob table. Hence they
130143
@ -- have not artifact ID (rid) and we thus must store their full
131144
@ -- UUID.
132145
@ --
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
+@ );
134151
@
135152
@ -- Artifacts that should not be pushed are stored in the "private"
136153
@ -- table. Private artifacts are omitted from the "unclustered" and
137154
@ -- "unsent" tables.
138155
@ --
@@ -140,17 +157,19 @@
140157
@
141158
@ -- An entry in this table describes a database query that generates a
142159
@ -- table of tickets.
143160
@ --
144161
@ 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
150168
@ );
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:
152171
@ #f2dcdc Active
153172
@ #e8e8e8 Review
154173
@ #cfe8bd Fixed
155174
@ #bde5d6 Tested
156175
@ #cacae5 Deferred
@@ -176,12 +195,13 @@
176195
@ --
177196
@ -- This table contains sensitive information and should not be shared
178197
@ -- with unauthorized users.
179198
@ --
180199
@ 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
183203
@ );
184204
;
185205
186206
const char zRepositorySchema2[] =
187207
@ -- Filenames
@@ -214,11 +234,11 @@
214234
@ --
215235
@ CREATE TABLE plink(
216236
@ pid INTEGER REFERENCES blob, -- Parent manifest
217237
@ cid INTEGER REFERENCES blob, -- Child manifest
218238
@ 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.
220240
@ UNIQUE(pid, cid)
221241
@ );
222242
@ CREATE INDEX plink_i2 ON plink(cid,pid);
223243
@
224244
@ -- A "leaf" checkin is a checkin that has no children in the same
@@ -232,11 +252,11 @@
232252
@
233253
@ -- Events used to generate a timeline
234254
@ --
235255
@ CREATE TABLE event(
236256
@ 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.
238258
@ objid INTEGER PRIMARY KEY, -- Associated record ID
239259
@ tagid INTEGER, -- Associated ticket or wiki name tag
240260
@ uid INTEGER REFERENCES user, -- User who caused the event
241261
@ bgcolor TEXT, -- Color set by 'bgcolor' property
242262
@ euser TEXT, -- User set by 'user' property
@@ -319,11 +339,11 @@
319339
@ tagid INTEGER REFERENCES tag, -- The tag that added or removed
320340
@ tagtype INTEGER, -- 0:-,cancel 1:+,single 2:*,propagate
321341
@ srcid INTEGER REFERENCES blob, -- Artifact of tag. 0 for propagated tags
322342
@ origid INTEGER REFERENCES blob, -- check-in holding propagated tag
323343
@ 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
325345
@ rid INTEGER REFERENCE blob, -- Artifact tag is applied to
326346
@ UNIQUE(rid, tagid)
327347
@ );
328348
@ CREATE INDEX tagxref_i1 ON tagxref(tagid, mtime);
329349
@
@@ -334,11 +354,11 @@
334354
@ --
335355
@ CREATE TABLE backlink(
336356
@ target TEXT, -- Where the hyperlink points to
337357
@ srctype INT, -- 0: check-in 1: ticket 2: wiki
338358
@ 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.
340360
@ UNIQUE(target, srctype, srcid)
341361
@ );
342362
@ CREATE INDEX backlink_src ON backlink(srcid, srctype);
343363
@
344364
@ -- Each attachment is an entry in the following table. Only
@@ -345,11 +365,11 @@
345365
@ -- the most recent attachment (identified by the D card) is saved.
346366
@ --
347367
@ CREATE TABLE attachment(
348368
@ attachid INTEGER PRIMARY KEY, -- Local id for this attachment
349369
@ 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.
351371
@ src TEXT, -- UUID of the attachment. NULL to delete
352372
@ target TEXT, -- Object attached to. Wikiname or Tkt UUID
353373
@ filename TEXT, -- Filename for the attachment
354374
@ comment TEXT, -- Comment associated with this attachment
355375
@ user TEXT -- Name of user adding attachment
@@ -443,11 +463,11 @@
443463
@ chnged INT DEFAULT 0, -- 0:unchnged 1:edited 2:m-chng 3:m-add
444464
@ deleted BOOLEAN DEFAULT 0, -- True if deleted
445465
@ isexe BOOLEAN, -- True if file should be executable
446466
@ rid INTEGER, -- Originally from this repository record
447467
@ 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
449469
@ pathname TEXT, -- Full pathname relative to root
450470
@ origname TEXT, -- Original pathname. NULL if unchanged
451471
@ UNIQUE(pathname,vid)
452472
@ );
453473
@
454474
--- src/schema.c
+++ src/schema.c
@@ -39,12 +39,12 @@
39 ** changes. The aux tables have an arbitrary version number (typically
40 ** a date) which can change frequently. When the content schema changes,
41 ** we have to execute special procedures to update the schema. When
42 ** the aux schema changes, all we need to do is rebuild the database.
43 */
44 #define CONTENT_SCHEMA "1"
45 #define AUX_SCHEMA "2011-02-25 14:52"
46
47 #endif /* INTERFACE */
48
49
50 /*
@@ -51,21 +51,25 @@
51 ** The schema for a repository database.
52 **
53 ** Schema1[] contains parts of the schema that are fixed and unchanging
54 ** across versions. Schema2[] contains parts of the schema that can
55 ** change from one version to the next. The information in Schema2[]
56 ** can be reconstructed from the information in Schema1[].
 
57 */
58 const char zRepositorySchema1[] =
59 @ -- The BLOB and DELTA tables contain all records held in the repository.
60 @ --
61 @ -- The BLOB.CONTENT column is always compressed using libz. This
62 @ -- column might hold the full text of the record or it might hold
63 @ -- a delta that is able to reconstruct the record from some other
64 @ -- record. If BLOB.CONTENT holds a delta, then a DELTA table entry
65 @ -- will exist for the record and that entry will point to another
66 @ -- entry that holds the source of the delta. Deltas can be chained.
 
 
 
67 @ --
68 @ CREATE TABLE blob(
69 @ rid INTEGER PRIMARY KEY, -- Record ID
70 @ rcvid INTEGER, -- Origin of this record
71 @ size INTEGER, -- Size of content. -1 for a phantom.
@@ -77,17 +81,24 @@
77 @ rid INTEGER PRIMARY KEY, -- Record ID
78 @ srcid INTEGER NOT NULL REFERENCES blob -- Record holding source document
79 @ );
80 @ CREATE INDEX delta_i1 ON delta(srcid);
81 @
 
 
 
 
 
 
 
82 @ -- Whenever new blobs are received into the repository, an entry
83 @ -- in this table records the source of the blob.
84 @ --
85 @ CREATE TABLE rcvfrom(
86 @ rcvid INTEGER PRIMARY KEY, -- Received-From ID
87 @ uid INTEGER REFERENCES user, -- User login
88 @ mtime DATETIME, -- Time or receipt
89 @ nonce TEXT UNIQUE, -- Nonce used for login
90 @ ipaddr TEXT -- Remote IP address. NULL for direct.
91 @ );
92 @
93 @ -- Information about users
@@ -99,26 +110,28 @@
99 @ -- hash based on the project-code, the user login, and the cleartext
100 @ -- password.
101 @ --
102 @ CREATE TABLE user(
103 @ uid INTEGER PRIMARY KEY, -- User ID
104 @ login TEXT, -- login name of the user
105 @ pw TEXT, -- password
106 @ cap TEXT, -- Capabilities of this user
107 @ cookie TEXT, -- WWW login cookie
108 @ ipaddr TEXT, -- IP address for which cookie is valid
109 @ cexpire DATETIME, -- Time when cookie expires
110 @ info TEXT, -- contact information
 
111 @ photo BLOB -- JPEG image of this user
112 @ );
113 @
114 @ -- The VAR table holds miscellanous information about the repository.
115 @ -- in the form of name-value pairs.
116 @ --
117 @ CREATE TABLE config(
118 @ name TEXT PRIMARY KEY NOT NULL, -- Primary name of the entry
119 @ value CLOB, -- Content of the named parameter
 
120 @ CHECK( typeof(name)='text' AND length(name)>=1 )
121 @ );
122 @
123 @ -- Artifacts that should not be processed are identified in the
124 @ -- "shun" table. Artifacts that are control-file forgeries or
@@ -128,11 +141,15 @@
128 @ --
129 @ -- Shunned artifacts do not exist in the blob table. Hence they
130 @ -- have not artifact ID (rid) and we thus must store their full
131 @ -- UUID.
132 @ --
133 @ CREATE TABLE shun(uuid UNIQUE);
 
 
 
 
134 @
135 @ -- Artifacts that should not be pushed are stored in the "private"
136 @ -- table. Private artifacts are omitted from the "unclustered" and
137 @ -- "unsent" tables.
138 @ --
@@ -140,17 +157,19 @@
140 @
141 @ -- An entry in this table describes a database query that generates a
142 @ -- table of tickets.
143 @ --
144 @ 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
 
150 @ );
151 @ INSERT INTO reportfmt(title,cols,sqlcode) VALUES('All Tickets','#ffffff Key:
 
152 @ #f2dcdc Active
153 @ #e8e8e8 Review
154 @ #cfe8bd Fixed
155 @ #bde5d6 Tested
156 @ #cacae5 Deferred
@@ -176,12 +195,13 @@
176 @ --
177 @ -- This table contains sensitive information and should not be shared
178 @ -- with unauthorized users.
179 @ --
180 @ CREATE TABLE concealed(
181 @ hash TEXT PRIMARY KEY,
182 @ content TEXT
 
183 @ );
184 ;
185
186 const char zRepositorySchema2[] =
187 @ -- Filenames
@@ -214,11 +234,11 @@
214 @ --
215 @ CREATE TABLE plink(
216 @ pid INTEGER REFERENCES blob, -- Parent manifest
217 @ cid INTEGER REFERENCES blob, -- Child manifest
218 @ isprim BOOLEAN, -- pid is the primary parent of cid
219 @ mtime DATETIME, -- the date/time stamp on cid
220 @ UNIQUE(pid, cid)
221 @ );
222 @ CREATE INDEX plink_i2 ON plink(cid,pid);
223 @
224 @ -- A "leaf" checkin is a checkin that has no children in the same
@@ -232,11 +252,11 @@
232 @
233 @ -- Events used to generate a timeline
234 @ --
235 @ CREATE TABLE event(
236 @ type TEXT, -- Type of event: 'ci', 'w', 'e', 't'
237 @ mtime DATETIME, -- Date and time when the event occurs
238 @ objid INTEGER PRIMARY KEY, -- Associated record ID
239 @ tagid INTEGER, -- Associated ticket or wiki name tag
240 @ uid INTEGER REFERENCES user, -- User who caused the event
241 @ bgcolor TEXT, -- Color set by 'bgcolor' property
242 @ euser TEXT, -- User set by 'user' property
@@ -319,11 +339,11 @@
319 @ tagid INTEGER REFERENCES tag, -- The tag that added or removed
320 @ tagtype INTEGER, -- 0:-,cancel 1:+,single 2:*,propagate
321 @ srcid INTEGER REFERENCES blob, -- Artifact of tag. 0 for propagated tags
322 @ origid INTEGER REFERENCES blob, -- check-in holding propagated tag
323 @ value TEXT, -- Value of the tag. Might be NULL.
324 @ mtime TIMESTAMP, -- Time of addition or removal
325 @ rid INTEGER REFERENCE blob, -- Artifact tag is applied to
326 @ UNIQUE(rid, tagid)
327 @ );
328 @ CREATE INDEX tagxref_i1 ON tagxref(tagid, mtime);
329 @
@@ -334,11 +354,11 @@
334 @ --
335 @ CREATE TABLE backlink(
336 @ target TEXT, -- Where the hyperlink points to
337 @ srctype INT, -- 0: check-in 1: ticket 2: wiki
338 @ srcid INT, -- rid for checkin or wiki. tkt_id for ticket.
339 @ mtime TIMESTAMP, -- time that the hyperlink was added
340 @ UNIQUE(target, srctype, srcid)
341 @ );
342 @ CREATE INDEX backlink_src ON backlink(srcid, srctype);
343 @
344 @ -- Each attachment is an entry in the following table. Only
@@ -345,11 +365,11 @@
345 @ -- the most recent attachment (identified by the D card) is saved.
346 @ --
347 @ CREATE TABLE attachment(
348 @ attachid INTEGER PRIMARY KEY, -- Local id for this attachment
349 @ isLatest BOOLEAN DEFAULT 0, -- True if this is the one to use
350 @ mtime TIMESTAMP, -- Time when attachment last changed
351 @ src TEXT, -- UUID of the attachment. NULL to delete
352 @ target TEXT, -- Object attached to. Wikiname or Tkt UUID
353 @ filename TEXT, -- Filename for the attachment
354 @ comment TEXT, -- Comment associated with this attachment
355 @ user TEXT -- Name of user adding attachment
@@ -443,11 +463,11 @@
443 @ chnged INT DEFAULT 0, -- 0:unchnged 1:edited 2:m-chng 3:m-add
444 @ deleted BOOLEAN DEFAULT 0, -- True if deleted
445 @ isexe BOOLEAN, -- True if file should be executable
446 @ rid INTEGER, -- Originally from this repository record
447 @ mrid INTEGER, -- Based on this record due to a merge
448 @ mtime INTEGER, -- Modification time of file on disk
449 @ pathname TEXT, -- Full pathname relative to root
450 @ origname TEXT, -- Original pathname. NULL if unchanged
451 @ UNIQUE(pathname,vid)
452 @ );
453 @
454
--- src/schema.c
+++ src/schema.c
@@ -39,12 +39,12 @@
39 ** changes. The aux tables have an arbitrary version number (typically
40 ** a date) which can change frequently. When the content schema changes,
41 ** we have to execute special procedures to update the schema. When
42 ** the aux schema changes, all we need to do is rebuild the database.
43 */
44 #define CONTENT_SCHEMA "2"
45 #define AUX_SCHEMA "2011-04-25 19:50"
46
47 #endif /* INTERFACE */
48
49
50 /*
@@ -51,21 +51,25 @@
51 ** The schema for a repository database.
52 **
53 ** Schema1[] contains parts of the schema that are fixed and unchanging
54 ** across versions. Schema2[] contains parts of the schema that can
55 ** change from one version to the next. The information in Schema2[]
56 ** is reconstructed from the information in Schema1[] by the "rebuild"
57 ** operation.
58 */
59 const char zRepositorySchema1[] =
60 @ -- The BLOB and DELTA tables contain all records held in the repository.
61 @ --
62 @ -- The BLOB.CONTENT column is always compressed using zlib. This
63 @ -- column might hold the full text of the record or it might hold
64 @ -- a delta that is able to reconstruct the record from some other
65 @ -- record. If BLOB.CONTENT holds a delta, then a DELTA table entry
66 @ -- will exist for the record and that entry will point to another
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.
71 @ --
72 @ CREATE TABLE blob(
73 @ rid INTEGER PRIMARY KEY, -- Record ID
74 @ rcvid INTEGER, -- Origin of this record
75 @ size INTEGER, -- Size of content. -1 for a phantom.
@@ -77,17 +81,24 @@
81 @ rid INTEGER PRIMARY KEY, -- Record ID
82 @ srcid INTEGER NOT NULL REFERENCES blob -- Record holding source document
83 @ );
84 @ CREATE INDEX delta_i1 ON delta(srcid);
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 @
93 @ -- Whenever new blobs are received into the repository, an entry
94 @ -- in this table records the source of the blob.
95 @ --
96 @ CREATE TABLE rcvfrom(
97 @ rcvid INTEGER PRIMARY KEY, -- Received-From ID
98 @ uid INTEGER REFERENCES user, -- User login
99 @ mtime DATETIME, -- Time of receipt. Julian day.
100 @ nonce TEXT UNIQUE, -- Nonce used for login
101 @ ipaddr TEXT -- Remote IP address. NULL for direct.
102 @ );
103 @
104 @ -- Information about users
@@ -99,26 +110,28 @@
110 @ -- hash based on the project-code, the user login, and the cleartext
111 @ -- password.
112 @ --
113 @ CREATE TABLE user(
114 @ uid INTEGER PRIMARY KEY, -- User ID
115 @ login TEXT UNIQUE, -- login name of the user
116 @ pw TEXT, -- password
117 @ cap TEXT, -- Capabilities of this user
118 @ cookie TEXT, -- WWW login cookie
119 @ ipaddr TEXT, -- IP address for which cookie is valid
120 @ cexpire DATETIME, -- Time when cookie expires
121 @ info TEXT, -- contact information
122 @ mtime DATE, -- last change. seconds since 1970
123 @ photo BLOB -- JPEG image of this user
124 @ );
125 @
126 @ -- The VAR table holds miscellanous information about the repository.
127 @ -- in the form of name-value pairs.
128 @ --
129 @ CREATE TABLE config(
130 @ name TEXT PRIMARY KEY NOT NULL, -- Primary name of the entry
131 @ value CLOB, -- Content of the named parameter
132 @ mtime DATE, -- last modified. seconds since 1970
133 @ CHECK( typeof(name)='text' AND length(name)>=1 )
134 @ );
135 @
136 @ -- Artifacts that should not be processed are identified in the
137 @ -- "shun" table. Artifacts that are control-file forgeries or
@@ -128,11 +141,15 @@
141 @ --
142 @ -- Shunned artifacts do not exist in the blob table. Hence they
143 @ -- have not artifact ID (rid) and we thus must store their full
144 @ -- UUID.
145 @ --
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 @ );
151 @
152 @ -- Artifacts that should not be pushed are stored in the "private"
153 @ -- table. Private artifacts are omitted from the "unclustered" and
154 @ -- "unsent" tables.
155 @ --
@@ -140,17 +157,19 @@
157 @
158 @ -- An entry in this table describes a database query that generates a
159 @ -- table of tickets.
160 @ --
161 @ CREATE TABLE reportfmt(
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
168 @ );
169 @ INSERT INTO reportfmt(title,mtime,cols,sqlcode)
170 @ VALUES('All Tickets',julianday('1970-01-01'),'#ffffff Key:
171 @ #f2dcdc Active
172 @ #e8e8e8 Review
173 @ #cfe8bd Fixed
174 @ #bde5d6 Tested
175 @ #cacae5 Deferred
@@ -176,12 +195,13 @@
195 @ --
196 @ -- This table contains sensitive information and should not be shared
197 @ -- with unauthorized users.
198 @ --
199 @ CREATE TABLE concealed(
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
203 @ );
204 ;
205
206 const char zRepositorySchema2[] =
207 @ -- Filenames
@@ -214,11 +234,11 @@
234 @ --
235 @ CREATE TABLE plink(
236 @ pid INTEGER REFERENCES blob, -- Parent manifest
237 @ cid INTEGER REFERENCES blob, -- Child manifest
238 @ isprim BOOLEAN, -- pid is the primary parent of cid
239 @ mtime DATETIME, -- the date/time stamp on cid. Julian day.
240 @ UNIQUE(pid, cid)
241 @ );
242 @ CREATE INDEX plink_i2 ON plink(cid,pid);
243 @
244 @ -- A "leaf" checkin is a checkin that has no children in the same
@@ -232,11 +252,11 @@
252 @
253 @ -- Events used to generate a timeline
254 @ --
255 @ CREATE TABLE event(
256 @ type TEXT, -- Type of event: 'ci', 'w', 'e', 't'
257 @ mtime DATETIME, -- Time of occurrence. Julian day.
258 @ objid INTEGER PRIMARY KEY, -- Associated record ID
259 @ tagid INTEGER, -- Associated ticket or wiki name tag
260 @ uid INTEGER REFERENCES user, -- User who caused the event
261 @ bgcolor TEXT, -- Color set by 'bgcolor' property
262 @ euser TEXT, -- User set by 'user' property
@@ -319,11 +339,11 @@
339 @ tagid INTEGER REFERENCES tag, -- The tag that added or removed
340 @ tagtype INTEGER, -- 0:-,cancel 1:+,single 2:*,propagate
341 @ srcid INTEGER REFERENCES blob, -- Artifact of tag. 0 for propagated tags
342 @ origid INTEGER REFERENCES blob, -- check-in holding propagated tag
343 @ value TEXT, -- Value of the tag. Might be NULL.
344 @ mtime TIMESTAMP, -- Time of addition or removal. Julian day
345 @ rid INTEGER REFERENCE blob, -- Artifact tag is applied to
346 @ UNIQUE(rid, tagid)
347 @ );
348 @ CREATE INDEX tagxref_i1 ON tagxref(tagid, mtime);
349 @
@@ -334,11 +354,11 @@
354 @ --
355 @ CREATE TABLE backlink(
356 @ target TEXT, -- Where the hyperlink points to
357 @ srctype INT, -- 0: check-in 1: ticket 2: wiki
358 @ srcid INT, -- rid for checkin or wiki. tkt_id for ticket.
359 @ mtime TIMESTAMP, -- time that the hyperlink was added. Julian day.
360 @ UNIQUE(target, srctype, srcid)
361 @ );
362 @ CREATE INDEX backlink_src ON backlink(srcid, srctype);
363 @
364 @ -- Each attachment is an entry in the following table. Only
@@ -345,11 +365,11 @@
365 @ -- the most recent attachment (identified by the D card) is saved.
366 @ --
367 @ CREATE TABLE attachment(
368 @ attachid INTEGER PRIMARY KEY, -- Local id for this attachment
369 @ isLatest BOOLEAN DEFAULT 0, -- True if this is the one to use
370 @ mtime TIMESTAMP, -- Last changed. Julian day.
371 @ src TEXT, -- UUID of the attachment. NULL to delete
372 @ target TEXT, -- Object attached to. Wikiname or Tkt UUID
373 @ filename TEXT, -- Filename for the attachment
374 @ comment TEXT, -- Comment associated with this attachment
375 @ user TEXT -- Name of user adding attachment
@@ -443,11 +463,11 @@
463 @ chnged INT DEFAULT 0, -- 0:unchnged 1:edited 2:m-chng 3:m-add
464 @ deleted BOOLEAN DEFAULT 0, -- True if deleted
465 @ isexe BOOLEAN, -- True if file should be executable
466 @ rid INTEGER, -- Originally from this repository record
467 @ mrid INTEGER, -- Based on this record due to a merge
468 @ mtime INTEGER, -- Mtime of file on disk. sec since 1970
469 @ pathname TEXT, -- Full pathname relative to root
470 @ origname TEXT, -- Original pathname. NULL if unchanged
471 @ UNIQUE(pathname,vid)
472 @ );
473 @
474
+7 -6
--- src/setup.c
+++ src/setup.c
@@ -354,12 +354,12 @@
354354
style_footer();
355355
return;
356356
}
357357
login_verify_csrf_secret();
358358
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())",
361361
uid, P("login"), P("info"), zPw, zCap
362362
);
363363
if( atoi(PD("all","0"))>0 ){
364364
Blob sql;
365365
char *zErr = 0;
@@ -375,11 +375,12 @@
375375
blob_appendf(&sql,
376376
"UPDATE user SET login=%Q,"
377377
" pw=coalesce(shared_secret(%Q,%Q,"
378378
"(SELECT value FROM config WHERE name='project-code')),pw),"
379379
" info=%Q,"
380
- " cap=%Q"
380
+ " cap=%Q,"
381
+ " mtime=now()"
381382
" WHERE login=%Q;",
382383
zLogin, P("pw"), zLogin, P("info"), zCap,
383384
zOldLogin
384385
);
385386
login_group_sql(blob_str(&sql), "<li> ", " </li>\n", &zErr);
@@ -1286,18 +1287,18 @@
12861287
if( P("set")!=0 && zMime && zMime[0] && szImg>0 ){
12871288
Blob img;
12881289
Stmt ins;
12891290
blob_init(&img, aImg, szImg);
12901291
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())"
12931294
);
12941295
db_bind_blob(&ins, ":bytes", &img);
12951296
db_step(&ins);
12961297
db_finalize(&ins);
12971298
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())",
12991300
zMime
13001301
);
13011302
db_end_transaction(0);
13021303
cgi_redirect("setup_logo");
13031304
}else if( P("clr")!=0 ){
13041305
--- src/setup.c
+++ src/setup.c
@@ -354,12 +354,12 @@
354 style_footer();
355 return;
356 }
357 login_verify_csrf_secret();
358 db_multi_exec(
359 "REPLACE INTO user(uid,login,info,pw,cap) "
360 "VALUES(nullif(%d,0),%Q,%Q,%Q,'%s')",
361 uid, P("login"), P("info"), zPw, zCap
362 );
363 if( atoi(PD("all","0"))>0 ){
364 Blob sql;
365 char *zErr = 0;
@@ -375,11 +375,12 @@
375 blob_appendf(&sql,
376 "UPDATE user SET login=%Q,"
377 " pw=coalesce(shared_secret(%Q,%Q,"
378 "(SELECT value FROM config WHERE name='project-code')),pw),"
379 " info=%Q,"
380 " cap=%Q"
 
381 " WHERE login=%Q;",
382 zLogin, P("pw"), zLogin, P("info"), zCap,
383 zOldLogin
384 );
385 login_group_sql(blob_str(&sql), "<li> ", " </li>\n", &zErr);
@@ -1286,18 +1287,18 @@
1286 if( P("set")!=0 && zMime && zMime[0] && szImg>0 ){
1287 Blob img;
1288 Stmt ins;
1289 blob_init(&img, aImg, szImg);
1290 db_prepare(&ins,
1291 "REPLACE INTO config(name, value)"
1292 " VALUES('logo-image',:bytes)"
1293 );
1294 db_bind_blob(&ins, ":bytes", &img);
1295 db_step(&ins);
1296 db_finalize(&ins);
1297 db_multi_exec(
1298 "REPLACE INTO config(name, value) VALUES('logo-mimetype',%Q)",
1299 zMime
1300 );
1301 db_end_transaction(0);
1302 cgi_redirect("setup_logo");
1303 }else if( P("clr")!=0 ){
1304
--- src/setup.c
+++ src/setup.c
@@ -354,12 +354,12 @@
354 style_footer();
355 return;
356 }
357 login_verify_csrf_secret();
358 db_multi_exec(
359 "REPLACE INTO user(uid,login,info,pw,cap,mtime) "
360 "VALUES(nullif(%d,0),%Q,%Q,%Q,'%s',now())",
361 uid, P("login"), P("info"), zPw, zCap
362 );
363 if( atoi(PD("all","0"))>0 ){
364 Blob sql;
365 char *zErr = 0;
@@ -375,11 +375,12 @@
375 blob_appendf(&sql,
376 "UPDATE user SET login=%Q,"
377 " pw=coalesce(shared_secret(%Q,%Q,"
378 "(SELECT value FROM config WHERE name='project-code')),pw),"
379 " info=%Q,"
380 " cap=%Q,"
381 " mtime=now()"
382 " WHERE login=%Q;",
383 zLogin, P("pw"), zLogin, P("info"), zCap,
384 zOldLogin
385 );
386 login_group_sql(blob_str(&sql), "<li> ", " </li>\n", &zErr);
@@ -1286,18 +1287,18 @@
1287 if( P("set")!=0 && zMime && zMime[0] && szImg>0 ){
1288 Blob img;
1289 Stmt ins;
1290 blob_init(&img, aImg, szImg);
1291 db_prepare(&ins,
1292 "REPLACE INTO config(name,value,mtime)"
1293 " VALUES('logo-image',:bytes,now())"
1294 );
1295 db_bind_blob(&ins, ":bytes", &img);
1296 db_step(&ins);
1297 db_finalize(&ins);
1298 db_multi_exec(
1299 "REPLACE INTO config(name,value,mtime) VALUES('logo-mimetype',%Q,now())",
1300 zMime
1301 );
1302 db_end_transaction(0);
1303 cgi_redirect("setup_logo");
1304 }else if( P("clr")!=0 ){
1305
+15 -1
--- src/shun.c
+++ src/shun.c
@@ -81,17 +81,31 @@
8181
@ <b>fossil rebuild</b> command-line before the artifact content
8282
@ can pulled in from other respositories.</p>
8383
}
8484
}
8585
if( zUuid && P("add") ){
86
+ int rid, tagid;
8687
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);
8891
@ <p class="shunned">Artifact
8992
@ <a href="%s(g.zTop)/artifact/%s(zUuid)">%s(zUuid)</a> has been
9093
@ shunned. It will no longer be pushed.
9194
@ It will be removed from the repository the next time the respository
9295
@ 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
+ }
93107
}
94108
@ <p>A shunned artifact will not be pushed nor accepted in a pull and the
95109
@ artifact content will be purged from the repository the next time the
96110
@ repository is rebuilt. A list of shunned artifacts can be seen at the
97111
@ bottom of this page.</p>
98112
--- src/shun.c
+++ src/shun.c
@@ -81,17 +81,31 @@
81 @ <b>fossil rebuild</b> command-line before the artifact content
82 @ can pulled in from other respositories.</p>
83 }
84 }
85 if( zUuid && P("add") ){
 
86 login_verify_csrf_secret();
87 db_multi_exec("INSERT OR IGNORE INTO shun VALUES('%s')", zUuid);
 
 
88 @ <p class="shunned">Artifact
89 @ <a href="%s(g.zTop)/artifact/%s(zUuid)">%s(zUuid)</a> has been
90 @ shunned. It will no longer be pushed.
91 @ It will be removed from the repository the next time the respository
92 @ is rebuilt using the <b>fossil rebuild</b> command-line</p>
 
 
 
 
 
 
 
 
 
 
 
93 }
94 @ <p>A shunned artifact will not be pushed nor accepted in a pull and the
95 @ artifact content will be purged from the repository the next time the
96 @ repository is rebuilt. A list of shunned artifacts can be seen at the
97 @ bottom of this page.</p>
98
--- src/shun.c
+++ src/shun.c
@@ -81,17 +81,31 @@
81 @ <b>fossil rebuild</b> command-line before the artifact content
82 @ can pulled in from other respositories.</p>
83 }
84 }
85 if( zUuid && P("add") ){
86 int rid, tagid;
87 login_verify_csrf_secret();
88 db_multi_exec(
89 "INSERT OR IGNORE INTO shun(uuid,mtime)"
90 " VALUES('%s', now())", zUuid);
91 @ <p class="shunned">Artifact
92 @ <a href="%s(g.zTop)/artifact/%s(zUuid)">%s(zUuid)</a> has been
93 @ shunned. It will no longer be pushed.
94 @ It will be removed from the repository the next time the respository
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 }
107 }
108 @ <p>A shunned artifact will not be pushed nor accepted in a pull and the
109 @ artifact content will be purged from the repository the next time the
110 @ repository is rebuilt. A list of shunned artifacts can be seen at the
111 @ bottom of this page.</p>
112
+27 -18
--- src/skins.c
+++ src/skins.c
@@ -25,11 +25,12 @@
2525
/*
2626
** A black-and-white theme with the project title in a bar across the top
2727
** and no logo image.
2828
*/
2929
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 */
3132
@ body {
3233
@ margin: 0ex 1ex;
3334
@ padding: 0px;
3435
@ background-color: white;
3536
@ font-family: sans-serif;
@@ -152,11 +153,11 @@
152153
@ table.label-value th {
153154
@ vertical-align: top;
154155
@ text-align: right;
155156
@ padding: 0.2ex 2ex;
156157
@ }');
157
-@ REPLACE INTO config VALUES('header','<html>
158
+@ REPLACE INTO config(name,mtime,value) VALUES('header',now(),'<html>
158159
@ <head>
159160
@ <title>$<project_name>: $<title></title>
160161
@ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
161162
@ href="$home/timeline.rss">
162163
@ <link rel="stylesheet" href="$home/style.css?blackwhite" type="text/css"
@@ -204,11 +205,12 @@
204205
@ } else {
205206
@ html "<a href=''$home/login''>Login</a> "
206207
@ }
207208
@ </th1></div>
208209
@ ');
209
-@ REPLACE INTO config VALUES('footer','<div class="footer">
210
+@ REPLACE INTO config(name,mtime,value)
211
+@ VALUES('footer',now(),'<div class="footer">
210212
@ Fossil version $manifest_version $manifest_date
211213
@ </div>
212214
@ </body></html>
213215
@ ');
214216
;
@@ -216,11 +218,12 @@
216218
/*
217219
** A tan theme with the project title above the user identification
218220
** and no logo image.
219221
*/
220222
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 */
222225
@ body {
223226
@ margin: 0ex 0ex;
224227
@ padding: 0px;
225228
@ background-color: #fef3bc;
226229
@ font-family: sans-serif;
@@ -354,11 +357,11 @@
354357
@ vertical-align: top;
355358
@ text-align: right;
356359
@ padding: 0.2ex 2ex;
357360
@ }
358361
@ ');
359
-@ REPLACE INTO config VALUES('header','<html>
362
+@ REPLACE INTO config(name,mtime,value) VALUES('header',now(),'<html>
360363
@ <head>
361364
@ <title>$<project_name>: $<title></title>
362365
@ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
363366
@ href="$home/timeline.rss">
364367
@ <link rel="stylesheet" href="$home/style.css?tan" type="text/css"
@@ -405,11 +408,12 @@
405408
@ } else {
406409
@ html "<a href=''$home/login''>Login</a> "
407410
@ }
408411
@ </th1></div>
409412
@ ');
410
-@ REPLACE INTO config VALUES('footer','<div class="footer">
413
+@ REPLACE INTO config(name,mtime,value)
414
+@ VALUES('footer',now(),'<div class="footer">
411415
@ Fossil version $manifest_version $manifest_date
412416
@ </div>
413417
@ </body></html>
414418
@ ');
415419
;
@@ -417,11 +421,12 @@
417421
/*
418422
** Black letters on a white or cream background with the main menu
419423
** stuck on the left-hand side.
420424
*/
421425
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 */
423428
@ body {
424429
@ margin:0px 0px 0px 0px;
425430
@ padding:0px;
426431
@ font-family:verdana, arial, helvetica, "sans serif";
427432
@ color:#333;
@@ -586,11 +591,11 @@
586591
@ table.label-value th {
587592
@ vertical-align: top;
588593
@ text-align: right;
589594
@ padding: 0.2ex 2ex;
590595
@ }');
591
-@ REPLACE INTO config VALUES('header','<html>
596
+@ REPLACE INTO config(name,mtime,value) VALUES('header',now(),'<html>
592597
@ <head>
593598
@ <title>$<project_name>: $<title></title>
594599
@ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
595600
@ href="$home/timeline.rss">
596601
@ <link rel="stylesheet" href="$home/style.css?black2" type="text/css"
@@ -640,11 +645,11 @@
640645
@ html "<li><a href=''$home/login''>Login</a></li>"
641646
@ }
642647
@ </th1></ul></div>
643648
@ <div id="container">
644649
@ ');
645
-@ REPLACE INTO config VALUES('footer','</div>
650
+@ REPLACE INTO config(name,mtime,value) VALUES('footer',now(),'</div>
646651
@ <div class="footer">
647652
@ Fossil version $manifest_version $manifest_date
648653
@ </div>
649654
@ </body></html>
650655
@ ');
@@ -653,11 +658,12 @@
653658
654659
/*
655660
** Gradients and rounded corners.
656661
*/
657662
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 */
659665
@ html {
660666
@ min-height: 100%;
661667
@ }
662668
@ body {
663669
@ margin: 0ex 1ex;
@@ -880,11 +886,11 @@
880886
@ }
881887
@
882888
@ textarea {
883889
@ font-size: 1em;
884890
@ }');
885
-@ REPLACE INTO config VALUES('header','<html>
891
+@ REPLACE INTO config(name,mtime,value) VALUES('header',now(),'<html>
886892
@ <head>
887893
@ <title>$<project_name>: $<title></title>
888894
@ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
889895
@ href="$home/timeline.rss">
890896
@ <link rel="stylesheet" href="$home/style.css?black2" type="text/css"
@@ -934,11 +940,11 @@
934940
@ html "<a href=''$home/login''>Login</a>"
935941
@ }
936942
@ </th1></ul></div>
937943
@ <div id="container">
938944
@ ');
939
-@ REPLACE INTO config VALUES('footer','</div>
945
+@ REPLACE INTO config(name,mtime,value) VALUES('footer',now(),'</div>
940946
@ <div class="footer">
941947
@ Fossil version $manifest_version $manifest_date
942948
@ </div>
943949
@ </body></html>
944950
@ ');
@@ -984,17 +990,20 @@
984990
** Memory to hold the returned string is obtained from malloc.
985991
*/
986992
static char *getSkin(int useDefault){
987993
Blob val;
988994
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",
990997
useDefault ? zDefaultCSS : db_get("css", (char*)zDefaultCSS)
991998
);
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",
9931001
useDefault ? zDefaultHeader : db_get("header", (char*)zDefaultHeader)
9941002
);
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",
9961005
useDefault ? zDefaultFooter : db_get("footer", (char*)zDefaultFooter)
9971006
);
9981007
return blob_str(&val);
9991008
}
10001009
@@ -1048,11 +1057,11 @@
10481057
if( db_exists("SELECT 1 FROM config WHERE name=%Q", zName)
10491058
|| strcmp(zName, "Default")==0 ){
10501059
zErr = mprintf("Skin name \"%h\" already exists. "
10511060
"Choose a different name.", P("sn"));
10521061
}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())",
10541063
zName, zCurrent
10551064
);
10561065
}
10571066
}
10581067
@@ -1069,13 +1078,13 @@
10691078
seen = db_exists("SELECT 1 FROM config WHERE name GLOB 'skin:*'"
10701079
" AND value=%Q", zCurrent);
10711080
}
10721081
if( !seen ){
10731082
db_multi_exec(
1074
- "INSERT INTO config VALUES("
1083
+ "INSERT INTO config(name,value,mtime) VALUES("
10751084
" strftime('skin:Backup On %%Y-%%m-%%d %%H:%%M:%%S'),"
1076
- " %Q)", zCurrent
1085
+ " %Q,now())", zCurrent
10771086
);
10781087
}
10791088
seen = 0;
10801089
for(i=0; i<sizeof(aBuiltinSkin)/sizeof(aBuiltinSkin[0]); i++){
10811090
if( strcmp(aBuiltinSkin[i].zName, z)==0 ){
10821091
--- src/skins.c
+++ src/skins.c
@@ -25,11 +25,12 @@
25 /*
26 ** A black-and-white theme with the project title in a bar across the top
27 ** and no logo image.
28 */
29 static const char zBuiltinSkin1[] =
30 @ REPLACE INTO config VALUES('css','/* General settings for the entire page */
 
31 @ body {
32 @ margin: 0ex 1ex;
33 @ padding: 0px;
34 @ background-color: white;
35 @ font-family: sans-serif;
@@ -152,11 +153,11 @@
152 @ table.label-value th {
153 @ vertical-align: top;
154 @ text-align: right;
155 @ padding: 0.2ex 2ex;
156 @ }');
157 @ REPLACE INTO config VALUES('header','<html>
158 @ <head>
159 @ <title>$<project_name>: $<title></title>
160 @ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
161 @ href="$home/timeline.rss">
162 @ <link rel="stylesheet" href="$home/style.css?blackwhite" type="text/css"
@@ -204,11 +205,12 @@
204 @ } else {
205 @ html "<a href=''$home/login''>Login</a> "
206 @ }
207 @ </th1></div>
208 @ ');
209 @ REPLACE INTO config VALUES('footer','<div class="footer">
 
210 @ Fossil version $manifest_version $manifest_date
211 @ </div>
212 @ </body></html>
213 @ ');
214 ;
@@ -216,11 +218,12 @@
216 /*
217 ** A tan theme with the project title above the user identification
218 ** and no logo image.
219 */
220 static const char zBuiltinSkin2[] =
221 @ REPLACE INTO config VALUES('css','/* General settings for the entire page */
 
222 @ body {
223 @ margin: 0ex 0ex;
224 @ padding: 0px;
225 @ background-color: #fef3bc;
226 @ font-family: sans-serif;
@@ -354,11 +357,11 @@
354 @ vertical-align: top;
355 @ text-align: right;
356 @ padding: 0.2ex 2ex;
357 @ }
358 @ ');
359 @ REPLACE INTO config VALUES('header','<html>
360 @ <head>
361 @ <title>$<project_name>: $<title></title>
362 @ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
363 @ href="$home/timeline.rss">
364 @ <link rel="stylesheet" href="$home/style.css?tan" type="text/css"
@@ -405,11 +408,12 @@
405 @ } else {
406 @ html "<a href=''$home/login''>Login</a> "
407 @ }
408 @ </th1></div>
409 @ ');
410 @ REPLACE INTO config VALUES('footer','<div class="footer">
 
411 @ Fossil version $manifest_version $manifest_date
412 @ </div>
413 @ </body></html>
414 @ ');
415 ;
@@ -417,11 +421,12 @@
417 /*
418 ** Black letters on a white or cream background with the main menu
419 ** stuck on the left-hand side.
420 */
421 static const char zBuiltinSkin3[] =
422 @ REPLACE INTO config VALUES('css','/* General settings for the entire page */
 
423 @ body {
424 @ margin:0px 0px 0px 0px;
425 @ padding:0px;
426 @ font-family:verdana, arial, helvetica, "sans serif";
427 @ color:#333;
@@ -586,11 +591,11 @@
586 @ table.label-value th {
587 @ vertical-align: top;
588 @ text-align: right;
589 @ padding: 0.2ex 2ex;
590 @ }');
591 @ REPLACE INTO config VALUES('header','<html>
592 @ <head>
593 @ <title>$<project_name>: $<title></title>
594 @ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
595 @ href="$home/timeline.rss">
596 @ <link rel="stylesheet" href="$home/style.css?black2" type="text/css"
@@ -640,11 +645,11 @@
640 @ html "<li><a href=''$home/login''>Login</a></li>"
641 @ }
642 @ </th1></ul></div>
643 @ <div id="container">
644 @ ');
645 @ REPLACE INTO config VALUES('footer','</div>
646 @ <div class="footer">
647 @ Fossil version $manifest_version $manifest_date
648 @ </div>
649 @ </body></html>
650 @ ');
@@ -653,11 +658,12 @@
653
654 /*
655 ** Gradients and rounded corners.
656 */
657 static const char zBuiltinSkin4[] =
658 @ REPLACE INTO config VALUES('css','/* General settings for the entire page */
 
659 @ html {
660 @ min-height: 100%;
661 @ }
662 @ body {
663 @ margin: 0ex 1ex;
@@ -880,11 +886,11 @@
880 @ }
881 @
882 @ textarea {
883 @ font-size: 1em;
884 @ }');
885 @ REPLACE INTO config VALUES('header','<html>
886 @ <head>
887 @ <title>$<project_name>: $<title></title>
888 @ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
889 @ href="$home/timeline.rss">
890 @ <link rel="stylesheet" href="$home/style.css?black2" type="text/css"
@@ -934,11 +940,11 @@
934 @ html "<a href=''$home/login''>Login</a>"
935 @ }
936 @ </th1></ul></div>
937 @ <div id="container">
938 @ ');
939 @ REPLACE INTO config VALUES('footer','</div>
940 @ <div class="footer">
941 @ Fossil version $manifest_version $manifest_date
942 @ </div>
943 @ </body></html>
944 @ ');
@@ -984,17 +990,20 @@
984 ** Memory to hold the returned string is obtained from malloc.
985 */
986 static char *getSkin(int useDefault){
987 Blob val;
988 blob_zero(&val);
989 blob_appendf(&val, "REPLACE INTO config VALUES('css',%Q);\n",
 
990 useDefault ? zDefaultCSS : db_get("css", (char*)zDefaultCSS)
991 );
992 blob_appendf(&val, "REPLACE INTO config VALUES('header',%Q);\n",
 
993 useDefault ? zDefaultHeader : db_get("header", (char*)zDefaultHeader)
994 );
995 blob_appendf(&val, "REPLACE INTO config VALUES('footer',%Q);\n",
 
996 useDefault ? zDefaultFooter : db_get("footer", (char*)zDefaultFooter)
997 );
998 return blob_str(&val);
999 }
1000
@@ -1048,11 +1057,11 @@
1048 if( db_exists("SELECT 1 FROM config WHERE name=%Q", zName)
1049 || strcmp(zName, "Default")==0 ){
1050 zErr = mprintf("Skin name \"%h\" already exists. "
1051 "Choose a different name.", P("sn"));
1052 }else{
1053 db_multi_exec("INSERT INTO config VALUES(%Q,%Q)",
1054 zName, zCurrent
1055 );
1056 }
1057 }
1058
@@ -1069,13 +1078,13 @@
1069 seen = db_exists("SELECT 1 FROM config WHERE name GLOB 'skin:*'"
1070 " AND value=%Q", zCurrent);
1071 }
1072 if( !seen ){
1073 db_multi_exec(
1074 "INSERT INTO config VALUES("
1075 " strftime('skin:Backup On %%Y-%%m-%%d %%H:%%M:%%S'),"
1076 " %Q)", zCurrent
1077 );
1078 }
1079 seen = 0;
1080 for(i=0; i<sizeof(aBuiltinSkin)/sizeof(aBuiltinSkin[0]); i++){
1081 if( strcmp(aBuiltinSkin[i].zName, z)==0 ){
1082
--- src/skins.c
+++ src/skins.c
@@ -25,11 +25,12 @@
25 /*
26 ** A black-and-white theme with the project title in a bar across the top
27 ** and no logo image.
28 */
29 static const char zBuiltinSkin1[] =
30 @ REPLACE INTO config(name,mtime,value)
31 @ VALUES('css',now(),'/* General settings for the entire page */
32 @ body {
33 @ margin: 0ex 1ex;
34 @ padding: 0px;
35 @ background-color: white;
36 @ font-family: sans-serif;
@@ -152,11 +153,11 @@
153 @ table.label-value th {
154 @ vertical-align: top;
155 @ text-align: right;
156 @ padding: 0.2ex 2ex;
157 @ }');
158 @ REPLACE INTO config(name,mtime,value) VALUES('header',now(),'<html>
159 @ <head>
160 @ <title>$<project_name>: $<title></title>
161 @ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
162 @ href="$home/timeline.rss">
163 @ <link rel="stylesheet" href="$home/style.css?blackwhite" type="text/css"
@@ -204,11 +205,12 @@
205 @ } else {
206 @ html "<a href=''$home/login''>Login</a> "
207 @ }
208 @ </th1></div>
209 @ ');
210 @ REPLACE INTO config(name,mtime,value)
211 @ VALUES('footer',now(),'<div class="footer">
212 @ Fossil version $manifest_version $manifest_date
213 @ </div>
214 @ </body></html>
215 @ ');
216 ;
@@ -216,11 +218,12 @@
218 /*
219 ** A tan theme with the project title above the user identification
220 ** and no logo image.
221 */
222 static const char zBuiltinSkin2[] =
223 @ REPLACE INTO config(name,mtime,value)
224 @ VALUES('css',now(),'/* General settings for the entire page */
225 @ body {
226 @ margin: 0ex 0ex;
227 @ padding: 0px;
228 @ background-color: #fef3bc;
229 @ font-family: sans-serif;
@@ -354,11 +357,11 @@
357 @ vertical-align: top;
358 @ text-align: right;
359 @ padding: 0.2ex 2ex;
360 @ }
361 @ ');
362 @ REPLACE INTO config(name,mtime,value) VALUES('header',now(),'<html>
363 @ <head>
364 @ <title>$<project_name>: $<title></title>
365 @ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
366 @ href="$home/timeline.rss">
367 @ <link rel="stylesheet" href="$home/style.css?tan" type="text/css"
@@ -405,11 +408,12 @@
408 @ } else {
409 @ html "<a href=''$home/login''>Login</a> "
410 @ }
411 @ </th1></div>
412 @ ');
413 @ REPLACE INTO config(name,mtime,value)
414 @ VALUES('footer',now(),'<div class="footer">
415 @ Fossil version $manifest_version $manifest_date
416 @ </div>
417 @ </body></html>
418 @ ');
419 ;
@@ -417,11 +421,12 @@
421 /*
422 ** Black letters on a white or cream background with the main menu
423 ** stuck on the left-hand side.
424 */
425 static const char zBuiltinSkin3[] =
426 @ REPLACE INTO config(name,mtime,value)
427 @ VALUES('css',now(),'/* General settings for the entire page */
428 @ body {
429 @ margin:0px 0px 0px 0px;
430 @ padding:0px;
431 @ font-family:verdana, arial, helvetica, "sans serif";
432 @ color:#333;
@@ -586,11 +591,11 @@
591 @ table.label-value th {
592 @ vertical-align: top;
593 @ text-align: right;
594 @ padding: 0.2ex 2ex;
595 @ }');
596 @ REPLACE INTO config(name,mtime,value) VALUES('header',now(),'<html>
597 @ <head>
598 @ <title>$<project_name>: $<title></title>
599 @ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
600 @ href="$home/timeline.rss">
601 @ <link rel="stylesheet" href="$home/style.css?black2" type="text/css"
@@ -640,11 +645,11 @@
645 @ html "<li><a href=''$home/login''>Login</a></li>"
646 @ }
647 @ </th1></ul></div>
648 @ <div id="container">
649 @ ');
650 @ REPLACE INTO config(name,mtime,value) VALUES('footer',now(),'</div>
651 @ <div class="footer">
652 @ Fossil version $manifest_version $manifest_date
653 @ </div>
654 @ </body></html>
655 @ ');
@@ -653,11 +658,12 @@
658
659 /*
660 ** Gradients and rounded corners.
661 */
662 static const char zBuiltinSkin4[] =
663 @ REPLACE INTO config(name,mtime,value)
664 @ VALUES('css',now(),'/* General settings for the entire page */
665 @ html {
666 @ min-height: 100%;
667 @ }
668 @ body {
669 @ margin: 0ex 1ex;
@@ -880,11 +886,11 @@
886 @ }
887 @
888 @ textarea {
889 @ font-size: 1em;
890 @ }');
891 @ REPLACE INTO config(name,mtime,value) VALUES('header',now(),'<html>
892 @ <head>
893 @ <title>$<project_name>: $<title></title>
894 @ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
895 @ href="$home/timeline.rss">
896 @ <link rel="stylesheet" href="$home/style.css?black2" type="text/css"
@@ -934,11 +940,11 @@
940 @ html "<a href=''$home/login''>Login</a>"
941 @ }
942 @ </th1></ul></div>
943 @ <div id="container">
944 @ ');
945 @ REPLACE INTO config(name,mtime,value) VALUES('footer',now(),'</div>
946 @ <div class="footer">
947 @ Fossil version $manifest_version $manifest_date
948 @ </div>
949 @ </body></html>
950 @ ');
@@ -984,17 +990,20 @@
990 ** Memory to hold the returned string is obtained from malloc.
991 */
992 static char *getSkin(int useDefault){
993 Blob val;
994 blob_zero(&val);
995 blob_appendf(&val,
996 "REPLACE INTO config(name,value,mtime) VALUES('css',%Q,now());\n",
997 useDefault ? zDefaultCSS : db_get("css", (char*)zDefaultCSS)
998 );
999 blob_appendf(&val,
1000 "REPLACE INTO config(name,value,mtime) VALUES('header',%Q,now());\n",
1001 useDefault ? zDefaultHeader : db_get("header", (char*)zDefaultHeader)
1002 );
1003 blob_appendf(&val,
1004 "REPLACE INTO config(name,value,mtime) VALUES('footer',%Q,now());\n",
1005 useDefault ? zDefaultFooter : db_get("footer", (char*)zDefaultFooter)
1006 );
1007 return blob_str(&val);
1008 }
1009
@@ -1048,11 +1057,11 @@
1057 if( db_exists("SELECT 1 FROM config WHERE name=%Q", zName)
1058 || strcmp(zName, "Default")==0 ){
1059 zErr = mprintf("Skin name \"%h\" already exists. "
1060 "Choose a different name.", P("sn"));
1061 }else{
1062 db_multi_exec("INSERT INTO config(name,value,mtime) VALUES(%Q,%Q,now())",
1063 zName, zCurrent
1064 );
1065 }
1066 }
1067
@@ -1069,13 +1078,13 @@
1078 seen = db_exists("SELECT 1 FROM config WHERE name GLOB 'skin:*'"
1079 " AND value=%Q", zCurrent);
1080 }
1081 if( !seen ){
1082 db_multi_exec(
1083 "INSERT INTO config(name,value,mtime) VALUES("
1084 " strftime('skin:Backup On %%Y-%%m-%%d %%H:%%M:%%S'),"
1085 " %Q,now())", zCurrent
1086 );
1087 }
1088 seen = 0;
1089 for(i=0; i<sizeof(aBuiltinSkin)/sizeof(aBuiltinSkin[0]); i++){
1090 if( strcmp(aBuiltinSkin[i].zName, z)==0 ){
1091
+9 -8
--- src/user.c
+++ src/user.c
@@ -204,12 +204,12 @@
204204
}else{
205205
prompt_for_password("password: ", &passwd, 1);
206206
}
207207
zPw = sha1_shared_secret(blob_str(&passwd), blob_str(&login), 0);
208208
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())",
211211
&login, zPw, &caps, &contact
212212
);
213213
free(zPw);
214214
}else if( n>=2 && strncmp(g.argv[2],"default",n)==0 ){
215215
user_select();
@@ -249,11 +249,12 @@
249249
}
250250
if( blob_size(&pw)==0 ){
251251
printf("password unchanged\n");
252252
}else{
253253
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);
255256
free(zSecret);
256257
}
257258
}else if( n>=2 && strncmp(g.argv[2],"capabilities",2)==0 ){
258259
int uid;
259260
if( g.argc!=4 && g.argc!=5 ){
@@ -263,12 +264,12 @@
263264
if( uid==0 ){
264265
fossil_fatal("no such user: %s", g.argv[3]);
265266
}
266267
if( g.argc==5 ){
267268
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
270271
);
271272
}
272273
printf("%s\n", db_text(0, "SELECT cap FROM user WHERE uid=%d", uid));
273274
}else{
274275
fossil_panic("user subcommand should be one of: "
@@ -340,12 +341,12 @@
340341
db_finalize(&s);
341342
}
342343
343344
if( g.userUid==0 ){
344345
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())"
347348
);
348349
g.userUid = db_last_insert_rowid();
349350
g.zLogin = "anonymous";
350351
}
351352
}
@@ -364,11 +365,11 @@
364365
if( g.argc!=3 ) usage("REPOSITORY");
365366
db_open_repository(g.argv[2]);
366367
sqlite3_create_function(g.db, "shared_secret", 2, SQLITE_UTF8, 0,
367368
sha1_shared_secret_sql_function, 0, 0);
368369
db_multi_exec(
369
- "UPDATE user SET pw=shared_secret(pw,login)"
370
+ "UPDATE user SET pw=shared_secret(pw,login), mtime=now()"
370371
" WHERE length(pw)>0 AND length(pw)!=40"
371372
);
372373
}
373374
374375
/*
375376
--- src/user.c
+++ src/user.c
@@ -204,12 +204,12 @@
204 }else{
205 prompt_for_password("password: ", &passwd, 1);
206 }
207 zPw = sha1_shared_secret(blob_str(&passwd), blob_str(&login), 0);
208 db_multi_exec(
209 "INSERT INTO user(login,pw,cap,info)"
210 "VALUES(%B,%Q,%B,%B)",
211 &login, zPw, &caps, &contact
212 );
213 free(zPw);
214 }else if( n>=2 && strncmp(g.argv[2],"default",n)==0 ){
215 user_select();
@@ -249,11 +249,12 @@
249 }
250 if( blob_size(&pw)==0 ){
251 printf("password unchanged\n");
252 }else{
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);
 
255 free(zSecret);
256 }
257 }else if( n>=2 && strncmp(g.argv[2],"capabilities",2)==0 ){
258 int uid;
259 if( g.argc!=4 && g.argc!=5 ){
@@ -263,12 +264,12 @@
263 if( uid==0 ){
264 fossil_fatal("no such user: %s", g.argv[3]);
265 }
266 if( g.argc==5 ){
267 db_multi_exec(
268 "UPDATE user SET cap=%Q WHERE uid=%d", g.argv[4],
269 uid
270 );
271 }
272 printf("%s\n", db_text(0, "SELECT cap FROM user WHERE uid=%d", uid));
273 }else{
274 fossil_panic("user subcommand should be one of: "
@@ -340,12 +341,12 @@
340 db_finalize(&s);
341 }
342
343 if( g.userUid==0 ){
344 db_multi_exec(
345 "INSERT INTO user(login, pw, cap, info)"
346 "VALUES('anonymous', '', 'cfghjkmnoqw', '')"
347 );
348 g.userUid = db_last_insert_rowid();
349 g.zLogin = "anonymous";
350 }
351 }
@@ -364,11 +365,11 @@
364 if( g.argc!=3 ) usage("REPOSITORY");
365 db_open_repository(g.argv[2]);
366 sqlite3_create_function(g.db, "shared_secret", 2, SQLITE_UTF8, 0,
367 sha1_shared_secret_sql_function, 0, 0);
368 db_multi_exec(
369 "UPDATE user SET pw=shared_secret(pw,login)"
370 " WHERE length(pw)>0 AND length(pw)!=40"
371 );
372 }
373
374 /*
375
--- src/user.c
+++ src/user.c
@@ -204,12 +204,12 @@
204 }else{
205 prompt_for_password("password: ", &passwd, 1);
206 }
207 zPw = sha1_shared_secret(blob_str(&passwd), blob_str(&login), 0);
208 db_multi_exec(
209 "INSERT INTO user(login,pw,cap,info,mtime)"
210 "VALUES(%B,%Q,%B,%B,now())",
211 &login, zPw, &caps, &contact
212 );
213 free(zPw);
214 }else if( n>=2 && strncmp(g.argv[2],"default",n)==0 ){
215 user_select();
@@ -249,11 +249,12 @@
249 }
250 if( blob_size(&pw)==0 ){
251 printf("password unchanged\n");
252 }else{
253 char *zSecret = sha1_shared_secret(blob_str(&pw), g.argv[3], 0);
254 db_multi_exec("UPDATE user SET pw=%Q, mtime=now() WHERE uid=%d",
255 zSecret, uid);
256 free(zSecret);
257 }
258 }else if( n>=2 && strncmp(g.argv[2],"capabilities",2)==0 ){
259 int uid;
260 if( g.argc!=4 && g.argc!=5 ){
@@ -263,12 +264,12 @@
264 if( uid==0 ){
265 fossil_fatal("no such user: %s", g.argv[3]);
266 }
267 if( g.argc==5 ){
268 db_multi_exec(
269 "UPDATE user SET cap=%Q, mtime=now() WHERE uid=%d",
270 g.argv[4], uid
271 );
272 }
273 printf("%s\n", db_text(0, "SELECT cap FROM user WHERE uid=%d", uid));
274 }else{
275 fossil_panic("user subcommand should be one of: "
@@ -340,12 +341,12 @@
341 db_finalize(&s);
342 }
343
344 if( g.userUid==0 ){
345 db_multi_exec(
346 "INSERT INTO user(login, pw, cap, info, mtime)"
347 "VALUES('anonymous', '', 'cfghjkmnoqw', '', now())"
348 );
349 g.userUid = db_last_insert_rowid();
350 g.zLogin = "anonymous";
351 }
352 }
@@ -364,11 +365,11 @@
365 if( g.argc!=3 ) usage("REPOSITORY");
366 db_open_repository(g.argv[2]);
367 sqlite3_create_function(g.db, "shared_secret", 2, SQLITE_UTF8, 0,
368 sha1_shared_secret_sql_function, 0, 0);
369 db_multi_exec(
370 "UPDATE user SET pw=shared_secret(pw,login), mtime=now()"
371 " WHERE length(pw)>0 AND length(pw)!=40"
372 );
373 }
374
375 /*
376
+36 -18
--- src/xfer.c
+++ src/xfer.c
@@ -454,11 +454,11 @@
454454
" (SELECT uuid FROM delta, blob"
455455
" WHERE delta.rid=:rid AND delta.srcid=blob.rid)"
456456
" FROM blob"
457457
" WHERE rid=:rid"
458458
" AND size>=0"
459
- " AND uuid NOT IN shun"
459
+ " AND NOT EXISTS(SELECT 1 FROM shun WHERE shun.uuid=blob.uuid)"
460460
);
461461
db_bind_int(&q1, ":rid", rid);
462462
rc = db_step(&q1);
463463
if( rc==SQLITE_ROW ){
464464
zUuid = db_column_text(&q1, 0);
@@ -738,13 +738,16 @@
738738
}
739739
db_finalize(&q);
740740
}
741741
742742
/*
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.
744747
*/
745
-static void send_config_card(Xfer *pXfer, const char *zName){
748
+static void send_legacy_config_card(Xfer *pXfer, const char *zName){
746749
if( zName[0]!='@' ){
747750
Blob val;
748751
blob_zero(&val);
749752
db_blob(&val, "SELECT value FROM config WHERE name=%Q", zName);
750753
if( blob_size(&val)>0 ){
@@ -760,11 +763,10 @@
760763
blob_appendf(pXfer->pOut, "config %s %d\n%s\n", zName,
761764
blob_size(&content), blob_str(&content));
762765
blob_reset(&content);
763766
}
764767
}
765
-
766768
767769
/*
768770
** Called when there is an attempt to transfer private content to and
769771
** from a server without authorization.
770772
*/
@@ -1007,11 +1009,11 @@
10071009
*/
10081010
if( blob_eq(&xfer.aToken[0], "login")
10091011
&& xfer.nToken==4
10101012
){
10111013
if( disableLogin ){
1012
- g.okRead = g.okWrite = g.okPrivate = 1;
1014
+ g.okRead = g.okWrite = g.okPrivate = g.okAdmin = 1;
10131015
}else{
10141016
if( check_tail_hash(&xfer.aToken[2], xfer.pIn)
10151017
|| check_login(&xfer.aToken[1], &xfer.aToken[2], &xfer.aToken[3])
10161018
){
10171019
cgi_reset_content();
@@ -1029,12 +1031,19 @@
10291031
if( blob_eq(&xfer.aToken[0], "reqconfig")
10301032
&& xfer.nToken==2
10311033
){
10321034
if( g.okRead ){
10331035
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);
10361045
}
10371046
}
10381047
}else
10391048
10401049
/* config NAME SIZE \n CONTENT
@@ -1052,11 +1061,11 @@
10521061
cgi_reset_content();
10531062
@ error not\sauthorized\sto\spush\sconfiguration
10541063
nErr++;
10551064
break;
10561065
}
1057
- if( !recvConfig ){
1066
+ if( !recvConfig && zName[0]=='@' ){
10581067
configure_prepare_to_receive(0);
10591068
recvConfig = 1;
10601069
}
10611070
configure_receive(zName, &content, CONFIGSET_ALL);
10621071
blob_reset(&content);
@@ -1185,14 +1194,14 @@
11851194
** gdb fossil
11861195
** r test-xfer out.txt
11871196
*/
11881197
void cmd_test_xfer(void){
11891198
int notUsed;
1199
+ db_find_and_open_repository(0,0);
11901200
if( g.argc!=2 && g.argc!=3 ){
11911201
usage("?MESSAGEFILE?");
11921202
}
1193
- db_must_be_within_tree();
11941203
blob_zero(&g.cgiIn);
11951204
blob_read_from_file(&g.cgiIn, g.argc==2 ? "-" : g.argv[2]);
11961205
disableLogin = 1;
11971206
page_xfer();
11981207
printf("%s\n", cgi_extract_content(&notUsed));
@@ -1331,25 +1340,32 @@
13311340
while( zName ){
13321341
blob_appendf(&send, "reqconfig %s\n", zName);
13331342
zName = configure_next_name(configRcvMask);
13341343
nCardSent++;
13351344
}
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);
13381350
}
13391351
origConfigRcvMask = configRcvMask;
13401352
configRcvMask = 0;
13411353
}
13421354
13431355
/* Send configuration parameters being pushed */
13441356
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);
13511367
}
13521368
configSendMask = 0;
13531369
}
13541370
13551371
/* Append randomness to the end of the message. This makes all
@@ -1647,11 +1663,13 @@
16471663
break;
16481664
}
16491665
blobarray_reset(xfer.aToken, xfer.nToken);
16501666
blob_reset(&xfer.line);
16511667
}
1652
- if( origConfigRcvMask & (CONFIGSET_TKT|CONFIGSET_USER) ){
1668
+ if( (configRcvMask & (CONFIGSET_USER|CONFIGSET_TKT))!=0
1669
+ && (configRcvMask & CONFIGSET_OLDFORMAT)!=0
1670
+ ){
16531671
configure_finalize_receive();
16541672
}
16551673
origConfigRcvMask = 0;
16561674
if( nCardRcvd>0 ){
16571675
fossil_print(zValueFormat, "Received:",
16581676
--- src/xfer.c
+++ src/xfer.c
@@ -454,11 +454,11 @@
454 " (SELECT uuid FROM delta, blob"
455 " WHERE delta.rid=:rid AND delta.srcid=blob.rid)"
456 " FROM blob"
457 " WHERE rid=:rid"
458 " AND size>=0"
459 " AND uuid NOT IN shun"
460 );
461 db_bind_int(&q1, ":rid", rid);
462 rc = db_step(&q1);
463 if( rc==SQLITE_ROW ){
464 zUuid = db_column_text(&q1, 0);
@@ -738,13 +738,16 @@
738 }
739 db_finalize(&q);
740 }
741
742 /*
743 ** Send a single config card for configuration item zName
 
 
 
744 */
745 static void send_config_card(Xfer *pXfer, const char *zName){
746 if( zName[0]!='@' ){
747 Blob val;
748 blob_zero(&val);
749 db_blob(&val, "SELECT value FROM config WHERE name=%Q", zName);
750 if( blob_size(&val)>0 ){
@@ -760,11 +763,10 @@
760 blob_appendf(pXfer->pOut, "config %s %d\n%s\n", zName,
761 blob_size(&content), blob_str(&content));
762 blob_reset(&content);
763 }
764 }
765
766
767 /*
768 ** Called when there is an attempt to transfer private content to and
769 ** from a server without authorization.
770 */
@@ -1007,11 +1009,11 @@
1007 */
1008 if( blob_eq(&xfer.aToken[0], "login")
1009 && xfer.nToken==4
1010 ){
1011 if( disableLogin ){
1012 g.okRead = g.okWrite = g.okPrivate = 1;
1013 }else{
1014 if( check_tail_hash(&xfer.aToken[2], xfer.pIn)
1015 || check_login(&xfer.aToken[1], &xfer.aToken[2], &xfer.aToken[3])
1016 ){
1017 cgi_reset_content();
@@ -1029,12 +1031,19 @@
1029 if( blob_eq(&xfer.aToken[0], "reqconfig")
1030 && xfer.nToken==2
1031 ){
1032 if( g.okRead ){
1033 char *zName = blob_str(&xfer.aToken[1]);
1034 if( configure_is_exportable(zName) ){
1035 send_config_card(&xfer, zName);
 
 
 
 
 
 
 
1036 }
1037 }
1038 }else
1039
1040 /* config NAME SIZE \n CONTENT
@@ -1052,11 +1061,11 @@
1052 cgi_reset_content();
1053 @ error not\sauthorized\sto\spush\sconfiguration
1054 nErr++;
1055 break;
1056 }
1057 if( !recvConfig ){
1058 configure_prepare_to_receive(0);
1059 recvConfig = 1;
1060 }
1061 configure_receive(zName, &content, CONFIGSET_ALL);
1062 blob_reset(&content);
@@ -1185,14 +1194,14 @@
1185 ** gdb fossil
1186 ** r test-xfer out.txt
1187 */
1188 void cmd_test_xfer(void){
1189 int notUsed;
 
1190 if( g.argc!=2 && g.argc!=3 ){
1191 usage("?MESSAGEFILE?");
1192 }
1193 db_must_be_within_tree();
1194 blob_zero(&g.cgiIn);
1195 blob_read_from_file(&g.cgiIn, g.argc==2 ? "-" : g.argv[2]);
1196 disableLogin = 1;
1197 page_xfer();
1198 printf("%s\n", cgi_extract_content(&notUsed));
@@ -1331,25 +1340,32 @@
1331 while( zName ){
1332 blob_appendf(&send, "reqconfig %s\n", zName);
1333 zName = configure_next_name(configRcvMask);
1334 nCardSent++;
1335 }
1336 if( configRcvMask & (CONFIGSET_USER|CONFIGSET_TKT) ){
1337 configure_prepare_to_receive(0);
 
 
 
1338 }
1339 origConfigRcvMask = configRcvMask;
1340 configRcvMask = 0;
1341 }
1342
1343 /* Send configuration parameters being pushed */
1344 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++;
 
 
 
 
1351 }
1352 configSendMask = 0;
1353 }
1354
1355 /* Append randomness to the end of the message. This makes all
@@ -1647,11 +1663,13 @@
1647 break;
1648 }
1649 blobarray_reset(xfer.aToken, xfer.nToken);
1650 blob_reset(&xfer.line);
1651 }
1652 if( origConfigRcvMask & (CONFIGSET_TKT|CONFIGSET_USER) ){
 
 
1653 configure_finalize_receive();
1654 }
1655 origConfigRcvMask = 0;
1656 if( nCardRcvd>0 ){
1657 fossil_print(zValueFormat, "Received:",
1658
--- src/xfer.c
+++ src/xfer.c
@@ -454,11 +454,11 @@
454 " (SELECT uuid FROM delta, blob"
455 " WHERE delta.rid=:rid AND delta.srcid=blob.rid)"
456 " FROM blob"
457 " WHERE rid=:rid"
458 " AND size>=0"
459 " AND NOT EXISTS(SELECT 1 FROM shun WHERE shun.uuid=blob.uuid)"
460 );
461 db_bind_int(&q1, ":rid", rid);
462 rc = db_step(&q1);
463 if( rc==SQLITE_ROW ){
464 zUuid = db_column_text(&q1, 0);
@@ -738,13 +738,16 @@
738 }
739 db_finalize(&q);
740 }
741
742 /*
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.
747 */
748 static void send_legacy_config_card(Xfer *pXfer, const char *zName){
749 if( zName[0]!='@' ){
750 Blob val;
751 blob_zero(&val);
752 db_blob(&val, "SELECT value FROM config WHERE name=%Q", zName);
753 if( blob_size(&val)>0 ){
@@ -760,11 +763,10 @@
763 blob_appendf(pXfer->pOut, "config %s %d\n%s\n", zName,
764 blob_size(&content), blob_str(&content));
765 blob_reset(&content);
766 }
767 }
 
768
769 /*
770 ** Called when there is an attempt to transfer private content to and
771 ** from a server without authorization.
772 */
@@ -1007,11 +1009,11 @@
1009 */
1010 if( blob_eq(&xfer.aToken[0], "login")
1011 && xfer.nToken==4
1012 ){
1013 if( disableLogin ){
1014 g.okRead = g.okWrite = g.okPrivate = g.okAdmin = 1;
1015 }else{
1016 if( check_tail_hash(&xfer.aToken[2], xfer.pIn)
1017 || check_login(&xfer.aToken[1], &xfer.aToken[2], &xfer.aToken[3])
1018 ){
1019 cgi_reset_content();
@@ -1029,12 +1031,19 @@
1031 if( blob_eq(&xfer.aToken[0], "reqconfig")
1032 && xfer.nToken==2
1033 ){
1034 if( g.okRead ){
1035 char *zName = blob_str(&xfer.aToken[1]);
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);
1045 }
1046 }
1047 }else
1048
1049 /* config NAME SIZE \n CONTENT
@@ -1052,11 +1061,11 @@
1061 cgi_reset_content();
1062 @ error not\sauthorized\sto\spush\sconfiguration
1063 nErr++;
1064 break;
1065 }
1066 if( !recvConfig && zName[0]=='@' ){
1067 configure_prepare_to_receive(0);
1068 recvConfig = 1;
1069 }
1070 configure_receive(zName, &content, CONFIGSET_ALL);
1071 blob_reset(&content);
@@ -1185,14 +1194,14 @@
1194 ** gdb fossil
1195 ** r test-xfer out.txt
1196 */
1197 void cmd_test_xfer(void){
1198 int notUsed;
1199 db_find_and_open_repository(0,0);
1200 if( g.argc!=2 && g.argc!=3 ){
1201 usage("?MESSAGEFILE?");
1202 }
 
1203 blob_zero(&g.cgiIn);
1204 blob_read_from_file(&g.cgiIn, g.argc==2 ? "-" : g.argv[2]);
1205 disableLogin = 1;
1206 page_xfer();
1207 printf("%s\n", cgi_extract_content(&notUsed));
@@ -1331,25 +1340,32 @@
1340 while( zName ){
1341 blob_appendf(&send, "reqconfig %s\n", zName);
1342 zName = configure_next_name(configRcvMask);
1343 nCardSent++;
1344 }
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);
1350 }
1351 origConfigRcvMask = configRcvMask;
1352 configRcvMask = 0;
1353 }
1354
1355 /* Send configuration parameters being pushed */
1356 if( configSendMask ){
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);
1367 }
1368 configSendMask = 0;
1369 }
1370
1371 /* Append randomness to the end of the message. This makes all
@@ -1647,11 +1663,13 @@
1663 break;
1664 }
1665 blobarray_reset(xfer.aToken, xfer.nToken);
1666 blob_reset(&xfer.line);
1667 }
1668 if( (configRcvMask & (CONFIGSET_USER|CONFIGSET_TKT))!=0
1669 && (configRcvMask & CONFIGSET_OLDFORMAT)!=0
1670 ){
1671 configure_finalize_receive();
1672 }
1673 origConfigRcvMask = 0;
1674 if( nCardRcvd>0 ){
1675 fossil_print(zValueFormat, "Received:",
1676

Keyboard Shortcuts

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