Fossil SCM

Merge recent changes into the dual-license branch.

drh 2010-02-08 18:16 clear-title merge
Commit 14c19fbc1c39554d869dfecfbb6c1721abc7f007
+4
--- src/add.c
+++ src/add.c
@@ -204,10 +204,14 @@
204204
char *zName;
205205
char *zPath;
206206
Blob pathname;
207207
208208
zName = mprintf("%/", g.argv[i]);
209
+ if( file_isdir(zName) ){
210
+ fossil_fatal("cannot remove directories -"
211
+ " remove individual files instead");
212
+ }
209213
file_tree_name(zName, &pathname, 1);
210214
zPath = blob_str(&pathname);
211215
if( !db_exists(
212216
"SELECT 1 FROM vfile WHERE pathname=%Q AND NOT deleted", zPath) ){
213217
fossil_fatal("not in the repository: %s", zName);
214218
--- src/add.c
+++ src/add.c
@@ -204,10 +204,14 @@
204 char *zName;
205 char *zPath;
206 Blob pathname;
207
208 zName = mprintf("%/", g.argv[i]);
 
 
 
 
209 file_tree_name(zName, &pathname, 1);
210 zPath = blob_str(&pathname);
211 if( !db_exists(
212 "SELECT 1 FROM vfile WHERE pathname=%Q AND NOT deleted", zPath) ){
213 fossil_fatal("not in the repository: %s", zName);
214
--- src/add.c
+++ src/add.c
@@ -204,10 +204,14 @@
204 char *zName;
205 char *zPath;
206 Blob pathname;
207
208 zName = mprintf("%/", g.argv[i]);
209 if( file_isdir(zName) ){
210 fossil_fatal("cannot remove directories -"
211 " remove individual files instead");
212 }
213 file_tree_name(zName, &pathname, 1);
214 zPath = blob_str(&pathname);
215 if( !db_exists(
216 "SELECT 1 FROM vfile WHERE pathname=%Q AND NOT deleted", zPath) ){
217 fossil_fatal("not in the repository: %s", zName);
218
+4
--- src/add.c
+++ src/add.c
@@ -204,10 +204,14 @@
204204
char *zName;
205205
char *zPath;
206206
Blob pathname;
207207
208208
zName = mprintf("%/", g.argv[i]);
209
+ if( file_isdir(zName) ){
210
+ fossil_fatal("cannot remove directories -"
211
+ " remove individual files instead");
212
+ }
209213
file_tree_name(zName, &pathname, 1);
210214
zPath = blob_str(&pathname);
211215
if( !db_exists(
212216
"SELECT 1 FROM vfile WHERE pathname=%Q AND NOT deleted", zPath) ){
213217
fossil_fatal("not in the repository: %s", zName);
214218
--- src/add.c
+++ src/add.c
@@ -204,10 +204,14 @@
204 char *zName;
205 char *zPath;
206 Blob pathname;
207
208 zName = mprintf("%/", g.argv[i]);
 
 
 
 
209 file_tree_name(zName, &pathname, 1);
210 zPath = blob_str(&pathname);
211 if( !db_exists(
212 "SELECT 1 FROM vfile WHERE pathname=%Q AND NOT deleted", zPath) ){
213 fossil_fatal("not in the repository: %s", zName);
214
--- src/add.c
+++ src/add.c
@@ -204,10 +204,14 @@
204 char *zName;
205 char *zPath;
206 Blob pathname;
207
208 zName = mprintf("%/", g.argv[i]);
209 if( file_isdir(zName) ){
210 fossil_fatal("cannot remove directories -"
211 " remove individual files instead");
212 }
213 file_tree_name(zName, &pathname, 1);
214 zPath = blob_str(&pathname);
215 if( !db_exists(
216 "SELECT 1 FROM vfile WHERE pathname=%Q AND NOT deleted", zPath) ){
217 fossil_fatal("not in the repository: %s", zName);
218
-8
--- src/cgi.c
+++ src/cgi.c
@@ -695,20 +695,12 @@
695695
process_multipart_form_data(z, len);
696696
}
697697
}else if( strcmp(zType, "application/x-fossil")==0 ){
698698
blob_read_from_channel(&g.cgiIn, g.httpIn, len);
699699
blob_uncompress(&g.cgiIn, &g.cgiIn);
700
- /* If the content type is application/x-fossil, then ignore
701
- ** the path in the first line of the HTTP header and always
702
- ** use the /xfer method since the /xfer method is the only
703
- ** method that understands the application/x-fossil content
704
- ** type.
705
- */
706
- cgi_replace_parameter("PATH_INFO", "/xfer");
707700
}else if( strcmp(zType, "application/x-fossil-debug")==0 ){
708701
blob_read_from_channel(&g.cgiIn, g.httpIn, len);
709
- cgi_replace_parameter("PATH_INFO", "/xfer"); /* See comment above */
710702
}
711703
}
712704
713705
z = (char*)P("HTTP_COOKIE");
714706
if( z ){
715707
--- src/cgi.c
+++ src/cgi.c
@@ -695,20 +695,12 @@
695 process_multipart_form_data(z, len);
696 }
697 }else if( strcmp(zType, "application/x-fossil")==0 ){
698 blob_read_from_channel(&g.cgiIn, g.httpIn, len);
699 blob_uncompress(&g.cgiIn, &g.cgiIn);
700 /* If the content type is application/x-fossil, then ignore
701 ** the path in the first line of the HTTP header and always
702 ** use the /xfer method since the /xfer method is the only
703 ** method that understands the application/x-fossil content
704 ** type.
705 */
706 cgi_replace_parameter("PATH_INFO", "/xfer");
707 }else if( strcmp(zType, "application/x-fossil-debug")==0 ){
708 blob_read_from_channel(&g.cgiIn, g.httpIn, len);
709 cgi_replace_parameter("PATH_INFO", "/xfer"); /* See comment above */
710 }
711 }
712
713 z = (char*)P("HTTP_COOKIE");
714 if( z ){
715
--- src/cgi.c
+++ src/cgi.c
@@ -695,20 +695,12 @@
695 process_multipart_form_data(z, len);
696 }
697 }else if( strcmp(zType, "application/x-fossil")==0 ){
698 blob_read_from_channel(&g.cgiIn, g.httpIn, len);
699 blob_uncompress(&g.cgiIn, &g.cgiIn);
 
 
 
 
 
 
 
700 }else if( strcmp(zType, "application/x-fossil-debug")==0 ){
701 blob_read_from_channel(&g.cgiIn, g.httpIn, len);
 
702 }
703 }
704
705 z = (char*)P("HTTP_COOKIE");
706 if( z ){
707
-8
--- src/cgi.c
+++ src/cgi.c
@@ -695,20 +695,12 @@
695695
process_multipart_form_data(z, len);
696696
}
697697
}else if( strcmp(zType, "application/x-fossil")==0 ){
698698
blob_read_from_channel(&g.cgiIn, g.httpIn, len);
699699
blob_uncompress(&g.cgiIn, &g.cgiIn);
700
- /* If the content type is application/x-fossil, then ignore
701
- ** the path in the first line of the HTTP header and always
702
- ** use the /xfer method since the /xfer method is the only
703
- ** method that understands the application/x-fossil content
704
- ** type.
705
- */
706
- cgi_replace_parameter("PATH_INFO", "/xfer");
707700
}else if( strcmp(zType, "application/x-fossil-debug")==0 ){
708701
blob_read_from_channel(&g.cgiIn, g.httpIn, len);
709
- cgi_replace_parameter("PATH_INFO", "/xfer"); /* See comment above */
710702
}
711703
}
712704
713705
z = (char*)P("HTTP_COOKIE");
714706
if( z ){
715707
--- src/cgi.c
+++ src/cgi.c
@@ -695,20 +695,12 @@
695 process_multipart_form_data(z, len);
696 }
697 }else if( strcmp(zType, "application/x-fossil")==0 ){
698 blob_read_from_channel(&g.cgiIn, g.httpIn, len);
699 blob_uncompress(&g.cgiIn, &g.cgiIn);
700 /* If the content type is application/x-fossil, then ignore
701 ** the path in the first line of the HTTP header and always
702 ** use the /xfer method since the /xfer method is the only
703 ** method that understands the application/x-fossil content
704 ** type.
705 */
706 cgi_replace_parameter("PATH_INFO", "/xfer");
707 }else if( strcmp(zType, "application/x-fossil-debug")==0 ){
708 blob_read_from_channel(&g.cgiIn, g.httpIn, len);
709 cgi_replace_parameter("PATH_INFO", "/xfer"); /* See comment above */
710 }
711 }
712
713 z = (char*)P("HTTP_COOKIE");
714 if( z ){
715
--- src/cgi.c
+++ src/cgi.c
@@ -695,20 +695,12 @@
695 process_multipart_form_data(z, len);
696 }
697 }else if( strcmp(zType, "application/x-fossil")==0 ){
698 blob_read_from_channel(&g.cgiIn, g.httpIn, len);
699 blob_uncompress(&g.cgiIn, &g.cgiIn);
 
 
 
 
 
 
 
700 }else if( strcmp(zType, "application/x-fossil-debug")==0 ){
701 blob_read_from_channel(&g.cgiIn, g.httpIn, len);
 
702 }
703 }
704
705 z = (char*)P("HTTP_COOKIE");
706 if( z ){
707
+19 -5
--- src/clone.c
+++ src/clone.c
@@ -30,25 +30,39 @@
3030
3131
3232
/*
3333
** COMMAND: clone
3434
**
35
-** Usage: %fossil clone URL FILENAME
35
+** Usage: %fossil clone ?OPTIONS? URL FILENAME
3636
**
3737
** Make a clone of a repository specified by URL in the local
3838
** file named FILENAME.
39
+**
40
+** By default, your current login name is used to create the default
41
+** admin user. This can be overridden using the -A|--admin-user
42
+** parameter.
43
+**
44
+** Options:
45
+**
46
+** --admin-user|-A USERNAME
47
+**
3948
*/
4049
void clone_cmd(void){
4150
char *zPassword;
51
+ const char *zDefaultUser; /* Optional name of the default user */
52
+
4253
url_proxy_options();
43
- if( g.argc!=4 ){
44
- usage("FILE-OR-URL NEW-REPOSITORY");
54
+ if( g.argc < 4 ){
55
+ usage("?OPTIONS? FILE-OR-URL NEW-REPOSITORY");
4556
}
4657
db_open_config(0);
4758
if( file_size(g.argv[3])>0 ){
4859
fossil_panic("file already exists: %s", g.argv[3]);
4960
}
61
+
62
+ zDefaultUser = find_option("admin-user","A",1);
63
+
5064
url_parse(g.argv[2]);
5165
if( g.urlIsFile ){
5266
file_copy(g.urlName, g.argv[3]);
5367
db_close();
5468
db_open_repository(g.argv[3]);
@@ -66,19 +80,19 @@
6680
"DELETE FROM private;"
6781
);
6882
shun_artifacts();
6983
g.zLogin = db_text(0, "SELECT login FROM user WHERE cap LIKE '%%s%%'");
7084
if( g.zLogin==0 ){
71
- db_create_default_users(1);
85
+ db_create_default_users(1,zDefaultUser);
7286
}
7387
printf("Repository cloned into %s\n", g.argv[3]);
7488
}else{
7589
db_create_repository(g.argv[3]);
7690
db_open_repository(g.argv[3]);
7791
db_begin_transaction();
7892
db_record_repository_filename(g.argv[3]);
79
- db_initial_setup(0, 0);
93
+ db_initial_setup(0, zDefaultUser, 0);
8094
user_select();
8195
db_set("content-schema", CONTENT_SCHEMA, 0);
8296
db_set("aux-schema", AUX_SCHEMA, 0);
8397
db_set("last-sync-url", g.argv[2], 0);
8498
db_multi_exec(
8599
--- src/clone.c
+++ src/clone.c
@@ -30,25 +30,39 @@
30
31
32 /*
33 ** COMMAND: clone
34 **
35 ** Usage: %fossil clone URL FILENAME
36 **
37 ** Make a clone of a repository specified by URL in the local
38 ** file named FILENAME.
 
 
 
 
 
 
 
 
 
39 */
40 void clone_cmd(void){
41 char *zPassword;
 
 
42 url_proxy_options();
43 if( g.argc!=4 ){
44 usage("FILE-OR-URL NEW-REPOSITORY");
45 }
46 db_open_config(0);
47 if( file_size(g.argv[3])>0 ){
48 fossil_panic("file already exists: %s", g.argv[3]);
49 }
 
 
 
50 url_parse(g.argv[2]);
51 if( g.urlIsFile ){
52 file_copy(g.urlName, g.argv[3]);
53 db_close();
54 db_open_repository(g.argv[3]);
@@ -66,19 +80,19 @@
66 "DELETE FROM private;"
67 );
68 shun_artifacts();
69 g.zLogin = db_text(0, "SELECT login FROM user WHERE cap LIKE '%%s%%'");
70 if( g.zLogin==0 ){
71 db_create_default_users(1);
72 }
73 printf("Repository cloned into %s\n", g.argv[3]);
74 }else{
75 db_create_repository(g.argv[3]);
76 db_open_repository(g.argv[3]);
77 db_begin_transaction();
78 db_record_repository_filename(g.argv[3]);
79 db_initial_setup(0, 0);
80 user_select();
81 db_set("content-schema", CONTENT_SCHEMA, 0);
82 db_set("aux-schema", AUX_SCHEMA, 0);
83 db_set("last-sync-url", g.argv[2], 0);
84 db_multi_exec(
85
--- src/clone.c
+++ src/clone.c
@@ -30,25 +30,39 @@
30
31
32 /*
33 ** COMMAND: clone
34 **
35 ** Usage: %fossil clone ?OPTIONS? URL FILENAME
36 **
37 ** Make a clone of a repository specified by URL in the local
38 ** file named FILENAME.
39 **
40 ** By default, your current login name is used to create the default
41 ** admin user. This can be overridden using the -A|--admin-user
42 ** parameter.
43 **
44 ** Options:
45 **
46 ** --admin-user|-A USERNAME
47 **
48 */
49 void clone_cmd(void){
50 char *zPassword;
51 const char *zDefaultUser; /* Optional name of the default user */
52
53 url_proxy_options();
54 if( g.argc < 4 ){
55 usage("?OPTIONS? FILE-OR-URL NEW-REPOSITORY");
56 }
57 db_open_config(0);
58 if( file_size(g.argv[3])>0 ){
59 fossil_panic("file already exists: %s", g.argv[3]);
60 }
61
62 zDefaultUser = find_option("admin-user","A",1);
63
64 url_parse(g.argv[2]);
65 if( g.urlIsFile ){
66 file_copy(g.urlName, g.argv[3]);
67 db_close();
68 db_open_repository(g.argv[3]);
@@ -66,19 +80,19 @@
80 "DELETE FROM private;"
81 );
82 shun_artifacts();
83 g.zLogin = db_text(0, "SELECT login FROM user WHERE cap LIKE '%%s%%'");
84 if( g.zLogin==0 ){
85 db_create_default_users(1,zDefaultUser);
86 }
87 printf("Repository cloned into %s\n", g.argv[3]);
88 }else{
89 db_create_repository(g.argv[3]);
90 db_open_repository(g.argv[3]);
91 db_begin_transaction();
92 db_record_repository_filename(g.argv[3]);
93 db_initial_setup(0, zDefaultUser, 0);
94 user_select();
95 db_set("content-schema", CONTENT_SCHEMA, 0);
96 db_set("aux-schema", AUX_SCHEMA, 0);
97 db_set("last-sync-url", g.argv[2], 0);
98 db_multi_exec(
99
+1 -1
--- src/configure.c
+++ src/configure.c
@@ -487,11 +487,11 @@
487487
if( (aConfig[i].groupMask & mask)==0 ) continue;
488488
if( zName[0]!='@' ){
489489
db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
490490
}else if( strcmp(zName,"@user")==0 ){
491491
db_multi_exec("DELETE FROM user");
492
- db_create_default_users(0);
492
+ db_create_default_users(0, 0);
493493
}else if( strcmp(zName,"@concealed")==0 ){
494494
db_multi_exec("DELETE FROM concealed");
495495
}else if( strcmp(zName,"@shun")==0 ){
496496
db_multi_exec("DELETE FROM shun");
497497
}else if( strcmp(zName,"@reportfmt")==0 ){
498498
--- src/configure.c
+++ src/configure.c
@@ -487,11 +487,11 @@
487 if( (aConfig[i].groupMask & mask)==0 ) continue;
488 if( zName[0]!='@' ){
489 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
490 }else if( strcmp(zName,"@user")==0 ){
491 db_multi_exec("DELETE FROM user");
492 db_create_default_users(0);
493 }else if( strcmp(zName,"@concealed")==0 ){
494 db_multi_exec("DELETE FROM concealed");
495 }else if( strcmp(zName,"@shun")==0 ){
496 db_multi_exec("DELETE FROM shun");
497 }else if( strcmp(zName,"@reportfmt")==0 ){
498
--- src/configure.c
+++ src/configure.c
@@ -487,11 +487,11 @@
487 if( (aConfig[i].groupMask & mask)==0 ) continue;
488 if( zName[0]!='@' ){
489 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
490 }else if( strcmp(zName,"@user")==0 ){
491 db_multi_exec("DELETE FROM user");
492 db_create_default_users(0, 0);
493 }else if( strcmp(zName,"@concealed")==0 ){
494 db_multi_exec("DELETE FROM concealed");
495 }else if( strcmp(zName,"@shun")==0 ){
496 db_multi_exec("DELETE FROM shun");
497 }else if( strcmp(zName,"@reportfmt")==0 ){
498
+23 -85
--- src/db.c
+++ src/db.c
@@ -506,86 +506,10 @@
506506
z = mprintf("%s", sqlite3_column_text(s.pStmt, 0));
507507
}
508508
db_finalize(&s);
509509
return z;
510510
}
511
-
512
-#ifdef __MINGW32__
513
-/*
514
-** These routines (copied out of the os_win.c driver for SQLite) convert
515
-** character strings in various microsoft multi-byte character formats
516
-** into UTF-8. Fossil and SQLite always use only UTF-8 internally. These
517
-** routines are needed in order to convert from the default character set
518
-** currently in use by windows into UTF-8 when strings are imported from
519
-** the outside world.
520
-*/
521
-/*
522
-** Convert microsoft unicode to UTF-8. Space to hold the returned string is
523
-** obtained from malloc().
524
-** Copied from sqlite3.c as is (petr)
525
-*/
526
-static char *unicodeToUtf8(const WCHAR *zWideFilename){
527
- int nByte;
528
- char *zFilename;
529
-
530
- nByte = WideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, 0, 0, 0, 0);
531
- zFilename = malloc( nByte );
532
- if( zFilename==0 ){
533
- return 0;
534
- }
535
- nByte = WideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, zFilename, nByte,
536
- 0, 0);
537
- if( nByte == 0 ){
538
- free(zFilename);
539
- zFilename = 0;
540
- }
541
- return zFilename;
542
-}
543
-/*
544
-** Convert an ansi string to microsoft unicode, based on the
545
-** current codepage settings for file apis.
546
-**
547
-** Space to hold the returned string is obtained
548
-** from malloc.
549
-*/
550
-static WCHAR *mbcsToUnicode(const char *zFilename){
551
- int nByte;
552
- WCHAR *zMbcsFilename;
553
- int codepage = CP_ACP;
554
-
555
- nByte = MultiByteToWideChar(codepage, 0, zFilename, -1, NULL,0)*sizeof(WCHAR);
556
- zMbcsFilename = malloc( nByte*sizeof(zMbcsFilename[0]) );
557
- if( zMbcsFilename==0 ){
558
- return 0;
559
- }
560
-
561
- nByte = MultiByteToWideChar(codepage, 0, zFilename, -1, zMbcsFilename, nByte);
562
- if( nByte==0 ){
563
- free(zMbcsFilename);
564
- zMbcsFilename = 0;
565
- }
566
- return zMbcsFilename;
567
-}
568
-/*
569
-** Convert multibyte character string to UTF-8. Space to hold the
570
-** returned string is obtained from malloc().
571
-*/
572
-static char *mbcsToUtf8(const char *zFilename){
573
- char *zFilenameUtf8;
574
- WCHAR *zTmpWide;
575
-
576
- zTmpWide = mbcsToUnicode(zFilename);
577
- if( zTmpWide==0 ){
578
- return 0;
579
- }
580
-
581
- zFilenameUtf8 = unicodeToUtf8(zTmpWide);
582
- free(zTmpWide);
583
- return zFilenameUtf8;
584
-}
585
-#endif /* __MINGW32__ */
586
-
587511
588512
/*
589513
** Initialize a new database file with the given schema. If anything
590514
** goes wrong, call db_err() to exit.
591515
*/
@@ -598,11 +522,11 @@
598522
int rc;
599523
const char *zSql;
600524
va_list ap;
601525
602526
#ifdef __MINGW32__
603
- zFileName = mbcsToUtf8(zFileName);
527
+ zFileName = sqlite3_win32_mbcs_to_utf8(zFileName);
604528
#endif
605529
rc = sqlite3_open(zFileName, &db);
606530
if( rc!=SQLITE_OK ){
607531
db_err(sqlite3_errmsg(db));
608532
}
@@ -633,11 +557,11 @@
633557
const char *zVfs;
634558
sqlite3 *db;
635559
636560
zVfs = getenv("FOSSIL_VFS");
637561
#ifdef __MINGW32__
638
- zDbName = mbcsToUtf8(zDbName);
562
+ zDbName = sqlite3_win32_mbcs_to_utf8(zDbName);
639563
#endif
640564
rc = sqlite3_open_v2(
641565
zDbName, &db,
642566
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
643567
zVfs
@@ -659,11 +583,11 @@
659583
if( !g.db ){
660584
g.db = openDatabase(zDbName);
661585
db_connection_init();
662586
}else{
663587
#ifdef __MINGW32__
664
- zDbName = mbcsToUtf8(zDbName);
588
+ zDbName = sqlite3_win32_mbcs_to_utf8(zDbName);
665589
#endif
666590
db_multi_exec("ATTACH DATABASE %Q AS %s", zDbName, zLabel);
667591
}
668592
}
669593
@@ -866,11 +790,11 @@
866790
if( g.repositoryOpen ){
867791
return;
868792
}
869793
rep_not_found:
870794
if( errIfNotFound ){
871
- fossil_fatal("use --repository or -R to specific the repository database");
795
+ fossil_fatal("use --repository or -R to specify the repository database");
872796
}
873797
}
874798
875799
/*
876800
** Open the local database. If unable, exit with an error.
@@ -916,13 +840,16 @@
916840
}
917841
918842
/*
919843
** Create the default user accounts in the USER table.
920844
*/
921
-void db_create_default_users(int setupUserOnly){
845
+void db_create_default_users(int setupUserOnly, const char *zDefaultUser){
922846
const char *zUser;
923847
zUser = db_get("default-user", 0);
848
+ if( zUser==0 ){
849
+ zUser = zDefaultUser;
850
+ }
924851
if( zUser==0 ){
925852
#ifdef __MINGW32__
926853
zUser = getenv("USERNAME");
927854
#else
928855
zUser = getenv("USER");
@@ -958,11 +885,11 @@
958885
** The zInitialDate parameter determines the date of the initial check-in
959886
** that is automatically created. If zInitialDate is 0 then no initial
960887
** check-in is created. The makeServerCodes flag determines whether or
961888
** not server and project codes are invented for this repository.
962889
*/
963
-void db_initial_setup (const char *zInitialDate, int makeServerCodes){
890
+void db_initial_setup (const char *zInitialDate, const char *zDefaultUser, int makeServerCodes){
964891
char *zDate;
965892
Blob hash;
966893
Blob manifest;
967894
968895
db_set("content-schema", CONTENT_SCHEMA, 0);
@@ -975,11 +902,11 @@
975902
" VALUES('project-code', lower(hex(randomblob(20))));"
976903
);
977904
}
978905
if( !db_is_global("autosync") ) db_set_int("autosync", 1, 0);
979906
if( !db_is_global("localauth") ) db_set_int("localauth", 0, 0);
980
- db_create_default_users(0);
907
+ db_create_default_users(0, zDefaultUser);
981908
user_select();
982909
983910
if( zInitialDate ){
984911
int rid;
985912
blob_zero(&manifest);
@@ -1002,30 +929,41 @@
1002929
}
1003930
1004931
/*
1005932
** COMMAND: new
1006933
**
1007
-** Usage: %fossil new FILENAME
934
+** Usage: %fossil new ?OPTIONS? FILENAME
1008935
**
1009936
** Create a repository for a new project in the file named FILENAME.
1010937
** This command is distinct from "clone". The "clone" command makes
1011938
** a copy of an existing project. This command starts a new project.
939
+**
940
+** By default, your current login name is used to create the default
941
+** admin user. This can be overridden using the -A|--admin-user
942
+** parameter.
943
+**
944
+** Options:
945
+**
946
+** --admin-user|-A USERNAME
947
+**
1012948
*/
1013949
void create_repository_cmd(void){
1014950
char *zPassword;
1015951
const char *zDate; /* Date of the initial check-in */
952
+ const char *zDefaultUser; /* Optional name of the default user */
1016953
1017954
zDate = find_option("date-override",0,1);
955
+ zDefaultUser = find_option("admin-user","A",1);
1018956
if( zDate==0 ) zDate = "now";
1019957
if( g.argc!=3 ){
1020958
usage("REPOSITORY-NAME");
1021959
}
1022960
db_create_repository(g.argv[2]);
1023961
db_open_repository(g.argv[2]);
1024962
db_open_config(0);
1025963
db_begin_transaction();
1026
- db_initial_setup(zDate, 1);
964
+ db_initial_setup(zDate, zDefaultUser, 1);
1027965
db_end_transaction(0);
1028966
printf("project-id: %s\n", db_get("project-code", 0));
1029967
printf("server-id: %s\n", db_get("server-code", 0));
1030968
zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
1031969
printf("admin-user: %s (initial password is \"%s\")\n", g.zLogin, zPassword);
1032970
--- src/db.c
+++ src/db.c
@@ -506,86 +506,10 @@
506 z = mprintf("%s", sqlite3_column_text(s.pStmt, 0));
507 }
508 db_finalize(&s);
509 return z;
510 }
511
512 #ifdef __MINGW32__
513 /*
514 ** These routines (copied out of the os_win.c driver for SQLite) convert
515 ** character strings in various microsoft multi-byte character formats
516 ** into UTF-8. Fossil and SQLite always use only UTF-8 internally. These
517 ** routines are needed in order to convert from the default character set
518 ** currently in use by windows into UTF-8 when strings are imported from
519 ** the outside world.
520 */
521 /*
522 ** Convert microsoft unicode to UTF-8. Space to hold the returned string is
523 ** obtained from malloc().
524 ** Copied from sqlite3.c as is (petr)
525 */
526 static char *unicodeToUtf8(const WCHAR *zWideFilename){
527 int nByte;
528 char *zFilename;
529
530 nByte = WideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, 0, 0, 0, 0);
531 zFilename = malloc( nByte );
532 if( zFilename==0 ){
533 return 0;
534 }
535 nByte = WideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, zFilename, nByte,
536 0, 0);
537 if( nByte == 0 ){
538 free(zFilename);
539 zFilename = 0;
540 }
541 return zFilename;
542 }
543 /*
544 ** Convert an ansi string to microsoft unicode, based on the
545 ** current codepage settings for file apis.
546 **
547 ** Space to hold the returned string is obtained
548 ** from malloc.
549 */
550 static WCHAR *mbcsToUnicode(const char *zFilename){
551 int nByte;
552 WCHAR *zMbcsFilename;
553 int codepage = CP_ACP;
554
555 nByte = MultiByteToWideChar(codepage, 0, zFilename, -1, NULL,0)*sizeof(WCHAR);
556 zMbcsFilename = malloc( nByte*sizeof(zMbcsFilename[0]) );
557 if( zMbcsFilename==0 ){
558 return 0;
559 }
560
561 nByte = MultiByteToWideChar(codepage, 0, zFilename, -1, zMbcsFilename, nByte);
562 if( nByte==0 ){
563 free(zMbcsFilename);
564 zMbcsFilename = 0;
565 }
566 return zMbcsFilename;
567 }
568 /*
569 ** Convert multibyte character string to UTF-8. Space to hold the
570 ** returned string is obtained from malloc().
571 */
572 static char *mbcsToUtf8(const char *zFilename){
573 char *zFilenameUtf8;
574 WCHAR *zTmpWide;
575
576 zTmpWide = mbcsToUnicode(zFilename);
577 if( zTmpWide==0 ){
578 return 0;
579 }
580
581 zFilenameUtf8 = unicodeToUtf8(zTmpWide);
582 free(zTmpWide);
583 return zFilenameUtf8;
584 }
585 #endif /* __MINGW32__ */
586
587
588 /*
589 ** Initialize a new database file with the given schema. If anything
590 ** goes wrong, call db_err() to exit.
591 */
@@ -598,11 +522,11 @@
598 int rc;
599 const char *zSql;
600 va_list ap;
601
602 #ifdef __MINGW32__
603 zFileName = mbcsToUtf8(zFileName);
604 #endif
605 rc = sqlite3_open(zFileName, &db);
606 if( rc!=SQLITE_OK ){
607 db_err(sqlite3_errmsg(db));
608 }
@@ -633,11 +557,11 @@
633 const char *zVfs;
634 sqlite3 *db;
635
636 zVfs = getenv("FOSSIL_VFS");
637 #ifdef __MINGW32__
638 zDbName = mbcsToUtf8(zDbName);
639 #endif
640 rc = sqlite3_open_v2(
641 zDbName, &db,
642 SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
643 zVfs
@@ -659,11 +583,11 @@
659 if( !g.db ){
660 g.db = openDatabase(zDbName);
661 db_connection_init();
662 }else{
663 #ifdef __MINGW32__
664 zDbName = mbcsToUtf8(zDbName);
665 #endif
666 db_multi_exec("ATTACH DATABASE %Q AS %s", zDbName, zLabel);
667 }
668 }
669
@@ -866,11 +790,11 @@
866 if( g.repositoryOpen ){
867 return;
868 }
869 rep_not_found:
870 if( errIfNotFound ){
871 fossil_fatal("use --repository or -R to specific the repository database");
872 }
873 }
874
875 /*
876 ** Open the local database. If unable, exit with an error.
@@ -916,13 +840,16 @@
916 }
917
918 /*
919 ** Create the default user accounts in the USER table.
920 */
921 void db_create_default_users(int setupUserOnly){
922 const char *zUser;
923 zUser = db_get("default-user", 0);
 
 
 
924 if( zUser==0 ){
925 #ifdef __MINGW32__
926 zUser = getenv("USERNAME");
927 #else
928 zUser = getenv("USER");
@@ -958,11 +885,11 @@
958 ** The zInitialDate parameter determines the date of the initial check-in
959 ** that is automatically created. If zInitialDate is 0 then no initial
960 ** check-in is created. The makeServerCodes flag determines whether or
961 ** not server and project codes are invented for this repository.
962 */
963 void db_initial_setup (const char *zInitialDate, int makeServerCodes){
964 char *zDate;
965 Blob hash;
966 Blob manifest;
967
968 db_set("content-schema", CONTENT_SCHEMA, 0);
@@ -975,11 +902,11 @@
975 " VALUES('project-code', lower(hex(randomblob(20))));"
976 );
977 }
978 if( !db_is_global("autosync") ) db_set_int("autosync", 1, 0);
979 if( !db_is_global("localauth") ) db_set_int("localauth", 0, 0);
980 db_create_default_users(0);
981 user_select();
982
983 if( zInitialDate ){
984 int rid;
985 blob_zero(&manifest);
@@ -1002,30 +929,41 @@
1002 }
1003
1004 /*
1005 ** COMMAND: new
1006 **
1007 ** Usage: %fossil new FILENAME
1008 **
1009 ** Create a repository for a new project in the file named FILENAME.
1010 ** This command is distinct from "clone". The "clone" command makes
1011 ** a copy of an existing project. This command starts a new project.
 
 
 
 
 
 
 
 
 
1012 */
1013 void create_repository_cmd(void){
1014 char *zPassword;
1015 const char *zDate; /* Date of the initial check-in */
 
1016
1017 zDate = find_option("date-override",0,1);
 
1018 if( zDate==0 ) zDate = "now";
1019 if( g.argc!=3 ){
1020 usage("REPOSITORY-NAME");
1021 }
1022 db_create_repository(g.argv[2]);
1023 db_open_repository(g.argv[2]);
1024 db_open_config(0);
1025 db_begin_transaction();
1026 db_initial_setup(zDate, 1);
1027 db_end_transaction(0);
1028 printf("project-id: %s\n", db_get("project-code", 0));
1029 printf("server-id: %s\n", db_get("server-code", 0));
1030 zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
1031 printf("admin-user: %s (initial password is \"%s\")\n", g.zLogin, zPassword);
1032
--- src/db.c
+++ src/db.c
@@ -506,86 +506,10 @@
506 z = mprintf("%s", sqlite3_column_text(s.pStmt, 0));
507 }
508 db_finalize(&s);
509 return z;
510 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
511
512 /*
513 ** Initialize a new database file with the given schema. If anything
514 ** goes wrong, call db_err() to exit.
515 */
@@ -598,11 +522,11 @@
522 int rc;
523 const char *zSql;
524 va_list ap;
525
526 #ifdef __MINGW32__
527 zFileName = sqlite3_win32_mbcs_to_utf8(zFileName);
528 #endif
529 rc = sqlite3_open(zFileName, &db);
530 if( rc!=SQLITE_OK ){
531 db_err(sqlite3_errmsg(db));
532 }
@@ -633,11 +557,11 @@
557 const char *zVfs;
558 sqlite3 *db;
559
560 zVfs = getenv("FOSSIL_VFS");
561 #ifdef __MINGW32__
562 zDbName = sqlite3_win32_mbcs_to_utf8(zDbName);
563 #endif
564 rc = sqlite3_open_v2(
565 zDbName, &db,
566 SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
567 zVfs
@@ -659,11 +583,11 @@
583 if( !g.db ){
584 g.db = openDatabase(zDbName);
585 db_connection_init();
586 }else{
587 #ifdef __MINGW32__
588 zDbName = sqlite3_win32_mbcs_to_utf8(zDbName);
589 #endif
590 db_multi_exec("ATTACH DATABASE %Q AS %s", zDbName, zLabel);
591 }
592 }
593
@@ -866,11 +790,11 @@
790 if( g.repositoryOpen ){
791 return;
792 }
793 rep_not_found:
794 if( errIfNotFound ){
795 fossil_fatal("use --repository or -R to specify the repository database");
796 }
797 }
798
799 /*
800 ** Open the local database. If unable, exit with an error.
@@ -916,13 +840,16 @@
840 }
841
842 /*
843 ** Create the default user accounts in the USER table.
844 */
845 void db_create_default_users(int setupUserOnly, const char *zDefaultUser){
846 const char *zUser;
847 zUser = db_get("default-user", 0);
848 if( zUser==0 ){
849 zUser = zDefaultUser;
850 }
851 if( zUser==0 ){
852 #ifdef __MINGW32__
853 zUser = getenv("USERNAME");
854 #else
855 zUser = getenv("USER");
@@ -958,11 +885,11 @@
885 ** The zInitialDate parameter determines the date of the initial check-in
886 ** that is automatically created. If zInitialDate is 0 then no initial
887 ** check-in is created. The makeServerCodes flag determines whether or
888 ** not server and project codes are invented for this repository.
889 */
890 void db_initial_setup (const char *zInitialDate, const char *zDefaultUser, int makeServerCodes){
891 char *zDate;
892 Blob hash;
893 Blob manifest;
894
895 db_set("content-schema", CONTENT_SCHEMA, 0);
@@ -975,11 +902,11 @@
902 " VALUES('project-code', lower(hex(randomblob(20))));"
903 );
904 }
905 if( !db_is_global("autosync") ) db_set_int("autosync", 1, 0);
906 if( !db_is_global("localauth") ) db_set_int("localauth", 0, 0);
907 db_create_default_users(0, zDefaultUser);
908 user_select();
909
910 if( zInitialDate ){
911 int rid;
912 blob_zero(&manifest);
@@ -1002,30 +929,41 @@
929 }
930
931 /*
932 ** COMMAND: new
933 **
934 ** Usage: %fossil new ?OPTIONS? FILENAME
935 **
936 ** Create a repository for a new project in the file named FILENAME.
937 ** This command is distinct from "clone". The "clone" command makes
938 ** a copy of an existing project. This command starts a new project.
939 **
940 ** By default, your current login name is used to create the default
941 ** admin user. This can be overridden using the -A|--admin-user
942 ** parameter.
943 **
944 ** Options:
945 **
946 ** --admin-user|-A USERNAME
947 **
948 */
949 void create_repository_cmd(void){
950 char *zPassword;
951 const char *zDate; /* Date of the initial check-in */
952 const char *zDefaultUser; /* Optional name of the default user */
953
954 zDate = find_option("date-override",0,1);
955 zDefaultUser = find_option("admin-user","A",1);
956 if( zDate==0 ) zDate = "now";
957 if( g.argc!=3 ){
958 usage("REPOSITORY-NAME");
959 }
960 db_create_repository(g.argv[2]);
961 db_open_repository(g.argv[2]);
962 db_open_config(0);
963 db_begin_transaction();
964 db_initial_setup(zDate, zDefaultUser, 1);
965 db_end_transaction(0);
966 printf("project-id: %s\n", db_get("project-code", 0));
967 printf("server-id: %s\n", db_get("server-code", 0));
968 zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
969 printf("admin-user: %s (initial password is \"%s\")\n", g.zLogin, zPassword);
970
+23 -85
--- src/db.c
+++ src/db.c
@@ -506,86 +506,10 @@
506506
z = mprintf("%s", sqlite3_column_text(s.pStmt, 0));
507507
}
508508
db_finalize(&s);
509509
return z;
510510
}
511
-
512
-#ifdef __MINGW32__
513
-/*
514
-** These routines (copied out of the os_win.c driver for SQLite) convert
515
-** character strings in various microsoft multi-byte character formats
516
-** into UTF-8. Fossil and SQLite always use only UTF-8 internally. These
517
-** routines are needed in order to convert from the default character set
518
-** currently in use by windows into UTF-8 when strings are imported from
519
-** the outside world.
520
-*/
521
-/*
522
-** Convert microsoft unicode to UTF-8. Space to hold the returned string is
523
-** obtained from malloc().
524
-** Copied from sqlite3.c as is (petr)
525
-*/
526
-static char *unicodeToUtf8(const WCHAR *zWideFilename){
527
- int nByte;
528
- char *zFilename;
529
-
530
- nByte = WideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, 0, 0, 0, 0);
531
- zFilename = malloc( nByte );
532
- if( zFilename==0 ){
533
- return 0;
534
- }
535
- nByte = WideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, zFilename, nByte,
536
- 0, 0);
537
- if( nByte == 0 ){
538
- free(zFilename);
539
- zFilename = 0;
540
- }
541
- return zFilename;
542
-}
543
-/*
544
-** Convert an ansi string to microsoft unicode, based on the
545
-** current codepage settings for file apis.
546
-**
547
-** Space to hold the returned string is obtained
548
-** from malloc.
549
-*/
550
-static WCHAR *mbcsToUnicode(const char *zFilename){
551
- int nByte;
552
- WCHAR *zMbcsFilename;
553
- int codepage = CP_ACP;
554
-
555
- nByte = MultiByteToWideChar(codepage, 0, zFilename, -1, NULL,0)*sizeof(WCHAR);
556
- zMbcsFilename = malloc( nByte*sizeof(zMbcsFilename[0]) );
557
- if( zMbcsFilename==0 ){
558
- return 0;
559
- }
560
-
561
- nByte = MultiByteToWideChar(codepage, 0, zFilename, -1, zMbcsFilename, nByte);
562
- if( nByte==0 ){
563
- free(zMbcsFilename);
564
- zMbcsFilename = 0;
565
- }
566
- return zMbcsFilename;
567
-}
568
-/*
569
-** Convert multibyte character string to UTF-8. Space to hold the
570
-** returned string is obtained from malloc().
571
-*/
572
-static char *mbcsToUtf8(const char *zFilename){
573
- char *zFilenameUtf8;
574
- WCHAR *zTmpWide;
575
-
576
- zTmpWide = mbcsToUnicode(zFilename);
577
- if( zTmpWide==0 ){
578
- return 0;
579
- }
580
-
581
- zFilenameUtf8 = unicodeToUtf8(zTmpWide);
582
- free(zTmpWide);
583
- return zFilenameUtf8;
584
-}
585
-#endif /* __MINGW32__ */
586
-
587511
588512
/*
589513
** Initialize a new database file with the given schema. If anything
590514
** goes wrong, call db_err() to exit.
591515
*/
@@ -598,11 +522,11 @@
598522
int rc;
599523
const char *zSql;
600524
va_list ap;
601525
602526
#ifdef __MINGW32__
603
- zFileName = mbcsToUtf8(zFileName);
527
+ zFileName = sqlite3_win32_mbcs_to_utf8(zFileName);
604528
#endif
605529
rc = sqlite3_open(zFileName, &db);
606530
if( rc!=SQLITE_OK ){
607531
db_err(sqlite3_errmsg(db));
608532
}
@@ -633,11 +557,11 @@
633557
const char *zVfs;
634558
sqlite3 *db;
635559
636560
zVfs = getenv("FOSSIL_VFS");
637561
#ifdef __MINGW32__
638
- zDbName = mbcsToUtf8(zDbName);
562
+ zDbName = sqlite3_win32_mbcs_to_utf8(zDbName);
639563
#endif
640564
rc = sqlite3_open_v2(
641565
zDbName, &db,
642566
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
643567
zVfs
@@ -659,11 +583,11 @@
659583
if( !g.db ){
660584
g.db = openDatabase(zDbName);
661585
db_connection_init();
662586
}else{
663587
#ifdef __MINGW32__
664
- zDbName = mbcsToUtf8(zDbName);
588
+ zDbName = sqlite3_win32_mbcs_to_utf8(zDbName);
665589
#endif
666590
db_multi_exec("ATTACH DATABASE %Q AS %s", zDbName, zLabel);
667591
}
668592
}
669593
@@ -866,11 +790,11 @@
866790
if( g.repositoryOpen ){
867791
return;
868792
}
869793
rep_not_found:
870794
if( errIfNotFound ){
871
- fossil_fatal("use --repository or -R to specific the repository database");
795
+ fossil_fatal("use --repository or -R to specify the repository database");
872796
}
873797
}
874798
875799
/*
876800
** Open the local database. If unable, exit with an error.
@@ -916,13 +840,16 @@
916840
}
917841
918842
/*
919843
** Create the default user accounts in the USER table.
920844
*/
921
-void db_create_default_users(int setupUserOnly){
845
+void db_create_default_users(int setupUserOnly, const char *zDefaultUser){
922846
const char *zUser;
923847
zUser = db_get("default-user", 0);
848
+ if( zUser==0 ){
849
+ zUser = zDefaultUser;
850
+ }
924851
if( zUser==0 ){
925852
#ifdef __MINGW32__
926853
zUser = getenv("USERNAME");
927854
#else
928855
zUser = getenv("USER");
@@ -958,11 +885,11 @@
958885
** The zInitialDate parameter determines the date of the initial check-in
959886
** that is automatically created. If zInitialDate is 0 then no initial
960887
** check-in is created. The makeServerCodes flag determines whether or
961888
** not server and project codes are invented for this repository.
962889
*/
963
-void db_initial_setup (const char *zInitialDate, int makeServerCodes){
890
+void db_initial_setup (const char *zInitialDate, const char *zDefaultUser, int makeServerCodes){
964891
char *zDate;
965892
Blob hash;
966893
Blob manifest;
967894
968895
db_set("content-schema", CONTENT_SCHEMA, 0);
@@ -975,11 +902,11 @@
975902
" VALUES('project-code', lower(hex(randomblob(20))));"
976903
);
977904
}
978905
if( !db_is_global("autosync") ) db_set_int("autosync", 1, 0);
979906
if( !db_is_global("localauth") ) db_set_int("localauth", 0, 0);
980
- db_create_default_users(0);
907
+ db_create_default_users(0, zDefaultUser);
981908
user_select();
982909
983910
if( zInitialDate ){
984911
int rid;
985912
blob_zero(&manifest);
@@ -1002,30 +929,41 @@
1002929
}
1003930
1004931
/*
1005932
** COMMAND: new
1006933
**
1007
-** Usage: %fossil new FILENAME
934
+** Usage: %fossil new ?OPTIONS? FILENAME
1008935
**
1009936
** Create a repository for a new project in the file named FILENAME.
1010937
** This command is distinct from "clone". The "clone" command makes
1011938
** a copy of an existing project. This command starts a new project.
939
+**
940
+** By default, your current login name is used to create the default
941
+** admin user. This can be overridden using the -A|--admin-user
942
+** parameter.
943
+**
944
+** Options:
945
+**
946
+** --admin-user|-A USERNAME
947
+**
1012948
*/
1013949
void create_repository_cmd(void){
1014950
char *zPassword;
1015951
const char *zDate; /* Date of the initial check-in */
952
+ const char *zDefaultUser; /* Optional name of the default user */
1016953
1017954
zDate = find_option("date-override",0,1);
955
+ zDefaultUser = find_option("admin-user","A",1);
1018956
if( zDate==0 ) zDate = "now";
1019957
if( g.argc!=3 ){
1020958
usage("REPOSITORY-NAME");
1021959
}
1022960
db_create_repository(g.argv[2]);
1023961
db_open_repository(g.argv[2]);
1024962
db_open_config(0);
1025963
db_begin_transaction();
1026
- db_initial_setup(zDate, 1);
964
+ db_initial_setup(zDate, zDefaultUser, 1);
1027965
db_end_transaction(0);
1028966
printf("project-id: %s\n", db_get("project-code", 0));
1029967
printf("server-id: %s\n", db_get("server-code", 0));
1030968
zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
1031969
printf("admin-user: %s (initial password is \"%s\")\n", g.zLogin, zPassword);
1032970
--- src/db.c
+++ src/db.c
@@ -506,86 +506,10 @@
506 z = mprintf("%s", sqlite3_column_text(s.pStmt, 0));
507 }
508 db_finalize(&s);
509 return z;
510 }
511
512 #ifdef __MINGW32__
513 /*
514 ** These routines (copied out of the os_win.c driver for SQLite) convert
515 ** character strings in various microsoft multi-byte character formats
516 ** into UTF-8. Fossil and SQLite always use only UTF-8 internally. These
517 ** routines are needed in order to convert from the default character set
518 ** currently in use by windows into UTF-8 when strings are imported from
519 ** the outside world.
520 */
521 /*
522 ** Convert microsoft unicode to UTF-8. Space to hold the returned string is
523 ** obtained from malloc().
524 ** Copied from sqlite3.c as is (petr)
525 */
526 static char *unicodeToUtf8(const WCHAR *zWideFilename){
527 int nByte;
528 char *zFilename;
529
530 nByte = WideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, 0, 0, 0, 0);
531 zFilename = malloc( nByte );
532 if( zFilename==0 ){
533 return 0;
534 }
535 nByte = WideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, zFilename, nByte,
536 0, 0);
537 if( nByte == 0 ){
538 free(zFilename);
539 zFilename = 0;
540 }
541 return zFilename;
542 }
543 /*
544 ** Convert an ansi string to microsoft unicode, based on the
545 ** current codepage settings for file apis.
546 **
547 ** Space to hold the returned string is obtained
548 ** from malloc.
549 */
550 static WCHAR *mbcsToUnicode(const char *zFilename){
551 int nByte;
552 WCHAR *zMbcsFilename;
553 int codepage = CP_ACP;
554
555 nByte = MultiByteToWideChar(codepage, 0, zFilename, -1, NULL,0)*sizeof(WCHAR);
556 zMbcsFilename = malloc( nByte*sizeof(zMbcsFilename[0]) );
557 if( zMbcsFilename==0 ){
558 return 0;
559 }
560
561 nByte = MultiByteToWideChar(codepage, 0, zFilename, -1, zMbcsFilename, nByte);
562 if( nByte==0 ){
563 free(zMbcsFilename);
564 zMbcsFilename = 0;
565 }
566 return zMbcsFilename;
567 }
568 /*
569 ** Convert multibyte character string to UTF-8. Space to hold the
570 ** returned string is obtained from malloc().
571 */
572 static char *mbcsToUtf8(const char *zFilename){
573 char *zFilenameUtf8;
574 WCHAR *zTmpWide;
575
576 zTmpWide = mbcsToUnicode(zFilename);
577 if( zTmpWide==0 ){
578 return 0;
579 }
580
581 zFilenameUtf8 = unicodeToUtf8(zTmpWide);
582 free(zTmpWide);
583 return zFilenameUtf8;
584 }
585 #endif /* __MINGW32__ */
586
587
588 /*
589 ** Initialize a new database file with the given schema. If anything
590 ** goes wrong, call db_err() to exit.
591 */
@@ -598,11 +522,11 @@
598 int rc;
599 const char *zSql;
600 va_list ap;
601
602 #ifdef __MINGW32__
603 zFileName = mbcsToUtf8(zFileName);
604 #endif
605 rc = sqlite3_open(zFileName, &db);
606 if( rc!=SQLITE_OK ){
607 db_err(sqlite3_errmsg(db));
608 }
@@ -633,11 +557,11 @@
633 const char *zVfs;
634 sqlite3 *db;
635
636 zVfs = getenv("FOSSIL_VFS");
637 #ifdef __MINGW32__
638 zDbName = mbcsToUtf8(zDbName);
639 #endif
640 rc = sqlite3_open_v2(
641 zDbName, &db,
642 SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
643 zVfs
@@ -659,11 +583,11 @@
659 if( !g.db ){
660 g.db = openDatabase(zDbName);
661 db_connection_init();
662 }else{
663 #ifdef __MINGW32__
664 zDbName = mbcsToUtf8(zDbName);
665 #endif
666 db_multi_exec("ATTACH DATABASE %Q AS %s", zDbName, zLabel);
667 }
668 }
669
@@ -866,11 +790,11 @@
866 if( g.repositoryOpen ){
867 return;
868 }
869 rep_not_found:
870 if( errIfNotFound ){
871 fossil_fatal("use --repository or -R to specific the repository database");
872 }
873 }
874
875 /*
876 ** Open the local database. If unable, exit with an error.
@@ -916,13 +840,16 @@
916 }
917
918 /*
919 ** Create the default user accounts in the USER table.
920 */
921 void db_create_default_users(int setupUserOnly){
922 const char *zUser;
923 zUser = db_get("default-user", 0);
 
 
 
924 if( zUser==0 ){
925 #ifdef __MINGW32__
926 zUser = getenv("USERNAME");
927 #else
928 zUser = getenv("USER");
@@ -958,11 +885,11 @@
958 ** The zInitialDate parameter determines the date of the initial check-in
959 ** that is automatically created. If zInitialDate is 0 then no initial
960 ** check-in is created. The makeServerCodes flag determines whether or
961 ** not server and project codes are invented for this repository.
962 */
963 void db_initial_setup (const char *zInitialDate, int makeServerCodes){
964 char *zDate;
965 Blob hash;
966 Blob manifest;
967
968 db_set("content-schema", CONTENT_SCHEMA, 0);
@@ -975,11 +902,11 @@
975 " VALUES('project-code', lower(hex(randomblob(20))));"
976 );
977 }
978 if( !db_is_global("autosync") ) db_set_int("autosync", 1, 0);
979 if( !db_is_global("localauth") ) db_set_int("localauth", 0, 0);
980 db_create_default_users(0);
981 user_select();
982
983 if( zInitialDate ){
984 int rid;
985 blob_zero(&manifest);
@@ -1002,30 +929,41 @@
1002 }
1003
1004 /*
1005 ** COMMAND: new
1006 **
1007 ** Usage: %fossil new FILENAME
1008 **
1009 ** Create a repository for a new project in the file named FILENAME.
1010 ** This command is distinct from "clone". The "clone" command makes
1011 ** a copy of an existing project. This command starts a new project.
 
 
 
 
 
 
 
 
 
1012 */
1013 void create_repository_cmd(void){
1014 char *zPassword;
1015 const char *zDate; /* Date of the initial check-in */
 
1016
1017 zDate = find_option("date-override",0,1);
 
1018 if( zDate==0 ) zDate = "now";
1019 if( g.argc!=3 ){
1020 usage("REPOSITORY-NAME");
1021 }
1022 db_create_repository(g.argv[2]);
1023 db_open_repository(g.argv[2]);
1024 db_open_config(0);
1025 db_begin_transaction();
1026 db_initial_setup(zDate, 1);
1027 db_end_transaction(0);
1028 printf("project-id: %s\n", db_get("project-code", 0));
1029 printf("server-id: %s\n", db_get("server-code", 0));
1030 zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
1031 printf("admin-user: %s (initial password is \"%s\")\n", g.zLogin, zPassword);
1032
--- src/db.c
+++ src/db.c
@@ -506,86 +506,10 @@
506 z = mprintf("%s", sqlite3_column_text(s.pStmt, 0));
507 }
508 db_finalize(&s);
509 return z;
510 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
511
512 /*
513 ** Initialize a new database file with the given schema. If anything
514 ** goes wrong, call db_err() to exit.
515 */
@@ -598,11 +522,11 @@
522 int rc;
523 const char *zSql;
524 va_list ap;
525
526 #ifdef __MINGW32__
527 zFileName = sqlite3_win32_mbcs_to_utf8(zFileName);
528 #endif
529 rc = sqlite3_open(zFileName, &db);
530 if( rc!=SQLITE_OK ){
531 db_err(sqlite3_errmsg(db));
532 }
@@ -633,11 +557,11 @@
557 const char *zVfs;
558 sqlite3 *db;
559
560 zVfs = getenv("FOSSIL_VFS");
561 #ifdef __MINGW32__
562 zDbName = sqlite3_win32_mbcs_to_utf8(zDbName);
563 #endif
564 rc = sqlite3_open_v2(
565 zDbName, &db,
566 SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
567 zVfs
@@ -659,11 +583,11 @@
583 if( !g.db ){
584 g.db = openDatabase(zDbName);
585 db_connection_init();
586 }else{
587 #ifdef __MINGW32__
588 zDbName = sqlite3_win32_mbcs_to_utf8(zDbName);
589 #endif
590 db_multi_exec("ATTACH DATABASE %Q AS %s", zDbName, zLabel);
591 }
592 }
593
@@ -866,11 +790,11 @@
790 if( g.repositoryOpen ){
791 return;
792 }
793 rep_not_found:
794 if( errIfNotFound ){
795 fossil_fatal("use --repository or -R to specify the repository database");
796 }
797 }
798
799 /*
800 ** Open the local database. If unable, exit with an error.
@@ -916,13 +840,16 @@
840 }
841
842 /*
843 ** Create the default user accounts in the USER table.
844 */
845 void db_create_default_users(int setupUserOnly, const char *zDefaultUser){
846 const char *zUser;
847 zUser = db_get("default-user", 0);
848 if( zUser==0 ){
849 zUser = zDefaultUser;
850 }
851 if( zUser==0 ){
852 #ifdef __MINGW32__
853 zUser = getenv("USERNAME");
854 #else
855 zUser = getenv("USER");
@@ -958,11 +885,11 @@
885 ** The zInitialDate parameter determines the date of the initial check-in
886 ** that is automatically created. If zInitialDate is 0 then no initial
887 ** check-in is created. The makeServerCodes flag determines whether or
888 ** not server and project codes are invented for this repository.
889 */
890 void db_initial_setup (const char *zInitialDate, const char *zDefaultUser, int makeServerCodes){
891 char *zDate;
892 Blob hash;
893 Blob manifest;
894
895 db_set("content-schema", CONTENT_SCHEMA, 0);
@@ -975,11 +902,11 @@
902 " VALUES('project-code', lower(hex(randomblob(20))));"
903 );
904 }
905 if( !db_is_global("autosync") ) db_set_int("autosync", 1, 0);
906 if( !db_is_global("localauth") ) db_set_int("localauth", 0, 0);
907 db_create_default_users(0, zDefaultUser);
908 user_select();
909
910 if( zInitialDate ){
911 int rid;
912 blob_zero(&manifest);
@@ -1002,30 +929,41 @@
929 }
930
931 /*
932 ** COMMAND: new
933 **
934 ** Usage: %fossil new ?OPTIONS? FILENAME
935 **
936 ** Create a repository for a new project in the file named FILENAME.
937 ** This command is distinct from "clone". The "clone" command makes
938 ** a copy of an existing project. This command starts a new project.
939 **
940 ** By default, your current login name is used to create the default
941 ** admin user. This can be overridden using the -A|--admin-user
942 ** parameter.
943 **
944 ** Options:
945 **
946 ** --admin-user|-A USERNAME
947 **
948 */
949 void create_repository_cmd(void){
950 char *zPassword;
951 const char *zDate; /* Date of the initial check-in */
952 const char *zDefaultUser; /* Optional name of the default user */
953
954 zDate = find_option("date-override",0,1);
955 zDefaultUser = find_option("admin-user","A",1);
956 if( zDate==0 ) zDate = "now";
957 if( g.argc!=3 ){
958 usage("REPOSITORY-NAME");
959 }
960 db_create_repository(g.argv[2]);
961 db_open_repository(g.argv[2]);
962 db_open_config(0);
963 db_begin_transaction();
964 db_initial_setup(zDate, zDefaultUser, 1);
965 db_end_transaction(0);
966 printf("project-id: %s\n", db_get("project-code", 0));
967 printf("server-id: %s\n", db_get("server-code", 0));
968 zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
969 printf("admin-user: %s (initial password is \"%s\")\n", g.zLogin, zPassword);
970
+4 -1
--- src/delta.c
+++ src/delta.c
@@ -30,10 +30,11 @@
3030
*/
3131
#include <stdio.h>
3232
#include <assert.h>
3333
#include <stdlib.h>
3434
#include <string.h>
35
+#include "delta.h"
3536
3637
/*
3738
** Macros for turning debugging printfs on and off
3839
*/
3940
#if 0
@@ -62,11 +63,11 @@
6263
}
6364
#else
6465
# define DEBUG2(X)
6566
#endif
6667
67
-
68
+#if INTERFACE
6869
/*
6970
** The "u32" type must be an unsigned 32-bit integer. Adjust this
7071
*/
7172
typedef unsigned int u32;
7273
@@ -73,10 +74,12 @@
7374
/*
7475
** Must be a 16-bit value
7576
*/
7677
typedef short int s16;
7778
typedef unsigned short int u16;
79
+
80
+#endif /* INTERFACE */
7881
7982
/*
8083
** The width of a hash window in bytes. The algorithm only works if this
8184
** is a power of 2.
8285
*/
8386
--- src/delta.c
+++ src/delta.c
@@ -30,10 +30,11 @@
30 */
31 #include <stdio.h>
32 #include <assert.h>
33 #include <stdlib.h>
34 #include <string.h>
 
35
36 /*
37 ** Macros for turning debugging printfs on and off
38 */
39 #if 0
@@ -62,11 +63,11 @@
62 }
63 #else
64 # define DEBUG2(X)
65 #endif
66
67
68 /*
69 ** The "u32" type must be an unsigned 32-bit integer. Adjust this
70 */
71 typedef unsigned int u32;
72
@@ -73,10 +74,12 @@
73 /*
74 ** Must be a 16-bit value
75 */
76 typedef short int s16;
77 typedef unsigned short int u16;
 
 
78
79 /*
80 ** The width of a hash window in bytes. The algorithm only works if this
81 ** is a power of 2.
82 */
83
--- src/delta.c
+++ src/delta.c
@@ -30,10 +30,11 @@
30 */
31 #include <stdio.h>
32 #include <assert.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include "delta.h"
36
37 /*
38 ** Macros for turning debugging printfs on and off
39 */
40 #if 0
@@ -62,11 +63,11 @@
63 }
64 #else
65 # define DEBUG2(X)
66 #endif
67
68 #if INTERFACE
69 /*
70 ** The "u32" type must be an unsigned 32-bit integer. Adjust this
71 */
72 typedef unsigned int u32;
73
@@ -73,10 +74,12 @@
74 /*
75 ** Must be a 16-bit value
76 */
77 typedef short int s16;
78 typedef unsigned short int u16;
79
80 #endif /* INTERFACE */
81
82 /*
83 ** The width of a hash window in bytes. The algorithm only works if this
84 ** is a power of 2.
85 */
86
+2 -1
--- src/file.c
+++ src/file.c
@@ -101,11 +101,11 @@
101101
int file_isdir(const char *zFilename){
102102
int rc;
103103
104104
if( zFilename ){
105105
char *zFN = mprintf("%s", zFilename);
106
- file_simplify_name(zFN, strlen(zFN));
106
+ file_simplify_name(zFN, -1);
107107
rc = getStat(zFN);
108108
free(zFN);
109109
}else{
110110
rc = getStat(0);
111111
}
@@ -229,10 +229,11 @@
229229
**
230230
** Changes are made in-place. Return the new name length.
231231
*/
232232
int file_simplify_name(char *z, int n){
233233
int i, j;
234
+ if( n<0 ) n = strlen(z);
234235
#ifdef __MINGW32__
235236
for(i=0; i<n; i++){
236237
if( z[i]=='\\' ) z[i] = '/';
237238
}
238239
#endif
239240
240241
ADDED src/graph.c
--- src/file.c
+++ src/file.c
@@ -101,11 +101,11 @@
101 int file_isdir(const char *zFilename){
102 int rc;
103
104 if( zFilename ){
105 char *zFN = mprintf("%s", zFilename);
106 file_simplify_name(zFN, strlen(zFN));
107 rc = getStat(zFN);
108 free(zFN);
109 }else{
110 rc = getStat(0);
111 }
@@ -229,10 +229,11 @@
229 **
230 ** Changes are made in-place. Return the new name length.
231 */
232 int file_simplify_name(char *z, int n){
233 int i, j;
 
234 #ifdef __MINGW32__
235 for(i=0; i<n; i++){
236 if( z[i]=='\\' ) z[i] = '/';
237 }
238 #endif
239
240 DDED src/graph.c
--- src/file.c
+++ src/file.c
@@ -101,11 +101,11 @@
101 int file_isdir(const char *zFilename){
102 int rc;
103
104 if( zFilename ){
105 char *zFN = mprintf("%s", zFilename);
106 file_simplify_name(zFN, -1);
107 rc = getStat(zFN);
108 free(zFN);
109 }else{
110 rc = getStat(0);
111 }
@@ -229,10 +229,11 @@
229 **
230 ** Changes are made in-place. Return the new name length.
231 */
232 int file_simplify_name(char *z, int n){
233 int i, j;
234 if( n<0 ) n = strlen(z);
235 #ifdef __MINGW32__
236 for(i=0; i<n; i++){
237 if( z[i]=='\\' ) z[i] = '/';
238 }
239 #endif
240
241 DDED src/graph.c
--- a/src/graph.c
+++ b/src/graph.c
@@ -0,0 +1,5 @@
1
+!=isLeaf /* True if tRailrailMap)!=isLeaf,!=isLeaf = isLeafBag allRids;
2
+ Bag nnRowpRow->isLeaf )else!=isLeaf /* True if tRailrailMap)!=isLeaf,!=isLeaf = isLeafBag allRids;
3
+ Bag ntop, int btm, u32 inUseMask;
4
+ int i; return i;
5
+=isLeaf /* True if tRailrailMap tRailrailMap)!=isLeaf,!=!=isLeaf /* True if tRailrailMapassert( pDesc!=0 );
--- a/src/graph.c
+++ b/src/graph.c
@@ -0,0 +1,5 @@
 
 
 
 
 
--- a/src/graph.c
+++ b/src/graph.c
@@ -0,0 +1,5 @@
1 !=isLeaf /* True if tRailrailMap)!=isLeaf,!=isLeaf = isLeafBag allRids;
2 Bag nnRowpRow->isLeaf )else!=isLeaf /* True if tRailrailMap)!=isLeaf,!=isLeaf = isLeafBag allRids;
3 Bag ntop, int btm, u32 inUseMask;
4 int i; return i;
5 =isLeaf /* True if tRailrailMap tRailrailMap)!=isLeaf,!=!=isLeaf /* True if tRailrailMapassert( pDesc!=0 );
+177 -54
--- src/main.c
+++ src/main.c
@@ -453,11 +453,11 @@
453453
printf("\n");
454454
}
455455
}
456456
457457
/*
458
-** COM MAND: commands
458
+** COM -off- MAND: commands
459459
**
460460
** Usage: %fossil commands
461461
** List all supported commands.
462462
*/
463463
void cmd_cmd_list(void){
@@ -541,11 +541,11 @@
541541
putchar('\n');
542542
}
543543
544544
/*
545545
** Set the g.zBaseURL value to the full URL for the toplevel of
546
-** the fossil tree. Set g.zHomeURL to g.zBaseURL without the
546
+** the fossil tree. Set g.zTop to g.zBaseURL without the
547547
** leading "http://" and the host and port.
548548
*/
549549
void set_base_url(void){
550550
int i;
551551
const char *zHost = PD("HTTP_HOST","");
@@ -567,31 +567,125 @@
567567
** Send an HTTP redirect back to the designated Index Page.
568568
*/
569569
void fossil_redirect_home(void){
570570
cgi_redirectf("%s%s", g.zBaseURL, db_get("index-page", "/index"));
571571
}
572
+
573
+/*
574
+** If running as root, chroot to the directory containing the
575
+** repository zRepo and then drop root privileges. Return the
576
+** new repository name.
577
+**
578
+** zRepo might be a directory itself. In that case chroot into
579
+** the directory zRepo.
580
+**
581
+** Assume the user-id and group-id of the repository, or if zRepo
582
+** is a directory, of that directory.
583
+*/
584
+static char *enter_chroot_jail(char *zRepo){
585
+#if !defined(__MINGW32__)
586
+ if( getuid()==0 ){
587
+ int i;
588
+ struct stat sStat;
589
+ Blob dir;
590
+ char *zDir;
591
+
592
+ file_canonical_name(zRepo, &dir);
593
+ zDir = blob_str(&dir);
594
+ if( file_isdir(zDir)==1 ){
595
+ chdir(zDir);
596
+ chroot(zDir);
597
+ zRepo = "/";
598
+ }else{
599
+ for(i=strlen(zDir)-1; i>0 && zDir[i]!='/'; i--){}
600
+ if( zDir[i]!='/' ) fossil_panic("bad repository name: %s", zRepo);
601
+ zDir[i] = 0;
602
+ chdir(zDir);
603
+ chroot(zDir);
604
+ zDir[i] = '/';
605
+ zRepo = &zDir[i];
606
+ }
607
+ if( stat(zRepo, &sStat)!=0 ){
608
+ fossil_fatal("cannot stat() repository: %s", zRepo);
609
+ }
610
+ setgid(sStat.st_gid);
611
+ setuid(sStat.st_uid);
612
+ }
613
+#endif
614
+ return zRepo;
615
+}
572616
573617
/*
574618
** Preconditions:
575619
**
576
-** * Environment variables are set up according to the CGI standard.
577
-** * The respository database has been located and opened.
620
+** * Environment variables are set up according to the CGI standard.
621
+**
622
+** If the repository is known, it has already been opened. If unknown,
623
+** then g.zRepositoryName holds the directory that contains the repository
624
+** and the actual repository is taken from the first element of PATH_INFO.
578625
**
579626
** Process the webpage specified by the PATH_INFO or REQUEST_URI
580627
** environment variable.
581628
*/
582
-static void process_one_web_page(void){
629
+static void process_one_web_page(const char *zNotFound){
583630
const char *zPathInfo;
584631
char *zPath = NULL;
585632
int idx;
586633
int i;
634
+
635
+ /* If the repository has not been opened already, then find the
636
+ ** repository based on the first element of PATH_INFO and open it.
637
+ */
638
+ zPathInfo = P("PATH_INFO");
639
+ if( !g.repositoryOpen ){
640
+ char *zRepo;
641
+ const char *zOldScript = PD("SCRIPT_NAME", "");
642
+ char *zNewScript;
643
+ int j, k;
644
+
645
+ i = 1;
646
+ while( zPathInfo[i] && zPathInfo[i]!='/' ){ i++; }
647
+ zRepo = mprintf("%s%.*s.fossil",g.zRepositoryName,i,zPathInfo);
648
+
649
+ /* To avoid mischief, make sure the repository basename contains no
650
+ ** characters other than alphanumerics, "-", and "_".
651
+ */
652
+ for(j=strlen(g.zRepositoryName)+1, k=0; k<i-1; j++, k++){
653
+ if( !isalnum(zRepo[j]) && zRepo[j]!='-' ) zRepo[j] = '_';
654
+ }
655
+
656
+ if( file_size(zRepo)<1024 ){
657
+ if( zNotFound ){
658
+ cgi_redirect(zNotFound);
659
+ }else{
660
+ @ <h1>Not Found</h1>
661
+ cgi_set_status(404, "not found");
662
+ cgi_reply();
663
+ }
664
+ return;
665
+ }
666
+ zNewScript = mprintf("%s%.*s", zOldScript, i, zPathInfo);
667
+ cgi_replace_parameter("PATH_INFO", &zPathInfo[i+1]);
668
+ zPathInfo += i;
669
+ cgi_replace_parameter("SCRIPT_NAME", zNewScript);
670
+ db_open_repository(zRepo);
671
+ if( g.fHttpTrace ){
672
+ fprintf(stderr,
673
+ "# repository: [%s]\n"
674
+ "# new PATH_INFO = [%s]\n"
675
+ "# new SCRIPT_NAME = [%s]\n",
676
+ zRepo, zPathInfo, zNewScript);
677
+ }
678
+ }
587679
588680
/* Find the page that the user has requested, construct and deliver that
589681
** page.
590682
*/
683
+ if( g.zContentType && memcmp(g.zContentType, "application/x-fossil", 20)==0 ){
684
+ zPathInfo = "/xfer";
685
+ }
591686
set_base_url();
592
- zPathInfo = P("PATH_INFO");
593687
if( zPathInfo==0 || zPathInfo[0]==0
594688
|| (zPathInfo[0]=='/' && zPathInfo[1]==0) ){
595689
fossil_redirect_home();
596690
}else{
597691
zPath = mprintf("%s", zPathInfo);
@@ -660,10 +754,11 @@
660754
** the repository, fossil will generate a webpage on stdout based on
661755
** the values of standard CGI environment variables.
662756
*/
663757
void cmd_cgi(void){
664758
const char *zFile;
759
+ const char *zNotFound = 0;
665760
Blob config, line, key, value;
666761
if( g.argc==3 && strcmp(g.argv[1],"cgi")==0 ){
667762
zFile = g.argv[2];
668763
}else{
669764
zFile = g.argv[1];
@@ -697,19 +792,59 @@
697792
continue;
698793
}
699794
if( blob_eq(&key, "repository:") && blob_token(&line, &value) ){
700795
db_open_repository(blob_str(&value));
701796
blob_reset(&value);
702
- blob_reset(&config);
703
- break;
797
+ continue;
798
+ }
799
+ if( blob_eq(&key, "directory:") && blob_token(&line, &value) ){
800
+ db_close();
801
+ g.zRepositoryName = mprintf("%s", blob_str(&value));
802
+ blob_reset(&value);
803
+ continue;
804
+ }
805
+ if( blob_eq(&key, "notfound:") && blob_token(&line, &value) ){
806
+ zNotFound = mprintf("%s", blob_str(&value));
807
+ blob_reset(&value);
808
+ continue;
704809
}
705810
}
706
- if( g.db==0 ){
811
+ blob_reset(&config);
812
+ if( g.db==0 && g.zRepositoryName==0 ){
707813
cgi_panic("Unable to find or open the project repository");
708814
}
709815
cgi_init();
710
- process_one_web_page();
816
+ process_one_web_page(zNotFound);
817
+}
818
+
819
+/*
820
+** If g.argv[2] exists then it is either the name of a repository
821
+** that will be used by a server, or else it is a directory that
822
+** contains multiple repositories that can be served. If g.argv[2]
823
+** is a directory, the repositories it contains must be named
824
+** "*.fossil". If g.argv[2] does not exists, then we must be within
825
+** a check-out and the repository to be served is the repository of
826
+** that check-out.
827
+**
828
+** Open the respository to be served if it is known. If g.argv[2] is
829
+** a directory full of repositories, then set g.zRepositoryName to
830
+** the name of that directory and the specific repository will be
831
+** opened later by process_one_web_page() based on the content of
832
+** the PATH_INFO variable.
833
+**
834
+** If disallowDir is set, then the directory full of repositories method
835
+** is disallowed.
836
+*/
837
+static void find_server_repository(int disallowDir){
838
+ if( g.argc<3 ){
839
+ db_must_be_within_tree();
840
+ }else if( !disallowDir && file_isdir(g.argv[2])==1 ){
841
+ g.zRepositoryName = mprintf("%s", g.argv[2]);
842
+ file_simplify_name(g.zRepositoryName, -1);
843
+ }else{
844
+ db_open_repository(g.argv[2]);
845
+ }
711846
}
712847
713848
/*
714849
** undocumented format:
715850
**
@@ -717,41 +852,30 @@
717852
**
718853
** The argv==6 form is used by the win32 server only.
719854
**
720855
** COMMAND: http
721856
**
722
-** Usage: %fossil http REPOSITORY
857
+** Usage: %fossil http REPOSITORY [--notfound URL]
723858
**
724859
** Handle a single HTTP request appearing on stdin. The resulting webpage
725860
** is delivered on stdout. This method is used to launch an HTTP request
726861
** handler from inetd, for example. The argument is the name of the
727862
** repository.
863
+**
864
+** If REPOSITORY is a directory that contains one or more respositories
865
+** with names of the form "*.fossil" then the first element of the URL
866
+** pathname selects among the various repositories. If the pathname does
867
+** not select a valid repository and the --notfound option is available,
868
+** then the server redirects (HTTP code 302) to the URL of --notfound.
728869
*/
729870
void cmd_http(void){
730871
const char *zIpAddr;
872
+ const char *zNotFound;
873
+ zNotFound = find_option("notfound", 0, 1);
731874
if( g.argc!=2 && g.argc!=3 && g.argc!=6 ){
732875
cgi_panic("no repository specified");
733876
}
734
-#if !defined(__MINGW32__)
735
- if( g.argc==3 && getuid()==0 ){
736
- int i;
737
- char *zRepo = g.argv[2];
738
- struct stat sStat;
739
- for(i=strlen(zRepo)-1; i>0 && zRepo[i]!='/'; i--){}
740
- if( zRepo[i]=='/' ){
741
- zRepo[i] = 0;
742
- chdir(g.argv[2]);
743
- chroot(g.argv[2]);
744
- g.argv[2] = &zRepo[i+1];
745
- }
746
- if( stat(g.argv[2], &sStat)!=0 ){
747
- fossil_fatal("cannot stat() repository: %s", g.argv[2]);
748
- }
749
- setgid(sStat.st_gid);
750
- setuid(sStat.st_uid);
751
- }
752
-#endif
753877
g.cgiPanic = 1;
754878
g.fullHttpReply = 1;
755879
if( g.argc==6 ){
756880
g.httpIn = fopen(g.argv[3], "rb");
757881
g.httpOut = fopen(g.argv[4], "wb");
@@ -759,17 +883,14 @@
759883
}else{
760884
g.httpIn = stdin;
761885
g.httpOut = stdout;
762886
zIpAddr = 0;
763887
}
764
- if( g.argc>=3 ){
765
- db_open_repository(g.argv[2]);
766
- }else{
767
- db_must_be_within_tree();
768
- }
888
+ find_server_repository(0);
889
+ g.zRepositoryName = enter_chroot_jail(g.zRepositoryName);
769890
cgi_handle_http_request(zIpAddr);
770
- process_one_web_page();
891
+ process_one_web_page(zNotFound);
771892
}
772893
773894
/*
774895
** COMMAND: test-http
775896
** Works like the http command but gives setup permission to all users.
@@ -817,16 +938,23 @@
817938
** The repository argument may be omitted if the working directory is
818939
** within an open checkout.
819940
**
820941
** The "ui" command automatically starts a web browser after initializing
821942
** the web server.
943
+**
944
+** In the "server" command, the REPOSITORY can be a directory (aka folder)
945
+** that contains one or more respositories with names ending in ".fossil".
946
+** In that case, the first element of the URL is used to select among the
947
+** various repositories.
822948
*/
823949
void cmd_webserver(void){
824
- int iPort, mxPort;
825
- const char *zPort;
826
- char *zBrowser;
827
- char *zBrowserCmd = 0;
950
+ int iPort, mxPort; /* Range of TCP ports allowed */
951
+ const char *zPort; /* Value of the --port option */
952
+ char *zBrowser; /* Name of web browser program */
953
+ char *zBrowserCmd = 0; /* Command to launch the web browser */
954
+ int isUiCmd; /* True if command is "ui", not "server' */
955
+ const char *zNotFound; /* The --notfound option or NULL */
828956
829957
#ifdef __MINGW32__
830958
const char *zStopperFile; /* Name of file used to terminate server */
831959
zStopperFile = find_option("stopper", 0, 1);
832960
#endif
@@ -834,25 +962,23 @@
834962
g.thTrace = find_option("th-trace", 0, 0)!=0;
835963
if( g.thTrace ){
836964
blob_zero(&g.thLog);
837965
}
838966
zPort = find_option("port", "P", 1);
967
+ zNotFound = find_option("notfound", 0, 1);
839968
if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?");
840
- if( g.argc==2 ){
841
- db_must_be_within_tree();
842
- }else{
843
- db_open_repository(g.argv[2]);
844
- }
969
+ isUiCmd = g.argv[1][0]=='u';
970
+ find_server_repository(isUiCmd);
845971
if( zPort ){
846972
iPort = mxPort = atoi(zPort);
847973
}else{
848974
iPort = db_get_int("http-port", 8080);
849975
mxPort = iPort+100;
850976
}
851977
#ifndef __MINGW32__
852978
/* Unix implementation */
853
- if( g.argv[1][0]=='u' ){
979
+ if( isUiCmd ){
854980
#if !defined(__DARWIN__) && !defined(__APPLE__)
855981
zBrowser = db_get("web-browser", 0);
856982
if( zBrowser==0 ){
857983
static char *azBrowserProg[] = { "xdg-open", "gnome-open", "firefox" };
858984
int i;
@@ -877,22 +1003,19 @@
8771003
g.httpOut = stdout;
8781004
if( g.fHttpTrace || g.fSqlTrace ){
8791005
fprintf(stderr, "====== SERVER pid %d =======\n", getpid());
8801006
}
8811007
g.cgiPanic = 1;
882
- if( g.argc==2 ){
883
- db_must_be_within_tree();
884
- }else{
885
- db_open_repository(g.argv[2]);
886
- }
1008
+ find_server_repository(isUiCmd);
1009
+ g.zRepositoryName = enter_chroot_jail(g.zRepositoryName);
8871010
cgi_handle_http_request(0);
888
- process_one_web_page();
1011
+ process_one_web_page(zNotFound);
8891012
#else
8901013
/* Win32 implementation */
891
- if( g.argv[1][0]=='u' ){
1014
+ if( isUiCmd ){
8921015
zBrowser = db_get("web-browser", "start");
8931016
zBrowserCmd = mprintf("%s http://127.0.0.1:%%d/", zBrowser);
8941017
}
8951018
db_close();
896
- win32_http_server(iPort, mxPort, zBrowserCmd, zStopperFile);
1019
+ win32_http_server(iPort, mxPort, zBrowserCmd, zStopperFile, zNotFound);
8971020
#endif
8981021
}
8991022
--- src/main.c
+++ src/main.c
@@ -453,11 +453,11 @@
453 printf("\n");
454 }
455 }
456
457 /*
458 ** COM MAND: commands
459 **
460 ** Usage: %fossil commands
461 ** List all supported commands.
462 */
463 void cmd_cmd_list(void){
@@ -541,11 +541,11 @@
541 putchar('\n');
542 }
543
544 /*
545 ** Set the g.zBaseURL value to the full URL for the toplevel of
546 ** the fossil tree. Set g.zHomeURL to g.zBaseURL without the
547 ** leading "http://" and the host and port.
548 */
549 void set_base_url(void){
550 int i;
551 const char *zHost = PD("HTTP_HOST","");
@@ -567,31 +567,125 @@
567 ** Send an HTTP redirect back to the designated Index Page.
568 */
569 void fossil_redirect_home(void){
570 cgi_redirectf("%s%s", g.zBaseURL, db_get("index-page", "/index"));
571 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
572
573 /*
574 ** Preconditions:
575 **
576 ** * Environment variables are set up according to the CGI standard.
577 ** * The respository database has been located and opened.
 
 
 
578 **
579 ** Process the webpage specified by the PATH_INFO or REQUEST_URI
580 ** environment variable.
581 */
582 static void process_one_web_page(void){
583 const char *zPathInfo;
584 char *zPath = NULL;
585 int idx;
586 int i;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
587
588 /* Find the page that the user has requested, construct and deliver that
589 ** page.
590 */
 
 
 
591 set_base_url();
592 zPathInfo = P("PATH_INFO");
593 if( zPathInfo==0 || zPathInfo[0]==0
594 || (zPathInfo[0]=='/' && zPathInfo[1]==0) ){
595 fossil_redirect_home();
596 }else{
597 zPath = mprintf("%s", zPathInfo);
@@ -660,10 +754,11 @@
660 ** the repository, fossil will generate a webpage on stdout based on
661 ** the values of standard CGI environment variables.
662 */
663 void cmd_cgi(void){
664 const char *zFile;
 
665 Blob config, line, key, value;
666 if( g.argc==3 && strcmp(g.argv[1],"cgi")==0 ){
667 zFile = g.argv[2];
668 }else{
669 zFile = g.argv[1];
@@ -697,19 +792,59 @@
697 continue;
698 }
699 if( blob_eq(&key, "repository:") && blob_token(&line, &value) ){
700 db_open_repository(blob_str(&value));
701 blob_reset(&value);
702 blob_reset(&config);
703 break;
 
 
 
 
 
 
 
 
 
 
704 }
705 }
706 if( g.db==0 ){
 
707 cgi_panic("Unable to find or open the project repository");
708 }
709 cgi_init();
710 process_one_web_page();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
711 }
712
713 /*
714 ** undocumented format:
715 **
@@ -717,41 +852,30 @@
717 **
718 ** The argv==6 form is used by the win32 server only.
719 **
720 ** COMMAND: http
721 **
722 ** Usage: %fossil http REPOSITORY
723 **
724 ** Handle a single HTTP request appearing on stdin. The resulting webpage
725 ** is delivered on stdout. This method is used to launch an HTTP request
726 ** handler from inetd, for example. The argument is the name of the
727 ** repository.
 
 
 
 
 
 
728 */
729 void cmd_http(void){
730 const char *zIpAddr;
 
 
731 if( g.argc!=2 && g.argc!=3 && g.argc!=6 ){
732 cgi_panic("no repository specified");
733 }
734 #if !defined(__MINGW32__)
735 if( g.argc==3 && getuid()==0 ){
736 int i;
737 char *zRepo = g.argv[2];
738 struct stat sStat;
739 for(i=strlen(zRepo)-1; i>0 && zRepo[i]!='/'; i--){}
740 if( zRepo[i]=='/' ){
741 zRepo[i] = 0;
742 chdir(g.argv[2]);
743 chroot(g.argv[2]);
744 g.argv[2] = &zRepo[i+1];
745 }
746 if( stat(g.argv[2], &sStat)!=0 ){
747 fossil_fatal("cannot stat() repository: %s", g.argv[2]);
748 }
749 setgid(sStat.st_gid);
750 setuid(sStat.st_uid);
751 }
752 #endif
753 g.cgiPanic = 1;
754 g.fullHttpReply = 1;
755 if( g.argc==6 ){
756 g.httpIn = fopen(g.argv[3], "rb");
757 g.httpOut = fopen(g.argv[4], "wb");
@@ -759,17 +883,14 @@
759 }else{
760 g.httpIn = stdin;
761 g.httpOut = stdout;
762 zIpAddr = 0;
763 }
764 if( g.argc>=3 ){
765 db_open_repository(g.argv[2]);
766 }else{
767 db_must_be_within_tree();
768 }
769 cgi_handle_http_request(zIpAddr);
770 process_one_web_page();
771 }
772
773 /*
774 ** COMMAND: test-http
775 ** Works like the http command but gives setup permission to all users.
@@ -817,16 +938,23 @@
817 ** The repository argument may be omitted if the working directory is
818 ** within an open checkout.
819 **
820 ** The "ui" command automatically starts a web browser after initializing
821 ** the web server.
 
 
 
 
 
822 */
823 void cmd_webserver(void){
824 int iPort, mxPort;
825 const char *zPort;
826 char *zBrowser;
827 char *zBrowserCmd = 0;
 
 
828
829 #ifdef __MINGW32__
830 const char *zStopperFile; /* Name of file used to terminate server */
831 zStopperFile = find_option("stopper", 0, 1);
832 #endif
@@ -834,25 +962,23 @@
834 g.thTrace = find_option("th-trace", 0, 0)!=0;
835 if( g.thTrace ){
836 blob_zero(&g.thLog);
837 }
838 zPort = find_option("port", "P", 1);
 
839 if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?");
840 if( g.argc==2 ){
841 db_must_be_within_tree();
842 }else{
843 db_open_repository(g.argv[2]);
844 }
845 if( zPort ){
846 iPort = mxPort = atoi(zPort);
847 }else{
848 iPort = db_get_int("http-port", 8080);
849 mxPort = iPort+100;
850 }
851 #ifndef __MINGW32__
852 /* Unix implementation */
853 if( g.argv[1][0]=='u' ){
854 #if !defined(__DARWIN__) && !defined(__APPLE__)
855 zBrowser = db_get("web-browser", 0);
856 if( zBrowser==0 ){
857 static char *azBrowserProg[] = { "xdg-open", "gnome-open", "firefox" };
858 int i;
@@ -877,22 +1003,19 @@
877 g.httpOut = stdout;
878 if( g.fHttpTrace || g.fSqlTrace ){
879 fprintf(stderr, "====== SERVER pid %d =======\n", getpid());
880 }
881 g.cgiPanic = 1;
882 if( g.argc==2 ){
883 db_must_be_within_tree();
884 }else{
885 db_open_repository(g.argv[2]);
886 }
887 cgi_handle_http_request(0);
888 process_one_web_page();
889 #else
890 /* Win32 implementation */
891 if( g.argv[1][0]=='u' ){
892 zBrowser = db_get("web-browser", "start");
893 zBrowserCmd = mprintf("%s http://127.0.0.1:%%d/", zBrowser);
894 }
895 db_close();
896 win32_http_server(iPort, mxPort, zBrowserCmd, zStopperFile);
897 #endif
898 }
899
--- src/main.c
+++ src/main.c
@@ -453,11 +453,11 @@
453 printf("\n");
454 }
455 }
456
457 /*
458 ** COM -off- MAND: commands
459 **
460 ** Usage: %fossil commands
461 ** List all supported commands.
462 */
463 void cmd_cmd_list(void){
@@ -541,11 +541,11 @@
541 putchar('\n');
542 }
543
544 /*
545 ** Set the g.zBaseURL value to the full URL for the toplevel of
546 ** the fossil tree. Set g.zTop to g.zBaseURL without the
547 ** leading "http://" and the host and port.
548 */
549 void set_base_url(void){
550 int i;
551 const char *zHost = PD("HTTP_HOST","");
@@ -567,31 +567,125 @@
567 ** Send an HTTP redirect back to the designated Index Page.
568 */
569 void fossil_redirect_home(void){
570 cgi_redirectf("%s%s", g.zBaseURL, db_get("index-page", "/index"));
571 }
572
573 /*
574 ** If running as root, chroot to the directory containing the
575 ** repository zRepo and then drop root privileges. Return the
576 ** new repository name.
577 **
578 ** zRepo might be a directory itself. In that case chroot into
579 ** the directory zRepo.
580 **
581 ** Assume the user-id and group-id of the repository, or if zRepo
582 ** is a directory, of that directory.
583 */
584 static char *enter_chroot_jail(char *zRepo){
585 #if !defined(__MINGW32__)
586 if( getuid()==0 ){
587 int i;
588 struct stat sStat;
589 Blob dir;
590 char *zDir;
591
592 file_canonical_name(zRepo, &dir);
593 zDir = blob_str(&dir);
594 if( file_isdir(zDir)==1 ){
595 chdir(zDir);
596 chroot(zDir);
597 zRepo = "/";
598 }else{
599 for(i=strlen(zDir)-1; i>0 && zDir[i]!='/'; i--){}
600 if( zDir[i]!='/' ) fossil_panic("bad repository name: %s", zRepo);
601 zDir[i] = 0;
602 chdir(zDir);
603 chroot(zDir);
604 zDir[i] = '/';
605 zRepo = &zDir[i];
606 }
607 if( stat(zRepo, &sStat)!=0 ){
608 fossil_fatal("cannot stat() repository: %s", zRepo);
609 }
610 setgid(sStat.st_gid);
611 setuid(sStat.st_uid);
612 }
613 #endif
614 return zRepo;
615 }
616
617 /*
618 ** Preconditions:
619 **
620 ** * Environment variables are set up according to the CGI standard.
621 **
622 ** If the repository is known, it has already been opened. If unknown,
623 ** then g.zRepositoryName holds the directory that contains the repository
624 ** and the actual repository is taken from the first element of PATH_INFO.
625 **
626 ** Process the webpage specified by the PATH_INFO or REQUEST_URI
627 ** environment variable.
628 */
629 static void process_one_web_page(const char *zNotFound){
630 const char *zPathInfo;
631 char *zPath = NULL;
632 int idx;
633 int i;
634
635 /* If the repository has not been opened already, then find the
636 ** repository based on the first element of PATH_INFO and open it.
637 */
638 zPathInfo = P("PATH_INFO");
639 if( !g.repositoryOpen ){
640 char *zRepo;
641 const char *zOldScript = PD("SCRIPT_NAME", "");
642 char *zNewScript;
643 int j, k;
644
645 i = 1;
646 while( zPathInfo[i] && zPathInfo[i]!='/' ){ i++; }
647 zRepo = mprintf("%s%.*s.fossil",g.zRepositoryName,i,zPathInfo);
648
649 /* To avoid mischief, make sure the repository basename contains no
650 ** characters other than alphanumerics, "-", and "_".
651 */
652 for(j=strlen(g.zRepositoryName)+1, k=0; k<i-1; j++, k++){
653 if( !isalnum(zRepo[j]) && zRepo[j]!='-' ) zRepo[j] = '_';
654 }
655
656 if( file_size(zRepo)<1024 ){
657 if( zNotFound ){
658 cgi_redirect(zNotFound);
659 }else{
660 @ <h1>Not Found</h1>
661 cgi_set_status(404, "not found");
662 cgi_reply();
663 }
664 return;
665 }
666 zNewScript = mprintf("%s%.*s", zOldScript, i, zPathInfo);
667 cgi_replace_parameter("PATH_INFO", &zPathInfo[i+1]);
668 zPathInfo += i;
669 cgi_replace_parameter("SCRIPT_NAME", zNewScript);
670 db_open_repository(zRepo);
671 if( g.fHttpTrace ){
672 fprintf(stderr,
673 "# repository: [%s]\n"
674 "# new PATH_INFO = [%s]\n"
675 "# new SCRIPT_NAME = [%s]\n",
676 zRepo, zPathInfo, zNewScript);
677 }
678 }
679
680 /* Find the page that the user has requested, construct and deliver that
681 ** page.
682 */
683 if( g.zContentType && memcmp(g.zContentType, "application/x-fossil", 20)==0 ){
684 zPathInfo = "/xfer";
685 }
686 set_base_url();
 
687 if( zPathInfo==0 || zPathInfo[0]==0
688 || (zPathInfo[0]=='/' && zPathInfo[1]==0) ){
689 fossil_redirect_home();
690 }else{
691 zPath = mprintf("%s", zPathInfo);
@@ -660,10 +754,11 @@
754 ** the repository, fossil will generate a webpage on stdout based on
755 ** the values of standard CGI environment variables.
756 */
757 void cmd_cgi(void){
758 const char *zFile;
759 const char *zNotFound = 0;
760 Blob config, line, key, value;
761 if( g.argc==3 && strcmp(g.argv[1],"cgi")==0 ){
762 zFile = g.argv[2];
763 }else{
764 zFile = g.argv[1];
@@ -697,19 +792,59 @@
792 continue;
793 }
794 if( blob_eq(&key, "repository:") && blob_token(&line, &value) ){
795 db_open_repository(blob_str(&value));
796 blob_reset(&value);
797 continue;
798 }
799 if( blob_eq(&key, "directory:") && blob_token(&line, &value) ){
800 db_close();
801 g.zRepositoryName = mprintf("%s", blob_str(&value));
802 blob_reset(&value);
803 continue;
804 }
805 if( blob_eq(&key, "notfound:") && blob_token(&line, &value) ){
806 zNotFound = mprintf("%s", blob_str(&value));
807 blob_reset(&value);
808 continue;
809 }
810 }
811 blob_reset(&config);
812 if( g.db==0 && g.zRepositoryName==0 ){
813 cgi_panic("Unable to find or open the project repository");
814 }
815 cgi_init();
816 process_one_web_page(zNotFound);
817 }
818
819 /*
820 ** If g.argv[2] exists then it is either the name of a repository
821 ** that will be used by a server, or else it is a directory that
822 ** contains multiple repositories that can be served. If g.argv[2]
823 ** is a directory, the repositories it contains must be named
824 ** "*.fossil". If g.argv[2] does not exists, then we must be within
825 ** a check-out and the repository to be served is the repository of
826 ** that check-out.
827 **
828 ** Open the respository to be served if it is known. If g.argv[2] is
829 ** a directory full of repositories, then set g.zRepositoryName to
830 ** the name of that directory and the specific repository will be
831 ** opened later by process_one_web_page() based on the content of
832 ** the PATH_INFO variable.
833 **
834 ** If disallowDir is set, then the directory full of repositories method
835 ** is disallowed.
836 */
837 static void find_server_repository(int disallowDir){
838 if( g.argc<3 ){
839 db_must_be_within_tree();
840 }else if( !disallowDir && file_isdir(g.argv[2])==1 ){
841 g.zRepositoryName = mprintf("%s", g.argv[2]);
842 file_simplify_name(g.zRepositoryName, -1);
843 }else{
844 db_open_repository(g.argv[2]);
845 }
846 }
847
848 /*
849 ** undocumented format:
850 **
@@ -717,41 +852,30 @@
852 **
853 ** The argv==6 form is used by the win32 server only.
854 **
855 ** COMMAND: http
856 **
857 ** Usage: %fossil http REPOSITORY [--notfound URL]
858 **
859 ** Handle a single HTTP request appearing on stdin. The resulting webpage
860 ** is delivered on stdout. This method is used to launch an HTTP request
861 ** handler from inetd, for example. The argument is the name of the
862 ** repository.
863 **
864 ** If REPOSITORY is a directory that contains one or more respositories
865 ** with names of the form "*.fossil" then the first element of the URL
866 ** pathname selects among the various repositories. If the pathname does
867 ** not select a valid repository and the --notfound option is available,
868 ** then the server redirects (HTTP code 302) to the URL of --notfound.
869 */
870 void cmd_http(void){
871 const char *zIpAddr;
872 const char *zNotFound;
873 zNotFound = find_option("notfound", 0, 1);
874 if( g.argc!=2 && g.argc!=3 && g.argc!=6 ){
875 cgi_panic("no repository specified");
876 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
877 g.cgiPanic = 1;
878 g.fullHttpReply = 1;
879 if( g.argc==6 ){
880 g.httpIn = fopen(g.argv[3], "rb");
881 g.httpOut = fopen(g.argv[4], "wb");
@@ -759,17 +883,14 @@
883 }else{
884 g.httpIn = stdin;
885 g.httpOut = stdout;
886 zIpAddr = 0;
887 }
888 find_server_repository(0);
889 g.zRepositoryName = enter_chroot_jail(g.zRepositoryName);
 
 
 
890 cgi_handle_http_request(zIpAddr);
891 process_one_web_page(zNotFound);
892 }
893
894 /*
895 ** COMMAND: test-http
896 ** Works like the http command but gives setup permission to all users.
@@ -817,16 +938,23 @@
938 ** The repository argument may be omitted if the working directory is
939 ** within an open checkout.
940 **
941 ** The "ui" command automatically starts a web browser after initializing
942 ** the web server.
943 **
944 ** In the "server" command, the REPOSITORY can be a directory (aka folder)
945 ** that contains one or more respositories with names ending in ".fossil".
946 ** In that case, the first element of the URL is used to select among the
947 ** various repositories.
948 */
949 void cmd_webserver(void){
950 int iPort, mxPort; /* Range of TCP ports allowed */
951 const char *zPort; /* Value of the --port option */
952 char *zBrowser; /* Name of web browser program */
953 char *zBrowserCmd = 0; /* Command to launch the web browser */
954 int isUiCmd; /* True if command is "ui", not "server' */
955 const char *zNotFound; /* The --notfound option or NULL */
956
957 #ifdef __MINGW32__
958 const char *zStopperFile; /* Name of file used to terminate server */
959 zStopperFile = find_option("stopper", 0, 1);
960 #endif
@@ -834,25 +962,23 @@
962 g.thTrace = find_option("th-trace", 0, 0)!=0;
963 if( g.thTrace ){
964 blob_zero(&g.thLog);
965 }
966 zPort = find_option("port", "P", 1);
967 zNotFound = find_option("notfound", 0, 1);
968 if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?");
969 isUiCmd = g.argv[1][0]=='u';
970 find_server_repository(isUiCmd);
 
 
 
971 if( zPort ){
972 iPort = mxPort = atoi(zPort);
973 }else{
974 iPort = db_get_int("http-port", 8080);
975 mxPort = iPort+100;
976 }
977 #ifndef __MINGW32__
978 /* Unix implementation */
979 if( isUiCmd ){
980 #if !defined(__DARWIN__) && !defined(__APPLE__)
981 zBrowser = db_get("web-browser", 0);
982 if( zBrowser==0 ){
983 static char *azBrowserProg[] = { "xdg-open", "gnome-open", "firefox" };
984 int i;
@@ -877,22 +1003,19 @@
1003 g.httpOut = stdout;
1004 if( g.fHttpTrace || g.fSqlTrace ){
1005 fprintf(stderr, "====== SERVER pid %d =======\n", getpid());
1006 }
1007 g.cgiPanic = 1;
1008 find_server_repository(isUiCmd);
1009 g.zRepositoryName = enter_chroot_jail(g.zRepositoryName);
 
 
 
1010 cgi_handle_http_request(0);
1011 process_one_web_page(zNotFound);
1012 #else
1013 /* Win32 implementation */
1014 if( isUiCmd ){
1015 zBrowser = db_get("web-browser", "start");
1016 zBrowserCmd = mprintf("%s http://127.0.0.1:%%d/", zBrowser);
1017 }
1018 db_close();
1019 win32_http_server(iPort, mxPort, zBrowserCmd, zStopperFile, zNotFound);
1020 #endif
1021 }
1022
+12 -2
--- src/main.mk
+++ src/main.mk
@@ -36,10 +36,11 @@
3636
$(SRCDIR)/diffcmd.c \
3737
$(SRCDIR)/doc.c \
3838
$(SRCDIR)/encode.c \
3939
$(SRCDIR)/file.c \
4040
$(SRCDIR)/finfo.c \
41
+ $(SRCDIR)/graph.c \
4142
$(SRCDIR)/http.c \
4243
$(SRCDIR)/http_socket.c \
4344
$(SRCDIR)/http_transport.c \
4445
$(SRCDIR)/info.c \
4546
$(SRCDIR)/login.c \
@@ -105,10 +106,11 @@
105106
diffcmd_.c \
106107
doc_.c \
107108
encode_.c \
108109
file_.c \
109110
finfo_.c \
111
+ graph_.c \
110112
http_.c \
111113
http_socket_.c \
112114
http_transport_.c \
113115
info_.c \
114116
login_.c \
@@ -174,10 +176,11 @@
174176
diffcmd.o \
175177
doc.o \
176178
encode.o \
177179
file.o \
178180
finfo.o \
181
+ graph.o \
179182
http.o \
180183
http_socket.o \
181184
http_transport.o \
182185
info.o \
183186
login.o \
@@ -258,16 +261,16 @@
258261
# noop
259262
260263
clean:
261264
rm -f *.o *_.c $(APPNAME) VERSION.h
262265
rm -f translate makeheaders mkindex page_index.h headers
263
- rm -f add.h allrepo.h bag.h blob.h branch.h browse.h captcha.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h content.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h finfo.h http.h http_socket.h http_transport.h info.h login.h main.h manifest.h md5.h merge.h merge3.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h schema.h search.h setup.h sha1.h shun.h skins.h stat.h style.h sync.h tag.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h
266
+ rm -f add.h allrepo.h bag.h blob.h branch.h browse.h captcha.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h content.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h finfo.h graph.h http.h http_socket.h http_transport.h info.h login.h main.h manifest.h md5.h merge.h merge3.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h schema.h search.h setup.h sha1.h shun.h skins.h stat.h style.h sync.h tag.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h
264267
265268
page_index.h: $(TRANS_SRC) mkindex
266269
./mkindex $(TRANS_SRC) >$@
267270
headers: page_index.h makeheaders VERSION.h
268
- ./makeheaders add_.c:add.h allrepo_.c:allrepo.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h finfo_.c:finfo.h http_.c:http.h http_socket_.c:http_socket.h http_transport_.c:http_transport.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h
271
+ ./makeheaders add_.c:add.h allrepo_.c:allrepo.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h finfo_.c:finfo.h graph_.c:graph.h http_.c:http.h http_socket_.c:http_socket.h http_transport_.c:http_transport.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h
269272
touch headers
270273
headers: Makefile
271274
Makefile:
272275
add_.c: $(SRCDIR)/add.c translate
273276
./translate $(SRCDIR)/add.c >add_.c
@@ -442,10 +445,17 @@
442445
443446
finfo.o: finfo_.c finfo.h $(SRCDIR)/config.h
444447
$(XTCC) -o finfo.o -c finfo_.c
445448
446449
finfo.h: headers
450
+graph_.c: $(SRCDIR)/graph.c translate
451
+ ./translate $(SRCDIR)/graph.c >graph_.c
452
+
453
+graph.o: graph_.c graph.h $(SRCDIR)/config.h
454
+ $(XTCC) -o graph.o -c graph_.c
455
+
456
+graph.h: headers
447457
http_.c: $(SRCDIR)/http.c translate
448458
./translate $(SRCDIR)/http.c >http_.c
449459
450460
http.o: http_.c http.h $(SRCDIR)/config.h
451461
$(XTCC) -o http.o -c http_.c
452462
--- src/main.mk
+++ src/main.mk
@@ -36,10 +36,11 @@
36 $(SRCDIR)/diffcmd.c \
37 $(SRCDIR)/doc.c \
38 $(SRCDIR)/encode.c \
39 $(SRCDIR)/file.c \
40 $(SRCDIR)/finfo.c \
 
41 $(SRCDIR)/http.c \
42 $(SRCDIR)/http_socket.c \
43 $(SRCDIR)/http_transport.c \
44 $(SRCDIR)/info.c \
45 $(SRCDIR)/login.c \
@@ -105,10 +106,11 @@
105 diffcmd_.c \
106 doc_.c \
107 encode_.c \
108 file_.c \
109 finfo_.c \
 
110 http_.c \
111 http_socket_.c \
112 http_transport_.c \
113 info_.c \
114 login_.c \
@@ -174,10 +176,11 @@
174 diffcmd.o \
175 doc.o \
176 encode.o \
177 file.o \
178 finfo.o \
 
179 http.o \
180 http_socket.o \
181 http_transport.o \
182 info.o \
183 login.o \
@@ -258,16 +261,16 @@
258 # noop
259
260 clean:
261 rm -f *.o *_.c $(APPNAME) VERSION.h
262 rm -f translate makeheaders mkindex page_index.h headers
263 rm -f add.h allrepo.h bag.h blob.h branch.h browse.h captcha.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h content.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h finfo.h http.h http_socket.h http_transport.h info.h login.h main.h manifest.h md5.h merge.h merge3.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h schema.h search.h setup.h sha1.h shun.h skins.h stat.h style.h sync.h tag.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h
264
265 page_index.h: $(TRANS_SRC) mkindex
266 ./mkindex $(TRANS_SRC) >$@
267 headers: page_index.h makeheaders VERSION.h
268 ./makeheaders add_.c:add.h allrepo_.c:allrepo.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h finfo_.c:finfo.h http_.c:http.h http_socket_.c:http_socket.h http_transport_.c:http_transport.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h
269 touch headers
270 headers: Makefile
271 Makefile:
272 add_.c: $(SRCDIR)/add.c translate
273 ./translate $(SRCDIR)/add.c >add_.c
@@ -442,10 +445,17 @@
442
443 finfo.o: finfo_.c finfo.h $(SRCDIR)/config.h
444 $(XTCC) -o finfo.o -c finfo_.c
445
446 finfo.h: headers
 
 
 
 
 
 
 
447 http_.c: $(SRCDIR)/http.c translate
448 ./translate $(SRCDIR)/http.c >http_.c
449
450 http.o: http_.c http.h $(SRCDIR)/config.h
451 $(XTCC) -o http.o -c http_.c
452
--- src/main.mk
+++ src/main.mk
@@ -36,10 +36,11 @@
36 $(SRCDIR)/diffcmd.c \
37 $(SRCDIR)/doc.c \
38 $(SRCDIR)/encode.c \
39 $(SRCDIR)/file.c \
40 $(SRCDIR)/finfo.c \
41 $(SRCDIR)/graph.c \
42 $(SRCDIR)/http.c \
43 $(SRCDIR)/http_socket.c \
44 $(SRCDIR)/http_transport.c \
45 $(SRCDIR)/info.c \
46 $(SRCDIR)/login.c \
@@ -105,10 +106,11 @@
106 diffcmd_.c \
107 doc_.c \
108 encode_.c \
109 file_.c \
110 finfo_.c \
111 graph_.c \
112 http_.c \
113 http_socket_.c \
114 http_transport_.c \
115 info_.c \
116 login_.c \
@@ -174,10 +176,11 @@
176 diffcmd.o \
177 doc.o \
178 encode.o \
179 file.o \
180 finfo.o \
181 graph.o \
182 http.o \
183 http_socket.o \
184 http_transport.o \
185 info.o \
186 login.o \
@@ -258,16 +261,16 @@
261 # noop
262
263 clean:
264 rm -f *.o *_.c $(APPNAME) VERSION.h
265 rm -f translate makeheaders mkindex page_index.h headers
266 rm -f add.h allrepo.h bag.h blob.h branch.h browse.h captcha.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h content.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h finfo.h graph.h http.h http_socket.h http_transport.h info.h login.h main.h manifest.h md5.h merge.h merge3.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h schema.h search.h setup.h sha1.h shun.h skins.h stat.h style.h sync.h tag.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h
267
268 page_index.h: $(TRANS_SRC) mkindex
269 ./mkindex $(TRANS_SRC) >$@
270 headers: page_index.h makeheaders VERSION.h
271 ./makeheaders add_.c:add.h allrepo_.c:allrepo.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h finfo_.c:finfo.h graph_.c:graph.h http_.c:http.h http_socket_.c:http_socket.h http_transport_.c:http_transport.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h
272 touch headers
273 headers: Makefile
274 Makefile:
275 add_.c: $(SRCDIR)/add.c translate
276 ./translate $(SRCDIR)/add.c >add_.c
@@ -442,10 +445,17 @@
445
446 finfo.o: finfo_.c finfo.h $(SRCDIR)/config.h
447 $(XTCC) -o finfo.o -c finfo_.c
448
449 finfo.h: headers
450 graph_.c: $(SRCDIR)/graph.c translate
451 ./translate $(SRCDIR)/graph.c >graph_.c
452
453 graph.o: graph_.c graph.h $(SRCDIR)/config.h
454 $(XTCC) -o graph.o -c graph_.c
455
456 graph.h: headers
457 http_.c: $(SRCDIR)/http.c translate
458 ./translate $(SRCDIR)/http.c >http_.c
459
460 http.o: http_.c http.h $(SRCDIR)/config.h
461 $(XTCC) -o http.o -c http_.c
462
+12 -2
--- src/main.mk
+++ src/main.mk
@@ -36,10 +36,11 @@
3636
$(SRCDIR)/diffcmd.c \
3737
$(SRCDIR)/doc.c \
3838
$(SRCDIR)/encode.c \
3939
$(SRCDIR)/file.c \
4040
$(SRCDIR)/finfo.c \
41
+ $(SRCDIR)/graph.c \
4142
$(SRCDIR)/http.c \
4243
$(SRCDIR)/http_socket.c \
4344
$(SRCDIR)/http_transport.c \
4445
$(SRCDIR)/info.c \
4546
$(SRCDIR)/login.c \
@@ -105,10 +106,11 @@
105106
diffcmd_.c \
106107
doc_.c \
107108
encode_.c \
108109
file_.c \
109110
finfo_.c \
111
+ graph_.c \
110112
http_.c \
111113
http_socket_.c \
112114
http_transport_.c \
113115
info_.c \
114116
login_.c \
@@ -174,10 +176,11 @@
174176
diffcmd.o \
175177
doc.o \
176178
encode.o \
177179
file.o \
178180
finfo.o \
181
+ graph.o \
179182
http.o \
180183
http_socket.o \
181184
http_transport.o \
182185
info.o \
183186
login.o \
@@ -258,16 +261,16 @@
258261
# noop
259262
260263
clean:
261264
rm -f *.o *_.c $(APPNAME) VERSION.h
262265
rm -f translate makeheaders mkindex page_index.h headers
263
- rm -f add.h allrepo.h bag.h blob.h branch.h browse.h captcha.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h content.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h finfo.h http.h http_socket.h http_transport.h info.h login.h main.h manifest.h md5.h merge.h merge3.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h schema.h search.h setup.h sha1.h shun.h skins.h stat.h style.h sync.h tag.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h
266
+ rm -f add.h allrepo.h bag.h blob.h branch.h browse.h captcha.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h content.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h finfo.h graph.h http.h http_socket.h http_transport.h info.h login.h main.h manifest.h md5.h merge.h merge3.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h schema.h search.h setup.h sha1.h shun.h skins.h stat.h style.h sync.h tag.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h
264267
265268
page_index.h: $(TRANS_SRC) mkindex
266269
./mkindex $(TRANS_SRC) >$@
267270
headers: page_index.h makeheaders VERSION.h
268
- ./makeheaders add_.c:add.h allrepo_.c:allrepo.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h finfo_.c:finfo.h http_.c:http.h http_socket_.c:http_socket.h http_transport_.c:http_transport.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h
271
+ ./makeheaders add_.c:add.h allrepo_.c:allrepo.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h finfo_.c:finfo.h graph_.c:graph.h http_.c:http.h http_socket_.c:http_socket.h http_transport_.c:http_transport.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h
269272
touch headers
270273
headers: Makefile
271274
Makefile:
272275
add_.c: $(SRCDIR)/add.c translate
273276
./translate $(SRCDIR)/add.c >add_.c
@@ -442,10 +445,17 @@
442445
443446
finfo.o: finfo_.c finfo.h $(SRCDIR)/config.h
444447
$(XTCC) -o finfo.o -c finfo_.c
445448
446449
finfo.h: headers
450
+graph_.c: $(SRCDIR)/graph.c translate
451
+ ./translate $(SRCDIR)/graph.c >graph_.c
452
+
453
+graph.o: graph_.c graph.h $(SRCDIR)/config.h
454
+ $(XTCC) -o graph.o -c graph_.c
455
+
456
+graph.h: headers
447457
http_.c: $(SRCDIR)/http.c translate
448458
./translate $(SRCDIR)/http.c >http_.c
449459
450460
http.o: http_.c http.h $(SRCDIR)/config.h
451461
$(XTCC) -o http.o -c http_.c
452462
--- src/main.mk
+++ src/main.mk
@@ -36,10 +36,11 @@
36 $(SRCDIR)/diffcmd.c \
37 $(SRCDIR)/doc.c \
38 $(SRCDIR)/encode.c \
39 $(SRCDIR)/file.c \
40 $(SRCDIR)/finfo.c \
 
41 $(SRCDIR)/http.c \
42 $(SRCDIR)/http_socket.c \
43 $(SRCDIR)/http_transport.c \
44 $(SRCDIR)/info.c \
45 $(SRCDIR)/login.c \
@@ -105,10 +106,11 @@
105 diffcmd_.c \
106 doc_.c \
107 encode_.c \
108 file_.c \
109 finfo_.c \
 
110 http_.c \
111 http_socket_.c \
112 http_transport_.c \
113 info_.c \
114 login_.c \
@@ -174,10 +176,11 @@
174 diffcmd.o \
175 doc.o \
176 encode.o \
177 file.o \
178 finfo.o \
 
179 http.o \
180 http_socket.o \
181 http_transport.o \
182 info.o \
183 login.o \
@@ -258,16 +261,16 @@
258 # noop
259
260 clean:
261 rm -f *.o *_.c $(APPNAME) VERSION.h
262 rm -f translate makeheaders mkindex page_index.h headers
263 rm -f add.h allrepo.h bag.h blob.h branch.h browse.h captcha.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h content.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h finfo.h http.h http_socket.h http_transport.h info.h login.h main.h manifest.h md5.h merge.h merge3.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h schema.h search.h setup.h sha1.h shun.h skins.h stat.h style.h sync.h tag.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h
264
265 page_index.h: $(TRANS_SRC) mkindex
266 ./mkindex $(TRANS_SRC) >$@
267 headers: page_index.h makeheaders VERSION.h
268 ./makeheaders add_.c:add.h allrepo_.c:allrepo.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h finfo_.c:finfo.h http_.c:http.h http_socket_.c:http_socket.h http_transport_.c:http_transport.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h
269 touch headers
270 headers: Makefile
271 Makefile:
272 add_.c: $(SRCDIR)/add.c translate
273 ./translate $(SRCDIR)/add.c >add_.c
@@ -442,10 +445,17 @@
442
443 finfo.o: finfo_.c finfo.h $(SRCDIR)/config.h
444 $(XTCC) -o finfo.o -c finfo_.c
445
446 finfo.h: headers
 
 
 
 
 
 
 
447 http_.c: $(SRCDIR)/http.c translate
448 ./translate $(SRCDIR)/http.c >http_.c
449
450 http.o: http_.c http.h $(SRCDIR)/config.h
451 $(XTCC) -o http.o -c http_.c
452
--- src/main.mk
+++ src/main.mk
@@ -36,10 +36,11 @@
36 $(SRCDIR)/diffcmd.c \
37 $(SRCDIR)/doc.c \
38 $(SRCDIR)/encode.c \
39 $(SRCDIR)/file.c \
40 $(SRCDIR)/finfo.c \
41 $(SRCDIR)/graph.c \
42 $(SRCDIR)/http.c \
43 $(SRCDIR)/http_socket.c \
44 $(SRCDIR)/http_transport.c \
45 $(SRCDIR)/info.c \
46 $(SRCDIR)/login.c \
@@ -105,10 +106,11 @@
106 diffcmd_.c \
107 doc_.c \
108 encode_.c \
109 file_.c \
110 finfo_.c \
111 graph_.c \
112 http_.c \
113 http_socket_.c \
114 http_transport_.c \
115 info_.c \
116 login_.c \
@@ -174,10 +176,11 @@
176 diffcmd.o \
177 doc.o \
178 encode.o \
179 file.o \
180 finfo.o \
181 graph.o \
182 http.o \
183 http_socket.o \
184 http_transport.o \
185 info.o \
186 login.o \
@@ -258,16 +261,16 @@
261 # noop
262
263 clean:
264 rm -f *.o *_.c $(APPNAME) VERSION.h
265 rm -f translate makeheaders mkindex page_index.h headers
266 rm -f add.h allrepo.h bag.h blob.h branch.h browse.h captcha.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h content.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h finfo.h graph.h http.h http_socket.h http_transport.h info.h login.h main.h manifest.h md5.h merge.h merge3.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h schema.h search.h setup.h sha1.h shun.h skins.h stat.h style.h sync.h tag.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h
267
268 page_index.h: $(TRANS_SRC) mkindex
269 ./mkindex $(TRANS_SRC) >$@
270 headers: page_index.h makeheaders VERSION.h
271 ./makeheaders add_.c:add.h allrepo_.c:allrepo.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h finfo_.c:finfo.h graph_.c:graph.h http_.c:http.h http_socket_.c:http_socket.h http_transport_.c:http_transport.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h
272 touch headers
273 headers: Makefile
274 Makefile:
275 add_.c: $(SRCDIR)/add.c translate
276 ./translate $(SRCDIR)/add.c >add_.c
@@ -442,10 +445,17 @@
445
446 finfo.o: finfo_.c finfo.h $(SRCDIR)/config.h
447 $(XTCC) -o finfo.o -c finfo_.c
448
449 finfo.h: headers
450 graph_.c: $(SRCDIR)/graph.c translate
451 ./translate $(SRCDIR)/graph.c >graph_.c
452
453 graph.o: graph_.c graph.h $(SRCDIR)/config.h
454 $(XTCC) -o graph.o -c graph_.c
455
456 graph.h: headers
457 http_.c: $(SRCDIR)/http.c translate
458 ./translate $(SRCDIR)/http.c >http_.c
459
460 http.o: http_.c http.h $(SRCDIR)/config.h
461 $(XTCC) -o http.o -c http_.c
462
--- src/makemake.tcl
+++ src/makemake.tcl
@@ -30,10 +30,11 @@
3030
diffcmd
3131
doc
3232
encode
3333
file
3434
finfo
35
+ graph
3536
http
3637
http_socket
3738
http_transport
3839
info
3940
login
4041
--- src/makemake.tcl
+++ src/makemake.tcl
@@ -30,10 +30,11 @@
30 diffcmd
31 doc
32 encode
33 file
34 finfo
 
35 http
36 http_socket
37 http_transport
38 info
39 login
40
--- src/makemake.tcl
+++ src/makemake.tcl
@@ -30,10 +30,11 @@
30 diffcmd
31 doc
32 encode
33 file
34 finfo
35 graph
36 http
37 http_socket
38 http_transport
39 info
40 login
41
--- src/makemake.tcl
+++ src/makemake.tcl
@@ -30,10 +30,11 @@
3030
diffcmd
3131
doc
3232
encode
3333
file
3434
finfo
35
+ graph
3536
http
3637
http_socket
3738
http_transport
3839
info
3940
login
4041
--- src/makemake.tcl
+++ src/makemake.tcl
@@ -30,10 +30,11 @@
30 diffcmd
31 doc
32 encode
33 file
34 finfo
 
35 http
36 http_socket
37 http_transport
38 info
39 login
40
--- src/makemake.tcl
+++ src/makemake.tcl
@@ -30,10 +30,11 @@
30 diffcmd
31 doc
32 encode
33 file
34 finfo
35 graph
36 http
37 http_socket
38 http_transport
39 info
40 login
41
+58 -1
--- src/name.c
+++ src/name.c
@@ -36,10 +36,16 @@
3636
** case and might only be a prefix of the full UUID and converts it
3737
** into the full-length UUID in canonical form.
3838
**
3939
** If the input is not a UUID or a UUID prefix, then try to resolve
4040
** the name as a tag. If multiple tags match, pick the latest.
41
+** If the input name matches "tag:*" then always resolve as a tag.
42
+**
43
+** If the input is not a tag, then try to match it as an ISO-8601 date
44
+** string YYYY-MM-DD HH:MM:SS and pick the nearest check-in to that date.
45
+** If the input is of the form "date:*" or "localtime:*" or "utc:*" then
46
+** always resolve the name as a date.
4147
**
4248
** Return the number of errors.
4349
*/
4450
int name_to_uuid(Blob *pName, int iErrPriority){
4551
int rc;
@@ -48,12 +54,17 @@
4854
if( sz>UUID_SIZE || sz<4 || !validate16(blob_buffer(pName), sz) ){
4955
char *zUuid;
5056
const char *zName = blob_str(pName);
5157
if( memcmp(zName, "tag:", 4)==0 ){
5258
zName += 4;
59
+ zUuid = tag_to_uuid(zName);
60
+ }else{
61
+ zUuid = tag_to_uuid(zName);
62
+ if( zUuid==0 ){
63
+ zUuid = date_to_uuid(zName);
64
+ }
5365
}
54
- zUuid = tag_to_uuid(zName);
5566
if( zUuid ){
5667
blob_reset(pName);
5768
blob_append(pName, zUuid, -1);
5869
free(zUuid);
5970
return 0;
@@ -123,10 +134,56 @@
123134
" ORDER BY event.mtime DESC ",
124135
zTag
125136
);
126137
return zUuid;
127138
}
139
+
140
+/*
141
+** Convert a date/time string into a UUID.
142
+**
143
+** Input forms accepted:
144
+**
145
+** date:DATE
146
+** local:DATE
147
+** utc:DATE
148
+**
149
+** The DATE is interpreted as localtime unless the "utc:" prefix is used
150
+** or a "utc" string appears at the end of the DATE string.
151
+*/
152
+char *date_to_uuid(const char *zDate){
153
+ int useUtc = 0;
154
+ int n;
155
+ char *zCopy = 0;
156
+ char *zUuid;
157
+
158
+ if( memcmp(zDate, "date:", 5)==0 ){
159
+ zDate += 5;
160
+ }else if( memcmp(zDate, "local:", 6)==0 ){
161
+ zDate += 6;
162
+ }else if( memcmp(zDate, "utc:", 4)==0 ){
163
+ zDate += 4;
164
+ useUtc = 1;
165
+ }
166
+ n = strlen(zDate);
167
+ if( n<10 || zDate[4]!='-' || zDate[7]!='-' ) return 0;
168
+ if( n>4 && sqlite3_strnicmp(&zDate[n-3], "utc", 3)==0 ){
169
+ zCopy = mprintf("%s", zDate);
170
+ zCopy[n-3] = 0;
171
+ zDate = zCopy;
172
+ n -= 3;
173
+ useUtc = 1;
174
+ }
175
+ zUuid = db_text(0,
176
+ "SELECT (SELECT uuid FROM blob WHERE rid=event.objid)"
177
+ " FROM event"
178
+ " WHERE mtime<=julianday(%Q %s) AND type='ci'"
179
+ " ORDER BY mtime DESC LIMIT 1",
180
+ zDate, useUtc ? "" : ",'utc'"
181
+ );
182
+ free(zCopy);
183
+ return zUuid;
184
+}
128185
129186
/*
130187
** COMMAND: test-name-to-id
131188
**
132189
** Convert a name to a full artifact ID.
133190
--- src/name.c
+++ src/name.c
@@ -36,10 +36,16 @@
36 ** case and might only be a prefix of the full UUID and converts it
37 ** into the full-length UUID in canonical form.
38 **
39 ** If the input is not a UUID or a UUID prefix, then try to resolve
40 ** the name as a tag. If multiple tags match, pick the latest.
 
 
 
 
 
 
41 **
42 ** Return the number of errors.
43 */
44 int name_to_uuid(Blob *pName, int iErrPriority){
45 int rc;
@@ -48,12 +54,17 @@
48 if( sz>UUID_SIZE || sz<4 || !validate16(blob_buffer(pName), sz) ){
49 char *zUuid;
50 const char *zName = blob_str(pName);
51 if( memcmp(zName, "tag:", 4)==0 ){
52 zName += 4;
 
 
 
 
 
 
53 }
54 zUuid = tag_to_uuid(zName);
55 if( zUuid ){
56 blob_reset(pName);
57 blob_append(pName, zUuid, -1);
58 free(zUuid);
59 return 0;
@@ -123,10 +134,56 @@
123 " ORDER BY event.mtime DESC ",
124 zTag
125 );
126 return zUuid;
127 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
129 /*
130 ** COMMAND: test-name-to-id
131 **
132 ** Convert a name to a full artifact ID.
133
--- src/name.c
+++ src/name.c
@@ -36,10 +36,16 @@
36 ** case and might only be a prefix of the full UUID and converts it
37 ** into the full-length UUID in canonical form.
38 **
39 ** If the input is not a UUID or a UUID prefix, then try to resolve
40 ** the name as a tag. If multiple tags match, pick the latest.
41 ** If the input name matches "tag:*" then always resolve as a tag.
42 **
43 ** If the input is not a tag, then try to match it as an ISO-8601 date
44 ** string YYYY-MM-DD HH:MM:SS and pick the nearest check-in to that date.
45 ** If the input is of the form "date:*" or "localtime:*" or "utc:*" then
46 ** always resolve the name as a date.
47 **
48 ** Return the number of errors.
49 */
50 int name_to_uuid(Blob *pName, int iErrPriority){
51 int rc;
@@ -48,12 +54,17 @@
54 if( sz>UUID_SIZE || sz<4 || !validate16(blob_buffer(pName), sz) ){
55 char *zUuid;
56 const char *zName = blob_str(pName);
57 if( memcmp(zName, "tag:", 4)==0 ){
58 zName += 4;
59 zUuid = tag_to_uuid(zName);
60 }else{
61 zUuid = tag_to_uuid(zName);
62 if( zUuid==0 ){
63 zUuid = date_to_uuid(zName);
64 }
65 }
 
66 if( zUuid ){
67 blob_reset(pName);
68 blob_append(pName, zUuid, -1);
69 free(zUuid);
70 return 0;
@@ -123,10 +134,56 @@
134 " ORDER BY event.mtime DESC ",
135 zTag
136 );
137 return zUuid;
138 }
139
140 /*
141 ** Convert a date/time string into a UUID.
142 **
143 ** Input forms accepted:
144 **
145 ** date:DATE
146 ** local:DATE
147 ** utc:DATE
148 **
149 ** The DATE is interpreted as localtime unless the "utc:" prefix is used
150 ** or a "utc" string appears at the end of the DATE string.
151 */
152 char *date_to_uuid(const char *zDate){
153 int useUtc = 0;
154 int n;
155 char *zCopy = 0;
156 char *zUuid;
157
158 if( memcmp(zDate, "date:", 5)==0 ){
159 zDate += 5;
160 }else if( memcmp(zDate, "local:", 6)==0 ){
161 zDate += 6;
162 }else if( memcmp(zDate, "utc:", 4)==0 ){
163 zDate += 4;
164 useUtc = 1;
165 }
166 n = strlen(zDate);
167 if( n<10 || zDate[4]!='-' || zDate[7]!='-' ) return 0;
168 if( n>4 && sqlite3_strnicmp(&zDate[n-3], "utc", 3)==0 ){
169 zCopy = mprintf("%s", zDate);
170 zCopy[n-3] = 0;
171 zDate = zCopy;
172 n -= 3;
173 useUtc = 1;
174 }
175 zUuid = db_text(0,
176 "SELECT (SELECT uuid FROM blob WHERE rid=event.objid)"
177 " FROM event"
178 " WHERE mtime<=julianday(%Q %s) AND type='ci'"
179 " ORDER BY mtime DESC LIMIT 1",
180 zDate, useUtc ? "" : ",'utc'"
181 );
182 free(zCopy);
183 return zUuid;
184 }
185
186 /*
187 ** COMMAND: test-name-to-id
188 **
189 ** Convert a name to a full artifact ID.
190
+30 -2
--- src/rss.c
+++ src/rss.c
@@ -47,15 +47,43 @@
4747
@ (SELECT count(*) FROM plink WHERE pid=blob.rid AND isprim),
4848
@ (SELECT count(*) FROM plink WHERE cid=blob.rid)
4949
@ FROM event, blob
5050
@ WHERE blob.rid=event.objid
5151
;
52
+
53
+ login_check_credentials();
54
+ if( !g.okRead && !g.okRdTkt && !g.okRdWiki ){
55
+ return;
56
+ }
57
+
5258
blob_zero(&bSQL);
5359
blob_append( &bSQL, zSQL1, -1 );
5460
5561
if( zType[0]!='a' ){
56
- blob_appendf(&bSQL, " AND event.type=%Q", zType);
62
+ if( zType[0]=='c' && !g.okRead ) zType = "x";
63
+ if( zType[0]=='w' && !g.okRdWiki ) zType = "x";
64
+ if( zType[0]=='t' && !g.okRdTkt ) zType = "x";
65
+ blob_appendf(&bSQL, " AND event.type=%Q", zType);
66
+ }else{
67
+ if( !g.okRead ){
68
+ if( g.okRdTkt && g.okRdWiki ){
69
+ blob_append(&bSQL, " AND event.type!='ci'", -1);
70
+ }else if( g.okRdTkt ){
71
+ blob_append(&bSQL, " AND event.type=='t'", -1);
72
+ }else{
73
+ blob_append(&bSQL, " AND event.type=='w'", -1);
74
+ }
75
+ }else if( !g.okRdWiki ){
76
+ if( g.okRdTkt ){
77
+ blob_append(&bSQL, " AND event.type!='w'", -1);
78
+ }else{
79
+ blob_append(&bSQL, " AND event.type=='ci'", -1);
80
+ }
81
+ }else if( !g.okRdTkt ){
82
+ assert( !g.okRdTkt &&& g.okRead && g.okRdWiki );
83
+ blob_append(&bSQL, " AND event.type!='t'", -1);
84
+ }
5785
}
5886
5987
blob_append( &bSQL, " ORDER BY event.mtime DESC", -1 );
6088
6189
cgi_set_content_type("application/rss+xml");
@@ -78,11 +106,11 @@
78106
@ <title>%h(zProjectName)</title>
79107
@ <link>%s(g.zBaseURL)</link>
80108
@ <description>%h(zProjectDescr)</description>
81109
@ <pubDate>%s(zPubDate)</pubDate>
82110
@ <generator>Fossil version %s(MANIFEST_VERSION) %s(MANIFEST_DATE)</generator>
83
- db_prepare(&q, blob_buffer(&bSQL));
111
+ db_prepare(&q, blob_str(&bSQL));
84112
blob_reset( &bSQL );
85113
while( db_step(&q)==SQLITE_ROW && nLine<=20 ){
86114
const char *zId = db_column_text(&q, 1);
87115
const char *zCom = db_column_text(&q, 3);
88116
const char *zAuthor = db_column_text(&q, 4);
89117
--- src/rss.c
+++ src/rss.c
@@ -47,15 +47,43 @@
47 @ (SELECT count(*) FROM plink WHERE pid=blob.rid AND isprim),
48 @ (SELECT count(*) FROM plink WHERE cid=blob.rid)
49 @ FROM event, blob
50 @ WHERE blob.rid=event.objid
51 ;
 
 
 
 
 
 
52 blob_zero(&bSQL);
53 blob_append( &bSQL, zSQL1, -1 );
54
55 if( zType[0]!='a' ){
56 blob_appendf(&bSQL, " AND event.type=%Q", zType);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57 }
58
59 blob_append( &bSQL, " ORDER BY event.mtime DESC", -1 );
60
61 cgi_set_content_type("application/rss+xml");
@@ -78,11 +106,11 @@
78 @ <title>%h(zProjectName)</title>
79 @ <link>%s(g.zBaseURL)</link>
80 @ <description>%h(zProjectDescr)</description>
81 @ <pubDate>%s(zPubDate)</pubDate>
82 @ <generator>Fossil version %s(MANIFEST_VERSION) %s(MANIFEST_DATE)</generator>
83 db_prepare(&q, blob_buffer(&bSQL));
84 blob_reset( &bSQL );
85 while( db_step(&q)==SQLITE_ROW && nLine<=20 ){
86 const char *zId = db_column_text(&q, 1);
87 const char *zCom = db_column_text(&q, 3);
88 const char *zAuthor = db_column_text(&q, 4);
89
--- src/rss.c
+++ src/rss.c
@@ -47,15 +47,43 @@
47 @ (SELECT count(*) FROM plink WHERE pid=blob.rid AND isprim),
48 @ (SELECT count(*) FROM plink WHERE cid=blob.rid)
49 @ FROM event, blob
50 @ WHERE blob.rid=event.objid
51 ;
52
53 login_check_credentials();
54 if( !g.okRead && !g.okRdTkt && !g.okRdWiki ){
55 return;
56 }
57
58 blob_zero(&bSQL);
59 blob_append( &bSQL, zSQL1, -1 );
60
61 if( zType[0]!='a' ){
62 if( zType[0]=='c' && !g.okRead ) zType = "x";
63 if( zType[0]=='w' && !g.okRdWiki ) zType = "x";
64 if( zType[0]=='t' && !g.okRdTkt ) zType = "x";
65 blob_appendf(&bSQL, " AND event.type=%Q", zType);
66 }else{
67 if( !g.okRead ){
68 if( g.okRdTkt && g.okRdWiki ){
69 blob_append(&bSQL, " AND event.type!='ci'", -1);
70 }else if( g.okRdTkt ){
71 blob_append(&bSQL, " AND event.type=='t'", -1);
72 }else{
73 blob_append(&bSQL, " AND event.type=='w'", -1);
74 }
75 }else if( !g.okRdWiki ){
76 if( g.okRdTkt ){
77 blob_append(&bSQL, " AND event.type!='w'", -1);
78 }else{
79 blob_append(&bSQL, " AND event.type=='ci'", -1);
80 }
81 }else if( !g.okRdTkt ){
82 assert( !g.okRdTkt &&& g.okRead && g.okRdWiki );
83 blob_append(&bSQL, " AND event.type!='t'", -1);
84 }
85 }
86
87 blob_append( &bSQL, " ORDER BY event.mtime DESC", -1 );
88
89 cgi_set_content_type("application/rss+xml");
@@ -78,11 +106,11 @@
106 @ <title>%h(zProjectName)</title>
107 @ <link>%s(g.zBaseURL)</link>
108 @ <description>%h(zProjectDescr)</description>
109 @ <pubDate>%s(zPubDate)</pubDate>
110 @ <generator>Fossil version %s(MANIFEST_VERSION) %s(MANIFEST_DATE)</generator>
111 db_prepare(&q, blob_str(&bSQL));
112 blob_reset( &bSQL );
113 while( db_step(&q)==SQLITE_ROW && nLine<=20 ){
114 const char *zId = db_column_text(&q, 1);
115 const char *zCom = db_column_text(&q, 3);
116 const char *zAuthor = db_column_text(&q, 4);
117
+12
--- src/setup.c
+++ src/setup.c
@@ -84,10 +84,12 @@
8484
"Show artifacts that are shunned by this repository");
8585
setup_menu_entry("Log", "rcvfromlist",
8686
"A record of received artifacts and their sources");
8787
setup_menu_entry("Stats", "stat",
8888
"Display repository statistics");
89
+ setup_menu_entry("Sync now", "setup_sync",
90
+ "Sync this repository with the 'remote-url' it was set up with");
8991
@ </table>
9092
9193
style_footer();
9294
}
9395
@@ -1045,5 +1047,15 @@
10451047
@ changing the logo to provoke your browser to reload the new logo image.
10461048
@ </p>
10471049
style_footer();
10481050
db_end_transaction(0);
10491051
}
1052
+
1053
+/*
1054
+** WEBPAGE: setup_sync
1055
+*/
1056
+void setup_sync(void){
1057
+ sync_cmd();
1058
+ style_header("Synchronized");
1059
+ @ <p>The project has been synchronized</p>
1060
+ style_footer();
1061
+}
10501062
--- src/setup.c
+++ src/setup.c
@@ -84,10 +84,12 @@
84 "Show artifacts that are shunned by this repository");
85 setup_menu_entry("Log", "rcvfromlist",
86 "A record of received artifacts and their sources");
87 setup_menu_entry("Stats", "stat",
88 "Display repository statistics");
 
 
89 @ </table>
90
91 style_footer();
92 }
93
@@ -1045,5 +1047,15 @@
1045 @ changing the logo to provoke your browser to reload the new logo image.
1046 @ </p>
1047 style_footer();
1048 db_end_transaction(0);
1049 }
 
 
 
 
 
 
 
 
 
 
1050
--- src/setup.c
+++ src/setup.c
@@ -84,10 +84,12 @@
84 "Show artifacts that are shunned by this repository");
85 setup_menu_entry("Log", "rcvfromlist",
86 "A record of received artifacts and their sources");
87 setup_menu_entry("Stats", "stat",
88 "Display repository statistics");
89 setup_menu_entry("Sync now", "setup_sync",
90 "Sync this repository with the 'remote-url' it was set up with");
91 @ </table>
92
93 style_footer();
94 }
95
@@ -1045,5 +1047,15 @@
1047 @ changing the logo to provoke your browser to reload the new logo image.
1048 @ </p>
1049 style_footer();
1050 db_end_transaction(0);
1051 }
1052
1053 /*
1054 ** WEBPAGE: setup_sync
1055 */
1056 void setup_sync(void){
1057 sync_cmd();
1058 style_header("Synchronized");
1059 @ <p>The project has been synchronized</p>
1060 style_footer();
1061 }
1062
+12
--- src/setup.c
+++ src/setup.c
@@ -84,10 +84,12 @@
8484
"Show artifacts that are shunned by this repository");
8585
setup_menu_entry("Log", "rcvfromlist",
8686
"A record of received artifacts and their sources");
8787
setup_menu_entry("Stats", "stat",
8888
"Display repository statistics");
89
+ setup_menu_entry("Sync now", "setup_sync",
90
+ "Sync this repository with the 'remote-url' it was set up with");
8991
@ </table>
9092
9193
style_footer();
9294
}
9395
@@ -1045,5 +1047,15 @@
10451047
@ changing the logo to provoke your browser to reload the new logo image.
10461048
@ </p>
10471049
style_footer();
10481050
db_end_transaction(0);
10491051
}
1052
+
1053
+/*
1054
+** WEBPAGE: setup_sync
1055
+*/
1056
+void setup_sync(void){
1057
+ sync_cmd();
1058
+ style_header("Synchronized");
1059
+ @ <p>The project has been synchronized</p>
1060
+ style_footer();
1061
+}
10501062
--- src/setup.c
+++ src/setup.c
@@ -84,10 +84,12 @@
84 "Show artifacts that are shunned by this repository");
85 setup_menu_entry("Log", "rcvfromlist",
86 "A record of received artifacts and their sources");
87 setup_menu_entry("Stats", "stat",
88 "Display repository statistics");
 
 
89 @ </table>
90
91 style_footer();
92 }
93
@@ -1045,5 +1047,15 @@
1045 @ changing the logo to provoke your browser to reload the new logo image.
1046 @ </p>
1047 style_footer();
1048 db_end_transaction(0);
1049 }
 
 
 
 
 
 
 
 
 
 
1050
--- src/setup.c
+++ src/setup.c
@@ -84,10 +84,12 @@
84 "Show artifacts that are shunned by this repository");
85 setup_menu_entry("Log", "rcvfromlist",
86 "A record of received artifacts and their sources");
87 setup_menu_entry("Stats", "stat",
88 "Display repository statistics");
89 setup_menu_entry("Sync now", "setup_sync",
90 "Sync this repository with the 'remote-url' it was set up with");
91 @ </table>
92
93 style_footer();
94 }
95
@@ -1045,5 +1047,15 @@
1047 @ changing the logo to provoke your browser to reload the new logo image.
1048 @ </p>
1049 style_footer();
1050 db_end_transaction(0);
1051 }
1052
1053 /*
1054 ** WEBPAGE: setup_sync
1055 */
1056 void setup_sync(void){
1057 sync_cmd();
1058 style_header("Synchronized");
1059 @ <p>The project has been synchronized</p>
1060 style_footer();
1061 }
1062
+1
--- src/sha1.c
+++ src/sha1.c
@@ -607,10 +607,11 @@
607607
int i;
608608
Blob in;
609609
Blob cksum;
610610
611611
for(i=2; i<g.argc; i++){
612
+ blob_init(&cksum, "************** not found ***************", -1);
612613
if( g.argv[i][0]=='-' && g.argv[i][1]==0 ){
613614
blob_read_from_channel(&in, stdin, -1);
614615
sha1sum_blob(&in, &cksum);
615616
}else{
616617
sha1sum_file(g.argv[i], &cksum);
617618
--- src/sha1.c
+++ src/sha1.c
@@ -607,10 +607,11 @@
607 int i;
608 Blob in;
609 Blob cksum;
610
611 for(i=2; i<g.argc; i++){
 
612 if( g.argv[i][0]=='-' && g.argv[i][1]==0 ){
613 blob_read_from_channel(&in, stdin, -1);
614 sha1sum_blob(&in, &cksum);
615 }else{
616 sha1sum_file(g.argv[i], &cksum);
617
--- src/sha1.c
+++ src/sha1.c
@@ -607,10 +607,11 @@
607 int i;
608 Blob in;
609 Blob cksum;
610
611 for(i=2; i<g.argc; i++){
612 blob_init(&cksum, "************** not found ***************", -1);
613 if( g.argv[i][0]=='-' && g.argv[i][1]==0 ){
614 blob_read_from_channel(&in, stdin, -1);
615 sha1sum_blob(&in, &cksum);
616 }else{
617 sha1sum_file(g.argv[i], &cksum);
618
+220 -100
--- src/timeline.c
+++ src/timeline.c
@@ -153,10 +153,11 @@
153153
*/
154154
#if INTERFACE
155155
#define TIMELINE_ARTID 0x0001 /* Show artifact IDs on non-check-in lines */
156156
#define TIMELINE_LEAFONLY 0x0002 /* Show "Leaf", but not "Merge", "Fork" etc */
157157
#define TIMELINE_BRIEF 0x0004 /* Combine adjacent elements of same object */
158
+#define TIMELINE_GRAPH 0x0008 /* Compute a graph */
158159
#endif
159160
160161
/*
161162
** Output a timeline in the web format given a query. The query
162163
** should return these columns:
@@ -184,18 +185,23 @@
184185
int mxWikiLen;
185186
Blob comment;
186187
int prevTagid = 0;
187188
int suppressCnt = 0;
188189
char zPrevDate[20];
190
+ GraphContext *pGraph = 0;
189191
190192
zPrevDate[0] = 0;
191193
mxWikiLen = db_get_int("timeline-max-comment", 0);
192194
if( db_get_boolean("timeline-block-markup", 0) ){
193195
wikiFlags = WIKI_INLINE;
194196
}else{
195197
wikiFlags = WIKI_INLINE | WIKI_NOBLOCK;
196198
}
199
+ if( tmFlags & TIMELINE_GRAPH ){
200
+ pGraph = graph_init();
201
+ @ <div id="canvas" style="position:relative;width:1px;height:1px;"></div>
202
+ }
197203
198204
db_multi_exec(
199205
"CREATE TEMP TABLE IF NOT EXISTS seen(rid INTEGER PRIMARY KEY);"
200206
"DELETE FROM seen;"
201207
);
@@ -212,10 +218,11 @@
212218
const char *zType = db_column_text(pQuery, 9);
213219
const char *zUser = db_column_text(pQuery, 4);
214220
const char *zTagList = db_column_text(pQuery, 10);
215221
int tagid = db_column_int(pQuery, 11);
216222
int commentColumn = 3; /* Column containing comment text */
223
+ char zTime[8];
217224
if( tagid ){
218225
if( tagid==prevTagid ){
219226
if( tmFlags & TIMELINE_BRIEF ){
220227
suppressCnt++;
221228
continue;
@@ -236,27 +243,29 @@
236243
continue;
237244
}
238245
db_multi_exec("INSERT OR IGNORE INTO seen VALUES(%d)", rid);
239246
if( memcmp(zDate, zPrevDate, 10) ){
240247
sprintf(zPrevDate, "%.10s", zDate);
241
- @ <tr><td colspan=3>
242
- @ <div class="divider">%s(zPrevDate)</div>
248
+ @ <tr><td>
249
+ @ <div class="divider"><nobr>%s(zPrevDate)</nobr></div>
243250
@ </td></tr>
244251
}
252
+ memcpy(zTime, &zDate[11], 5);
253
+ zTime[5] = 0;
245254
@ <tr>
246
- @ <td valign="top">%s(&zDate[11])</td>
255
+ @ <td valign="top" align="right">%s(zTime)</td>
247256
@ <td width="20" align="center" valign="top">
248
- @ <font id="m%d(rid)" size="+1" color="white">*</font></td>
257
+ @ <div id="m%d(rid)"></div>
249258
if( zBgClr && zBgClr[0] ){
250259
@ <td valign="top" align="left" bgcolor="%h(zBgClr)">
251260
}else{
252261
@ <td valign="top" align="left">
253262
}
254263
if( zType[0]=='c' ){
255264
const char *azTag[5];
256265
int nTag = 0;
257
- hyperlink_to_uuid_with_mouseover(zUuid, "xin", "xout", rid);
266
+ hyperlink_to_uuid(zUuid);
258267
if( (tmFlags & TIMELINE_LEAFONLY)==0 ){
259268
if( nParent>1 ){
260269
azTag[nTag++] = "Merge";
261270
}
262271
if( nPChild>1 ){
@@ -280,10 +289,37 @@
280289
int i;
281290
for(i=0; i<nTag; i++){
282291
@ <b>%s(azTag[i])%s(i==nTag-1?"":",")</b>
283292
}
284293
}
294
+ if( pGraph ){
295
+ int nParent = 0;
296
+ int aParent[32];
297
+ const char *zBr;
298
+ static Stmt qparent;
299
+ static Stmt qbranch;
300
+ db_static_prepare(&qparent,
301
+ "SELECT pid FROM plink WHERE cid=:rid ORDER BY isprim DESC"
302
+ );
303
+ db_static_prepare(&qbranch,
304
+ "SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0 AND rid=:rid",
305
+ TAG_BRANCH
306
+ );
307
+ db_bind_int(&qparent, ":rid", rid);
308
+ while( db_step(&qparent)==SQLITE_ROW && nParent<32 ){
309
+ aParent[nParent++] = db_column_int(&qparent, 0);
310
+ }
311
+ db_reset(&qparent);
312
+ db_bind_int(&qbranch, ":rid", rid);
313
+ if( db_step(&qbranch)==SQLITE_ROW ){
314
+ zBr = db_column_text(&qbranch, 0);
315
+ }else{
316
+ zBr = "trunk";
317
+ }
318
+ graph_add_row(pGraph, rid, isLeaf, nParent, aParent, zBr);
319
+ db_reset(&qbranch);
320
+ }
285321
}else if( (tmFlags & TIMELINE_ARTID)!=0 ){
286322
hyperlink_to_uuid(zUuid);
287323
}
288324
db_column_blob(pQuery, commentColumn, &comment);
289325
if( mxWikiLen>0 && blob_size(&comment)>mxWikiLen ){
@@ -310,12 +346,188 @@
310346
if( suppressCnt ){
311347
@ <tr><td><td><td>
312348
@ <small><i>... %d(suppressCnt) similar
313349
@ event%s(suppressCnt>1?"s":"") omitted.</i></small></tr>
314350
suppressCnt = 0;
351
+ }
352
+ if( pGraph ){
353
+ graph_finish(pGraph);
354
+ if( pGraph->nErr ){
355
+ graph_free(pGraph);
356
+ pGraph = 0;
357
+ }else{
358
+ @ <tr><td><td><div style="width:%d(pGraph->mxRail*20+30)px;"></div>
359
+ }
315360
}
316361
@ </table>
362
+ if( pGraph && pGraph->nErr==0 ){
363
+ GraphRow *pRow;
364
+ int i;
365
+ char cSep;
366
+ @ <script type="text/JavaScript">
367
+ cgi_printf("var rowinfo = [\n");
368
+ for(pRow=pGraph->pFirst; pRow; pRow=pRow->pNext){
369
+ cgi_printf("{id:\"m%d\",r:%d,d:%d,mo:%d,mu:%d,u:%d,au:",
370
+ pRow->rid,
371
+ pRow->iRail,
372
+ pRow->bDescender,
373
+ pRow->mergeOut,
374
+ pRow->mergeUpto,
375
+ pRow->aiRaiser[pRow->iRail]
376
+ );
377
+ cSep = '[';
378
+ for(i=0; i<GR_MAX_RAIL; i++){
379
+ if( i==pRow->iRail ) continue;
380
+ if( pRow->aiRaiser[i]>0 ){
381
+ cgi_printf("%c%d,%d", cSep, pGraph->railMap[i], pRow->aiRaiser[i]);
382
+ cSep = ',';
383
+ }
384
+ }
385
+ if( cSep=='[' ) cgi_printf("[");
386
+ cgi_printf("],mi:");
387
+ cSep = '[';
388
+ for(i=0; i<GR_MAX_RAIL; i++){
389
+ if( pRow->mergeIn & (1<<i) ){
390
+ cgi_printf("%c%d", cSep, pGraph->railMap[i]);
391
+ cSep = ',';
392
+ }
393
+ }
394
+ if( cSep=='[' ) cgi_printf("[");
395
+ cgi_printf("]}%s", pRow->pNext ? ",\n" : "];\n");
396
+ }
397
+ cgi_printf("var nrail = %d\n", pGraph->mxRail+1);
398
+ graph_free(pGraph);
399
+ @ var canvasDiv = document.getElementById("canvas");
400
+ @ function drawBox(color,x0,y0,x1,y1){
401
+ @ var n = document.createElement("div");
402
+ @ if( x0>x1 ){ var t=x0; x0=x1; x1=t; }
403
+ @ if( y0>y1 ){ var t=y0; y0=y1; y1=t; }
404
+ @ var w = x1-x0+1;
405
+ @ var h = y1-y0+1;
406
+ @ n.setAttribute("style",
407
+ @ "position:absolute;"+
408
+ @ "left:"+x0+"px;"+
409
+ @ "top:"+y0+"px;"+
410
+ @ "width:"+w+"px;"+
411
+ @ "height:"+h+"px;"+
412
+ @ "background-color:"+color+";"
413
+ @ );
414
+ @ canvasDiv.appendChild(n);
415
+ @ }
416
+ @ function absoluteY(id){
417
+ @ var obj = document.getElementById(id);
418
+ @ if( !obj ) return;
419
+ @ var top = 0;
420
+ @ if( obj.offsetParent ){
421
+ @ do{
422
+ @ top += obj.offsetTop;
423
+ @ }while( obj = obj.offsetParent );
424
+ @ }
425
+ @ return top;
426
+ @ }
427
+ @ function absoluteX(id){
428
+ @ var obj = document.getElementById(id);
429
+ @ if( !obj ) return;
430
+ @ var left = 0;
431
+ @ if( obj.offsetParent ){
432
+ @ do{
433
+ @ left += obj.offsetLeft;
434
+ @ }while( obj = obj.offsetParent );
435
+ @ }
436
+ @ return left;
437
+ @ }
438
+ @ function drawUpArrow(x,y0,y1){
439
+ @ drawBox("black",x,y0,x+1,y1);
440
+ @ if( y0+8>=y1 ){
441
+ @ drawBox("black",x-1,y0+1,x+2,y0+2);
442
+ @ drawBox("black",x-2,y0+3,x+3,y0+4);
443
+ @ }else{
444
+ @ drawBox("black",x-1,y0+2,x+2,y0+4);
445
+ @ drawBox("black",x-2,y0+5,x+3,y0+7);
446
+ @ }
447
+ @ }
448
+ @ function drawThinArrow(y,xFrom,xTo){
449
+ @ if( xFrom<xTo ){
450
+ @ drawBox("black",xFrom,y,xTo,y);
451
+ @ drawBox("black",xTo-4,y-1,xTo-2,y+1);
452
+ @ if( xTo>xFrom-8 ) drawBox("black",xTo-6,y-2,xTo-5,y+2);
453
+ @ }else{
454
+ @ drawBox("black",xTo,y,xFrom,y);
455
+ @ drawBox("black",xTo+2,y-1,xTo+4,y+1);
456
+ @ if( xTo+8<xFrom ) drawBox("black",xTo+5,y-2,xTo+6,y+2);
457
+ @ }
458
+ @ }
459
+ @ function drawThinLine(x0,y0,x1,y1){
460
+ @ drawBox("black",x0,y0,x1,y1);
461
+ @ }
462
+ @ function drawNode(p, left, btm){
463
+ @ drawBox("black",p.x-5,p.y-5,p.x+6,p.y+6);
464
+ @ drawBox("white",p.x-4,p.y-4,p.x+5,p.y+5);
465
+ @ if( p.u>0 ){
466
+ @ var u = rowinfo[p.u-1];
467
+ @ drawUpArrow(p.x, u.y+6, p.y-5);
468
+ @ }
469
+ @ if( p.d ){
470
+ @ drawUpArrow(p.x, p.y+6, btm);
471
+ @ }
472
+ @ if( p.mo>=0 ){
473
+ @ var x1 = p.mo*20 + left;
474
+ @ var y1 = p.y-3;
475
+ @ var x0 = x1>p.x ? p.x+7 : p.x-6;
476
+ @ var u = rowinfo[p.mu-1];
477
+ @ var y0 = u.y+5;
478
+ @ drawThinLine(x0,y1,x1,y1);
479
+ @ drawThinLine(x1,y0,x1,y1);
480
+ @ }
481
+ @ var n = p.au.length;
482
+ @ for(var i=0; i<n; i+=2){
483
+ @ var x1 = p.au[i]*20 + left;
484
+ @ var x0 = x1>p.x ? p.x+7 : p.x-6;
485
+ @ drawBox("black",x0,p.y,x1,p.y+1);
486
+ @ var u = rowinfo[p.au[i+1]-1];
487
+ @ drawUpArrow(x1, u.y+6, p.y);
488
+ @ }
489
+ @ for(var j in p.mi){
490
+ @ var y0 = p.y+5;
491
+ @ var mx = p.mi[j]*20 + left;
492
+ @ if( mx>p.x ){
493
+ @ drawThinArrow(y0,mx,p.x+5);
494
+ @ }else{
495
+ @ drawThinArrow(y0,mx,p.x-5);
496
+ @ }
497
+ @ }
498
+ @ }
499
+ @ function renderGraph(){
500
+ @ var canvasDiv = document.getElementById("canvas");
501
+ @ for(var i=canvasDiv.childNodes.length-1; i>=0; i--){
502
+ @ var c = canvasDiv.childNodes[i];
503
+ @ delete canvasDiv.removeChild(c);
504
+ @ }
505
+ @ var canvasY = absoluteY("canvas");
506
+ @ var left = absoluteX(rowinfo[0].id) - absoluteX("canvas") + 15;
507
+ @ for(var i in rowinfo){
508
+ @ rowinfo[i].y = absoluteY(rowinfo[i].id) + 10 - canvasY;
509
+ @ rowinfo[i].x = left + rowinfo[i].r*20;
510
+ @ }
511
+ @ var btm = rowinfo[rowinfo.length-1].y + 20;
512
+ @ for(var i in rowinfo){
513
+ @ drawNode(rowinfo[i], left, btm);
514
+ @ }
515
+ @ }
516
+ @ var lastId = rowinfo[rowinfo.length-1].id;
517
+ @ var lastY = 0;
518
+ @ function checkHeight(){
519
+ @ var h = absoluteY(lastId);
520
+ @ if( h!=lastY ){
521
+ @ renderGraph();
522
+ @ lastY = h;
523
+ @ }
524
+ @ setTimeout("checkHeight();", 1000);
525
+ @ }
526
+ @ checkHeight();
527
+ @ </script>
528
+ }
317529
}
318530
319531
/*
320532
** Create a temporary table suitable for storing timeline data.
321533
*/
@@ -461,13 +673,13 @@
461673
tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'", zTagName);
462674
}else{
463675
tagid = 0;
464676
}
465677
if( zType[0]=='a' ){
466
- tmFlags = TIMELINE_BRIEF;
678
+ tmFlags = TIMELINE_BRIEF | TIMELINE_GRAPH;
467679
}else{
468
- tmFlags = 0;
680
+ tmFlags = TIMELINE_GRAPH;
469681
}
470682
471683
style_header("Timeline");
472684
login_anonymous_available();
473685
timeline_temp_table();
@@ -645,10 +857,11 @@
645857
if( zUser ){
646858
blob_appendf(&desc, " by user %h", zUser);
647859
}
648860
if( tagid>0 ){
649861
blob_appendf(&desc, " tagged with \"%h\"", zTagName);
862
+ tmFlags &= ~TIMELINE_GRAPH;
650863
}
651864
if( zAfter ){
652865
blob_appendf(&desc, " occurring on or after %h.<br>", zAfter);
653866
}else if( zBefore ){
654867
blob_appendf(&desc, " occurring on or before %h.<br>", zBefore);
@@ -691,103 +904,10 @@
691904
db_prepare(&q, "SELECT * FROM timeline ORDER BY timestamp DESC");
692905
@ <h2>%b(&desc)</h2>
693906
blob_reset(&desc);
694907
www_print_timeline(&q, tmFlags, 0);
695908
db_finalize(&q);
696
-
697
- @ <script>
698
- @ var parentof = new Object();
699
- @ var childof = new Object();
700
- db_prepare(&q, "SELECT rid FROM seen");
701
- while( db_step(&q)==SQLITE_ROW ){
702
- int rid = db_column_int(&q, 0);
703
- Stmt q2;
704
- const char *zSep;
705
- Blob *pOut = cgi_output_blob();
706
-
707
- db_prepare(&q2, "SELECT pid FROM plink WHERE cid=%d", rid);
708
- zSep = "";
709
- blob_appendf(pOut, "parentof[\"m%d\"] = [", rid);
710
- while( db_step(&q2)==SQLITE_ROW ){
711
- int pid = db_column_int(&q2, 0);
712
- blob_appendf(pOut, "%s\"m%d\"", zSep, pid);
713
- zSep = ",";
714
- }
715
- db_finalize(&q2);
716
- blob_appendf(pOut, "];\n");
717
- db_prepare(&q2, "SELECT cid FROM plink WHERE pid=%d", rid);
718
- zSep = "";
719
- blob_appendf(pOut, "childof[\"m%d\"] = [", rid);
720
- while( db_step(&q2)==SQLITE_ROW ){
721
- int pid = db_column_int(&q2, 0);
722
- blob_appendf(pOut, "%s\"m%d\"", zSep, pid);
723
- zSep = ",";
724
- }
725
- db_finalize(&q2);
726
- blob_appendf(pOut, "];\n");
727
- }
728
- db_finalize(&q);
729
- @ function setall(value){
730
- @ for(var x in parentof){
731
- @ setone(x,value);
732
- @ }
733
- @ }
734
- @ setall("#ffffff");
735
- @ function setone(id, clr){
736
- @ if( parentof[id]==null ) return 0;
737
- @ var w = document.getElementById(id);
738
- @ if( w.style.color==clr ){
739
- @ return 0
740
- @ }else{
741
- @ w.style.color = clr
742
- @ return 1
743
- @ }
744
- @ }
745
- @ function xin(id) {
746
- @ setall("#ffffff");
747
- @ setone(id,"#ff0000");
748
- @ set_children(id, "#b0b0b0");
749
- @ set_parents(id, "#b0b0b0");
750
- @ for(var x in parentof[id]){
751
- @ var pid = parentof[id][x]
752
- @ var w = document.getElementById(pid);
753
- @ if( w!=null ){
754
- @ w.style.color = "#000000";
755
- @ }
756
- @ }
757
- @ for(var x in childof[id]){
758
- @ var cid = childof[id][x]
759
- @ var w = document.getElementById(cid);
760
- @ if( w!=null ){
761
- @ w.style.color = "#000000";
762
- @ }
763
- @ }
764
- @ }
765
- @ function xout(id) {
766
- @ /* setall("#000000"); */
767
- @ }
768
- @ function set_parents(id, clr){
769
- @ var plist = parentof[id];
770
- @ if( plist==null ) return;
771
- @ for(var x in plist){
772
- @ var pid = plist[x];
773
- @ if( setone(pid,clr)==1 ){
774
- @ set_parents(pid,clr);
775
- @ }
776
- @ }
777
- @ }
778
- @ function set_children(id,clr){
779
- @ var clist = childof[id];
780
- @ if( clist==null ) return;
781
- @ for(var x in clist){
782
- @ var cid = clist[x];
783
- @ if( setone(cid,clr)==1 ){
784
- @ set_children(cid,clr);
785
- @ }
786
- @ }
787
- @ }
788
- @ </script>
789909
style_footer();
790910
}
791911
792912
/*
793913
** The input query q selects various records. Print a human-readable
794914
--- src/timeline.c
+++ src/timeline.c
@@ -153,10 +153,11 @@
153 */
154 #if INTERFACE
155 #define TIMELINE_ARTID 0x0001 /* Show artifact IDs on non-check-in lines */
156 #define TIMELINE_LEAFONLY 0x0002 /* Show "Leaf", but not "Merge", "Fork" etc */
157 #define TIMELINE_BRIEF 0x0004 /* Combine adjacent elements of same object */
 
158 #endif
159
160 /*
161 ** Output a timeline in the web format given a query. The query
162 ** should return these columns:
@@ -184,18 +185,23 @@
184 int mxWikiLen;
185 Blob comment;
186 int prevTagid = 0;
187 int suppressCnt = 0;
188 char zPrevDate[20];
 
189
190 zPrevDate[0] = 0;
191 mxWikiLen = db_get_int("timeline-max-comment", 0);
192 if( db_get_boolean("timeline-block-markup", 0) ){
193 wikiFlags = WIKI_INLINE;
194 }else{
195 wikiFlags = WIKI_INLINE | WIKI_NOBLOCK;
196 }
 
 
 
 
197
198 db_multi_exec(
199 "CREATE TEMP TABLE IF NOT EXISTS seen(rid INTEGER PRIMARY KEY);"
200 "DELETE FROM seen;"
201 );
@@ -212,10 +218,11 @@
212 const char *zType = db_column_text(pQuery, 9);
213 const char *zUser = db_column_text(pQuery, 4);
214 const char *zTagList = db_column_text(pQuery, 10);
215 int tagid = db_column_int(pQuery, 11);
216 int commentColumn = 3; /* Column containing comment text */
 
217 if( tagid ){
218 if( tagid==prevTagid ){
219 if( tmFlags & TIMELINE_BRIEF ){
220 suppressCnt++;
221 continue;
@@ -236,27 +243,29 @@
236 continue;
237 }
238 db_multi_exec("INSERT OR IGNORE INTO seen VALUES(%d)", rid);
239 if( memcmp(zDate, zPrevDate, 10) ){
240 sprintf(zPrevDate, "%.10s", zDate);
241 @ <tr><td colspan=3>
242 @ <div class="divider">%s(zPrevDate)</div>
243 @ </td></tr>
244 }
 
 
245 @ <tr>
246 @ <td valign="top">%s(&zDate[11])</td>
247 @ <td width="20" align="center" valign="top">
248 @ <font id="m%d(rid)" size="+1" color="white">*</font></td>
249 if( zBgClr && zBgClr[0] ){
250 @ <td valign="top" align="left" bgcolor="%h(zBgClr)">
251 }else{
252 @ <td valign="top" align="left">
253 }
254 if( zType[0]=='c' ){
255 const char *azTag[5];
256 int nTag = 0;
257 hyperlink_to_uuid_with_mouseover(zUuid, "xin", "xout", rid);
258 if( (tmFlags & TIMELINE_LEAFONLY)==0 ){
259 if( nParent>1 ){
260 azTag[nTag++] = "Merge";
261 }
262 if( nPChild>1 ){
@@ -280,10 +289,37 @@
280 int i;
281 for(i=0; i<nTag; i++){
282 @ <b>%s(azTag[i])%s(i==nTag-1?"":",")</b>
283 }
284 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285 }else if( (tmFlags & TIMELINE_ARTID)!=0 ){
286 hyperlink_to_uuid(zUuid);
287 }
288 db_column_blob(pQuery, commentColumn, &comment);
289 if( mxWikiLen>0 && blob_size(&comment)>mxWikiLen ){
@@ -310,12 +346,188 @@
310 if( suppressCnt ){
311 @ <tr><td><td><td>
312 @ <small><i>... %d(suppressCnt) similar
313 @ event%s(suppressCnt>1?"s":"") omitted.</i></small></tr>
314 suppressCnt = 0;
 
 
 
 
 
 
 
 
 
315 }
316 @ </table>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
317 }
318
319 /*
320 ** Create a temporary table suitable for storing timeline data.
321 */
@@ -461,13 +673,13 @@
461 tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'", zTagName);
462 }else{
463 tagid = 0;
464 }
465 if( zType[0]=='a' ){
466 tmFlags = TIMELINE_BRIEF;
467 }else{
468 tmFlags = 0;
469 }
470
471 style_header("Timeline");
472 login_anonymous_available();
473 timeline_temp_table();
@@ -645,10 +857,11 @@
645 if( zUser ){
646 blob_appendf(&desc, " by user %h", zUser);
647 }
648 if( tagid>0 ){
649 blob_appendf(&desc, " tagged with \"%h\"", zTagName);
 
650 }
651 if( zAfter ){
652 blob_appendf(&desc, " occurring on or after %h.<br>", zAfter);
653 }else if( zBefore ){
654 blob_appendf(&desc, " occurring on or before %h.<br>", zBefore);
@@ -691,103 +904,10 @@
691 db_prepare(&q, "SELECT * FROM timeline ORDER BY timestamp DESC");
692 @ <h2>%b(&desc)</h2>
693 blob_reset(&desc);
694 www_print_timeline(&q, tmFlags, 0);
695 db_finalize(&q);
696
697 @ <script>
698 @ var parentof = new Object();
699 @ var childof = new Object();
700 db_prepare(&q, "SELECT rid FROM seen");
701 while( db_step(&q)==SQLITE_ROW ){
702 int rid = db_column_int(&q, 0);
703 Stmt q2;
704 const char *zSep;
705 Blob *pOut = cgi_output_blob();
706
707 db_prepare(&q2, "SELECT pid FROM plink WHERE cid=%d", rid);
708 zSep = "";
709 blob_appendf(pOut, "parentof[\"m%d\"] = [", rid);
710 while( db_step(&q2)==SQLITE_ROW ){
711 int pid = db_column_int(&q2, 0);
712 blob_appendf(pOut, "%s\"m%d\"", zSep, pid);
713 zSep = ",";
714 }
715 db_finalize(&q2);
716 blob_appendf(pOut, "];\n");
717 db_prepare(&q2, "SELECT cid FROM plink WHERE pid=%d", rid);
718 zSep = "";
719 blob_appendf(pOut, "childof[\"m%d\"] = [", rid);
720 while( db_step(&q2)==SQLITE_ROW ){
721 int pid = db_column_int(&q2, 0);
722 blob_appendf(pOut, "%s\"m%d\"", zSep, pid);
723 zSep = ",";
724 }
725 db_finalize(&q2);
726 blob_appendf(pOut, "];\n");
727 }
728 db_finalize(&q);
729 @ function setall(value){
730 @ for(var x in parentof){
731 @ setone(x,value);
732 @ }
733 @ }
734 @ setall("#ffffff");
735 @ function setone(id, clr){
736 @ if( parentof[id]==null ) return 0;
737 @ var w = document.getElementById(id);
738 @ if( w.style.color==clr ){
739 @ return 0
740 @ }else{
741 @ w.style.color = clr
742 @ return 1
743 @ }
744 @ }
745 @ function xin(id) {
746 @ setall("#ffffff");
747 @ setone(id,"#ff0000");
748 @ set_children(id, "#b0b0b0");
749 @ set_parents(id, "#b0b0b0");
750 @ for(var x in parentof[id]){
751 @ var pid = parentof[id][x]
752 @ var w = document.getElementById(pid);
753 @ if( w!=null ){
754 @ w.style.color = "#000000";
755 @ }
756 @ }
757 @ for(var x in childof[id]){
758 @ var cid = childof[id][x]
759 @ var w = document.getElementById(cid);
760 @ if( w!=null ){
761 @ w.style.color = "#000000";
762 @ }
763 @ }
764 @ }
765 @ function xout(id) {
766 @ /* setall("#000000"); */
767 @ }
768 @ function set_parents(id, clr){
769 @ var plist = parentof[id];
770 @ if( plist==null ) return;
771 @ for(var x in plist){
772 @ var pid = plist[x];
773 @ if( setone(pid,clr)==1 ){
774 @ set_parents(pid,clr);
775 @ }
776 @ }
777 @ }
778 @ function set_children(id,clr){
779 @ var clist = childof[id];
780 @ if( clist==null ) return;
781 @ for(var x in clist){
782 @ var cid = clist[x];
783 @ if( setone(cid,clr)==1 ){
784 @ set_children(cid,clr);
785 @ }
786 @ }
787 @ }
788 @ </script>
789 style_footer();
790 }
791
792 /*
793 ** The input query q selects various records. Print a human-readable
794
--- src/timeline.c
+++ src/timeline.c
@@ -153,10 +153,11 @@
153 */
154 #if INTERFACE
155 #define TIMELINE_ARTID 0x0001 /* Show artifact IDs on non-check-in lines */
156 #define TIMELINE_LEAFONLY 0x0002 /* Show "Leaf", but not "Merge", "Fork" etc */
157 #define TIMELINE_BRIEF 0x0004 /* Combine adjacent elements of same object */
158 #define TIMELINE_GRAPH 0x0008 /* Compute a graph */
159 #endif
160
161 /*
162 ** Output a timeline in the web format given a query. The query
163 ** should return these columns:
@@ -184,18 +185,23 @@
185 int mxWikiLen;
186 Blob comment;
187 int prevTagid = 0;
188 int suppressCnt = 0;
189 char zPrevDate[20];
190 GraphContext *pGraph = 0;
191
192 zPrevDate[0] = 0;
193 mxWikiLen = db_get_int("timeline-max-comment", 0);
194 if( db_get_boolean("timeline-block-markup", 0) ){
195 wikiFlags = WIKI_INLINE;
196 }else{
197 wikiFlags = WIKI_INLINE | WIKI_NOBLOCK;
198 }
199 if( tmFlags & TIMELINE_GRAPH ){
200 pGraph = graph_init();
201 @ <div id="canvas" style="position:relative;width:1px;height:1px;"></div>
202 }
203
204 db_multi_exec(
205 "CREATE TEMP TABLE IF NOT EXISTS seen(rid INTEGER PRIMARY KEY);"
206 "DELETE FROM seen;"
207 );
@@ -212,10 +218,11 @@
218 const char *zType = db_column_text(pQuery, 9);
219 const char *zUser = db_column_text(pQuery, 4);
220 const char *zTagList = db_column_text(pQuery, 10);
221 int tagid = db_column_int(pQuery, 11);
222 int commentColumn = 3; /* Column containing comment text */
223 char zTime[8];
224 if( tagid ){
225 if( tagid==prevTagid ){
226 if( tmFlags & TIMELINE_BRIEF ){
227 suppressCnt++;
228 continue;
@@ -236,27 +243,29 @@
243 continue;
244 }
245 db_multi_exec("INSERT OR IGNORE INTO seen VALUES(%d)", rid);
246 if( memcmp(zDate, zPrevDate, 10) ){
247 sprintf(zPrevDate, "%.10s", zDate);
248 @ <tr><td>
249 @ <div class="divider"><nobr>%s(zPrevDate)</nobr></div>
250 @ </td></tr>
251 }
252 memcpy(zTime, &zDate[11], 5);
253 zTime[5] = 0;
254 @ <tr>
255 @ <td valign="top" align="right">%s(zTime)</td>
256 @ <td width="20" align="center" valign="top">
257 @ <div id="m%d(rid)"></div>
258 if( zBgClr && zBgClr[0] ){
259 @ <td valign="top" align="left" bgcolor="%h(zBgClr)">
260 }else{
261 @ <td valign="top" align="left">
262 }
263 if( zType[0]=='c' ){
264 const char *azTag[5];
265 int nTag = 0;
266 hyperlink_to_uuid(zUuid);
267 if( (tmFlags & TIMELINE_LEAFONLY)==0 ){
268 if( nParent>1 ){
269 azTag[nTag++] = "Merge";
270 }
271 if( nPChild>1 ){
@@ -280,10 +289,37 @@
289 int i;
290 for(i=0; i<nTag; i++){
291 @ <b>%s(azTag[i])%s(i==nTag-1?"":",")</b>
292 }
293 }
294 if( pGraph ){
295 int nParent = 0;
296 int aParent[32];
297 const char *zBr;
298 static Stmt qparent;
299 static Stmt qbranch;
300 db_static_prepare(&qparent,
301 "SELECT pid FROM plink WHERE cid=:rid ORDER BY isprim DESC"
302 );
303 db_static_prepare(&qbranch,
304 "SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0 AND rid=:rid",
305 TAG_BRANCH
306 );
307 db_bind_int(&qparent, ":rid", rid);
308 while( db_step(&qparent)==SQLITE_ROW && nParent<32 ){
309 aParent[nParent++] = db_column_int(&qparent, 0);
310 }
311 db_reset(&qparent);
312 db_bind_int(&qbranch, ":rid", rid);
313 if( db_step(&qbranch)==SQLITE_ROW ){
314 zBr = db_column_text(&qbranch, 0);
315 }else{
316 zBr = "trunk";
317 }
318 graph_add_row(pGraph, rid, isLeaf, nParent, aParent, zBr);
319 db_reset(&qbranch);
320 }
321 }else if( (tmFlags & TIMELINE_ARTID)!=0 ){
322 hyperlink_to_uuid(zUuid);
323 }
324 db_column_blob(pQuery, commentColumn, &comment);
325 if( mxWikiLen>0 && blob_size(&comment)>mxWikiLen ){
@@ -310,12 +346,188 @@
346 if( suppressCnt ){
347 @ <tr><td><td><td>
348 @ <small><i>... %d(suppressCnt) similar
349 @ event%s(suppressCnt>1?"s":"") omitted.</i></small></tr>
350 suppressCnt = 0;
351 }
352 if( pGraph ){
353 graph_finish(pGraph);
354 if( pGraph->nErr ){
355 graph_free(pGraph);
356 pGraph = 0;
357 }else{
358 @ <tr><td><td><div style="width:%d(pGraph->mxRail*20+30)px;"></div>
359 }
360 }
361 @ </table>
362 if( pGraph && pGraph->nErr==0 ){
363 GraphRow *pRow;
364 int i;
365 char cSep;
366 @ <script type="text/JavaScript">
367 cgi_printf("var rowinfo = [\n");
368 for(pRow=pGraph->pFirst; pRow; pRow=pRow->pNext){
369 cgi_printf("{id:\"m%d\",r:%d,d:%d,mo:%d,mu:%d,u:%d,au:",
370 pRow->rid,
371 pRow->iRail,
372 pRow->bDescender,
373 pRow->mergeOut,
374 pRow->mergeUpto,
375 pRow->aiRaiser[pRow->iRail]
376 );
377 cSep = '[';
378 for(i=0; i<GR_MAX_RAIL; i++){
379 if( i==pRow->iRail ) continue;
380 if( pRow->aiRaiser[i]>0 ){
381 cgi_printf("%c%d,%d", cSep, pGraph->railMap[i], pRow->aiRaiser[i]);
382 cSep = ',';
383 }
384 }
385 if( cSep=='[' ) cgi_printf("[");
386 cgi_printf("],mi:");
387 cSep = '[';
388 for(i=0; i<GR_MAX_RAIL; i++){
389 if( pRow->mergeIn & (1<<i) ){
390 cgi_printf("%c%d", cSep, pGraph->railMap[i]);
391 cSep = ',';
392 }
393 }
394 if( cSep=='[' ) cgi_printf("[");
395 cgi_printf("]}%s", pRow->pNext ? ",\n" : "];\n");
396 }
397 cgi_printf("var nrail = %d\n", pGraph->mxRail+1);
398 graph_free(pGraph);
399 @ var canvasDiv = document.getElementById("canvas");
400 @ function drawBox(color,x0,y0,x1,y1){
401 @ var n = document.createElement("div");
402 @ if( x0>x1 ){ var t=x0; x0=x1; x1=t; }
403 @ if( y0>y1 ){ var t=y0; y0=y1; y1=t; }
404 @ var w = x1-x0+1;
405 @ var h = y1-y0+1;
406 @ n.setAttribute("style",
407 @ "position:absolute;"+
408 @ "left:"+x0+"px;"+
409 @ "top:"+y0+"px;"+
410 @ "width:"+w+"px;"+
411 @ "height:"+h+"px;"+
412 @ "background-color:"+color+";"
413 @ );
414 @ canvasDiv.appendChild(n);
415 @ }
416 @ function absoluteY(id){
417 @ var obj = document.getElementById(id);
418 @ if( !obj ) return;
419 @ var top = 0;
420 @ if( obj.offsetParent ){
421 @ do{
422 @ top += obj.offsetTop;
423 @ }while( obj = obj.offsetParent );
424 @ }
425 @ return top;
426 @ }
427 @ function absoluteX(id){
428 @ var obj = document.getElementById(id);
429 @ if( !obj ) return;
430 @ var left = 0;
431 @ if( obj.offsetParent ){
432 @ do{
433 @ left += obj.offsetLeft;
434 @ }while( obj = obj.offsetParent );
435 @ }
436 @ return left;
437 @ }
438 @ function drawUpArrow(x,y0,y1){
439 @ drawBox("black",x,y0,x+1,y1);
440 @ if( y0+8>=y1 ){
441 @ drawBox("black",x-1,y0+1,x+2,y0+2);
442 @ drawBox("black",x-2,y0+3,x+3,y0+4);
443 @ }else{
444 @ drawBox("black",x-1,y0+2,x+2,y0+4);
445 @ drawBox("black",x-2,y0+5,x+3,y0+7);
446 @ }
447 @ }
448 @ function drawThinArrow(y,xFrom,xTo){
449 @ if( xFrom<xTo ){
450 @ drawBox("black",xFrom,y,xTo,y);
451 @ drawBox("black",xTo-4,y-1,xTo-2,y+1);
452 @ if( xTo>xFrom-8 ) drawBox("black",xTo-6,y-2,xTo-5,y+2);
453 @ }else{
454 @ drawBox("black",xTo,y,xFrom,y);
455 @ drawBox("black",xTo+2,y-1,xTo+4,y+1);
456 @ if( xTo+8<xFrom ) drawBox("black",xTo+5,y-2,xTo+6,y+2);
457 @ }
458 @ }
459 @ function drawThinLine(x0,y0,x1,y1){
460 @ drawBox("black",x0,y0,x1,y1);
461 @ }
462 @ function drawNode(p, left, btm){
463 @ drawBox("black",p.x-5,p.y-5,p.x+6,p.y+6);
464 @ drawBox("white",p.x-4,p.y-4,p.x+5,p.y+5);
465 @ if( p.u>0 ){
466 @ var u = rowinfo[p.u-1];
467 @ drawUpArrow(p.x, u.y+6, p.y-5);
468 @ }
469 @ if( p.d ){
470 @ drawUpArrow(p.x, p.y+6, btm);
471 @ }
472 @ if( p.mo>=0 ){
473 @ var x1 = p.mo*20 + left;
474 @ var y1 = p.y-3;
475 @ var x0 = x1>p.x ? p.x+7 : p.x-6;
476 @ var u = rowinfo[p.mu-1];
477 @ var y0 = u.y+5;
478 @ drawThinLine(x0,y1,x1,y1);
479 @ drawThinLine(x1,y0,x1,y1);
480 @ }
481 @ var n = p.au.length;
482 @ for(var i=0; i<n; i+=2){
483 @ var x1 = p.au[i]*20 + left;
484 @ var x0 = x1>p.x ? p.x+7 : p.x-6;
485 @ drawBox("black",x0,p.y,x1,p.y+1);
486 @ var u = rowinfo[p.au[i+1]-1];
487 @ drawUpArrow(x1, u.y+6, p.y);
488 @ }
489 @ for(var j in p.mi){
490 @ var y0 = p.y+5;
491 @ var mx = p.mi[j]*20 + left;
492 @ if( mx>p.x ){
493 @ drawThinArrow(y0,mx,p.x+5);
494 @ }else{
495 @ drawThinArrow(y0,mx,p.x-5);
496 @ }
497 @ }
498 @ }
499 @ function renderGraph(){
500 @ var canvasDiv = document.getElementById("canvas");
501 @ for(var i=canvasDiv.childNodes.length-1; i>=0; i--){
502 @ var c = canvasDiv.childNodes[i];
503 @ delete canvasDiv.removeChild(c);
504 @ }
505 @ var canvasY = absoluteY("canvas");
506 @ var left = absoluteX(rowinfo[0].id) - absoluteX("canvas") + 15;
507 @ for(var i in rowinfo){
508 @ rowinfo[i].y = absoluteY(rowinfo[i].id) + 10 - canvasY;
509 @ rowinfo[i].x = left + rowinfo[i].r*20;
510 @ }
511 @ var btm = rowinfo[rowinfo.length-1].y + 20;
512 @ for(var i in rowinfo){
513 @ drawNode(rowinfo[i], left, btm);
514 @ }
515 @ }
516 @ var lastId = rowinfo[rowinfo.length-1].id;
517 @ var lastY = 0;
518 @ function checkHeight(){
519 @ var h = absoluteY(lastId);
520 @ if( h!=lastY ){
521 @ renderGraph();
522 @ lastY = h;
523 @ }
524 @ setTimeout("checkHeight();", 1000);
525 @ }
526 @ checkHeight();
527 @ </script>
528 }
529 }
530
531 /*
532 ** Create a temporary table suitable for storing timeline data.
533 */
@@ -461,13 +673,13 @@
673 tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'", zTagName);
674 }else{
675 tagid = 0;
676 }
677 if( zType[0]=='a' ){
678 tmFlags = TIMELINE_BRIEF | TIMELINE_GRAPH;
679 }else{
680 tmFlags = TIMELINE_GRAPH;
681 }
682
683 style_header("Timeline");
684 login_anonymous_available();
685 timeline_temp_table();
@@ -645,10 +857,11 @@
857 if( zUser ){
858 blob_appendf(&desc, " by user %h", zUser);
859 }
860 if( tagid>0 ){
861 blob_appendf(&desc, " tagged with \"%h\"", zTagName);
862 tmFlags &= ~TIMELINE_GRAPH;
863 }
864 if( zAfter ){
865 blob_appendf(&desc, " occurring on or after %h.<br>", zAfter);
866 }else if( zBefore ){
867 blob_appendf(&desc, " occurring on or before %h.<br>", zBefore);
@@ -691,103 +904,10 @@
904 db_prepare(&q, "SELECT * FROM timeline ORDER BY timestamp DESC");
905 @ <h2>%b(&desc)</h2>
906 blob_reset(&desc);
907 www_print_timeline(&q, tmFlags, 0);
908 db_finalize(&q);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
909 style_footer();
910 }
911
912 /*
913 ** The input query q selects various records. Print a human-readable
914
+1 -1
--- src/update.c
+++ src/update.c
@@ -78,10 +78,11 @@
7878
fossil_fatal("cannot find current version");
7979
}
8080
if( db_exists("SELECT 1 FROM vmerge") ){
8181
fossil_fatal("cannot update an uncommitted merge");
8282
}
83
+ if( !nochangeFlag ) autosync(AUTOSYNC_PULL);
8384
8485
if( g.argc>=3 ){
8586
if( strcmp(g.argv[2], "current")==0 ){
8687
/* If VERSION is "current", then use the same algorithm to find the
8788
** target as if VERSION were omitted. */
@@ -97,11 +98,10 @@
9798
}else if( !is_a_version(tid) ){
9899
fossil_fatal("no such version: %s", g.argv[2]);
99100
}
100101
}
101102
}
102
- if( !nochangeFlag ) autosync(AUTOSYNC_PULL);
103103
104104
if( tid==0 ){
105105
compute_leaves(vid, 1);
106106
if( !latestFlag && db_int(0, "SELECT count(*) FROM leaves")>1 ){
107107
db_prepare(&q,
108108
--- src/update.c
+++ src/update.c
@@ -78,10 +78,11 @@
78 fossil_fatal("cannot find current version");
79 }
80 if( db_exists("SELECT 1 FROM vmerge") ){
81 fossil_fatal("cannot update an uncommitted merge");
82 }
 
83
84 if( g.argc>=3 ){
85 if( strcmp(g.argv[2], "current")==0 ){
86 /* If VERSION is "current", then use the same algorithm to find the
87 ** target as if VERSION were omitted. */
@@ -97,11 +98,10 @@
97 }else if( !is_a_version(tid) ){
98 fossil_fatal("no such version: %s", g.argv[2]);
99 }
100 }
101 }
102 if( !nochangeFlag ) autosync(AUTOSYNC_PULL);
103
104 if( tid==0 ){
105 compute_leaves(vid, 1);
106 if( !latestFlag && db_int(0, "SELECT count(*) FROM leaves")>1 ){
107 db_prepare(&q,
108
--- src/update.c
+++ src/update.c
@@ -78,10 +78,11 @@
78 fossil_fatal("cannot find current version");
79 }
80 if( db_exists("SELECT 1 FROM vmerge") ){
81 fossil_fatal("cannot update an uncommitted merge");
82 }
83 if( !nochangeFlag ) autosync(AUTOSYNC_PULL);
84
85 if( g.argc>=3 ){
86 if( strcmp(g.argv[2], "current")==0 ){
87 /* If VERSION is "current", then use the same algorithm to find the
88 ** target as if VERSION were omitted. */
@@ -97,11 +98,10 @@
98 }else if( !is_a_version(tid) ){
99 fossil_fatal("no such version: %s", g.argv[2]);
100 }
101 }
102 }
 
103
104 if( tid==0 ){
105 compute_leaves(vid, 1);
106 if( !latestFlag && db_int(0, "SELECT count(*) FROM leaves")>1 ){
107 db_prepare(&q,
108
+29
--- src/wiki.c
+++ src/wiki.c
@@ -151,10 +151,13 @@
151151
if( g.okNewWiki ){
152152
@ <li> Create a <a href="%s(g.zBaseURL)/wikinew">new wiki page</a>.</li>
153153
}
154154
@ <li> <a href="%s(g.zBaseURL)/wcontent">List of All Wiki Pages</a>
155155
@ available on this server.</li>
156
+ @ <li> <form method="GET" action="%s(g.zBaseURL)/wfind">
157
+ @ Search wiki titles: <input type="text" name="title"/> &nbsp; <input type="submit" />
158
+ @ </li>
156159
@ </ul>
157160
style_footer();
158161
return;
159162
}
160163
if( check_name(zPageName) ) return;
@@ -614,10 +617,36 @@
614617
@ <ul>
615618
db_prepare(&q,
616619
"SELECT substr(tagname, 6, 1000) FROM tag WHERE tagname GLOB 'wiki-*'"
617620
" ORDER BY lower(tagname)"
618621
);
622
+ while( db_step(&q)==SQLITE_ROW ){
623
+ const char *zName = db_column_text(&q, 0);
624
+ @ <li><a href="%s(g.zBaseURL)/wiki?name=%T(zName)">%h(zName)</a></li>
625
+ }
626
+ db_finalize(&q);
627
+ @ </ul>
628
+ style_footer();
629
+}
630
+
631
+/*
632
+** WEBPAGE: wfind
633
+**
634
+** URL: /wfind?title=TITLE
635
+** List all wiki pages whose titles contain the search text
636
+*/
637
+void wfind_page(void){
638
+ Stmt q;
639
+ const char * zTitle;
640
+ login_check_credentials();
641
+ if( !g.okRdWiki ){ login_needed(); return; }
642
+ zTitle = PD("title","*");
643
+ style_header("Wiki Pages Found");
644
+ @ <ul>
645
+ db_prepare(&q,
646
+ "SELECT substr(tagname, 6, 1000) FROM tag WHERE tagname like 'wiki-%%%q%%' ORDER BY lower(tagname)" ,
647
+ zTitle);
619648
while( db_step(&q)==SQLITE_ROW ){
620649
const char *zName = db_column_text(&q, 0);
621650
@ <li><a href="%s(g.zBaseURL)/wiki?name=%T(zName)">%h(zName)</a></li>
622651
}
623652
db_finalize(&q);
624653
--- src/wiki.c
+++ src/wiki.c
@@ -151,10 +151,13 @@
151 if( g.okNewWiki ){
152 @ <li> Create a <a href="%s(g.zBaseURL)/wikinew">new wiki page</a>.</li>
153 }
154 @ <li> <a href="%s(g.zBaseURL)/wcontent">List of All Wiki Pages</a>
155 @ available on this server.</li>
 
 
 
156 @ </ul>
157 style_footer();
158 return;
159 }
160 if( check_name(zPageName) ) return;
@@ -614,10 +617,36 @@
614 @ <ul>
615 db_prepare(&q,
616 "SELECT substr(tagname, 6, 1000) FROM tag WHERE tagname GLOB 'wiki-*'"
617 " ORDER BY lower(tagname)"
618 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
619 while( db_step(&q)==SQLITE_ROW ){
620 const char *zName = db_column_text(&q, 0);
621 @ <li><a href="%s(g.zBaseURL)/wiki?name=%T(zName)">%h(zName)</a></li>
622 }
623 db_finalize(&q);
624
--- src/wiki.c
+++ src/wiki.c
@@ -151,10 +151,13 @@
151 if( g.okNewWiki ){
152 @ <li> Create a <a href="%s(g.zBaseURL)/wikinew">new wiki page</a>.</li>
153 }
154 @ <li> <a href="%s(g.zBaseURL)/wcontent">List of All Wiki Pages</a>
155 @ available on this server.</li>
156 @ <li> <form method="GET" action="%s(g.zBaseURL)/wfind">
157 @ Search wiki titles: <input type="text" name="title"/> &nbsp; <input type="submit" />
158 @ </li>
159 @ </ul>
160 style_footer();
161 return;
162 }
163 if( check_name(zPageName) ) return;
@@ -614,10 +617,36 @@
617 @ <ul>
618 db_prepare(&q,
619 "SELECT substr(tagname, 6, 1000) FROM tag WHERE tagname GLOB 'wiki-*'"
620 " ORDER BY lower(tagname)"
621 );
622 while( db_step(&q)==SQLITE_ROW ){
623 const char *zName = db_column_text(&q, 0);
624 @ <li><a href="%s(g.zBaseURL)/wiki?name=%T(zName)">%h(zName)</a></li>
625 }
626 db_finalize(&q);
627 @ </ul>
628 style_footer();
629 }
630
631 /*
632 ** WEBPAGE: wfind
633 **
634 ** URL: /wfind?title=TITLE
635 ** List all wiki pages whose titles contain the search text
636 */
637 void wfind_page(void){
638 Stmt q;
639 const char * zTitle;
640 login_check_credentials();
641 if( !g.okRdWiki ){ login_needed(); return; }
642 zTitle = PD("title","*");
643 style_header("Wiki Pages Found");
644 @ <ul>
645 db_prepare(&q,
646 "SELECT substr(tagname, 6, 1000) FROM tag WHERE tagname like 'wiki-%%%q%%' ORDER BY lower(tagname)" ,
647 zTitle);
648 while( db_step(&q)==SQLITE_ROW ){
649 const char *zName = db_column_text(&q, 0);
650 @ <li><a href="%s(g.zBaseURL)/wiki?name=%T(zName)">%h(zName)</a></li>
651 }
652 db_finalize(&q);
653
+12 -3
--- src/winhttp.c
+++ src/winhttp.c
@@ -36,10 +36,11 @@
3636
typedef struct HttpRequest HttpRequest;
3737
struct HttpRequest {
3838
int id; /* ID counter */
3939
SOCKET s; /* Socket on which to receive data */
4040
SOCKADDR_IN addr; /* Address from which data is coming */
41
+ const char *zNotFound; /* --notfound option, or an empty string */
4142
};
4243
4344
/*
4445
** Prefix for a temporary file.
4546
*/
@@ -109,13 +110,13 @@
109110
}
110111
wanted -= got;
111112
}
112113
fclose(out);
113114
out = 0;
114
- sprintf(zCmd, "\"%s\" http \"%s\" %s %s %s",
115
+ sprintf(zCmd, "\"%s\" http \"%s\" %s %s %s%s",
115116
g.argv[0], g.zRepositoryName, zRequestFName, zReplyFName,
116
- inet_ntoa(p->addr.sin_addr)
117
+ inet_ntoa(p->addr.sin_addr), p->zNotFound
117118
);
118119
portable_system(zCmd);
119120
in = fopen(zReplyFName, "rb");
120121
if( in ){
121122
while( (got = fread(zHdr, 1, sizeof(zHdr), in))>0 ){
@@ -137,19 +138,26 @@
137138
** that socket.
138139
*/
139140
void win32_http_server(
140141
int mnPort, int mxPort, /* Range of allowed TCP port numbers */
141142
char *zBrowser, /* Command to launch browser. (Or NULL) */
142
- char *zStopper /* Stop server when this file is exists (Or NULL) */
143
+ char *zStopper, /* Stop server when this file is exists (Or NULL) */
144
+ char *zNotFound /* The --notfound option, or NULL */
143145
){
144146
WSADATA wd;
145147
SOCKET s = INVALID_SOCKET;
146148
SOCKADDR_IN addr;
147149
int idCnt = 0;
148150
int iPort = mnPort;
151
+ char *zNotFoundOption;
149152
150153
if( zStopper ) unlink(zStopper);
154
+ if( zNotFound ){
155
+ zNotFoundOption = mprintf(" --notfound %s", zNotFound);
156
+ }else{
157
+ zNotFoundOption = "";
158
+ }
151159
if( WSAStartup(MAKEWORD(1,1), &wd) ){
152160
fossil_fatal("unable to initialize winsock");
153161
}
154162
while( iPort<=mxPort ){
155163
s = socket(AF_INET, SOCK_STREAM, 0);
@@ -206,12 +214,13 @@
206214
fossil_fatal("out of memory");
207215
}
208216
p->id = ++idCnt;
209217
p->s = client;
210218
p->addr = client_addr;
219
+ p->zNotFound = zNotFoundOption;
211220
_beginthread(win32_process_one_http_request, 0, (void*)p);
212221
}
213222
closesocket(s);
214223
WSACleanup();
215224
}
216225
217226
#endif /* __MINGW32__ -- This code is for win32 only */
218227
--- src/winhttp.c
+++ src/winhttp.c
@@ -36,10 +36,11 @@
36 typedef struct HttpRequest HttpRequest;
37 struct HttpRequest {
38 int id; /* ID counter */
39 SOCKET s; /* Socket on which to receive data */
40 SOCKADDR_IN addr; /* Address from which data is coming */
 
41 };
42
43 /*
44 ** Prefix for a temporary file.
45 */
@@ -109,13 +110,13 @@
109 }
110 wanted -= got;
111 }
112 fclose(out);
113 out = 0;
114 sprintf(zCmd, "\"%s\" http \"%s\" %s %s %s",
115 g.argv[0], g.zRepositoryName, zRequestFName, zReplyFName,
116 inet_ntoa(p->addr.sin_addr)
117 );
118 portable_system(zCmd);
119 in = fopen(zReplyFName, "rb");
120 if( in ){
121 while( (got = fread(zHdr, 1, sizeof(zHdr), in))>0 ){
@@ -137,19 +138,26 @@
137 ** that socket.
138 */
139 void win32_http_server(
140 int mnPort, int mxPort, /* Range of allowed TCP port numbers */
141 char *zBrowser, /* Command to launch browser. (Or NULL) */
142 char *zStopper /* Stop server when this file is exists (Or NULL) */
 
143 ){
144 WSADATA wd;
145 SOCKET s = INVALID_SOCKET;
146 SOCKADDR_IN addr;
147 int idCnt = 0;
148 int iPort = mnPort;
 
149
150 if( zStopper ) unlink(zStopper);
 
 
 
 
 
151 if( WSAStartup(MAKEWORD(1,1), &wd) ){
152 fossil_fatal("unable to initialize winsock");
153 }
154 while( iPort<=mxPort ){
155 s = socket(AF_INET, SOCK_STREAM, 0);
@@ -206,12 +214,13 @@
206 fossil_fatal("out of memory");
207 }
208 p->id = ++idCnt;
209 p->s = client;
210 p->addr = client_addr;
 
211 _beginthread(win32_process_one_http_request, 0, (void*)p);
212 }
213 closesocket(s);
214 WSACleanup();
215 }
216
217 #endif /* __MINGW32__ -- This code is for win32 only */
218
--- src/winhttp.c
+++ src/winhttp.c
@@ -36,10 +36,11 @@
36 typedef struct HttpRequest HttpRequest;
37 struct HttpRequest {
38 int id; /* ID counter */
39 SOCKET s; /* Socket on which to receive data */
40 SOCKADDR_IN addr; /* Address from which data is coming */
41 const char *zNotFound; /* --notfound option, or an empty string */
42 };
43
44 /*
45 ** Prefix for a temporary file.
46 */
@@ -109,13 +110,13 @@
110 }
111 wanted -= got;
112 }
113 fclose(out);
114 out = 0;
115 sprintf(zCmd, "\"%s\" http \"%s\" %s %s %s%s",
116 g.argv[0], g.zRepositoryName, zRequestFName, zReplyFName,
117 inet_ntoa(p->addr.sin_addr), p->zNotFound
118 );
119 portable_system(zCmd);
120 in = fopen(zReplyFName, "rb");
121 if( in ){
122 while( (got = fread(zHdr, 1, sizeof(zHdr), in))>0 ){
@@ -137,19 +138,26 @@
138 ** that socket.
139 */
140 void win32_http_server(
141 int mnPort, int mxPort, /* Range of allowed TCP port numbers */
142 char *zBrowser, /* Command to launch browser. (Or NULL) */
143 char *zStopper, /* Stop server when this file is exists (Or NULL) */
144 char *zNotFound /* The --notfound option, or NULL */
145 ){
146 WSADATA wd;
147 SOCKET s = INVALID_SOCKET;
148 SOCKADDR_IN addr;
149 int idCnt = 0;
150 int iPort = mnPort;
151 char *zNotFoundOption;
152
153 if( zStopper ) unlink(zStopper);
154 if( zNotFound ){
155 zNotFoundOption = mprintf(" --notfound %s", zNotFound);
156 }else{
157 zNotFoundOption = "";
158 }
159 if( WSAStartup(MAKEWORD(1,1), &wd) ){
160 fossil_fatal("unable to initialize winsock");
161 }
162 while( iPort<=mxPort ){
163 s = socket(AF_INET, SOCK_STREAM, 0);
@@ -206,12 +214,13 @@
214 fossil_fatal("out of memory");
215 }
216 p->id = ++idCnt;
217 p->s = client;
218 p->addr = client_addr;
219 p->zNotFound = zNotFoundOption;
220 _beginthread(win32_process_one_http_request, 0, (void*)p);
221 }
222 closesocket(s);
223 WSACleanup();
224 }
225
226 #endif /* __MINGW32__ -- This code is for win32 only */
227
+1 -1
--- www/index.wiki
+++ www/index.wiki
@@ -56,11 +56,11 @@
5656
3. <b>Autosync</b> -
5757
Fossil supports [./concepts.wiki#workflow | "autosync" mode]
5858
which helps to keep projects moving
5959
forward by reducing the amount of needless
6060
[./branching.wiki | forking and merging] often
61
- associated distributed projects.
61
+ associated with distributed projects.
6262
6363
4. <b>Self-Contained</b> -
6464
Fossil is a single stand-alone executable that contains everything
6565
needed to do configuration management.
6666
Installation is trivial: simply download a
6767
--- www/index.wiki
+++ www/index.wiki
@@ -56,11 +56,11 @@
56 3. <b>Autosync</b> -
57 Fossil supports [./concepts.wiki#workflow | "autosync" mode]
58 which helps to keep projects moving
59 forward by reducing the amount of needless
60 [./branching.wiki | forking and merging] often
61 associated distributed projects.
62
63 4. <b>Self-Contained</b> -
64 Fossil is a single stand-alone executable that contains everything
65 needed to do configuration management.
66 Installation is trivial: simply download a
67
--- www/index.wiki
+++ www/index.wiki
@@ -56,11 +56,11 @@
56 3. <b>Autosync</b> -
57 Fossil supports [./concepts.wiki#workflow | "autosync" mode]
58 which helps to keep projects moving
59 forward by reducing the amount of needless
60 [./branching.wiki | forking and merging] often
61 associated with distributed projects.
62
63 4. <b>Self-Contained</b> -
64 Fossil is a single stand-alone executable that contains everything
65 needed to do configuration management.
66 Installation is trivial: simply download a
67

Keyboard Shortcuts

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