Fossil SCM

Bring in changes preparatory for merge to trunk.

andybradford 2025-03-25 01:53 whatis-hashonly merge
Commit e6e6aa9930c6e6ee3c1422726524a840d230ebaf14c0d01805851256bb9fb7c1
--- .fossil-settings/ignore-glob
+++ .fossil-settings/ignore-glob
@@ -5,5 +5,6 @@
55
fossil
66
fossil.exe
77
win/fossil.exe
88
*shell-see.*
99
*sqlite3-see.*
10
+bld
1011
--- .fossil-settings/ignore-glob
+++ .fossil-settings/ignore-glob
@@ -5,5 +5,6 @@
5 fossil
6 fossil.exe
7 win/fossil.exe
8 *shell-see.*
9 *sqlite3-see.*
 
10
--- .fossil-settings/ignore-glob
+++ .fossil-settings/ignore-glob
@@ -5,5 +5,6 @@
5 fossil
6 fossil.exe
7 win/fossil.exe
8 *shell-see.*
9 *sqlite3-see.*
10 bld
11
+4 -1
--- src/db.c
+++ src/db.c
@@ -1629,10 +1629,12 @@
16291629
sqlite3_create_function(db, "chat_msg_from_event", 4,
16301630
SQLITE_UTF8 | SQLITE_INNOCUOUS, 0,
16311631
chat_msg_from_event, 0, 0);
16321632
sqlite3_create_function(db, "inode", 1, SQLITE_UTF8, 0,
16331633
file_inode_sql_func,0,0);
1634
+ sqlite3_create_function(db, "artifact_to_json", 1, SQLITE_UTF8, 0,
1635
+ artifact_to_json_sql_func,0,0);
16341636
16351637
}
16361638
16371639
#if USE_SEE
16381640
/*
@@ -5067,11 +5069,12 @@
50675069
**
50685070
** All repositories are searched (in lexicographical order) and the first
50695071
** repository with a non-zero "repolist-skin" value is used as the skin
50705072
** for the repository list page. If none of the repositories on the list
50715073
** have a non-zero "repolist-skin" setting then the repository list is
5072
-** displayed using unadorned HTML ("skinless").
5074
+** displayed using unadorned HTML ("skinless"), with the page title taken
5075
+** from the FOSSIL_REPOLIST_TITLE environment variable.
50735076
**
50745077
** If repolist-skin has a value of 2, then the repository is omitted from
50755078
** the list in use cases 1 through 4, but not for 5 and 6.
50765079
*/
50775080
/*
50785081
--- src/db.c
+++ src/db.c
@@ -1629,10 +1629,12 @@
1629 sqlite3_create_function(db, "chat_msg_from_event", 4,
1630 SQLITE_UTF8 | SQLITE_INNOCUOUS, 0,
1631 chat_msg_from_event, 0, 0);
1632 sqlite3_create_function(db, "inode", 1, SQLITE_UTF8, 0,
1633 file_inode_sql_func,0,0);
 
 
1634
1635 }
1636
1637 #if USE_SEE
1638 /*
@@ -5067,11 +5069,12 @@
5067 **
5068 ** All repositories are searched (in lexicographical order) and the first
5069 ** repository with a non-zero "repolist-skin" value is used as the skin
5070 ** for the repository list page. If none of the repositories on the list
5071 ** have a non-zero "repolist-skin" setting then the repository list is
5072 ** displayed using unadorned HTML ("skinless").
 
5073 **
5074 ** If repolist-skin has a value of 2, then the repository is omitted from
5075 ** the list in use cases 1 through 4, but not for 5 and 6.
5076 */
5077 /*
5078
--- src/db.c
+++ src/db.c
@@ -1629,10 +1629,12 @@
1629 sqlite3_create_function(db, "chat_msg_from_event", 4,
1630 SQLITE_UTF8 | SQLITE_INNOCUOUS, 0,
1631 chat_msg_from_event, 0, 0);
1632 sqlite3_create_function(db, "inode", 1, SQLITE_UTF8, 0,
1633 file_inode_sql_func,0,0);
1634 sqlite3_create_function(db, "artifact_to_json", 1, SQLITE_UTF8, 0,
1635 artifact_to_json_sql_func,0,0);
1636
1637 }
1638
1639 #if USE_SEE
1640 /*
@@ -5067,11 +5069,12 @@
5069 **
5070 ** All repositories are searched (in lexicographical order) and the first
5071 ** repository with a non-zero "repolist-skin" value is used as the skin
5072 ** for the repository list page. If none of the repositories on the list
5073 ** have a non-zero "repolist-skin" setting then the repository list is
5074 ** displayed using unadorned HTML ("skinless"), with the page title taken
5075 ** from the FOSSIL_REPOLIST_TITLE environment variable.
5076 **
5077 ** If repolist-skin has a value of 2, then the repository is omitted from
5078 ** the list in use cases 1 through 4, but not for 5 and 6.
5079 */
5080 /*
5081
+28 -5
--- src/main.c
+++ src/main.c
@@ -2542,12 +2542,16 @@
25422542
** setenv: NAME
25432543
**
25442544
** Sets environment variable NAME to VALUE. If VALUE is omitted, then
25452545
** the environment variable is unset.
25462546
*/
2547
- blob_token(&line,&value2);
2548
- fossil_setenv(blob_str(&value), blob_str(&value2));
2547
+ char *zValue;
2548
+ blob_tail(&line,&value2);
2549
+ blob_trim(&value2);
2550
+ zValue = blob_str(&value2);
2551
+ while( fossil_isspace(zValue[0]) ){ zValue++; }
2552
+ fossil_setenv(blob_str(&value), zValue);
25492553
blob_reset(&value);
25502554
blob_reset(&value2);
25512555
continue;
25522556
}
25532557
if( blob_eq(&key, "errorlog:") && blob_token(&line, &value) ){
@@ -3200,10 +3204,13 @@
32003204
** --chroot DIR Use directory for chroot instead of repository path
32013205
** --ckout-alias NAME Treat URIs of the form /doc/NAME/... as if they were
32023206
** /doc/ckout/...
32033207
** --create Create a new REPOSITORY if it does not already exist
32043208
** --errorlog FILE Append HTTP error messages to FILE
3209
+** --extpage FILE Shortcut for "--extroot DIR --page ext/TAIL" where
3210
+** DIR is the directory holding FILE and TAIL is the
3211
+** filename at the end of FILE. Only works for "ui".
32053212
** --extroot DIR Document root for the /ext extension mechanism
32063213
** --files GLOBLIST Comma-separated list of glob patterns for static files
32073214
** --fossilcmd PATH The pathname of the "fossil" executable on the remote
32083215
** system when REPOSITORY is remote.
32093216
** --from PATH Use PATH as the diff baseline for the /ckout page
@@ -3278,10 +3285,11 @@
32783285
int findServerArg = 2; /* argv index for find_server_repository() */
32793286
char *zRemote = 0; /* Remote host on which to run "fossil ui" */
32803287
const char *zJsMode; /* The --jsmode parameter */
32813288
const char *zFossilCmd =0; /* Name of "fossil" binary on remote system */
32823289
const char *zFrom; /* Value for --from */
3290
+ const char *zExtPage = 0; /* Argument to --extpage */
32833291
32843292
32853293
#if USE_SEE
32863294
db_setup_for_saved_encryption_key();
32873295
#endif
@@ -3319,12 +3327,20 @@
33193327
zFrom = find_option("from", 0, 1);
33203328
if( zFrom && zFrom==file_tail(zFrom) ){
33213329
fossil_fatal("the argument to --from must be a pathname for"
33223330
" the \"ui\" command");
33233331
}
3324
- zInitPage = find_option("page", "p", 1);
3325
- if( zInitPage && zInitPage[0]=='/' ) zInitPage++;
3332
+ zExtPage = find_option("extpage",0,1);
3333
+ if( zExtPage ){
3334
+ char *zFullPath = file_canonical_name_dup(zExtPage);
3335
+ g.zExtRoot = file_dirname(zFullPath);
3336
+ zInitPage = mprintf("ext/%s",file_tail(zFullPath));
3337
+ fossil_free(zFullPath);
3338
+ }else{
3339
+ zInitPage = find_option("page", "p", 1);
3340
+ if( zInitPage && zInitPage[0]=='/' ) zInitPage++;
3341
+ }
33263342
zFossilCmd = find_option("fossilcmd", 0, 1);
33273343
if( zFrom && zInitPage==0 ){
33283344
zInitPage = mprintf("ckout?exbase=%H", zFrom);
33293345
}
33303346
}
@@ -3493,11 +3509,18 @@
34933509
}
34943510
blob_appendf(&ssh, " ui --nobrowser --localauth --port %d", iPort);
34953511
if( zNotFound ) blob_appendf(&ssh, " --notfound %!$", zNotFound);
34963512
if( zFileGlob ) blob_appendf(&ssh, " --files-urlenc %T", zFileGlob);
34973513
if( g.zCkoutAlias ) blob_appendf(&ssh," --ckout-alias %!$",g.zCkoutAlias);
3498
- if( g.zExtRoot ) blob_appendf(&ssh, " --extroot %$", g.zExtRoot);
3514
+ if( zExtPage ){
3515
+ if( !file_is_absolute_path(zExtPage) ){
3516
+ zExtPage = mprintf("%s/%s", g.argv[2], zExtPage);
3517
+ }
3518
+ blob_appendf(&ssh, " --extpage %$", zExtPage);
3519
+ }else if( g.zExtRoot ){
3520
+ blob_appendf(&ssh, " --extroot %$", g.zExtRoot);
3521
+ }
34993522
if( skin_in_use() ) blob_appendf(&ssh, " --skin %s", skin_in_use());
35003523
if( zJsMode ) blob_appendf(&ssh, " --jsmode %s", zJsMode);
35013524
if( fCreate ) blob_appendf(&ssh, " --create");
35023525
blob_appendf(&ssh, " %$", g.argv[2]);
35033526
if( isRetry ){
35043527
--- src/main.c
+++ src/main.c
@@ -2542,12 +2542,16 @@
2542 ** setenv: NAME
2543 **
2544 ** Sets environment variable NAME to VALUE. If VALUE is omitted, then
2545 ** the environment variable is unset.
2546 */
2547 blob_token(&line,&value2);
2548 fossil_setenv(blob_str(&value), blob_str(&value2));
 
 
 
 
2549 blob_reset(&value);
2550 blob_reset(&value2);
2551 continue;
2552 }
2553 if( blob_eq(&key, "errorlog:") && blob_token(&line, &value) ){
@@ -3200,10 +3204,13 @@
3200 ** --chroot DIR Use directory for chroot instead of repository path
3201 ** --ckout-alias NAME Treat URIs of the form /doc/NAME/... as if they were
3202 ** /doc/ckout/...
3203 ** --create Create a new REPOSITORY if it does not already exist
3204 ** --errorlog FILE Append HTTP error messages to FILE
 
 
 
3205 ** --extroot DIR Document root for the /ext extension mechanism
3206 ** --files GLOBLIST Comma-separated list of glob patterns for static files
3207 ** --fossilcmd PATH The pathname of the "fossil" executable on the remote
3208 ** system when REPOSITORY is remote.
3209 ** --from PATH Use PATH as the diff baseline for the /ckout page
@@ -3278,10 +3285,11 @@
3278 int findServerArg = 2; /* argv index for find_server_repository() */
3279 char *zRemote = 0; /* Remote host on which to run "fossil ui" */
3280 const char *zJsMode; /* The --jsmode parameter */
3281 const char *zFossilCmd =0; /* Name of "fossil" binary on remote system */
3282 const char *zFrom; /* Value for --from */
 
3283
3284
3285 #if USE_SEE
3286 db_setup_for_saved_encryption_key();
3287 #endif
@@ -3319,12 +3327,20 @@
3319 zFrom = find_option("from", 0, 1);
3320 if( zFrom && zFrom==file_tail(zFrom) ){
3321 fossil_fatal("the argument to --from must be a pathname for"
3322 " the \"ui\" command");
3323 }
3324 zInitPage = find_option("page", "p", 1);
3325 if( zInitPage && zInitPage[0]=='/' ) zInitPage++;
 
 
 
 
 
 
 
 
3326 zFossilCmd = find_option("fossilcmd", 0, 1);
3327 if( zFrom && zInitPage==0 ){
3328 zInitPage = mprintf("ckout?exbase=%H", zFrom);
3329 }
3330 }
@@ -3493,11 +3509,18 @@
3493 }
3494 blob_appendf(&ssh, " ui --nobrowser --localauth --port %d", iPort);
3495 if( zNotFound ) blob_appendf(&ssh, " --notfound %!$", zNotFound);
3496 if( zFileGlob ) blob_appendf(&ssh, " --files-urlenc %T", zFileGlob);
3497 if( g.zCkoutAlias ) blob_appendf(&ssh," --ckout-alias %!$",g.zCkoutAlias);
3498 if( g.zExtRoot ) blob_appendf(&ssh, " --extroot %$", g.zExtRoot);
 
 
 
 
 
 
 
3499 if( skin_in_use() ) blob_appendf(&ssh, " --skin %s", skin_in_use());
3500 if( zJsMode ) blob_appendf(&ssh, " --jsmode %s", zJsMode);
3501 if( fCreate ) blob_appendf(&ssh, " --create");
3502 blob_appendf(&ssh, " %$", g.argv[2]);
3503 if( isRetry ){
3504
--- src/main.c
+++ src/main.c
@@ -2542,12 +2542,16 @@
2542 ** setenv: NAME
2543 **
2544 ** Sets environment variable NAME to VALUE. If VALUE is omitted, then
2545 ** the environment variable is unset.
2546 */
2547 char *zValue;
2548 blob_tail(&line,&value2);
2549 blob_trim(&value2);
2550 zValue = blob_str(&value2);
2551 while( fossil_isspace(zValue[0]) ){ zValue++; }
2552 fossil_setenv(blob_str(&value), zValue);
2553 blob_reset(&value);
2554 blob_reset(&value2);
2555 continue;
2556 }
2557 if( blob_eq(&key, "errorlog:") && blob_token(&line, &value) ){
@@ -3200,10 +3204,13 @@
3204 ** --chroot DIR Use directory for chroot instead of repository path
3205 ** --ckout-alias NAME Treat URIs of the form /doc/NAME/... as if they were
3206 ** /doc/ckout/...
3207 ** --create Create a new REPOSITORY if it does not already exist
3208 ** --errorlog FILE Append HTTP error messages to FILE
3209 ** --extpage FILE Shortcut for "--extroot DIR --page ext/TAIL" where
3210 ** DIR is the directory holding FILE and TAIL is the
3211 ** filename at the end of FILE. Only works for "ui".
3212 ** --extroot DIR Document root for the /ext extension mechanism
3213 ** --files GLOBLIST Comma-separated list of glob patterns for static files
3214 ** --fossilcmd PATH The pathname of the "fossil" executable on the remote
3215 ** system when REPOSITORY is remote.
3216 ** --from PATH Use PATH as the diff baseline for the /ckout page
@@ -3278,10 +3285,11 @@
3285 int findServerArg = 2; /* argv index for find_server_repository() */
3286 char *zRemote = 0; /* Remote host on which to run "fossil ui" */
3287 const char *zJsMode; /* The --jsmode parameter */
3288 const char *zFossilCmd =0; /* Name of "fossil" binary on remote system */
3289 const char *zFrom; /* Value for --from */
3290 const char *zExtPage = 0; /* Argument to --extpage */
3291
3292
3293 #if USE_SEE
3294 db_setup_for_saved_encryption_key();
3295 #endif
@@ -3319,12 +3327,20 @@
3327 zFrom = find_option("from", 0, 1);
3328 if( zFrom && zFrom==file_tail(zFrom) ){
3329 fossil_fatal("the argument to --from must be a pathname for"
3330 " the \"ui\" command");
3331 }
3332 zExtPage = find_option("extpage",0,1);
3333 if( zExtPage ){
3334 char *zFullPath = file_canonical_name_dup(zExtPage);
3335 g.zExtRoot = file_dirname(zFullPath);
3336 zInitPage = mprintf("ext/%s",file_tail(zFullPath));
3337 fossil_free(zFullPath);
3338 }else{
3339 zInitPage = find_option("page", "p", 1);
3340 if( zInitPage && zInitPage[0]=='/' ) zInitPage++;
3341 }
3342 zFossilCmd = find_option("fossilcmd", 0, 1);
3343 if( zFrom && zInitPage==0 ){
3344 zInitPage = mprintf("ckout?exbase=%H", zFrom);
3345 }
3346 }
@@ -3493,11 +3509,18 @@
3509 }
3510 blob_appendf(&ssh, " ui --nobrowser --localauth --port %d", iPort);
3511 if( zNotFound ) blob_appendf(&ssh, " --notfound %!$", zNotFound);
3512 if( zFileGlob ) blob_appendf(&ssh, " --files-urlenc %T", zFileGlob);
3513 if( g.zCkoutAlias ) blob_appendf(&ssh," --ckout-alias %!$",g.zCkoutAlias);
3514 if( zExtPage ){
3515 if( !file_is_absolute_path(zExtPage) ){
3516 zExtPage = mprintf("%s/%s", g.argv[2], zExtPage);
3517 }
3518 blob_appendf(&ssh, " --extpage %$", zExtPage);
3519 }else if( g.zExtRoot ){
3520 blob_appendf(&ssh, " --extroot %$", g.zExtRoot);
3521 }
3522 if( skin_in_use() ) blob_appendf(&ssh, " --skin %s", skin_in_use());
3523 if( zJsMode ) blob_appendf(&ssh, " --jsmode %s", zJsMode);
3524 if( fCreate ) blob_appendf(&ssh, " --create");
3525 blob_appendf(&ssh, " %$", g.argv[2]);
3526 if( isRetry ){
3527
--- src/manifest.c
+++ src/manifest.c
@@ -2911,5 +2911,293 @@
29112911
if( g.argc!=3 ) usage("RECORDID");
29122912
rid = name_to_rid(g.argv[2]);
29132913
content_get(rid, &content);
29142914
manifest_crosslink(rid, &content, MC_NONE);
29152915
}
2916
+
2917
+/*
2918
+** For a given CATYPE_... value, returns a human-friendly name, or
2919
+** NULL if typeId is unknown or is CFTYPE_ANY. The names returned by
2920
+** this function are geared towards use with artifact_to_json(), and
2921
+** may differ from some historical uses. e.g. CFTYPE_CONTROL artifacts
2922
+** are called "tag" artifacts by this function.
2923
+*/
2924
+const char * artifact_type_to_name(int typeId){
2925
+ switch(typeId){
2926
+ case CFTYPE_MANIFEST: return "checkin";
2927
+ case CFTYPE_CLUSTER: return "cluster";
2928
+ case CFTYPE_CONTROL: return "tag";
2929
+ case CFTYPE_WIKI: return "wiki";
2930
+ case CFTYPE_TICKET: return "ticket";
2931
+ case CFTYPE_ATTACHMENT: return "attachment";
2932
+ case CFTYPE_EVENT: return "event";
2933
+ case CFTYPE_FORUM: return "forumpost";
2934
+ }
2935
+ return NULL;
2936
+}
2937
+
2938
+/*
2939
+** Creates a JSON representation of p, appending it to b.
2940
+**
2941
+** b is not cleared before rendering, so the caller needs to do that
2942
+** if it's important for their use case.
2943
+**
2944
+** Pedantic note: this routine traverses p->aFile directly, rather
2945
+** than using manifest_file_next(), so that delta manifests are
2946
+** rendered as-is instead of containing their derived F-cards. If that
2947
+** policy is ever changed, p will need to be non-const.
2948
+*/
2949
+void artifact_to_json(Manifest const *p, Blob *b){
2950
+ int i;
2951
+
2952
+ blob_append_literal(b, "{");
2953
+ blob_appendf(b, "\"uuid\": \"%z\"", rid_to_uuid(p->rid));
2954
+ /*blob_appendf(b, ", \"rid\": %d", p->rid); not portable across repos*/
2955
+ blob_appendf(b, ", \"type\": %!j", artifact_type_to_name(p->type));
2956
+#define ISA(TYPE) if( p->type==TYPE )
2957
+#define CARD_LETTER(LETTER) \
2958
+ blob_append_literal(b, ",\"" #LETTER "\": ")
2959
+#define CARD_STR(LETTER, VAL) \
2960
+ assert( VAL ); CARD_LETTER(LETTER); blob_appendf(b, "%!j", VAL)
2961
+#define CARD_STR2(LETTER, VAL) \
2962
+ if( VAL ) { CARD_STR(LETTER, VAL); } (void)0
2963
+#define STR_OR_NULL(VAL) \
2964
+ if( VAL ) blob_appendf(b, "%!j", VAL); \
2965
+ else blob_append(b, "null", 4)
2966
+#define KVP_STR(ADDCOMMA, KEY,VAL) \
2967
+ if(ADDCOMMA) blob_append_char(b, ','); \
2968
+ blob_appendf(b, "%!j: ", #KEY); \
2969
+ STR_OR_NULL(VAL)
2970
+
2971
+ ISA( CFTYPE_ATTACHMENT ){
2972
+ CARD_LETTER(A);
2973
+ blob_append_char(b, '{');
2974
+ KVP_STR(0, filename, p->zAttachName);
2975
+ KVP_STR(1, target, p->zAttachTarget);
2976
+ KVP_STR(1, source, p->zAttachSrc);
2977
+ blob_append_char(b, '}');
2978
+ }
2979
+ CARD_STR2(B, p->zBaseline);
2980
+ CARD_STR2(C, p->zComment);
2981
+ CARD_LETTER(D); blob_appendf(b, "%f", p->rDate);
2982
+ ISA( CFTYPE_EVENT ){
2983
+ blob_appendf(b, ", \"E\": {\"time\": %f, \"id\": %!j}",
2984
+ p->rEventDate, p->zEventId);
2985
+ }
2986
+ ISA( CFTYPE_MANIFEST ){
2987
+ CARD_LETTER(F);
2988
+ blob_append_char(b, '[');
2989
+ for( i = 0; i < p->nFile; ++i ){
2990
+ ManifestFile const * const pF = &p->aFile[i];
2991
+ if( i>0 ) blob_append_char(b, ',');
2992
+ blob_append_char(b, '{');
2993
+ KVP_STR(0, name, pF->zName);
2994
+ KVP_STR(1, uuid, pF->zUuid);
2995
+ KVP_STR(1, perm, pF->zPerm);
2996
+ KVP_STR(1, oldName, pF->zPrior);
2997
+ blob_append_char(b, '}');
2998
+ }
2999
+ /* Special case: model checkins with no F-card as having an empty
3000
+ ** array, rather than no F-cards, to hypothetically simplify
3001
+ ** handling in JSON queries. */
3002
+ blob_append_char(b, ']');
3003
+ }
3004
+ CARD_STR2(G, p->zThreadRoot);
3005
+ CARD_STR2(H, p->zThreadTitle);
3006
+ CARD_STR2(I, p->zInReplyTo);
3007
+ if( p->nField ){
3008
+ CARD_LETTER(J);
3009
+ blob_append_char(b, '[');
3010
+ for( i = 0; i < p->nField; ++i ){
3011
+ if( i>0 ) blob_append_char(b, ',');
3012
+ blob_append_char(b, '{');
3013
+ KVP_STR(0, name, p->aField[i].zName);
3014
+ KVP_STR(1, value, p->aField[i].zValue);
3015
+ blob_append_char(b, '}');
3016
+ }
3017
+ blob_append_char(b, ']');
3018
+ }
3019
+ CARD_STR2(K, p->zTicketUuid);
3020
+ CARD_STR2(L, p->zWikiTitle);
3021
+ ISA( CFTYPE_CLUSTER ){
3022
+ CARD_LETTER(M);
3023
+ blob_append_char(b, '[');
3024
+ for( int i = 0; i < p->nCChild; ++i ){
3025
+ if( i>0 ) blob_append_char(b, ',');
3026
+ blob_appendf(b, "%!j", p->azCChild[i]);
3027
+ }
3028
+ blob_append_char(b, ']');
3029
+ }
3030
+ CARD_STR2(N, p->zMimetype);
3031
+ ISA( CFTYPE_MANIFEST ){
3032
+ CARD_LETTER(P);
3033
+ blob_append_char(b, '[');
3034
+ if( p->nParent ){
3035
+ for( i = 0; i < p->nParent; ++i ){
3036
+ if( i>0 ) blob_append_char(b, ',');
3037
+ blob_appendf(b, "%!j", p->azParent[i]);
3038
+ }
3039
+ }
3040
+ /* Special case: model checkins with no P-card as having an empty
3041
+ ** array, as per F-cards. */
3042
+ blob_append_char(b, ']');
3043
+ }
3044
+ if( p->nCherrypick ){
3045
+ CARD_LETTER(Q);
3046
+ blob_append_char(b, '[');
3047
+ for( i = 0; i < p->nCherrypick; ++i ){
3048
+ if( i>0 ) blob_append_char(b, ',');
3049
+ blob_append_char(b, '{');
3050
+ blob_appendf(b, "\"type\": \"%c\"", p->aCherrypick[i].zCPTarget[0]);
3051
+ KVP_STR(1, target, &p->aCherrypick[i].zCPTarget[1]);
3052
+ KVP_STR(1, base, p->aCherrypick[i].zCPBase);
3053
+ blob_append_char(b, '}');
3054
+ }
3055
+ blob_append_char(b, ']');
3056
+ }
3057
+ CARD_STR2(R, p->zRepoCksum);
3058
+ if( p->nTag ){
3059
+ CARD_LETTER(T);
3060
+ blob_append_char(b, '[');
3061
+ for( int i = 0; i < p->nTag; ++i ){
3062
+ const char *zName = p->aTag[i].zName;
3063
+ if( i>0 ) blob_append_char(b, ',');
3064
+ blob_append_char(b, '{');
3065
+ blob_appendf(b, "\"type\": \"%c\"", *zName);
3066
+ KVP_STR(1, name, &zName[1]);
3067
+ KVP_STR(1, target, p->aTag[i].zUuid ? p->aTag[i].zUuid : "*")
3068
+ /* We could arguably resolve the "*" as null or p's uuid. */;
3069
+ KVP_STR(1, value, p->aTag[i].zValue);
3070
+ blob_append_char(b, '}');
3071
+ }
3072
+ blob_append_char(b, ']');
3073
+ }
3074
+ CARD_STR2(U, p->zUser);
3075
+ CARD_STR2(W, p->zWiki);
3076
+ blob_append_literal(b, "}");
3077
+#undef CARD_FMT
3078
+#undef CARD_LETTER
3079
+#undef CARD_STR
3080
+#undef CARD_STR2
3081
+#undef ISA
3082
+#undef KVP_STR
3083
+#undef STR_OR_NULL
3084
+}
3085
+
3086
+/*
3087
+** Convenience wrapper around artifact_to_json() which expects rid to
3088
+** be the blob.rid of any artifact type. If it can load a Manifest
3089
+** with that rid, it returns rid, else it returns 0.
3090
+*/
3091
+int artifact_to_json_by_rid(int rid, Blob *pOut){
3092
+ Manifest * const p = manifest_get(rid, CFTYPE_ANY, 0);
3093
+ if( p ){
3094
+ artifact_to_json(p, pOut);
3095
+ manifest_destroy(p);
3096
+ }else{
3097
+ rid = 0;
3098
+ }
3099
+ return rid;
3100
+}
3101
+
3102
+/*
3103
+** Convenience wrapper around artifact_to_json() which accepts any
3104
+** artifact name which is legal for symbolic_name_to_rid(). On success
3105
+** it returns the rid of the artifact. Returns 0 if no such artifact
3106
+** exists and a negative value if the name is ambiguous.
3107
+**
3108
+** pOut is not cleared before rendering, so the caller needs to do
3109
+** that if it's important for their use case.
3110
+*/
3111
+int artifact_to_json_by_name(const char *zName, Blob *pOut){
3112
+ const int rid = symbolic_name_to_rid(zName, 0);
3113
+ return rid>0
3114
+ ? artifact_to_json_by_rid(rid, pOut)
3115
+ : rid;
3116
+}
3117
+
3118
+/*
3119
+** SQLite UDF for artifact_to_json(). Its single argument should be
3120
+** either an INTEGER (blob.rid value) or a TEXT symbolic artifact
3121
+** name, as per symbolic_name_to_rid(). If an artifact is found then
3122
+** the result of the UDF is that JSON as a string, else it evaluates
3123
+** to NULL.
3124
+*/
3125
+void artifact_to_json_sql_func(
3126
+ sqlite3_context *context,
3127
+ int argc,
3128
+ sqlite3_value **argv
3129
+){
3130
+ int rid = 0;
3131
+ Blob b = empty_blob;
3132
+
3133
+ if(1 != argc){
3134
+ goto error_usage;
3135
+ }
3136
+ switch( sqlite3_value_type(argv[0]) ){
3137
+ case SQLITE_INTEGER:
3138
+ rid = artifact_to_json_by_rid(sqlite3_value_int(argv[0]), &b);
3139
+ break;
3140
+ case SQLITE_TEXT:{
3141
+ const char * z = (const char *)sqlite3_value_text(argv[0]);
3142
+ if( z ){
3143
+ rid = artifact_to_json_by_name(z, &b);
3144
+ }
3145
+ break;
3146
+ }
3147
+ default:
3148
+ goto error_usage;
3149
+ }
3150
+ if( rid>0 ){
3151
+ sqlite3_result_text(context, blob_str(&b), blob_size(&b),
3152
+ SQLITE_TRANSIENT);
3153
+ blob_reset(&b);
3154
+ }else{
3155
+ /* We should arguably error out if rid<0 (ambiguous name) */
3156
+ sqlite3_result_null(context);
3157
+ }
3158
+ return;
3159
+error_usage:
3160
+ sqlite3_result_error(context, "Expecting one argument: blob.rid or "
3161
+ "artifact symbolic name", -1);
3162
+}
3163
+
3164
+
3165
+
3166
+/*
3167
+** COMMAND: test-artifact-to-json
3168
+**
3169
+** Usage: %fossil test-artifact-to-json ?-pretty? symbolic-name [...names]
3170
+**
3171
+** Tests the artifact_to_json() and artifact_to_json_by_name() APIs.
3172
+*/
3173
+void test_manifest_to_json(void){
3174
+ int i;
3175
+ Blob b = empty_blob;
3176
+ Stmt q;
3177
+ const int bPretty = find_option("pretty",0,0)!=0;
3178
+ int nErr = 0;
3179
+
3180
+ db_find_and_open_repository(0,0);
3181
+ db_prepare(&q, "select json_pretty(:json)");
3182
+ for( i=2; i<g.argc; ++i ){
3183
+ char const *zName = g.argv[i];
3184
+ const int rc = artifact_to_json_by_name(zName, &b);
3185
+ if( rc<=0 ){
3186
+ ++nErr;
3187
+ fossil_warning("Error reading artifact %Q", zName);
3188
+ continue;
3189
+ }else if( bPretty ){
3190
+ db_bind_blob(&q, ":json", &b);
3191
+ b.nUsed = 0;
3192
+ db_step(&q);
3193
+ db_column_blob(&q, 0, &b);
3194
+ db_reset(&q);
3195
+ }
3196
+ fossil_print("%b\n", &b);
3197
+ blob_reset(&b);
3198
+ }
3199
+ db_finalize(&q);
3200
+ if( nErr ){
3201
+ fossil_warning("Error count: %d", nErr);
3202
+ }
3203
+}
29163204
--- src/manifest.c
+++ src/manifest.c
@@ -2911,5 +2911,293 @@
2911 if( g.argc!=3 ) usage("RECORDID");
2912 rid = name_to_rid(g.argv[2]);
2913 content_get(rid, &content);
2914 manifest_crosslink(rid, &content, MC_NONE);
2915 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2916
--- src/manifest.c
+++ src/manifest.c
@@ -2911,5 +2911,293 @@
2911 if( g.argc!=3 ) usage("RECORDID");
2912 rid = name_to_rid(g.argv[2]);
2913 content_get(rid, &content);
2914 manifest_crosslink(rid, &content, MC_NONE);
2915 }
2916
2917 /*
2918 ** For a given CATYPE_... value, returns a human-friendly name, or
2919 ** NULL if typeId is unknown or is CFTYPE_ANY. The names returned by
2920 ** this function are geared towards use with artifact_to_json(), and
2921 ** may differ from some historical uses. e.g. CFTYPE_CONTROL artifacts
2922 ** are called "tag" artifacts by this function.
2923 */
2924 const char * artifact_type_to_name(int typeId){
2925 switch(typeId){
2926 case CFTYPE_MANIFEST: return "checkin";
2927 case CFTYPE_CLUSTER: return "cluster";
2928 case CFTYPE_CONTROL: return "tag";
2929 case CFTYPE_WIKI: return "wiki";
2930 case CFTYPE_TICKET: return "ticket";
2931 case CFTYPE_ATTACHMENT: return "attachment";
2932 case CFTYPE_EVENT: return "event";
2933 case CFTYPE_FORUM: return "forumpost";
2934 }
2935 return NULL;
2936 }
2937
2938 /*
2939 ** Creates a JSON representation of p, appending it to b.
2940 **
2941 ** b is not cleared before rendering, so the caller needs to do that
2942 ** if it's important for their use case.
2943 **
2944 ** Pedantic note: this routine traverses p->aFile directly, rather
2945 ** than using manifest_file_next(), so that delta manifests are
2946 ** rendered as-is instead of containing their derived F-cards. If that
2947 ** policy is ever changed, p will need to be non-const.
2948 */
2949 void artifact_to_json(Manifest const *p, Blob *b){
2950 int i;
2951
2952 blob_append_literal(b, "{");
2953 blob_appendf(b, "\"uuid\": \"%z\"", rid_to_uuid(p->rid));
2954 /*blob_appendf(b, ", \"rid\": %d", p->rid); not portable across repos*/
2955 blob_appendf(b, ", \"type\": %!j", artifact_type_to_name(p->type));
2956 #define ISA(TYPE) if( p->type==TYPE )
2957 #define CARD_LETTER(LETTER) \
2958 blob_append_literal(b, ",\"" #LETTER "\": ")
2959 #define CARD_STR(LETTER, VAL) \
2960 assert( VAL ); CARD_LETTER(LETTER); blob_appendf(b, "%!j", VAL)
2961 #define CARD_STR2(LETTER, VAL) \
2962 if( VAL ) { CARD_STR(LETTER, VAL); } (void)0
2963 #define STR_OR_NULL(VAL) \
2964 if( VAL ) blob_appendf(b, "%!j", VAL); \
2965 else blob_append(b, "null", 4)
2966 #define KVP_STR(ADDCOMMA, KEY,VAL) \
2967 if(ADDCOMMA) blob_append_char(b, ','); \
2968 blob_appendf(b, "%!j: ", #KEY); \
2969 STR_OR_NULL(VAL)
2970
2971 ISA( CFTYPE_ATTACHMENT ){
2972 CARD_LETTER(A);
2973 blob_append_char(b, '{');
2974 KVP_STR(0, filename, p->zAttachName);
2975 KVP_STR(1, target, p->zAttachTarget);
2976 KVP_STR(1, source, p->zAttachSrc);
2977 blob_append_char(b, '}');
2978 }
2979 CARD_STR2(B, p->zBaseline);
2980 CARD_STR2(C, p->zComment);
2981 CARD_LETTER(D); blob_appendf(b, "%f", p->rDate);
2982 ISA( CFTYPE_EVENT ){
2983 blob_appendf(b, ", \"E\": {\"time\": %f, \"id\": %!j}",
2984 p->rEventDate, p->zEventId);
2985 }
2986 ISA( CFTYPE_MANIFEST ){
2987 CARD_LETTER(F);
2988 blob_append_char(b, '[');
2989 for( i = 0; i < p->nFile; ++i ){
2990 ManifestFile const * const pF = &p->aFile[i];
2991 if( i>0 ) blob_append_char(b, ',');
2992 blob_append_char(b, '{');
2993 KVP_STR(0, name, pF->zName);
2994 KVP_STR(1, uuid, pF->zUuid);
2995 KVP_STR(1, perm, pF->zPerm);
2996 KVP_STR(1, oldName, pF->zPrior);
2997 blob_append_char(b, '}');
2998 }
2999 /* Special case: model checkins with no F-card as having an empty
3000 ** array, rather than no F-cards, to hypothetically simplify
3001 ** handling in JSON queries. */
3002 blob_append_char(b, ']');
3003 }
3004 CARD_STR2(G, p->zThreadRoot);
3005 CARD_STR2(H, p->zThreadTitle);
3006 CARD_STR2(I, p->zInReplyTo);
3007 if( p->nField ){
3008 CARD_LETTER(J);
3009 blob_append_char(b, '[');
3010 for( i = 0; i < p->nField; ++i ){
3011 if( i>0 ) blob_append_char(b, ',');
3012 blob_append_char(b, '{');
3013 KVP_STR(0, name, p->aField[i].zName);
3014 KVP_STR(1, value, p->aField[i].zValue);
3015 blob_append_char(b, '}');
3016 }
3017 blob_append_char(b, ']');
3018 }
3019 CARD_STR2(K, p->zTicketUuid);
3020 CARD_STR2(L, p->zWikiTitle);
3021 ISA( CFTYPE_CLUSTER ){
3022 CARD_LETTER(M);
3023 blob_append_char(b, '[');
3024 for( int i = 0; i < p->nCChild; ++i ){
3025 if( i>0 ) blob_append_char(b, ',');
3026 blob_appendf(b, "%!j", p->azCChild[i]);
3027 }
3028 blob_append_char(b, ']');
3029 }
3030 CARD_STR2(N, p->zMimetype);
3031 ISA( CFTYPE_MANIFEST ){
3032 CARD_LETTER(P);
3033 blob_append_char(b, '[');
3034 if( p->nParent ){
3035 for( i = 0; i < p->nParent; ++i ){
3036 if( i>0 ) blob_append_char(b, ',');
3037 blob_appendf(b, "%!j", p->azParent[i]);
3038 }
3039 }
3040 /* Special case: model checkins with no P-card as having an empty
3041 ** array, as per F-cards. */
3042 blob_append_char(b, ']');
3043 }
3044 if( p->nCherrypick ){
3045 CARD_LETTER(Q);
3046 blob_append_char(b, '[');
3047 for( i = 0; i < p->nCherrypick; ++i ){
3048 if( i>0 ) blob_append_char(b, ',');
3049 blob_append_char(b, '{');
3050 blob_appendf(b, "\"type\": \"%c\"", p->aCherrypick[i].zCPTarget[0]);
3051 KVP_STR(1, target, &p->aCherrypick[i].zCPTarget[1]);
3052 KVP_STR(1, base, p->aCherrypick[i].zCPBase);
3053 blob_append_char(b, '}');
3054 }
3055 blob_append_char(b, ']');
3056 }
3057 CARD_STR2(R, p->zRepoCksum);
3058 if( p->nTag ){
3059 CARD_LETTER(T);
3060 blob_append_char(b, '[');
3061 for( int i = 0; i < p->nTag; ++i ){
3062 const char *zName = p->aTag[i].zName;
3063 if( i>0 ) blob_append_char(b, ',');
3064 blob_append_char(b, '{');
3065 blob_appendf(b, "\"type\": \"%c\"", *zName);
3066 KVP_STR(1, name, &zName[1]);
3067 KVP_STR(1, target, p->aTag[i].zUuid ? p->aTag[i].zUuid : "*")
3068 /* We could arguably resolve the "*" as null or p's uuid. */;
3069 KVP_STR(1, value, p->aTag[i].zValue);
3070 blob_append_char(b, '}');
3071 }
3072 blob_append_char(b, ']');
3073 }
3074 CARD_STR2(U, p->zUser);
3075 CARD_STR2(W, p->zWiki);
3076 blob_append_literal(b, "}");
3077 #undef CARD_FMT
3078 #undef CARD_LETTER
3079 #undef CARD_STR
3080 #undef CARD_STR2
3081 #undef ISA
3082 #undef KVP_STR
3083 #undef STR_OR_NULL
3084 }
3085
3086 /*
3087 ** Convenience wrapper around artifact_to_json() which expects rid to
3088 ** be the blob.rid of any artifact type. If it can load a Manifest
3089 ** with that rid, it returns rid, else it returns 0.
3090 */
3091 int artifact_to_json_by_rid(int rid, Blob *pOut){
3092 Manifest * const p = manifest_get(rid, CFTYPE_ANY, 0);
3093 if( p ){
3094 artifact_to_json(p, pOut);
3095 manifest_destroy(p);
3096 }else{
3097 rid = 0;
3098 }
3099 return rid;
3100 }
3101
3102 /*
3103 ** Convenience wrapper around artifact_to_json() which accepts any
3104 ** artifact name which is legal for symbolic_name_to_rid(). On success
3105 ** it returns the rid of the artifact. Returns 0 if no such artifact
3106 ** exists and a negative value if the name is ambiguous.
3107 **
3108 ** pOut is not cleared before rendering, so the caller needs to do
3109 ** that if it's important for their use case.
3110 */
3111 int artifact_to_json_by_name(const char *zName, Blob *pOut){
3112 const int rid = symbolic_name_to_rid(zName, 0);
3113 return rid>0
3114 ? artifact_to_json_by_rid(rid, pOut)
3115 : rid;
3116 }
3117
3118 /*
3119 ** SQLite UDF for artifact_to_json(). Its single argument should be
3120 ** either an INTEGER (blob.rid value) or a TEXT symbolic artifact
3121 ** name, as per symbolic_name_to_rid(). If an artifact is found then
3122 ** the result of the UDF is that JSON as a string, else it evaluates
3123 ** to NULL.
3124 */
3125 void artifact_to_json_sql_func(
3126 sqlite3_context *context,
3127 int argc,
3128 sqlite3_value **argv
3129 ){
3130 int rid = 0;
3131 Blob b = empty_blob;
3132
3133 if(1 != argc){
3134 goto error_usage;
3135 }
3136 switch( sqlite3_value_type(argv[0]) ){
3137 case SQLITE_INTEGER:
3138 rid = artifact_to_json_by_rid(sqlite3_value_int(argv[0]), &b);
3139 break;
3140 case SQLITE_TEXT:{
3141 const char * z = (const char *)sqlite3_value_text(argv[0]);
3142 if( z ){
3143 rid = artifact_to_json_by_name(z, &b);
3144 }
3145 break;
3146 }
3147 default:
3148 goto error_usage;
3149 }
3150 if( rid>0 ){
3151 sqlite3_result_text(context, blob_str(&b), blob_size(&b),
3152 SQLITE_TRANSIENT);
3153 blob_reset(&b);
3154 }else{
3155 /* We should arguably error out if rid<0 (ambiguous name) */
3156 sqlite3_result_null(context);
3157 }
3158 return;
3159 error_usage:
3160 sqlite3_result_error(context, "Expecting one argument: blob.rid or "
3161 "artifact symbolic name", -1);
3162 }
3163
3164
3165
3166 /*
3167 ** COMMAND: test-artifact-to-json
3168 **
3169 ** Usage: %fossil test-artifact-to-json ?-pretty? symbolic-name [...names]
3170 **
3171 ** Tests the artifact_to_json() and artifact_to_json_by_name() APIs.
3172 */
3173 void test_manifest_to_json(void){
3174 int i;
3175 Blob b = empty_blob;
3176 Stmt q;
3177 const int bPretty = find_option("pretty",0,0)!=0;
3178 int nErr = 0;
3179
3180 db_find_and_open_repository(0,0);
3181 db_prepare(&q, "select json_pretty(:json)");
3182 for( i=2; i<g.argc; ++i ){
3183 char const *zName = g.argv[i];
3184 const int rc = artifact_to_json_by_name(zName, &b);
3185 if( rc<=0 ){
3186 ++nErr;
3187 fossil_warning("Error reading artifact %Q", zName);
3188 continue;
3189 }else if( bPretty ){
3190 db_bind_blob(&q, ":json", &b);
3191 b.nUsed = 0;
3192 db_step(&q);
3193 db_column_blob(&q, 0, &b);
3194 db_reset(&q);
3195 }
3196 fossil_print("%b\n", &b);
3197 blob_reset(&b);
3198 }
3199 db_finalize(&q);
3200 if( nErr ){
3201 fossil_warning("Error count: %d", nErr);
3202 }
3203 }
3204
+3 -2
--- src/repolist.c
+++ src/repolist.c
@@ -316,20 +316,21 @@
316316
style_header("Repository List");
317317
@ %s(blob_str(&html))
318318
style_table_sorter();
319319
style_finish_page();
320320
}else{
321
+ const char *zTitle = PD("FOSSIL_REPOLIST_TITLE","Repository List");
321322
/* If no repositories were found that had the "repolist_skin"
322323
** property set, then use a default skin */
323324
@ <html>
324325
@ <head>
325326
@ <base href="%s(g.zBaseURL)/">
326327
@ <meta name="viewport" content="width=device-width, initial-scale=1.0">
327
- @ <title>Repository List</title>
328
+ @ <title>%h(zTitle)</title>
328329
@ </head>
329330
@ <body>
330
- @ <h1 align="center">Fossil Repositories</h1>
331
+ @ <h1 align="center">%h(zTitle)</h1>
331332
@ %s(blob_str(&html))
332333
@ <script>%s(builtin_text("sorttable.js"))</script>
333334
@ </body>
334335
@ </html>
335336
}
336337
--- src/repolist.c
+++ src/repolist.c
@@ -316,20 +316,21 @@
316 style_header("Repository List");
317 @ %s(blob_str(&html))
318 style_table_sorter();
319 style_finish_page();
320 }else{
 
321 /* If no repositories were found that had the "repolist_skin"
322 ** property set, then use a default skin */
323 @ <html>
324 @ <head>
325 @ <base href="%s(g.zBaseURL)/">
326 @ <meta name="viewport" content="width=device-width, initial-scale=1.0">
327 @ <title>Repository List</title>
328 @ </head>
329 @ <body>
330 @ <h1 align="center">Fossil Repositories</h1>
331 @ %s(blob_str(&html))
332 @ <script>%s(builtin_text("sorttable.js"))</script>
333 @ </body>
334 @ </html>
335 }
336
--- src/repolist.c
+++ src/repolist.c
@@ -316,20 +316,21 @@
316 style_header("Repository List");
317 @ %s(blob_str(&html))
318 style_table_sorter();
319 style_finish_page();
320 }else{
321 const char *zTitle = PD("FOSSIL_REPOLIST_TITLE","Repository List");
322 /* If no repositories were found that had the "repolist_skin"
323 ** property set, then use a default skin */
324 @ <html>
325 @ <head>
326 @ <base href="%s(g.zBaseURL)/">
327 @ <meta name="viewport" content="width=device-width, initial-scale=1.0">
328 @ <title>%h(zTitle)</title>
329 @ </head>
330 @ <body>
331 @ <h1 align="center">%h(zTitle)</h1>
332 @ %s(blob_str(&html))
333 @ <script>%s(builtin_text("sorttable.js"))</script>
334 @ </body>
335 @ </html>
336 }
337
--- src/terminal.c
+++ src/terminal.c
@@ -144,12 +144,51 @@
144144
/*
145145
** Return true if it is reasonable is emit VT100 escape codes.
146146
*/
147147
int terminal_is_vt100(void){
148148
char *zNoColor;
149
+#ifdef _WIN32
150
+ if( !win32_terminal_is_vt100(1) ) return 0;
151
+#endif /* _WIN32 */
149152
if( !fossil_isatty(1) ) return 0;
150153
zNoColor =fossil_getenv("NO_COLOR");
151154
if( zNoColor==0 ) return 1;
152155
if( zNoColor[0]==0 ) return 1;
153156
if( is_false(zNoColor) ) return 1;
154157
return 0;
155158
}
159
+
160
+#ifdef _WIN32
161
+/*
162
+** Return true if the Windows console supports VT100 escape codes.
163
+**
164
+** Support for VT100 escape codes is enabled by default in Windows Terminal
165
+** on Windows 10 and Windows 11, and disabled by default in Legacy Consoles
166
+** and on older versions of Windows. Programs can turn on VT100 support for
167
+** Legacy Consoles using the ENABLE_VIRTUAL_TERMINAL_PROCESSING flag.
168
+**
169
+** NOTE: If this function needs to be called in more complex scenarios with
170
+** reassigned stdout and stderr streams, the following CRT calls are useful
171
+** to translate from CRT streams to file descriptors and to Win32 handles:
172
+**
173
+** HANDLE hOutputHandle = (HANDLE)_get_osfhandle(_fileno(<FILE*>));
174
+*/
175
+#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
176
+#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
177
+#endif
178
+int win32_terminal_is_vt100(int fd){
179
+ HANDLE hConsole = NULL;
180
+ DWORD dwConsoleMode;
181
+ switch( fd ){
182
+ case 1:
183
+ hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
184
+ break;
185
+ case 2:
186
+ hConsole = GetStdHandle(STD_ERROR_HANDLE);
187
+ break;
188
+ }
189
+ if( GetConsoleMode(hConsole,&dwConsoleMode) ){
190
+ return (dwConsoleMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)!=0;
191
+ }
192
+ return 0;
193
+}
194
+#endif /* _WIN32 */
156195
--- src/terminal.c
+++ src/terminal.c
@@ -144,12 +144,51 @@
144 /*
145 ** Return true if it is reasonable is emit VT100 escape codes.
146 */
147 int terminal_is_vt100(void){
148 char *zNoColor;
 
 
 
149 if( !fossil_isatty(1) ) return 0;
150 zNoColor =fossil_getenv("NO_COLOR");
151 if( zNoColor==0 ) return 1;
152 if( zNoColor[0]==0 ) return 1;
153 if( is_false(zNoColor) ) return 1;
154 return 0;
155 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
--- src/terminal.c
+++ src/terminal.c
@@ -144,12 +144,51 @@
144 /*
145 ** Return true if it is reasonable is emit VT100 escape codes.
146 */
147 int terminal_is_vt100(void){
148 char *zNoColor;
149 #ifdef _WIN32
150 if( !win32_terminal_is_vt100(1) ) return 0;
151 #endif /* _WIN32 */
152 if( !fossil_isatty(1) ) return 0;
153 zNoColor =fossil_getenv("NO_COLOR");
154 if( zNoColor==0 ) return 1;
155 if( zNoColor[0]==0 ) return 1;
156 if( is_false(zNoColor) ) return 1;
157 return 0;
158 }
159
160 #ifdef _WIN32
161 /*
162 ** Return true if the Windows console supports VT100 escape codes.
163 **
164 ** Support for VT100 escape codes is enabled by default in Windows Terminal
165 ** on Windows 10 and Windows 11, and disabled by default in Legacy Consoles
166 ** and on older versions of Windows. Programs can turn on VT100 support for
167 ** Legacy Consoles using the ENABLE_VIRTUAL_TERMINAL_PROCESSING flag.
168 **
169 ** NOTE: If this function needs to be called in more complex scenarios with
170 ** reassigned stdout and stderr streams, the following CRT calls are useful
171 ** to translate from CRT streams to file descriptors and to Win32 handles:
172 **
173 ** HANDLE hOutputHandle = (HANDLE)_get_osfhandle(_fileno(<FILE*>));
174 */
175 #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
176 #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
177 #endif
178 int win32_terminal_is_vt100(int fd){
179 HANDLE hConsole = NULL;
180 DWORD dwConsoleMode;
181 switch( fd ){
182 case 1:
183 hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
184 break;
185 case 2:
186 hConsole = GetStdHandle(STD_ERROR_HANDLE);
187 break;
188 }
189 if( GetConsoleMode(hConsole,&dwConsoleMode) ){
190 return (dwConsoleMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)!=0;
191 }
192 return 0;
193 }
194 #endif /* _WIN32 */
195
+4 -2
--- src/timeline.c
+++ src/timeline.c
@@ -3419,11 +3419,13 @@
34193419
}
34203420
34213421
/*
34223422
** wiki_to_text(TEXT)
34233423
**
3424
-** Return a plain-text rendering of Fossil-Wiki TEXT.
3424
+** Return a text rendering of Fossil-Wiki TEXT, intended for display
3425
+** on a timeline. The timeline-plaintext and timeline-hard-newlines
3426
+** settings are considered when doing this rendering.
34253427
*/
34263428
static void wiki_to_text_sqlfunc(
34273429
sqlite3_context *context,
34283430
int argc,
34293431
sqlite3_value **argv
@@ -3434,11 +3436,11 @@
34343436
zIn = (const char*)sqlite3_value_text(argv[0]);
34353437
if( zIn==0 ) return;
34363438
nIn = sqlite3_value_bytes(argv[0]);
34373439
blob_init(&in, zIn, nIn);
34383440
blob_init(&html, 0, 0);
3439
- wiki_convert(&in, &html, WIKI_INLINE);
3441
+ wiki_convert(&in, &html, wiki_convert_flags(0));
34403442
blob_reset(&in);
34413443
blob_init(&txt, 0, 0);
34423444
html_to_plaintext(blob_str(&html), &txt, 0);
34433445
blob_reset(&html);
34443446
nOut = blob_size(&txt);
34453447
--- src/timeline.c
+++ src/timeline.c
@@ -3419,11 +3419,13 @@
3419 }
3420
3421 /*
3422 ** wiki_to_text(TEXT)
3423 **
3424 ** Return a plain-text rendering of Fossil-Wiki TEXT.
 
 
3425 */
3426 static void wiki_to_text_sqlfunc(
3427 sqlite3_context *context,
3428 int argc,
3429 sqlite3_value **argv
@@ -3434,11 +3436,11 @@
3434 zIn = (const char*)sqlite3_value_text(argv[0]);
3435 if( zIn==0 ) return;
3436 nIn = sqlite3_value_bytes(argv[0]);
3437 blob_init(&in, zIn, nIn);
3438 blob_init(&html, 0, 0);
3439 wiki_convert(&in, &html, WIKI_INLINE);
3440 blob_reset(&in);
3441 blob_init(&txt, 0, 0);
3442 html_to_plaintext(blob_str(&html), &txt, 0);
3443 blob_reset(&html);
3444 nOut = blob_size(&txt);
3445
--- src/timeline.c
+++ src/timeline.c
@@ -3419,11 +3419,13 @@
3419 }
3420
3421 /*
3422 ** wiki_to_text(TEXT)
3423 **
3424 ** Return a text rendering of Fossil-Wiki TEXT, intended for display
3425 ** on a timeline. The timeline-plaintext and timeline-hard-newlines
3426 ** settings are considered when doing this rendering.
3427 */
3428 static void wiki_to_text_sqlfunc(
3429 sqlite3_context *context,
3430 int argc,
3431 sqlite3_value **argv
@@ -3434,11 +3436,11 @@
3436 zIn = (const char*)sqlite3_value_text(argv[0]);
3437 if( zIn==0 ) return;
3438 nIn = sqlite3_value_bytes(argv[0]);
3439 blob_init(&in, zIn, nIn);
3440 blob_init(&html, 0, 0);
3441 wiki_convert(&in, &html, wiki_convert_flags(0));
3442 blob_reset(&in);
3443 blob_init(&txt, 0, 0);
3444 html_to_plaintext(blob_str(&html), &txt, 0);
3445 blob_reset(&html);
3446 nOut = blob_size(&txt);
3447
+5 -1
--- www/cgi.wiki
+++ www/cgi.wiki
@@ -73,12 +73,16 @@
7373
of available Fossil repositories.
7474
7575
The "skin" of the reply is determined by the first
7676
repository in the list that has a non-zero
7777
[/help?cmd=repolist-skin|repolist-skin] setting.
78
+
7879
If no repository has such a non-zero repolist-skin setting, then
79
-the repository list is generic HTML without any decoration.
80
+the repository list is generic HTML without any decoration, with
81
+the page title taken from the <tt>FOSSIL_REPOLIST_TITLE</tt>
82
+environment variable. The variable can be defined in the CGI
83
+control file using the [#setenv|<tt>setenv:</tt>] statement.
8084
8185
The repolist-generated page recurses into subdirectories and will list
8286
all <tt>*.fossil</tt> files found, with the following exceptions:
8387
8488
* Filenames starting with a period are treated as "hidden" and skipped.
8589
--- www/cgi.wiki
+++ www/cgi.wiki
@@ -73,12 +73,16 @@
73 of available Fossil repositories.
74
75 The "skin" of the reply is determined by the first
76 repository in the list that has a non-zero
77 [/help?cmd=repolist-skin|repolist-skin] setting.
 
78 If no repository has such a non-zero repolist-skin setting, then
79 the repository list is generic HTML without any decoration.
 
 
 
80
81 The repolist-generated page recurses into subdirectories and will list
82 all <tt>*.fossil</tt> files found, with the following exceptions:
83
84 * Filenames starting with a period are treated as "hidden" and skipped.
85
--- www/cgi.wiki
+++ www/cgi.wiki
@@ -73,12 +73,16 @@
73 of available Fossil repositories.
74
75 The "skin" of the reply is determined by the first
76 repository in the list that has a non-zero
77 [/help?cmd=repolist-skin|repolist-skin] setting.
78
79 If no repository has such a non-zero repolist-skin setting, then
80 the repository list is generic HTML without any decoration, with
81 the page title taken from the <tt>FOSSIL_REPOLIST_TITLE</tt>
82 environment variable. The variable can be defined in the CGI
83 control file using the [#setenv|<tt>setenv:</tt>] statement.
84
85 The repolist-generated page recurses into subdirectories and will list
86 all <tt>*.fossil</tt> files found, with the following exceptions:
87
88 * Filenames starting with a period are treated as "hidden" and skipped.
89
--- www/env-opts.md
+++ www/env-opts.md
@@ -148,10 +148,15 @@
148148
149149
150150
`FOSSIL_HOME`: Location of [configuration database][configdb].
151151
See the [configuration database location][configloc] description
152152
for additional information.
153
+
154
+`FOSSIL_REPOLIST_TITLE`: The page title of the "Repository List" page
155
+loaded by the `fossil all ui` or `fossil ui /` commands. Only used if
156
+none of the listed repositories has the `repolist_skin` property set.
157
+Can be set from the [CGI control file][cgictlfile].
153158
154159
`FOSSIL_USE_SEE_TEXTKEY`: If set, treat the encryption key string for
155160
SEE as text to be hashed into the actual encryption key. This has no
156161
effect if Fossil was not compiled with SEE support enabled.
157162
@@ -493,5 +498,6 @@
493498
On Windows platforms, it assumes that `start` is the command to open
494499
a URL in the user's configured default browser.
495500
496501
[configdb]: ./tech_overview.wiki#configdb
497502
[configloc]: ./tech_overview.wiki#configloc
503
+[cgictlfile]: ./cgi.wiki
498504
--- www/env-opts.md
+++ www/env-opts.md
@@ -148,10 +148,15 @@
148
149
150 `FOSSIL_HOME`: Location of [configuration database][configdb].
151 See the [configuration database location][configloc] description
152 for additional information.
 
 
 
 
 
153
154 `FOSSIL_USE_SEE_TEXTKEY`: If set, treat the encryption key string for
155 SEE as text to be hashed into the actual encryption key. This has no
156 effect if Fossil was not compiled with SEE support enabled.
157
@@ -493,5 +498,6 @@
493 On Windows platforms, it assumes that `start` is the command to open
494 a URL in the user's configured default browser.
495
496 [configdb]: ./tech_overview.wiki#configdb
497 [configloc]: ./tech_overview.wiki#configloc
 
498
--- www/env-opts.md
+++ www/env-opts.md
@@ -148,10 +148,15 @@
148
149
150 `FOSSIL_HOME`: Location of [configuration database][configdb].
151 See the [configuration database location][configloc] description
152 for additional information.
153
154 `FOSSIL_REPOLIST_TITLE`: The page title of the "Repository List" page
155 loaded by the `fossil all ui` or `fossil ui /` commands. Only used if
156 none of the listed repositories has the `repolist_skin` property set.
157 Can be set from the [CGI control file][cgictlfile].
158
159 `FOSSIL_USE_SEE_TEXTKEY`: If set, treat the encryption key string for
160 SEE as text to be hashed into the actual encryption key. This has no
161 effect if Fossil was not compiled with SEE support enabled.
162
@@ -493,5 +498,6 @@
498 On Windows platforms, it assumes that `start` is the command to open
499 a URL in the user's configured default browser.
500
501 [configdb]: ./tech_overview.wiki#configdb
502 [configloc]: ./tech_overview.wiki#configloc
503 [cgictlfile]: ./cgi.wiki
504
--- www/fileformat.wiki
+++ www/fileformat.wiki
@@ -61,11 +61,11 @@
6161
artifacts:
6262
6363
<ul>
6464
<li> [#manifest | Manifests] </li>
6565
<li> [#cluster | Clusters] </li>
66
-<li> [#ctrl | Control Artifacts] </li>
66
+<li> [#ctrl | Control (a.k.a. Tag) Artifacts] </li>
6767
<li> [#wikichng | Wiki Pages] </li>
6868
<li> [#tktchng | Ticket Changes] </li>
6969
<li> [#attachment | Attachments] </li>
7070
<li> [#event | TechNotes] </li>
7171
<li> [#forum | Forum Posts] </li>
@@ -276,22 +276,26 @@
276276
prior cards in the cluster. The <b>Z</b> card is required.
277277
278278
An example cluster from Fossil can be seen
279279
[/artifact/d03dbdd73a2a8 | here].
280280
281
-<h3 id="ctrl">2.3 Control Artifacts</h3>
281
+<h3 id="ctrl">2.3 Control (a.k.a. Tag) Artifacts</h3>
282282
283283
Control artifacts are used to assign properties to other artifacts
284
-within the repository.
285
-Allowed cards in a control artifact are as follows:
284
+within the repository. Allowed cards in a control artifact are as
285
+follows:
286286
287287
<div class="indent">
288288
<b>D</b> <i>time-and-date-stamp</i><br />
289289
<b>T</b> (<b>+</b>|<b>-</b>|<b>*</b>)<i>tag-name</i> <i>artifact-id</i> ?<i>value</i>?<br />
290290
<b>U</b> <i>user-name</i><br />
291291
<b>Z</b> <i>checksum</i><br />
292292
</div>
293
+
294
+Control articles are also referred to as Tag artifacts, but tags can
295
+also be applied via other artifact types, as described in
296
+[#summary|the Card Summary table].
293297
294298
A control artifact must have one <b>D</b> card, one <b>U</b> card, one <b>Z</b> card and
295299
one or more <b>T</b> cards. No other cards or other text is
296300
allowed in a control artifact. Control artifacts might be PGP
297301
clearsigned.
298302
--- www/fileformat.wiki
+++ www/fileformat.wiki
@@ -61,11 +61,11 @@
61 artifacts:
62
63 <ul>
64 <li> [#manifest | Manifests] </li>
65 <li> [#cluster | Clusters] </li>
66 <li> [#ctrl | Control Artifacts] </li>
67 <li> [#wikichng | Wiki Pages] </li>
68 <li> [#tktchng | Ticket Changes] </li>
69 <li> [#attachment | Attachments] </li>
70 <li> [#event | TechNotes] </li>
71 <li> [#forum | Forum Posts] </li>
@@ -276,22 +276,26 @@
276 prior cards in the cluster. The <b>Z</b> card is required.
277
278 An example cluster from Fossil can be seen
279 [/artifact/d03dbdd73a2a8 | here].
280
281 <h3 id="ctrl">2.3 Control Artifacts</h3>
282
283 Control artifacts are used to assign properties to other artifacts
284 within the repository.
285 Allowed cards in a control artifact are as follows:
286
287 <div class="indent">
288 <b>D</b> <i>time-and-date-stamp</i><br />
289 <b>T</b> (<b>+</b>|<b>-</b>|<b>*</b>)<i>tag-name</i> <i>artifact-id</i> ?<i>value</i>?<br />
290 <b>U</b> <i>user-name</i><br />
291 <b>Z</b> <i>checksum</i><br />
292 </div>
 
 
 
 
293
294 A control artifact must have one <b>D</b> card, one <b>U</b> card, one <b>Z</b> card and
295 one or more <b>T</b> cards. No other cards or other text is
296 allowed in a control artifact. Control artifacts might be PGP
297 clearsigned.
298
--- www/fileformat.wiki
+++ www/fileformat.wiki
@@ -61,11 +61,11 @@
61 artifacts:
62
63 <ul>
64 <li> [#manifest | Manifests] </li>
65 <li> [#cluster | Clusters] </li>
66 <li> [#ctrl | Control (a.k.a. Tag) Artifacts] </li>
67 <li> [#wikichng | Wiki Pages] </li>
68 <li> [#tktchng | Ticket Changes] </li>
69 <li> [#attachment | Attachments] </li>
70 <li> [#event | TechNotes] </li>
71 <li> [#forum | Forum Posts] </li>
@@ -276,22 +276,26 @@
276 prior cards in the cluster. The <b>Z</b> card is required.
277
278 An example cluster from Fossil can be seen
279 [/artifact/d03dbdd73a2a8 | here].
280
281 <h3 id="ctrl">2.3 Control (a.k.a. Tag) Artifacts</h3>
282
283 Control artifacts are used to assign properties to other artifacts
284 within the repository. Allowed cards in a control artifact are as
285 follows:
286
287 <div class="indent">
288 <b>D</b> <i>time-and-date-stamp</i><br />
289 <b>T</b> (<b>+</b>|<b>-</b>|<b>*</b>)<i>tag-name</i> <i>artifact-id</i> ?<i>value</i>?<br />
290 <b>U</b> <i>user-name</i><br />
291 <b>Z</b> <i>checksum</i><br />
292 </div>
293
294 Control articles are also referred to as Tag artifacts, but tags can
295 also be applied via other artifact types, as described in
296 [#summary|the Card Summary table].
297
298 A control artifact must have one <b>D</b> card, one <b>U</b> card, one <b>Z</b> card and
299 one or more <b>T</b> cards. No other cards or other text is
300 allowed in a control artifact. Control artifacts might be PGP
301 clearsigned.
302

Keyboard Shortcuts

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