Fossil SCM

Sync w/trunk.

larrybr 2021-03-31 10:57 panic-reduction merge
Commit 79be1156a9f4dbfcc1ba23572adfe5a16dee04ecda8943aa2607f61a00918cc1
--- skins/darkmode/css.txt
+++ skins/darkmode/css.txt
@@ -548,5 +548,10 @@
548548
}
549549
550550
pre.udiff {
551551
overflow-x: auto;
552552
}
553
+
554
+body.report table.report tr td { color: black }
555
+body.report table.report a { color: blue }
556
+body.tkt td.tktDspValue { color: black }
557
+body.tkt td.tktDspValue a { color: blue }
553558
--- skins/darkmode/css.txt
+++ skins/darkmode/css.txt
@@ -548,5 +548,10 @@
548 }
549
550 pre.udiff {
551 overflow-x: auto;
552 }
 
 
 
 
 
553
--- skins/darkmode/css.txt
+++ skins/darkmode/css.txt
@@ -548,5 +548,10 @@
548 }
549
550 pre.udiff {
551 overflow-x: auto;
552 }
553
554 body.report table.report tr td { color: black }
555 body.report table.report a { color: blue }
556 body.tkt td.tktDspValue { color: black }
557 body.tkt td.tktDspValue a { color: blue }
558
+28 -11
--- src/add.c
+++ src/add.c
@@ -439,21 +439,10 @@
439439
Blob fullName = empty_blob;
440440
441441
/* file_tree_name() throws a fatal error if g.argv[i] is outside of the
442442
** checkout. */
443443
file_tree_name(g.argv[i], &fullName, 0, 1);
444
- if(0==allowReservedFlag
445
- && 0!=file_is_win_reserved(blob_str(&fullName))){
446
- /* Note that the 'add' internal machinery already _silently_
447
- ** skips over any names for which file_is_reserved_name()
448
- ** returns true or which is in the fossil_reserved_name()
449
- ** list. We do not need to warn for those, as they're outright
450
- ** verboten. */
451
- fossil_fatal("Filename is reserved: %b\n"
452
- "Use --allow-reserved to permit "
453
- "reserved filenames.", &fullName);
454
- }
455444
blob_reset(&fullName);
456445
file_canonical_name(g.argv[i], &fullName, 0);
457446
zName = blob_str(&fullName);
458447
isDir = file_isdir(zName, RepoFILE);
459448
if( isDir==1 ){
@@ -486,10 +475,38 @@
486475
blob_reset(&fullName);
487476
}
488477
glob_free(pIgnore);
489478
glob_free(pClean);
490479
480
+ /** Check for Windows-reserved names and warn or exit, as
481
+ ** appopriate. Note that the 'add' internal machinery already
482
+ ** _silently_ skips over any names for which
483
+ ** file_is_reserved_name() returns true or which is in the
484
+ ** fossil_reserved_name() list. We do not need to warn for those,
485
+ ** as they're outright verboten. */
486
+ if(db_exists("SELECT 1 FROM sfile WHERE win_reserved(pathname)")){
487
+ Stmt q = empty_Stmt;
488
+ db_prepare(&q,"SELECT pathname FROM sfile "
489
+ "WHERE win_reserved(pathname)");
490
+ int reservedCount = 0;
491
+ while( db_step(&q)==SQLITE_ROW ){
492
+ const char * zName = db_column_text(&q, 0);
493
+ ++reservedCount;
494
+ if(allowReservedFlag){
495
+ fossil_warning("WARNING: Windows-reserved "
496
+ "filename: %s", zName);
497
+ }else{
498
+ fossil_warning("ERROR: Windows-reserved filename: %s", zName);
499
+ }
500
+ }
501
+ db_finalize(&q);
502
+ if(allowReservedFlag==0){
503
+ fossil_fatal("ERROR: %d Windows-reserved filename(s) added. "
504
+ "Use --allow-reserved to permit such names.",
505
+ reservedCount);
506
+ }
507
+ }
491508
add_files_in_sfile(vid);
492509
db_end_transaction(0);
493510
}
494511
495512
/*
496513
--- src/add.c
+++ src/add.c
@@ -439,21 +439,10 @@
439 Blob fullName = empty_blob;
440
441 /* file_tree_name() throws a fatal error if g.argv[i] is outside of the
442 ** checkout. */
443 file_tree_name(g.argv[i], &fullName, 0, 1);
444 if(0==allowReservedFlag
445 && 0!=file_is_win_reserved(blob_str(&fullName))){
446 /* Note that the 'add' internal machinery already _silently_
447 ** skips over any names for which file_is_reserved_name()
448 ** returns true or which is in the fossil_reserved_name()
449 ** list. We do not need to warn for those, as they're outright
450 ** verboten. */
451 fossil_fatal("Filename is reserved: %b\n"
452 "Use --allow-reserved to permit "
453 "reserved filenames.", &fullName);
454 }
455 blob_reset(&fullName);
456 file_canonical_name(g.argv[i], &fullName, 0);
457 zName = blob_str(&fullName);
458 isDir = file_isdir(zName, RepoFILE);
459 if( isDir==1 ){
@@ -486,10 +475,38 @@
486 blob_reset(&fullName);
487 }
488 glob_free(pIgnore);
489 glob_free(pClean);
490
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
491 add_files_in_sfile(vid);
492 db_end_transaction(0);
493 }
494
495 /*
496
--- src/add.c
+++ src/add.c
@@ -439,21 +439,10 @@
439 Blob fullName = empty_blob;
440
441 /* file_tree_name() throws a fatal error if g.argv[i] is outside of the
442 ** checkout. */
443 file_tree_name(g.argv[i], &fullName, 0, 1);
 
 
 
 
 
 
 
 
 
 
 
444 blob_reset(&fullName);
445 file_canonical_name(g.argv[i], &fullName, 0);
446 zName = blob_str(&fullName);
447 isDir = file_isdir(zName, RepoFILE);
448 if( isDir==1 ){
@@ -486,10 +475,38 @@
475 blob_reset(&fullName);
476 }
477 glob_free(pIgnore);
478 glob_free(pClean);
479
480 /** Check for Windows-reserved names and warn or exit, as
481 ** appopriate. Note that the 'add' internal machinery already
482 ** _silently_ skips over any names for which
483 ** file_is_reserved_name() returns true or which is in the
484 ** fossil_reserved_name() list. We do not need to warn for those,
485 ** as they're outright verboten. */
486 if(db_exists("SELECT 1 FROM sfile WHERE win_reserved(pathname)")){
487 Stmt q = empty_Stmt;
488 db_prepare(&q,"SELECT pathname FROM sfile "
489 "WHERE win_reserved(pathname)");
490 int reservedCount = 0;
491 while( db_step(&q)==SQLITE_ROW ){
492 const char * zName = db_column_text(&q, 0);
493 ++reservedCount;
494 if(allowReservedFlag){
495 fossil_warning("WARNING: Windows-reserved "
496 "filename: %s", zName);
497 }else{
498 fossil_warning("ERROR: Windows-reserved filename: %s", zName);
499 }
500 }
501 db_finalize(&q);
502 if(allowReservedFlag==0){
503 fossil_fatal("ERROR: %d Windows-reserved filename(s) added. "
504 "Use --allow-reserved to permit such names.",
505 reservedCount);
506 }
507 }
508 add_files_in_sfile(vid);
509 db_end_transaction(0);
510 }
511
512 /*
513
-2
--- src/chat.c
+++ src/chat.c
@@ -163,12 +163,10 @@
163163
@ placeholder="Type message here." autocomplete="off">
164164
@ <textarea rows="8" id="chat-input-multi" \
165165
@ placeholder="Type message here. Ctrl-Enter sends it." \
166166
@ class="hidden"></textarea>
167167
@ <input type="submit" value="Send" id="chat-message-submit">
168
- @ <button id="chat-scroll-top" class="hidden">&uarr;</button>
169
- @ <button id="chat-scroll-bottom" class="hidden">&darr;</button>
170168
@ <span id="chat-settings-button" class="settings-icon" \
171169
@ aria-label="Settings..." aria-haspopup="true" ></span>
172170
@ </div>
173171
@ <div id='chat-input-file-area'>
174172
@ <div class='file-selection-wrapper'>
175173
--- src/chat.c
+++ src/chat.c
@@ -163,12 +163,10 @@
163 @ placeholder="Type message here." autocomplete="off">
164 @ <textarea rows="8" id="chat-input-multi" \
165 @ placeholder="Type message here. Ctrl-Enter sends it." \
166 @ class="hidden"></textarea>
167 @ <input type="submit" value="Send" id="chat-message-submit">
168 @ <button id="chat-scroll-top" class="hidden">&uarr;</button>
169 @ <button id="chat-scroll-bottom" class="hidden">&darr;</button>
170 @ <span id="chat-settings-button" class="settings-icon" \
171 @ aria-label="Settings..." aria-haspopup="true" ></span>
172 @ </div>
173 @ <div id='chat-input-file-area'>
174 @ <div class='file-selection-wrapper'>
175
--- src/chat.c
+++ src/chat.c
@@ -163,12 +163,10 @@
163 @ placeholder="Type message here." autocomplete="off">
164 @ <textarea rows="8" id="chat-input-multi" \
165 @ placeholder="Type message here. Ctrl-Enter sends it." \
166 @ class="hidden"></textarea>
167 @ <input type="submit" value="Send" id="chat-message-submit">
 
 
168 @ <span id="chat-settings-button" class="settings-icon" \
169 @ aria-label="Settings..." aria-haspopup="true" ></span>
170 @ </div>
171 @ <div id='chat-input-file-area'>
172 @ <div class='file-selection-wrapper'>
173
+1 -28
--- src/chat.js
+++ src/chat.js
@@ -107,13 +107,11 @@
107107
btnSubmit: E1('#chat-message-submit'),
108108
inputSingle: E1('#chat-input-single'),
109109
inputMulti: E1('#chat-input-multi'),
110110
inputCurrent: undefined/*one of inputSingle or inputMulti*/,
111111
inputFile: E1('#chat-input-file'),
112
- contentDiv: E1('div.content'),
113
- btnMsgHome: E1('#chat-scroll-top'),
114
- btnMsgEnd: E1('#chat-scroll-bottom')
112
+ contentDiv: E1('div.content')
115113
},
116114
me: F.user.name,
117115
mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
118116
mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/,
119117
pageIsActive: 'visible'===document.visibilityState,
@@ -339,18 +337,10 @@
339337
}
340338
},
341339
toggleChatOnlyMode: function(){
342340
return this.chatOnlyMode(!this.isChatOnlyMode());
343341
},
344
- /* Turn the message area top/bottom buttons on (yes===true), off
345
- (yes==false), or toggle them (no arguments). Returns this. */
346
- toggleNavButtons: function(yes){
347
- const e = [this.e.btnMsgHome, this.e.btnMsgEnd], c = 'hidden';
348
- if(0===arguments.length) D.toggleClass(e, c);
349
- else if(!arguments[0]) D.addClass(e, c);
350
- else D.removeClass(e, c);
351
- },
352342
messageIsInView: function(e){
353343
return e ? overlapsElemView(e, this.e.messagesWrapper) : false;
354344
},
355345
settings:{
356346
get: (k,dflt)=>F.storage.get(k,dflt),
@@ -965,14 +955,10 @@
965955
boolValue: ()=>Chat.settings.getBool('images-inline'),
966956
callback: function(){
967957
const v = Chat.settings.toggle('images-inline');
968958
F.toast.message("Image mode set to "+(v ? "inline" : "hyperlink")+".");
969959
}
970
- },{
971
- label: "Message home/end buttons",
972
- boolValue: ()=>!Chat.e.btnMsgHome.classList.contains('hidden'),
973
- callback: ()=>Chat.toggleNavButtons()
974960
}];
975961
976962
/** Set up selection list of notification sounds. */
977963
if(true/*flip this to false to enable selection of audio files*/){
978964
settingsOps.push({
@@ -1083,23 +1069,10 @@
10831069
settingsPopup.options.adjustY = function(y){
10841070
const rect = settingsButton.getBoundingClientRect();
10851071
return rect.top - popupSize.height -2;
10861072
};
10871073
})()/*#chat-settings-button setup*/;
1088
-
1089
- (function(){ /* buttons to scroll to the begin/end of the messages. */
1090
- Chat.e.btnMsgEnd.addEventListener('click',function(ev){
1091
- ev.preventDefault();
1092
- Chat.scrollMessagesTo(1);
1093
- return false;
1094
- });
1095
- Chat.e.btnMsgHome.addEventListener('click',function(ev){
1096
- ev.preventDefault();
1097
- Chat.scrollMessagesTo(-1);
1098
- return false;
1099
- });
1100
- })();
11011074
11021075
/** Callback for poll() to inject new content into the page. jx ==
11031076
the response from /chat-poll. If atEnd is true, the message is
11041077
appended to the end of the chat list (for loading older
11051078
messages), else the beginning (the default). */
11061079
--- src/chat.js
+++ src/chat.js
@@ -107,13 +107,11 @@
107 btnSubmit: E1('#chat-message-submit'),
108 inputSingle: E1('#chat-input-single'),
109 inputMulti: E1('#chat-input-multi'),
110 inputCurrent: undefined/*one of inputSingle or inputMulti*/,
111 inputFile: E1('#chat-input-file'),
112 contentDiv: E1('div.content'),
113 btnMsgHome: E1('#chat-scroll-top'),
114 btnMsgEnd: E1('#chat-scroll-bottom')
115 },
116 me: F.user.name,
117 mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
118 mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/,
119 pageIsActive: 'visible'===document.visibilityState,
@@ -339,18 +337,10 @@
339 }
340 },
341 toggleChatOnlyMode: function(){
342 return this.chatOnlyMode(!this.isChatOnlyMode());
343 },
344 /* Turn the message area top/bottom buttons on (yes===true), off
345 (yes==false), or toggle them (no arguments). Returns this. */
346 toggleNavButtons: function(yes){
347 const e = [this.e.btnMsgHome, this.e.btnMsgEnd], c = 'hidden';
348 if(0===arguments.length) D.toggleClass(e, c);
349 else if(!arguments[0]) D.addClass(e, c);
350 else D.removeClass(e, c);
351 },
352 messageIsInView: function(e){
353 return e ? overlapsElemView(e, this.e.messagesWrapper) : false;
354 },
355 settings:{
356 get: (k,dflt)=>F.storage.get(k,dflt),
@@ -965,14 +955,10 @@
965 boolValue: ()=>Chat.settings.getBool('images-inline'),
966 callback: function(){
967 const v = Chat.settings.toggle('images-inline');
968 F.toast.message("Image mode set to "+(v ? "inline" : "hyperlink")+".");
969 }
970 },{
971 label: "Message home/end buttons",
972 boolValue: ()=>!Chat.e.btnMsgHome.classList.contains('hidden'),
973 callback: ()=>Chat.toggleNavButtons()
974 }];
975
976 /** Set up selection list of notification sounds. */
977 if(true/*flip this to false to enable selection of audio files*/){
978 settingsOps.push({
@@ -1083,23 +1069,10 @@
1083 settingsPopup.options.adjustY = function(y){
1084 const rect = settingsButton.getBoundingClientRect();
1085 return rect.top - popupSize.height -2;
1086 };
1087 })()/*#chat-settings-button setup*/;
1088
1089 (function(){ /* buttons to scroll to the begin/end of the messages. */
1090 Chat.e.btnMsgEnd.addEventListener('click',function(ev){
1091 ev.preventDefault();
1092 Chat.scrollMessagesTo(1);
1093 return false;
1094 });
1095 Chat.e.btnMsgHome.addEventListener('click',function(ev){
1096 ev.preventDefault();
1097 Chat.scrollMessagesTo(-1);
1098 return false;
1099 });
1100 })();
1101
1102 /** Callback for poll() to inject new content into the page. jx ==
1103 the response from /chat-poll. If atEnd is true, the message is
1104 appended to the end of the chat list (for loading older
1105 messages), else the beginning (the default). */
1106
--- src/chat.js
+++ src/chat.js
@@ -107,13 +107,11 @@
107 btnSubmit: E1('#chat-message-submit'),
108 inputSingle: E1('#chat-input-single'),
109 inputMulti: E1('#chat-input-multi'),
110 inputCurrent: undefined/*one of inputSingle or inputMulti*/,
111 inputFile: E1('#chat-input-file'),
112 contentDiv: E1('div.content')
 
 
113 },
114 me: F.user.name,
115 mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
116 mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/,
117 pageIsActive: 'visible'===document.visibilityState,
@@ -339,18 +337,10 @@
337 }
338 },
339 toggleChatOnlyMode: function(){
340 return this.chatOnlyMode(!this.isChatOnlyMode());
341 },
 
 
 
 
 
 
 
 
342 messageIsInView: function(e){
343 return e ? overlapsElemView(e, this.e.messagesWrapper) : false;
344 },
345 settings:{
346 get: (k,dflt)=>F.storage.get(k,dflt),
@@ -965,14 +955,10 @@
955 boolValue: ()=>Chat.settings.getBool('images-inline'),
956 callback: function(){
957 const v = Chat.settings.toggle('images-inline');
958 F.toast.message("Image mode set to "+(v ? "inline" : "hyperlink")+".");
959 }
 
 
 
 
960 }];
961
962 /** Set up selection list of notification sounds. */
963 if(true/*flip this to false to enable selection of audio files*/){
964 settingsOps.push({
@@ -1083,23 +1069,10 @@
1069 settingsPopup.options.adjustY = function(y){
1070 const rect = settingsButton.getBoundingClientRect();
1071 return rect.top - popupSize.height -2;
1072 };
1073 })()/*#chat-settings-button setup*/;
 
 
 
 
 
 
 
 
 
 
 
 
 
1074
1075 /** Callback for poll() to inject new content into the page. jx ==
1076 the response from /chat-poll. If atEnd is true, the message is
1077 appended to the end of the chat list (for loading older
1078 messages), else the beginning (the default). */
1079
+19
--- src/db.c
+++ src/db.c
@@ -1376,10 +1376,13 @@
13761376
alert_display_name_func, 0, 0);
13771377
sqlite3_create_function(db, "obscure", 1, SQLITE_UTF8, 0,
13781378
db_obscure, 0, 0);
13791379
sqlite3_create_function(db, "protected_setting", 1, SQLITE_UTF8, 0,
13801380
db_protected_setting_func, 0, 0);
1381
+ sqlite3_create_function(db, "win_reserved", 1, SQLITE_UTF8, 0,
1382
+ db_win_reserved_func,0,0
1383
+ );
13811384
}
13821385
13831386
#if USE_SEE
13841387
/*
13851388
** This is a pointer to the saved database encryption key string.
@@ -2876,10 +2879,26 @@
28762879
assert( rc==0 || rc==1 );
28772880
if( sqlite3_value_type(argv[2-rc])==SQLITE_NULL ) rc = 1-rc;
28782881
sqlite3_result_value(context, argv[2-rc]);
28792882
}
28802883
}
2884
+
2885
+/*
2886
+** Implementation of the "win_reserved(X)" SQL function, a wrapper
2887
+** for file_is_win_reserved(X) which returns true if X is
2888
+** a Windows-reserved filename.
2889
+*/
2890
+LOCAL void db_win_reserved_func(
2891
+ sqlite3_context *context,
2892
+ int argc,
2893
+ sqlite3_value **argv
2894
+){
2895
+ const char * zName = (const char *)sqlite3_value_text(argv[0]);
2896
+ if( zName!=0 ){
2897
+ sqlite3_result_int(context, file_is_win_reserved(zName)!=0);
2898
+ }
2899
+}
28812900
28822901
/*
28832902
** Convert the input string into a artifact hash. Make a notation in the
28842903
** CONCEALED table so that the hash can be undo using the db_reveal()
28852904
** function at some later time.
28862905
--- src/db.c
+++ src/db.c
@@ -1376,10 +1376,13 @@
1376 alert_display_name_func, 0, 0);
1377 sqlite3_create_function(db, "obscure", 1, SQLITE_UTF8, 0,
1378 db_obscure, 0, 0);
1379 sqlite3_create_function(db, "protected_setting", 1, SQLITE_UTF8, 0,
1380 db_protected_setting_func, 0, 0);
 
 
 
1381 }
1382
1383 #if USE_SEE
1384 /*
1385 ** This is a pointer to the saved database encryption key string.
@@ -2876,10 +2879,26 @@
2876 assert( rc==0 || rc==1 );
2877 if( sqlite3_value_type(argv[2-rc])==SQLITE_NULL ) rc = 1-rc;
2878 sqlite3_result_value(context, argv[2-rc]);
2879 }
2880 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2881
2882 /*
2883 ** Convert the input string into a artifact hash. Make a notation in the
2884 ** CONCEALED table so that the hash can be undo using the db_reveal()
2885 ** function at some later time.
2886
--- src/db.c
+++ src/db.c
@@ -1376,10 +1376,13 @@
1376 alert_display_name_func, 0, 0);
1377 sqlite3_create_function(db, "obscure", 1, SQLITE_UTF8, 0,
1378 db_obscure, 0, 0);
1379 sqlite3_create_function(db, "protected_setting", 1, SQLITE_UTF8, 0,
1380 db_protected_setting_func, 0, 0);
1381 sqlite3_create_function(db, "win_reserved", 1, SQLITE_UTF8, 0,
1382 db_win_reserved_func,0,0
1383 );
1384 }
1385
1386 #if USE_SEE
1387 /*
1388 ** This is a pointer to the saved database encryption key string.
@@ -2876,10 +2879,26 @@
2879 assert( rc==0 || rc==1 );
2880 if( sqlite3_value_type(argv[2-rc])==SQLITE_NULL ) rc = 1-rc;
2881 sqlite3_result_value(context, argv[2-rc]);
2882 }
2883 }
2884
2885 /*
2886 ** Implementation of the "win_reserved(X)" SQL function, a wrapper
2887 ** for file_is_win_reserved(X) which returns true if X is
2888 ** a Windows-reserved filename.
2889 */
2890 LOCAL void db_win_reserved_func(
2891 sqlite3_context *context,
2892 int argc,
2893 sqlite3_value **argv
2894 ){
2895 const char * zName = (const char *)sqlite3_value_text(argv[0]);
2896 if( zName!=0 ){
2897 sqlite3_result_int(context, file_is_win_reserved(zName)!=0);
2898 }
2899 }
2900
2901 /*
2902 ** Convert the input string into a artifact hash. Make a notation in the
2903 ** CONCEALED table so that the hash can be undo using the db_reveal()
2904 ** function at some later time.
2905
+19
--- src/db.c
+++ src/db.c
@@ -1376,10 +1376,13 @@
13761376
alert_display_name_func, 0, 0);
13771377
sqlite3_create_function(db, "obscure", 1, SQLITE_UTF8, 0,
13781378
db_obscure, 0, 0);
13791379
sqlite3_create_function(db, "protected_setting", 1, SQLITE_UTF8, 0,
13801380
db_protected_setting_func, 0, 0);
1381
+ sqlite3_create_function(db, "win_reserved", 1, SQLITE_UTF8, 0,
1382
+ db_win_reserved_func,0,0
1383
+ );
13811384
}
13821385
13831386
#if USE_SEE
13841387
/*
13851388
** This is a pointer to the saved database encryption key string.
@@ -2876,10 +2879,26 @@
28762879
assert( rc==0 || rc==1 );
28772880
if( sqlite3_value_type(argv[2-rc])==SQLITE_NULL ) rc = 1-rc;
28782881
sqlite3_result_value(context, argv[2-rc]);
28792882
}
28802883
}
2884
+
2885
+/*
2886
+** Implementation of the "win_reserved(X)" SQL function, a wrapper
2887
+** for file_is_win_reserved(X) which returns true if X is
2888
+** a Windows-reserved filename.
2889
+*/
2890
+LOCAL void db_win_reserved_func(
2891
+ sqlite3_context *context,
2892
+ int argc,
2893
+ sqlite3_value **argv
2894
+){
2895
+ const char * zName = (const char *)sqlite3_value_text(argv[0]);
2896
+ if( zName!=0 ){
2897
+ sqlite3_result_int(context, file_is_win_reserved(zName)!=0);
2898
+ }
2899
+}
28812900
28822901
/*
28832902
** Convert the input string into a artifact hash. Make a notation in the
28842903
** CONCEALED table so that the hash can be undo using the db_reveal()
28852904
** function at some later time.
28862905
--- src/db.c
+++ src/db.c
@@ -1376,10 +1376,13 @@
1376 alert_display_name_func, 0, 0);
1377 sqlite3_create_function(db, "obscure", 1, SQLITE_UTF8, 0,
1378 db_obscure, 0, 0);
1379 sqlite3_create_function(db, "protected_setting", 1, SQLITE_UTF8, 0,
1380 db_protected_setting_func, 0, 0);
 
 
 
1381 }
1382
1383 #if USE_SEE
1384 /*
1385 ** This is a pointer to the saved database encryption key string.
@@ -2876,10 +2879,26 @@
2876 assert( rc==0 || rc==1 );
2877 if( sqlite3_value_type(argv[2-rc])==SQLITE_NULL ) rc = 1-rc;
2878 sqlite3_result_value(context, argv[2-rc]);
2879 }
2880 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2881
2882 /*
2883 ** Convert the input string into a artifact hash. Make a notation in the
2884 ** CONCEALED table so that the hash can be undo using the db_reveal()
2885 ** function at some later time.
2886
--- src/db.c
+++ src/db.c
@@ -1376,10 +1376,13 @@
1376 alert_display_name_func, 0, 0);
1377 sqlite3_create_function(db, "obscure", 1, SQLITE_UTF8, 0,
1378 db_obscure, 0, 0);
1379 sqlite3_create_function(db, "protected_setting", 1, SQLITE_UTF8, 0,
1380 db_protected_setting_func, 0, 0);
1381 sqlite3_create_function(db, "win_reserved", 1, SQLITE_UTF8, 0,
1382 db_win_reserved_func,0,0
1383 );
1384 }
1385
1386 #if USE_SEE
1387 /*
1388 ** This is a pointer to the saved database encryption key string.
@@ -2876,10 +2879,26 @@
2879 assert( rc==0 || rc==1 );
2880 if( sqlite3_value_type(argv[2-rc])==SQLITE_NULL ) rc = 1-rc;
2881 sqlite3_result_value(context, argv[2-rc]);
2882 }
2883 }
2884
2885 /*
2886 ** Implementation of the "win_reserved(X)" SQL function, a wrapper
2887 ** for file_is_win_reserved(X) which returns true if X is
2888 ** a Windows-reserved filename.
2889 */
2890 LOCAL void db_win_reserved_func(
2891 sqlite3_context *context,
2892 int argc,
2893 sqlite3_value **argv
2894 ){
2895 const char * zName = (const char *)sqlite3_value_text(argv[0]);
2896 if( zName!=0 ){
2897 sqlite3_result_int(context, file_is_win_reserved(zName)!=0);
2898 }
2899 }
2900
2901 /*
2902 ** Convert the input string into a artifact hash. Make a notation in the
2903 ** CONCEALED table so that the hash can be undo using the db_reveal()
2904 ** function at some later time.
2905
+25 -4
--- src/report.c
+++ src/report.c
@@ -968,14 +968,18 @@
968968
}
969969
970970
/*
971971
** WEBPAGE: rptview
972972
**
973
-** Generate a report. The rn query parameter is the report number
974
-** corresponding to REPORTFMT.RN. If the tablist query parameter exists,
973
+** Generate a report. The "rn" query parameter is the report number
974
+** corresponding to REPORTFMT.RN. If the "tablist" query parameter exists,
975975
** then the output consists of lines of tab-separated fields instead of
976
-** an HTML table.
976
+** an HTML table. If the "rvsmpl" query parameter is set then report's
977
+** submenu will contain an extra hyperlink that have a value-driven
978
+** label and target.
979
+**
980
+** "rvsmpl" stands for Report View SubMenu's Parametric Link.
977981
*/
978982
void rptview_page(void){
979983
int count = 0;
980984
int rn, rc;
981985
char *zSql;
@@ -1029,14 +1033,31 @@
10291033
}
10301034
10311035
count = 0;
10321036
if( !tabs ){
10331037
struct GenerateHTML sState = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
1038
+ const char *zQS = PD("QUERY_STRING","");
10341039
10351040
db_multi_exec("PRAGMA empty_result_callbacks=ON");
10361041
style_set_current_feature("report");
1037
- style_submenu_element("Raw", "rptview?tablist=1&rn=%d&%h", rn, PD("QUERY_STRING","") );
1042
+ /*
1043
+ ** Lets use a funcy button for /reportlist since that page may be
1044
+ ** heavily customized by the user. Some variants: ⊚ ⦾ ❊ ⊛ ⚛ ⸎ 💠
1045
+ ** Enclosing it inside of square brackets makes its position
1046
+ ** determenistic and clearly distincts regular submenu links from
1047
+ ** those that are induced by the query string parameters.
1048
+ */
1049
+ if( zQS[0] ){
1050
+ style_submenu_element("Raw","%R/%s?tablist=1&%s",g.zPath,zQS);
1051
+ style_submenu_element("[⊚]","%R/reportlist?%s",zQS);
1052
+ } else {
1053
+ style_submenu_element("Raw","%R/%s?tablist=1",g.zPath);
1054
+ style_submenu_element("[⊚]","%R/reportlist");
1055
+ }
1056
+ style_submenu_parametric("rptview_",5);
1057
+ style_submenu_parametric("rv",5);
1058
+
10381059
if( g.perm.Admin
10391060
|| (g.perm.TktFmt && g.zLogin && fossil_strcmp(g.zLogin,zOwner)==0) ){
10401061
style_submenu_element("Edit", "rptedit?rn=%d", rn);
10411062
}
10421063
if( g.perm.TktFmt ){
10431064
--- src/report.c
+++ src/report.c
@@ -968,14 +968,18 @@
968 }
969
970 /*
971 ** WEBPAGE: rptview
972 **
973 ** Generate a report. The rn query parameter is the report number
974 ** corresponding to REPORTFMT.RN. If the tablist query parameter exists,
975 ** then the output consists of lines of tab-separated fields instead of
976 ** an HTML table.
 
 
 
 
977 */
978 void rptview_page(void){
979 int count = 0;
980 int rn, rc;
981 char *zSql;
@@ -1029,14 +1033,31 @@
1029 }
1030
1031 count = 0;
1032 if( !tabs ){
1033 struct GenerateHTML sState = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
 
1034
1035 db_multi_exec("PRAGMA empty_result_callbacks=ON");
1036 style_set_current_feature("report");
1037 style_submenu_element("Raw", "rptview?tablist=1&rn=%d&%h", rn, PD("QUERY_STRING","") );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1038 if( g.perm.Admin
1039 || (g.perm.TktFmt && g.zLogin && fossil_strcmp(g.zLogin,zOwner)==0) ){
1040 style_submenu_element("Edit", "rptedit?rn=%d", rn);
1041 }
1042 if( g.perm.TktFmt ){
1043
--- src/report.c
+++ src/report.c
@@ -968,14 +968,18 @@
968 }
969
970 /*
971 ** WEBPAGE: rptview
972 **
973 ** Generate a report. The "rn" query parameter is the report number
974 ** corresponding to REPORTFMT.RN. If the "tablist" query parameter exists,
975 ** then the output consists of lines of tab-separated fields instead of
976 ** an HTML table. If the "rvsmpl" query parameter is set then report's
977 ** submenu will contain an extra hyperlink that have a value-driven
978 ** label and target.
979 **
980 ** "rvsmpl" stands for Report View SubMenu's Parametric Link.
981 */
982 void rptview_page(void){
983 int count = 0;
984 int rn, rc;
985 char *zSql;
@@ -1029,14 +1033,31 @@
1033 }
1034
1035 count = 0;
1036 if( !tabs ){
1037 struct GenerateHTML sState = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
1038 const char *zQS = PD("QUERY_STRING","");
1039
1040 db_multi_exec("PRAGMA empty_result_callbacks=ON");
1041 style_set_current_feature("report");
1042 /*
1043 ** Lets use a funcy button for /reportlist since that page may be
1044 ** heavily customized by the user. Some variants: ⊚ ⦾ ❊ ⊛ ⚛ ⸎ 💠
1045 ** Enclosing it inside of square brackets makes its position
1046 ** determenistic and clearly distincts regular submenu links from
1047 ** those that are induced by the query string parameters.
1048 */
1049 if( zQS[0] ){
1050 style_submenu_element("Raw","%R/%s?tablist=1&%s",g.zPath,zQS);
1051 style_submenu_element("[⊚]","%R/reportlist?%s",zQS);
1052 } else {
1053 style_submenu_element("Raw","%R/%s?tablist=1",g.zPath);
1054 style_submenu_element("[⊚]","%R/reportlist");
1055 }
1056 style_submenu_parametric("rptview_",5);
1057 style_submenu_parametric("rv",5);
1058
1059 if( g.perm.Admin
1060 || (g.perm.TktFmt && g.zLogin && fossil_strcmp(g.zLogin,zOwner)==0) ){
1061 style_submenu_element("Edit", "rptedit?rn=%d", rn);
1062 }
1063 if( g.perm.TktFmt ){
1064
+4 -7
--- src/sqlcmd.c
+++ src/sqlcmd.c
@@ -146,12 +146,12 @@
146146
){
147147
gather_artifact_stats(1);
148148
}
149149
150150
/*
151
-** Add the content(), compress(), and decompress() SQL functions to
152
-** database connection db.
151
+** Add the content(), compress(), decompress(), and
152
+** gather_artifact_stats() SQL functions to database connection db.
153153
*/
154154
int add_content_sql_commands(sqlite3 *db){
155155
sqlite3_create_function(db, "content", 1, SQLITE_UTF8, 0,
156156
sqlcmd_content, 0, 0);
157157
sqlite3_create_function(db, "compress", 1, SQLITE_UTF8, 0,
@@ -171,18 +171,18 @@
171171
**
172172
** These invoke the corresponding C routines.
173173
**
174174
** WARNING:
175175
** Do not instantiate these functions for any Fossil webpage or command
176
-** method of than the "fossil sql" command. If an attacker gains access
176
+** method other than the "fossil sql" command. If an attacker gains access
177177
** to these functions, he will be able to disable other defense mechanisms.
178178
**
179179
** This routines are for interactiving testing only. They are experimental
180180
** and undocumented (apart from this comments) and might go away or change
181181
** in future releases.
182182
**
183
-** 2020-11-29: This functions are now only available if the "fossil sql"
183
+** 2020-11-29: These functions are now only available if the "fossil sql"
184184
** command is started with the --test option.
185185
*/
186186
static void sqlcmd_db_protect(
187187
sqlite3_context *context,
188188
int argc,
@@ -204,13 +204,10 @@
204204
int argc,
205205
sqlite3_value **argv
206206
){
207207
if( !local_bSqlCmdTest ) db_protect_pop();
208208
}
209
-
210
-
211
-
212209
213210
/*
214211
** This is the "automatic extension" initializer that runs right after
215212
** the connection to the repository database is opened. Set up the
216213
** database connection to be more useful to the human operator.
217214
--- src/sqlcmd.c
+++ src/sqlcmd.c
@@ -146,12 +146,12 @@
146 ){
147 gather_artifact_stats(1);
148 }
149
150 /*
151 ** Add the content(), compress(), and decompress() SQL functions to
152 ** database connection db.
153 */
154 int add_content_sql_commands(sqlite3 *db){
155 sqlite3_create_function(db, "content", 1, SQLITE_UTF8, 0,
156 sqlcmd_content, 0, 0);
157 sqlite3_create_function(db, "compress", 1, SQLITE_UTF8, 0,
@@ -171,18 +171,18 @@
171 **
172 ** These invoke the corresponding C routines.
173 **
174 ** WARNING:
175 ** Do not instantiate these functions for any Fossil webpage or command
176 ** method of than the "fossil sql" command. If an attacker gains access
177 ** to these functions, he will be able to disable other defense mechanisms.
178 **
179 ** This routines are for interactiving testing only. They are experimental
180 ** and undocumented (apart from this comments) and might go away or change
181 ** in future releases.
182 **
183 ** 2020-11-29: This functions are now only available if the "fossil sql"
184 ** command is started with the --test option.
185 */
186 static void sqlcmd_db_protect(
187 sqlite3_context *context,
188 int argc,
@@ -204,13 +204,10 @@
204 int argc,
205 sqlite3_value **argv
206 ){
207 if( !local_bSqlCmdTest ) db_protect_pop();
208 }
209
210
211
212
213 /*
214 ** This is the "automatic extension" initializer that runs right after
215 ** the connection to the repository database is opened. Set up the
216 ** database connection to be more useful to the human operator.
217
--- src/sqlcmd.c
+++ src/sqlcmd.c
@@ -146,12 +146,12 @@
146 ){
147 gather_artifact_stats(1);
148 }
149
150 /*
151 ** Add the content(), compress(), decompress(), and
152 ** gather_artifact_stats() SQL functions to database connection db.
153 */
154 int add_content_sql_commands(sqlite3 *db){
155 sqlite3_create_function(db, "content", 1, SQLITE_UTF8, 0,
156 sqlcmd_content, 0, 0);
157 sqlite3_create_function(db, "compress", 1, SQLITE_UTF8, 0,
@@ -171,18 +171,18 @@
171 **
172 ** These invoke the corresponding C routines.
173 **
174 ** WARNING:
175 ** Do not instantiate these functions for any Fossil webpage or command
176 ** method other than the "fossil sql" command. If an attacker gains access
177 ** to these functions, he will be able to disable other defense mechanisms.
178 **
179 ** This routines are for interactiving testing only. They are experimental
180 ** and undocumented (apart from this comments) and might go away or change
181 ** in future releases.
182 **
183 ** 2020-11-29: These functions are now only available if the "fossil sql"
184 ** command is started with the --test option.
185 */
186 static void sqlcmd_db_protect(
187 sqlite3_context *context,
188 int argc,
@@ -204,13 +204,10 @@
204 int argc,
205 sqlite3_value **argv
206 ){
207 if( !local_bSqlCmdTest ) db_protect_pop();
208 }
 
 
 
209
210 /*
211 ** This is the "automatic extension" initializer that runs right after
212 ** the connection to the repository database is opened. Set up the
213 ** database connection to be more useful to the human operator.
214
+4 -7
--- src/sqlcmd.c
+++ src/sqlcmd.c
@@ -146,12 +146,12 @@
146146
){
147147
gather_artifact_stats(1);
148148
}
149149
150150
/*
151
-** Add the content(), compress(), and decompress() SQL functions to
152
-** database connection db.
151
+** Add the content(), compress(), decompress(), and
152
+** gather_artifact_stats() SQL functions to database connection db.
153153
*/
154154
int add_content_sql_commands(sqlite3 *db){
155155
sqlite3_create_function(db, "content", 1, SQLITE_UTF8, 0,
156156
sqlcmd_content, 0, 0);
157157
sqlite3_create_function(db, "compress", 1, SQLITE_UTF8, 0,
@@ -171,18 +171,18 @@
171171
**
172172
** These invoke the corresponding C routines.
173173
**
174174
** WARNING:
175175
** Do not instantiate these functions for any Fossil webpage or command
176
-** method of than the "fossil sql" command. If an attacker gains access
176
+** method other than the "fossil sql" command. If an attacker gains access
177177
** to these functions, he will be able to disable other defense mechanisms.
178178
**
179179
** This routines are for interactiving testing only. They are experimental
180180
** and undocumented (apart from this comments) and might go away or change
181181
** in future releases.
182182
**
183
-** 2020-11-29: This functions are now only available if the "fossil sql"
183
+** 2020-11-29: These functions are now only available if the "fossil sql"
184184
** command is started with the --test option.
185185
*/
186186
static void sqlcmd_db_protect(
187187
sqlite3_context *context,
188188
int argc,
@@ -204,13 +204,10 @@
204204
int argc,
205205
sqlite3_value **argv
206206
){
207207
if( !local_bSqlCmdTest ) db_protect_pop();
208208
}
209
-
210
-
211
-
212209
213210
/*
214211
** This is the "automatic extension" initializer that runs right after
215212
** the connection to the repository database is opened. Set up the
216213
** database connection to be more useful to the human operator.
217214
--- src/sqlcmd.c
+++ src/sqlcmd.c
@@ -146,12 +146,12 @@
146 ){
147 gather_artifact_stats(1);
148 }
149
150 /*
151 ** Add the content(), compress(), and decompress() SQL functions to
152 ** database connection db.
153 */
154 int add_content_sql_commands(sqlite3 *db){
155 sqlite3_create_function(db, "content", 1, SQLITE_UTF8, 0,
156 sqlcmd_content, 0, 0);
157 sqlite3_create_function(db, "compress", 1, SQLITE_UTF8, 0,
@@ -171,18 +171,18 @@
171 **
172 ** These invoke the corresponding C routines.
173 **
174 ** WARNING:
175 ** Do not instantiate these functions for any Fossil webpage or command
176 ** method of than the "fossil sql" command. If an attacker gains access
177 ** to these functions, he will be able to disable other defense mechanisms.
178 **
179 ** This routines are for interactiving testing only. They are experimental
180 ** and undocumented (apart from this comments) and might go away or change
181 ** in future releases.
182 **
183 ** 2020-11-29: This functions are now only available if the "fossil sql"
184 ** command is started with the --test option.
185 */
186 static void sqlcmd_db_protect(
187 sqlite3_context *context,
188 int argc,
@@ -204,13 +204,10 @@
204 int argc,
205 sqlite3_value **argv
206 ){
207 if( !local_bSqlCmdTest ) db_protect_pop();
208 }
209
210
211
212
213 /*
214 ** This is the "automatic extension" initializer that runs right after
215 ** the connection to the repository database is opened. Set up the
216 ** database connection to be more useful to the human operator.
217
--- src/sqlcmd.c
+++ src/sqlcmd.c
@@ -146,12 +146,12 @@
146 ){
147 gather_artifact_stats(1);
148 }
149
150 /*
151 ** Add the content(), compress(), decompress(), and
152 ** gather_artifact_stats() SQL functions to database connection db.
153 */
154 int add_content_sql_commands(sqlite3 *db){
155 sqlite3_create_function(db, "content", 1, SQLITE_UTF8, 0,
156 sqlcmd_content, 0, 0);
157 sqlite3_create_function(db, "compress", 1, SQLITE_UTF8, 0,
@@ -171,18 +171,18 @@
171 **
172 ** These invoke the corresponding C routines.
173 **
174 ** WARNING:
175 ** Do not instantiate these functions for any Fossil webpage or command
176 ** method other than the "fossil sql" command. If an attacker gains access
177 ** to these functions, he will be able to disable other defense mechanisms.
178 **
179 ** This routines are for interactiving testing only. They are experimental
180 ** and undocumented (apart from this comments) and might go away or change
181 ** in future releases.
182 **
183 ** 2020-11-29: These functions are now only available if the "fossil sql"
184 ** command is started with the --test option.
185 */
186 static void sqlcmd_db_protect(
187 sqlite3_context *context,
188 int argc,
@@ -204,13 +204,10 @@
204 int argc,
205 sqlite3_value **argv
206 ){
207 if( !local_bSqlCmdTest ) db_protect_pop();
208 }
 
 
 
209
210 /*
211 ** This is the "automatic extension" initializer that runs right after
212 ** the connection to the repository database is opened. Set up the
213 ** database connection to be more useful to the human operator.
214
+62
--- src/style.c
+++ src/style.c
@@ -329,10 +329,72 @@
329329
aSubmenuCtrl[nSubmenuCtrl].eVisible = STYLE_NORMAL;
330330
aSubmenuCtrl[nSubmenuCtrl].eType = FF_MULTI;
331331
nSubmenuCtrl++;
332332
}
333333
}
334
+
335
+/* Add hyperlinks depending on the existence and values of special
336
+** parameters in the request's query string. The names of these
337
+** parameters that are investigated are obtainted by concatenation
338
+** of zPrefix with suffix "smplX", where X is either nothing or
339
+** a positive digit <= nMaxDigit. zPrefix must start with a lowercase
340
+** letter, be short and have no strange characters. A value is
341
+** well-formed if its first filepath segment (separated by '/')
342
+** has no strange characters. The labels of the resulting submenu items
343
+** are equal to the well-formed values that are prepended by "✧"
344
+** unless a value starts with a lowercase letter.
345
+** Malformed values are silently ignored.
346
+*/
347
+void style_submenu_parametric(
348
+ const char *zPrefix, /* common prefix of the query parameters names */
349
+ const int nMaxDigit /* maximal digit on the end of param names */
350
+){
351
+ const char *zQS; /* QUERY_STRING */
352
+ const char *suffix = "smpl"; /* common suffix for all parameters */
353
+ const short sfxlen = 4; /* length of the above suffix */
354
+ char zN[32]; /* short names => no dynamic allocations */
355
+ short i,l;
356
+
357
+ /* zPrefix must be tidy and short; also filter out ENV/CGI variables */
358
+ assert( zPrefix != 0 && fossil_islower(zPrefix[0]) );
359
+ l = strnlen( zPrefix, sizeof(zN) );
360
+ assert( l+sfxlen+2 <= sizeof(zN) );
361
+ assert( fossil_no_strange_characters(zPrefix) );
362
+ /* concatenate zPrefix and suffix */
363
+ strcpy( zN, zPrefix );
364
+ strcpy( zN + l, suffix );
365
+ l += sfxlen;
366
+ zN[l+1] = 0; /* nul-terminator after digit's placeholder (if any) */
367
+ zQS = PD("QUERY_STRING","");
368
+ for( i = 0; i <= 9 && i <= nMaxDigit; i++ ){
369
+ const char *zV, *z;
370
+ zN[l] = ( i == 0 ? 0 : '0' + i ); /* ...smpl instead of ...smpl0 */
371
+ zV = PD(zN,"");
372
+ if( zV[0] == 0 || zV[0] == '/' ){
373
+ continue;
374
+ }
375
+ /* require the first path segment to be unfancy ASCII string */
376
+ for( z = zV; z[0] && z[0] != '/' ;){
377
+ if( fossil_isalnum(z[0]) || z[0]=='_' || z[0]=='-' ) z++;
378
+ else break;
379
+ }
380
+ if( z[0] != 0 && z[0] != '/' )
381
+ continue;
382
+ assert( nSubmenu < count(aSubmenu) );
383
+ if(fossil_islower(zV[0])){
384
+ aSubmenu[nSubmenu].zLabel = mprintf( "%s",zV); /* memory leak? */
385
+ }else{
386
+ aSubmenu[nSubmenu].zLabel = mprintf("✧%s",zV); /* maybe: ◦✧⸰⸎ ✨ */
387
+ }
388
+ if( zQS[0] ){
389
+ aSubmenu[nSubmenu].zLink = mprintf("%R/%s?%s",zV,zQS);
390
+ }else{
391
+ aSubmenu[nSubmenu].zLink = mprintf("%R/%s",zV);
392
+ }
393
+ nSubmenu++;
394
+ }
395
+}
334396
335397
/*
336398
** Disable or enable the submenu
337399
*/
338400
void style_submenu_enable(int onOff){
339401
--- src/style.c
+++ src/style.c
@@ -329,10 +329,72 @@
329 aSubmenuCtrl[nSubmenuCtrl].eVisible = STYLE_NORMAL;
330 aSubmenuCtrl[nSubmenuCtrl].eType = FF_MULTI;
331 nSubmenuCtrl++;
332 }
333 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
334
335 /*
336 ** Disable or enable the submenu
337 */
338 void style_submenu_enable(int onOff){
339
--- src/style.c
+++ src/style.c
@@ -329,10 +329,72 @@
329 aSubmenuCtrl[nSubmenuCtrl].eVisible = STYLE_NORMAL;
330 aSubmenuCtrl[nSubmenuCtrl].eType = FF_MULTI;
331 nSubmenuCtrl++;
332 }
333 }
334
335 /* Add hyperlinks depending on the existence and values of special
336 ** parameters in the request's query string. The names of these
337 ** parameters that are investigated are obtainted by concatenation
338 ** of zPrefix with suffix "smplX", where X is either nothing or
339 ** a positive digit <= nMaxDigit. zPrefix must start with a lowercase
340 ** letter, be short and have no strange characters. A value is
341 ** well-formed if its first filepath segment (separated by '/')
342 ** has no strange characters. The labels of the resulting submenu items
343 ** are equal to the well-formed values that are prepended by "✧"
344 ** unless a value starts with a lowercase letter.
345 ** Malformed values are silently ignored.
346 */
347 void style_submenu_parametric(
348 const char *zPrefix, /* common prefix of the query parameters names */
349 const int nMaxDigit /* maximal digit on the end of param names */
350 ){
351 const char *zQS; /* QUERY_STRING */
352 const char *suffix = "smpl"; /* common suffix for all parameters */
353 const short sfxlen = 4; /* length of the above suffix */
354 char zN[32]; /* short names => no dynamic allocations */
355 short i,l;
356
357 /* zPrefix must be tidy and short; also filter out ENV/CGI variables */
358 assert( zPrefix != 0 && fossil_islower(zPrefix[0]) );
359 l = strnlen( zPrefix, sizeof(zN) );
360 assert( l+sfxlen+2 <= sizeof(zN) );
361 assert( fossil_no_strange_characters(zPrefix) );
362 /* concatenate zPrefix and suffix */
363 strcpy( zN, zPrefix );
364 strcpy( zN + l, suffix );
365 l += sfxlen;
366 zN[l+1] = 0; /* nul-terminator after digit's placeholder (if any) */
367 zQS = PD("QUERY_STRING","");
368 for( i = 0; i <= 9 && i <= nMaxDigit; i++ ){
369 const char *zV, *z;
370 zN[l] = ( i == 0 ? 0 : '0' + i ); /* ...smpl instead of ...smpl0 */
371 zV = PD(zN,"");
372 if( zV[0] == 0 || zV[0] == '/' ){
373 continue;
374 }
375 /* require the first path segment to be unfancy ASCII string */
376 for( z = zV; z[0] && z[0] != '/' ;){
377 if( fossil_isalnum(z[0]) || z[0]=='_' || z[0]=='-' ) z++;
378 else break;
379 }
380 if( z[0] != 0 && z[0] != '/' )
381 continue;
382 assert( nSubmenu < count(aSubmenu) );
383 if(fossil_islower(zV[0])){
384 aSubmenu[nSubmenu].zLabel = mprintf( "%s",zV); /* memory leak? */
385 }else{
386 aSubmenu[nSubmenu].zLabel = mprintf("✧%s",zV); /* maybe: ◦✧⸰⸎ ✨ */
387 }
388 if( zQS[0] ){
389 aSubmenu[nSubmenu].zLink = mprintf("%R/%s?%s",zV,zQS);
390 }else{
391 aSubmenu[nSubmenu].zLink = mprintf("%R/%s",zV);
392 }
393 nSubmenu++;
394 }
395 }
396
397 /*
398 ** Disable or enable the submenu
399 */
400 void style_submenu_enable(int onOff){
401
+1
--- src/wiki.c
+++ src/wiki.c
@@ -593,10 +593,11 @@
593593
}else if( rid && g.perm.ApndWiki ){
594594
style_submenu_element("Edit", "%R/wikiappend?name=%T", zPageName);
595595
}
596596
if( g.perm.Hyperlink ){
597597
style_submenu_element("History", "%R/whistory?name=%T", zPageName);
598
+ style_submenu_parametric("wiki",7);
598599
}
599600
}
600601
if( !isPopup ){
601602
style_set_current_page("%T?name=%T", g.zPath, zPageName);
602603
wiki_page_header(WIKITYPE_UNKNOWN, zPageName, "");
603604
--- src/wiki.c
+++ src/wiki.c
@@ -593,10 +593,11 @@
593 }else if( rid && g.perm.ApndWiki ){
594 style_submenu_element("Edit", "%R/wikiappend?name=%T", zPageName);
595 }
596 if( g.perm.Hyperlink ){
597 style_submenu_element("History", "%R/whistory?name=%T", zPageName);
 
598 }
599 }
600 if( !isPopup ){
601 style_set_current_page("%T?name=%T", g.zPath, zPageName);
602 wiki_page_header(WIKITYPE_UNKNOWN, zPageName, "");
603
--- src/wiki.c
+++ src/wiki.c
@@ -593,10 +593,11 @@
593 }else if( rid && g.perm.ApndWiki ){
594 style_submenu_element("Edit", "%R/wikiappend?name=%T", zPageName);
595 }
596 if( g.perm.Hyperlink ){
597 style_submenu_element("History", "%R/whistory?name=%T", zPageName);
598 style_submenu_parametric("wiki",7);
599 }
600 }
601 if( !isPopup ){
602 style_set_current_page("%T?name=%T", g.zPath, zPageName);
603 wiki_page_header(WIKITYPE_UNKNOWN, zPageName, "");
604
--- www/changes.wiki
+++ www/changes.wiki
@@ -71,10 +71,15 @@
7171
that prints the minimum SQLite version required by the current version
7272
of Fossil. This might be used by integrators who insist on building
7373
Fossil to link against the system SQLite library rather than the
7474
built-in copy of SQLite, to verify that their system SQLite library
7575
is recent enough.
76
+ * Webpage that shows [/help?cmd=/whistory|history of a wiki page]
77
+ gained client-side UI to help with comparison between two arbitrary
78
+ versions of a wiki (by the means of anchoring a "baseline" version)
79
+ and the ability to squeeze several sequential edits made by the same
80
+ user into a single "recycled" row (the latest edit in that sequence).
7681
7782
<a name='v2_14'></a>
7883
<h2>Changes for Version 2.14 (2021-01-20)</h2>
7984
8085
* <b>Schema Update Notice #1:</b>
8186
--- www/changes.wiki
+++ www/changes.wiki
@@ -71,10 +71,15 @@
71 that prints the minimum SQLite version required by the current version
72 of Fossil. This might be used by integrators who insist on building
73 Fossil to link against the system SQLite library rather than the
74 built-in copy of SQLite, to verify that their system SQLite library
75 is recent enough.
 
 
 
 
 
76
77 <a name='v2_14'></a>
78 <h2>Changes for Version 2.14 (2021-01-20)</h2>
79
80 * <b>Schema Update Notice #1:</b>
81
--- www/changes.wiki
+++ www/changes.wiki
@@ -71,10 +71,15 @@
71 that prints the minimum SQLite version required by the current version
72 of Fossil. This might be used by integrators who insist on building
73 Fossil to link against the system SQLite library rather than the
74 built-in copy of SQLite, to verify that their system SQLite library
75 is recent enough.
76 * Webpage that shows [/help?cmd=/whistory|history of a wiki page]
77 gained client-side UI to help with comparison between two arbitrary
78 versions of a wiki (by the means of anchoring a "baseline" version)
79 and the ability to squeeze several sequential edits made by the same
80 user into a single "recycled" row (the latest edit in that sequence).
81
82 <a name='v2_14'></a>
83 <h2>Changes for Version 2.14 (2021-01-20)</h2>
84
85 * <b>Schema Update Notice #1:</b>
86
--- www/quickstart.wiki
+++ www/quickstart.wiki
@@ -242,10 +242,36 @@
242242
</blockquote>
243243
244244
<p>"fossil diff" is the difference between your tree on disk now and as the tree was
245245
when you did "fossil open". An open is the first checkout from a repository
246246
into a new directory. </p>
247
+
248
+<p>To see the most recent changes made to the repository by other users, use "fossil timeline" to
249
+find out the most recent commit, and then "fossil diff" between that commit and the
250
+current tree: </p>
251
+<blockquote>
252
+<b>
253
+ fossil timeline <br><tt>
254
+ === 2021-03-28 === <br>
255
+ 03:18:54 [ad75dfa4a0] *CURRENT* Added details to frobnicate command (user: user-one tags: trunk) <br>
256
+ === 2021-03-27 === <br>
257
+ 23:58:05 [ab975c6632] Update README.md. (user: user-two tags: trunk) <br>
258
+ ⋮ <br>
259
+ </tt><br>
260
+ fossil diff --from current --to ab975c6632 <br><tt>
261
+ Index: frobnicate.c<br>
262
+ ============================================================<br>
263
+ --- frobnicate.c<br>
264
+ +++ frobnicate.c<br>
265
+ @@ -1,10 +1,11 @@<br>
266
+ +/* made a change to the source file */<br>
267
+ # Original text<br>
268
+</tt></b>
269
+</blockquote>
270
+
271
+"current" is an alias for the most recent version, so the command
272
+"fossil diff --from ad75dfa4a0 --to ab975c6632" gives identical results.
247273
248274
<p>To commit your changes to a local-only repository:</p>
249275
<blockquote>
250276
<b>
251277
fossil commit </b><i>(... Fossil will start your editor, if defined)</i><b><br><tt>
252278
--- www/quickstart.wiki
+++ www/quickstart.wiki
@@ -242,10 +242,36 @@
242 </blockquote>
243
244 <p>"fossil diff" is the difference between your tree on disk now and as the tree was
245 when you did "fossil open". An open is the first checkout from a repository
246 into a new directory. </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
248 <p>To commit your changes to a local-only repository:</p>
249 <blockquote>
250 <b>
251 fossil commit </b><i>(... Fossil will start your editor, if defined)</i><b><br><tt>
252
--- www/quickstart.wiki
+++ www/quickstart.wiki
@@ -242,10 +242,36 @@
242 </blockquote>
243
244 <p>"fossil diff" is the difference between your tree on disk now and as the tree was
245 when you did "fossil open". An open is the first checkout from a repository
246 into a new directory. </p>
247
248 <p>To see the most recent changes made to the repository by other users, use "fossil timeline" to
249 find out the most recent commit, and then "fossil diff" between that commit and the
250 current tree: </p>
251 <blockquote>
252 <b>
253 fossil timeline <br><tt>
254 === 2021-03-28 === <br>
255 03:18:54 [ad75dfa4a0] *CURRENT* Added details to frobnicate command (user: user-one tags: trunk) <br>
256 === 2021-03-27 === <br>
257 23:58:05 [ab975c6632] Update README.md. (user: user-two tags: trunk) <br>
258 ⋮ <br>
259 </tt><br>
260 fossil diff --from current --to ab975c6632 <br><tt>
261 Index: frobnicate.c<br>
262 ============================================================<br>
263 --- frobnicate.c<br>
264 +++ frobnicate.c<br>
265 @@ -1,10 +1,11 @@<br>
266 +/* made a change to the source file */<br>
267 # Original text<br>
268 </tt></b>
269 </blockquote>
270
271 "current" is an alias for the most recent version, so the command
272 "fossil diff --from ad75dfa4a0 --to ab975c6632" gives identical results.
273
274 <p>To commit your changes to a local-only repository:</p>
275 <blockquote>
276 <b>
277 fossil commit </b><i>(... Fossil will start your editor, if defined)</i><b><br><tt>
278
--- www/rebaseharm.md
+++ www/rebaseharm.md
@@ -293,22 +293,16 @@
293293
unique timestamps for C3' and C5' but then you lose the information
294294
about when those check-ins were originally created, which can make
295295
historical analysis of changes more difficult. It might also
296296
complicate the legal defense of prior art claims.
297297
298
-## <a name="lying"></a>5.0 Rebasing is lying about the project history
298
+## <a name="lying"></a>5.0 Rebase misrepresents the project history
299299
300300
By discarding parentage information, rebase attempts to deceive the
301301
reader about how the code actually came together.
302302
303
-You may be tempted to dismiss this as an anti-Git opinion on a Fossil
304
-web site, but it’s spelled out just like that [in the Git rebase
305
-documentation][gitrebase]. It speaks of “lying,” “telling stories,”
306
-and “blasphemy.”
307
-
308
-That section of the Git docs is contrasting rebase with merge, which we
309
-cover [above](#cap-loss), but Git’s rebase feature is more than just an
303
+Git’s rebase feature is more than just an
310304
alternative to merging: it also provides mechanisms for changing the
311305
project history in order to make editorial changes. Fossil shows that
312306
you can get similar effects without modifying historical records,
313307
allowing users to:
314308
315309
--- www/rebaseharm.md
+++ www/rebaseharm.md
@@ -293,22 +293,16 @@
293 unique timestamps for C3' and C5' but then you lose the information
294 about when those check-ins were originally created, which can make
295 historical analysis of changes more difficult. It might also
296 complicate the legal defense of prior art claims.
297
298 ## <a name="lying"></a>5.0 Rebasing is lying about the project history
299
300 By discarding parentage information, rebase attempts to deceive the
301 reader about how the code actually came together.
302
303 You may be tempted to dismiss this as an anti-Git opinion on a Fossil
304 web site, but it’s spelled out just like that [in the Git rebase
305 documentation][gitrebase]. It speaks of “lying,” “telling stories,”
306 and “blasphemy.”
307
308 That section of the Git docs is contrasting rebase with merge, which we
309 cover [above](#cap-loss), but Git’s rebase feature is more than just an
310 alternative to merging: it also provides mechanisms for changing the
311 project history in order to make editorial changes. Fossil shows that
312 you can get similar effects without modifying historical records,
313 allowing users to:
314
315
--- www/rebaseharm.md
+++ www/rebaseharm.md
@@ -293,22 +293,16 @@
293 unique timestamps for C3' and C5' but then you lose the information
294 about when those check-ins were originally created, which can make
295 historical analysis of changes more difficult. It might also
296 complicate the legal defense of prior art claims.
297
298 ## <a name="lying"></a>5.0 Rebase misrepresents the project history
299
300 By discarding parentage information, rebase attempts to deceive the
301 reader about how the code actually came together.
302
303 Git’s rebase feature is more than just an
 
 
 
 
 
 
304 alternative to merging: it also provides mechanisms for changing the
305 project history in order to make editorial changes. Fossil shows that
306 you can get similar effects without modifying historical records,
307 allowing users to:
308
309

Keyboard Shortcuts

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