Fossil SCM

Merge the latest trunk changes into this branch.

drh 2025-03-14 09:53 comment-markdown-links merge
Commit 2cb7fefcea129767bb7f508bdee726c4eea3b709341479144203b650a3db41ef
--- .fossil-settings/crlf-glob
+++ .fossil-settings/crlf-glob
@@ -1,5 +1,7 @@
11
compat/zlib/*
22
setup/fossil.iss
33
test/th1-docs-input.txt
44
test/th1-hooks-input.txt
5
+win/build32.bat
6
+win/build64.bat
57
win/buildmsvc.bat
68
--- .fossil-settings/crlf-glob
+++ .fossil-settings/crlf-glob
@@ -1,5 +1,7 @@
1 compat/zlib/*
2 setup/fossil.iss
3 test/th1-docs-input.txt
4 test/th1-hooks-input.txt
 
 
5 win/buildmsvc.bat
6
--- .fossil-settings/crlf-glob
+++ .fossil-settings/crlf-glob
@@ -1,5 +1,7 @@
1 compat/zlib/*
2 setup/fossil.iss
3 test/th1-docs-input.txt
4 test/th1-hooks-input.txt
5 win/build32.bat
6 win/build64.bat
7 win/buildmsvc.bat
8
+1 -1
--- src/add.c
+++ src/add.c
@@ -78,11 +78,11 @@
7878
static int numManifests;
7979
8080
if( cachedManifest == -1 ){
8181
int i;
8282
Blob repo;
83
- cachedManifest = db_get_manifest_setting();
83
+ cachedManifest = db_get_manifest_setting(0);
8484
numManifests = 0;
8585
for(i=0; i<count(aManifestflags); i++){
8686
if( cachedManifest&aManifestflags[i].flg ) {
8787
azManifests[numManifests++] = aManifestflags[i].fname;
8888
}
8989
--- src/add.c
+++ src/add.c
@@ -78,11 +78,11 @@
78 static int numManifests;
79
80 if( cachedManifest == -1 ){
81 int i;
82 Blob repo;
83 cachedManifest = db_get_manifest_setting();
84 numManifests = 0;
85 for(i=0; i<count(aManifestflags); i++){
86 if( cachedManifest&aManifestflags[i].flg ) {
87 azManifests[numManifests++] = aManifestflags[i].fname;
88 }
89
--- src/add.c
+++ src/add.c
@@ -78,11 +78,11 @@
78 static int numManifests;
79
80 if( cachedManifest == -1 ){
81 int i;
82 Blob repo;
83 cachedManifest = db_get_manifest_setting(0);
84 numManifests = 0;
85 for(i=0; i<count(aManifestflags); i++){
86 if( cachedManifest&aManifestflags[i].flg ) {
87 azManifests[numManifests++] = aManifestflags[i].fname;
88 }
89
+24 -3
--- src/alerts.c
+++ src/alerts.c
@@ -51,10 +51,11 @@
5151
@ -- f - Forum posts
5252
@ -- k - ** Special: Unsubscribed using /oneclickunsub
5353
@ -- n - New forum threads
5454
@ -- r - Replies to my own forum posts
5555
@ -- t - Ticket changes
56
+@ -- u - Elevation of users' permissions (admins only)
5657
@ -- w - Wiki changes
5758
@ -- x - Edits to forum posts
5859
@ -- Probably different codes will be added in the future. In the future
5960
@ -- we might also add a separate table that allows subscribing to email
6061
@ -- notifications for specific branches or tags or tickets.
@@ -1563,10 +1564,11 @@
15631564
if( g.perm.Read && PB("sc") ) ssub[nsub++] = 'c';
15641565
if( g.perm.RdForum && PB("sf") ) ssub[nsub++] = 'f';
15651566
if( g.perm.RdForum && PB("sn") ) ssub[nsub++] = 'n';
15661567
if( g.perm.RdForum && PB("sr") ) ssub[nsub++] = 'r';
15671568
if( g.perm.RdTkt && PB("st") ) ssub[nsub++] = 't';
1569
+ if( g.perm.Admin && PB("su") ) ssub[nsub++] = 'u';
15681570
if( g.perm.RdWiki && PB("sw") ) ssub[nsub++] = 'w';
15691571
if( g.perm.RdForum && PB("sx") ) ssub[nsub++] = 'x';
15701572
ssub[nsub] = 0;
15711573
zCode = db_text(0,
15721574
"INSERT INTO subscriber(semail,suname,"
@@ -1627,10 +1629,11 @@
16271629
if( g.perm.Read ) cgi_set_parameter_nocopy("sc","1",1);
16281630
if( g.perm.RdForum ) cgi_set_parameter_nocopy("sf","1",1);
16291631
if( g.perm.RdForum ) cgi_set_parameter_nocopy("sn","1",1);
16301632
if( g.perm.RdForum ) cgi_set_parameter_nocopy("sr","1",1);
16311633
if( g.perm.RdTkt ) cgi_set_parameter_nocopy("st","1",1);
1634
+ if( g.perm.Admin ) cgi_set_parameter_nocopy("su","1",1);
16321635
if( g.perm.RdWiki ) cgi_set_parameter_nocopy("sw","1",1);
16331636
}
16341637
@ <p>To receive email notifications for changes to this
16351638
@ repository, fill out the form below and press the "Submit" button.</p>
16361639
form_begin(0, "%R/subscribe");
@@ -1699,10 +1702,14 @@
16991702
}
17001703
if( g.perm.RdWiki ){
17011704
@ <label><input type="checkbox" name="sw" %s(PCK("sw"))> \
17021705
@ Wiki</label><br>
17031706
}
1707
+ if( g.perm.Admin ){
1708
+ @ <label><input type="checkbox" name="su" %s(PCK("su"))> \
1709
+ @ User permission elevation</label>
1710
+ }
17041711
di = PB("di");
17051712
@ </td></tr>
17061713
@ <tr>
17071714
@ <td class="form_label">Delivery:</td>
17081715
@ <td><select size="1" name="di">
@@ -1820,11 +1827,11 @@
18201827
** verifying the email address.
18211828
*/
18221829
void alert_page(void){
18231830
const char *zName = 0; /* Value of the name= query parameter */
18241831
Stmt q; /* For querying the database */
1825
- int sa, sc, sf, st, sw, sx; /* Types of notifications requested */
1832
+ int sa, sc, sf, st, su, sw, sx; /* Types of notifications requested */
18261833
int sn, sr;
18271834
int sdigest = 0, sdonotcall = 0, sverified = 0; /* Other fields */
18281835
int isLogin; /* True if logged in as an individual */
18291836
const char *ssub = 0; /* Subscription flags */
18301837
const char *semail = 0; /* Email address */
@@ -1881,10 +1888,11 @@
18811888
if( g.perm.Read && PB("sc") ) newSsub[nsub++] = 'c';
18821889
if( g.perm.RdForum && PB("sf") ) newSsub[nsub++] = 'f';
18831890
if( g.perm.RdForum && PB("sn") ) newSsub[nsub++] = 'n';
18841891
if( g.perm.RdForum && PB("sr") ) newSsub[nsub++] = 'r';
18851892
if( g.perm.RdTkt && PB("st") ) newSsub[nsub++] = 't';
1893
+ if( g.perm.Admin && PB("su") ) newSsub[nsub++] = 'u';
18861894
if( g.perm.RdWiki && PB("sw") ) newSsub[nsub++] = 'w';
18871895
if( g.perm.RdForum && PB("sx") ) newSsub[nsub++] = 'x';
18881896
newSsub[nsub] = 0;
18891897
ssub = newSsub;
18901898
blob_init(&update, "UPDATE subscriber SET", -1);
@@ -1979,10 +1987,11 @@
19791987
sc = strchr(ssub,'c')!=0;
19801988
sf = strchr(ssub,'f')!=0;
19811989
sn = strchr(ssub,'n')!=0;
19821990
sr = strchr(ssub,'r')!=0;
19831991
st = strchr(ssub,'t')!=0;
1992
+ su = strchr(ssub,'u')!=0;
19841993
sw = strchr(ssub,'w')!=0;
19851994
sx = strchr(ssub,'x')!=0;
19861995
smip = db_column_text(&q, 5);
19871996
mtime = db_column_text(&q, 7);
19881997
sctime = db_column_text(&q, 8);
@@ -2097,11 +2106,19 @@
20972106
@ <label><input type="checkbox" name="st" %s(st?"checked":"")>\
20982107
@ Ticket changes</label><br>
20992108
}
21002109
if( g.perm.RdWiki ){
21012110
@ <label><input type="checkbox" name="sw" %s(sw?"checked":"")>\
2102
- @ Wiki</label>
2111
+ @ Wiki</label><br>
2112
+ }
2113
+ if( g.perm.Admin ){
2114
+ /* Corner-case bug: if an admin assigns 'u' to a non-admin, that
2115
+ ** subscription will get removed if the user later edits their
2116
+ ** subscriptions, as non-admins are not permitted to add that
2117
+ ** subscription. */
2118
+ @ <label><input type="checkbox" name="su" %s(su?"checked":"")>\
2119
+ @ User permission elevation</label>
21032120
}
21042121
@ </td></tr>
21052122
if( strchr(ssub,'k')!=0 ){
21062123
@ <tr><td></td><td>&nbsp;&uarr;&nbsp;
21072124
@ Note: User did a one-click unsubscribe</td></tr>
@@ -2529,15 +2546,16 @@
25292546
** f An original forum post
25302547
** n New forum threads
25312548
** r Replies to my forum posts
25322549
** x An edit to a prior forum post
25332550
** t A new ticket or a change to an existing ticket
2551
+** u A user was added or received new permissions
25342552
** w A change to a wiki page
25352553
** x Edits to forum posts
25362554
*/
25372555
struct EmailEvent {
2538
- int type; /* 'c', 'f', 'n', 'r', 't', 'w', 'x' */
2556
+ int type; /* 'c', 'f', 'n', 'r', 't', 'u', 'w', 'x' */
25392557
int needMod; /* Pending moderator approval */
25402558
Blob hdr; /* Header content, for forum entries */
25412559
Blob txt; /* Text description to appear in an alert */
25422560
char *zFromName; /* Human name of the sender */
25432561
char *zPriors; /* Upthread sender IDs for forum posts */
@@ -2932,10 +2950,11 @@
29322950
);
29332951
if( strchr(zSub, 'a') ) blob_appendf(pBody, " * Announcements\n");
29342952
if( strchr(zSub, 'c') ) blob_appendf(pBody, " * Check-ins\n");
29352953
if( strchr(zSub, 'f') ) blob_appendf(pBody, " * Forum posts\n");
29362954
if( strchr(zSub, 't') ) blob_appendf(pBody, " * Ticket changes\n");
2955
+ if( strchr(zSub, 'u') ) blob_appendf(pBody, " * User permission elevation\n");
29372956
if( strchr(zSub, 'w') ) blob_appendf(pBody, " * Wiki changes\n");
29382957
blob_appendf(pBody, "\n"
29392958
"If you take no action, your subscription will expire and you will be\n"
29402959
"unsubscribed in about %d days. To make other changes or to unsubscribe\n"
29412960
"immediately, visit the following webpage:\n\n"
@@ -3144,10 +3163,11 @@
31443163
switch( p->type ){
31453164
case 'x': case 'f':
31463165
case 'n': case 'r': xType = '5'; break;
31473166
case 't': xType = 'q'; break;
31483167
case 'w': xType = 'l'; break;
3168
+ /* Note: case 'u' is not handled here */
31493169
}
31503170
if( strchr(zCap,xType)==0 ) continue;
31513171
}
31523172
}else if( strchr(zCap,'s')!=0 || strchr(zCap,'a')!=0 ){
31533173
/* Setup and admin users can get any notification that does not
@@ -3160,10 +3180,11 @@
31603180
case 'c': xType = 'o'; break;
31613181
case 'x': case 'f':
31623182
case 'n': case 'r': xType = '2'; break;
31633183
case 't': xType = 'r'; break;
31643184
case 'w': xType = 'j'; break;
3185
+ /* Note: case 'u' is not handled here */
31653186
}
31663187
if( strchr(zCap,xType)==0 ) continue;
31673188
}
31683189
if( blob_size(&p->hdr)>0 ){
31693190
/* This alert should be sent as a separate email */
31703191
--- src/alerts.c
+++ src/alerts.c
@@ -51,10 +51,11 @@
51 @ -- f - Forum posts
52 @ -- k - ** Special: Unsubscribed using /oneclickunsub
53 @ -- n - New forum threads
54 @ -- r - Replies to my own forum posts
55 @ -- t - Ticket changes
 
56 @ -- w - Wiki changes
57 @ -- x - Edits to forum posts
58 @ -- Probably different codes will be added in the future. In the future
59 @ -- we might also add a separate table that allows subscribing to email
60 @ -- notifications for specific branches or tags or tickets.
@@ -1563,10 +1564,11 @@
1563 if( g.perm.Read && PB("sc") ) ssub[nsub++] = 'c';
1564 if( g.perm.RdForum && PB("sf") ) ssub[nsub++] = 'f';
1565 if( g.perm.RdForum && PB("sn") ) ssub[nsub++] = 'n';
1566 if( g.perm.RdForum && PB("sr") ) ssub[nsub++] = 'r';
1567 if( g.perm.RdTkt && PB("st") ) ssub[nsub++] = 't';
 
1568 if( g.perm.RdWiki && PB("sw") ) ssub[nsub++] = 'w';
1569 if( g.perm.RdForum && PB("sx") ) ssub[nsub++] = 'x';
1570 ssub[nsub] = 0;
1571 zCode = db_text(0,
1572 "INSERT INTO subscriber(semail,suname,"
@@ -1627,10 +1629,11 @@
1627 if( g.perm.Read ) cgi_set_parameter_nocopy("sc","1",1);
1628 if( g.perm.RdForum ) cgi_set_parameter_nocopy("sf","1",1);
1629 if( g.perm.RdForum ) cgi_set_parameter_nocopy("sn","1",1);
1630 if( g.perm.RdForum ) cgi_set_parameter_nocopy("sr","1",1);
1631 if( g.perm.RdTkt ) cgi_set_parameter_nocopy("st","1",1);
 
1632 if( g.perm.RdWiki ) cgi_set_parameter_nocopy("sw","1",1);
1633 }
1634 @ <p>To receive email notifications for changes to this
1635 @ repository, fill out the form below and press the "Submit" button.</p>
1636 form_begin(0, "%R/subscribe");
@@ -1699,10 +1702,14 @@
1699 }
1700 if( g.perm.RdWiki ){
1701 @ <label><input type="checkbox" name="sw" %s(PCK("sw"))> \
1702 @ Wiki</label><br>
1703 }
 
 
 
 
1704 di = PB("di");
1705 @ </td></tr>
1706 @ <tr>
1707 @ <td class="form_label">Delivery:</td>
1708 @ <td><select size="1" name="di">
@@ -1820,11 +1827,11 @@
1820 ** verifying the email address.
1821 */
1822 void alert_page(void){
1823 const char *zName = 0; /* Value of the name= query parameter */
1824 Stmt q; /* For querying the database */
1825 int sa, sc, sf, st, sw, sx; /* Types of notifications requested */
1826 int sn, sr;
1827 int sdigest = 0, sdonotcall = 0, sverified = 0; /* Other fields */
1828 int isLogin; /* True if logged in as an individual */
1829 const char *ssub = 0; /* Subscription flags */
1830 const char *semail = 0; /* Email address */
@@ -1881,10 +1888,11 @@
1881 if( g.perm.Read && PB("sc") ) newSsub[nsub++] = 'c';
1882 if( g.perm.RdForum && PB("sf") ) newSsub[nsub++] = 'f';
1883 if( g.perm.RdForum && PB("sn") ) newSsub[nsub++] = 'n';
1884 if( g.perm.RdForum && PB("sr") ) newSsub[nsub++] = 'r';
1885 if( g.perm.RdTkt && PB("st") ) newSsub[nsub++] = 't';
 
1886 if( g.perm.RdWiki && PB("sw") ) newSsub[nsub++] = 'w';
1887 if( g.perm.RdForum && PB("sx") ) newSsub[nsub++] = 'x';
1888 newSsub[nsub] = 0;
1889 ssub = newSsub;
1890 blob_init(&update, "UPDATE subscriber SET", -1);
@@ -1979,10 +1987,11 @@
1979 sc = strchr(ssub,'c')!=0;
1980 sf = strchr(ssub,'f')!=0;
1981 sn = strchr(ssub,'n')!=0;
1982 sr = strchr(ssub,'r')!=0;
1983 st = strchr(ssub,'t')!=0;
 
1984 sw = strchr(ssub,'w')!=0;
1985 sx = strchr(ssub,'x')!=0;
1986 smip = db_column_text(&q, 5);
1987 mtime = db_column_text(&q, 7);
1988 sctime = db_column_text(&q, 8);
@@ -2097,11 +2106,19 @@
2097 @ <label><input type="checkbox" name="st" %s(st?"checked":"")>\
2098 @ Ticket changes</label><br>
2099 }
2100 if( g.perm.RdWiki ){
2101 @ <label><input type="checkbox" name="sw" %s(sw?"checked":"")>\
2102 @ Wiki</label>
 
 
 
 
 
 
 
 
2103 }
2104 @ </td></tr>
2105 if( strchr(ssub,'k')!=0 ){
2106 @ <tr><td></td><td>&nbsp;&uarr;&nbsp;
2107 @ Note: User did a one-click unsubscribe</td></tr>
@@ -2529,15 +2546,16 @@
2529 ** f An original forum post
2530 ** n New forum threads
2531 ** r Replies to my forum posts
2532 ** x An edit to a prior forum post
2533 ** t A new ticket or a change to an existing ticket
 
2534 ** w A change to a wiki page
2535 ** x Edits to forum posts
2536 */
2537 struct EmailEvent {
2538 int type; /* 'c', 'f', 'n', 'r', 't', 'w', 'x' */
2539 int needMod; /* Pending moderator approval */
2540 Blob hdr; /* Header content, for forum entries */
2541 Blob txt; /* Text description to appear in an alert */
2542 char *zFromName; /* Human name of the sender */
2543 char *zPriors; /* Upthread sender IDs for forum posts */
@@ -2932,10 +2950,11 @@
2932 );
2933 if( strchr(zSub, 'a') ) blob_appendf(pBody, " * Announcements\n");
2934 if( strchr(zSub, 'c') ) blob_appendf(pBody, " * Check-ins\n");
2935 if( strchr(zSub, 'f') ) blob_appendf(pBody, " * Forum posts\n");
2936 if( strchr(zSub, 't') ) blob_appendf(pBody, " * Ticket changes\n");
 
2937 if( strchr(zSub, 'w') ) blob_appendf(pBody, " * Wiki changes\n");
2938 blob_appendf(pBody, "\n"
2939 "If you take no action, your subscription will expire and you will be\n"
2940 "unsubscribed in about %d days. To make other changes or to unsubscribe\n"
2941 "immediately, visit the following webpage:\n\n"
@@ -3144,10 +3163,11 @@
3144 switch( p->type ){
3145 case 'x': case 'f':
3146 case 'n': case 'r': xType = '5'; break;
3147 case 't': xType = 'q'; break;
3148 case 'w': xType = 'l'; break;
 
3149 }
3150 if( strchr(zCap,xType)==0 ) continue;
3151 }
3152 }else if( strchr(zCap,'s')!=0 || strchr(zCap,'a')!=0 ){
3153 /* Setup and admin users can get any notification that does not
@@ -3160,10 +3180,11 @@
3160 case 'c': xType = 'o'; break;
3161 case 'x': case 'f':
3162 case 'n': case 'r': xType = '2'; break;
3163 case 't': xType = 'r'; break;
3164 case 'w': xType = 'j'; break;
 
3165 }
3166 if( strchr(zCap,xType)==0 ) continue;
3167 }
3168 if( blob_size(&p->hdr)>0 ){
3169 /* This alert should be sent as a separate email */
3170
--- src/alerts.c
+++ src/alerts.c
@@ -51,10 +51,11 @@
51 @ -- f - Forum posts
52 @ -- k - ** Special: Unsubscribed using /oneclickunsub
53 @ -- n - New forum threads
54 @ -- r - Replies to my own forum posts
55 @ -- t - Ticket changes
56 @ -- u - Elevation of users' permissions (admins only)
57 @ -- w - Wiki changes
58 @ -- x - Edits to forum posts
59 @ -- Probably different codes will be added in the future. In the future
60 @ -- we might also add a separate table that allows subscribing to email
61 @ -- notifications for specific branches or tags or tickets.
@@ -1563,10 +1564,11 @@
1564 if( g.perm.Read && PB("sc") ) ssub[nsub++] = 'c';
1565 if( g.perm.RdForum && PB("sf") ) ssub[nsub++] = 'f';
1566 if( g.perm.RdForum && PB("sn") ) ssub[nsub++] = 'n';
1567 if( g.perm.RdForum && PB("sr") ) ssub[nsub++] = 'r';
1568 if( g.perm.RdTkt && PB("st") ) ssub[nsub++] = 't';
1569 if( g.perm.Admin && PB("su") ) ssub[nsub++] = 'u';
1570 if( g.perm.RdWiki && PB("sw") ) ssub[nsub++] = 'w';
1571 if( g.perm.RdForum && PB("sx") ) ssub[nsub++] = 'x';
1572 ssub[nsub] = 0;
1573 zCode = db_text(0,
1574 "INSERT INTO subscriber(semail,suname,"
@@ -1627,10 +1629,11 @@
1629 if( g.perm.Read ) cgi_set_parameter_nocopy("sc","1",1);
1630 if( g.perm.RdForum ) cgi_set_parameter_nocopy("sf","1",1);
1631 if( g.perm.RdForum ) cgi_set_parameter_nocopy("sn","1",1);
1632 if( g.perm.RdForum ) cgi_set_parameter_nocopy("sr","1",1);
1633 if( g.perm.RdTkt ) cgi_set_parameter_nocopy("st","1",1);
1634 if( g.perm.Admin ) cgi_set_parameter_nocopy("su","1",1);
1635 if( g.perm.RdWiki ) cgi_set_parameter_nocopy("sw","1",1);
1636 }
1637 @ <p>To receive email notifications for changes to this
1638 @ repository, fill out the form below and press the "Submit" button.</p>
1639 form_begin(0, "%R/subscribe");
@@ -1699,10 +1702,14 @@
1702 }
1703 if( g.perm.RdWiki ){
1704 @ <label><input type="checkbox" name="sw" %s(PCK("sw"))> \
1705 @ Wiki</label><br>
1706 }
1707 if( g.perm.Admin ){
1708 @ <label><input type="checkbox" name="su" %s(PCK("su"))> \
1709 @ User permission elevation</label>
1710 }
1711 di = PB("di");
1712 @ </td></tr>
1713 @ <tr>
1714 @ <td class="form_label">Delivery:</td>
1715 @ <td><select size="1" name="di">
@@ -1820,11 +1827,11 @@
1827 ** verifying the email address.
1828 */
1829 void alert_page(void){
1830 const char *zName = 0; /* Value of the name= query parameter */
1831 Stmt q; /* For querying the database */
1832 int sa, sc, sf, st, su, sw, sx; /* Types of notifications requested */
1833 int sn, sr;
1834 int sdigest = 0, sdonotcall = 0, sverified = 0; /* Other fields */
1835 int isLogin; /* True if logged in as an individual */
1836 const char *ssub = 0; /* Subscription flags */
1837 const char *semail = 0; /* Email address */
@@ -1881,10 +1888,11 @@
1888 if( g.perm.Read && PB("sc") ) newSsub[nsub++] = 'c';
1889 if( g.perm.RdForum && PB("sf") ) newSsub[nsub++] = 'f';
1890 if( g.perm.RdForum && PB("sn") ) newSsub[nsub++] = 'n';
1891 if( g.perm.RdForum && PB("sr") ) newSsub[nsub++] = 'r';
1892 if( g.perm.RdTkt && PB("st") ) newSsub[nsub++] = 't';
1893 if( g.perm.Admin && PB("su") ) newSsub[nsub++] = 'u';
1894 if( g.perm.RdWiki && PB("sw") ) newSsub[nsub++] = 'w';
1895 if( g.perm.RdForum && PB("sx") ) newSsub[nsub++] = 'x';
1896 newSsub[nsub] = 0;
1897 ssub = newSsub;
1898 blob_init(&update, "UPDATE subscriber SET", -1);
@@ -1979,10 +1987,11 @@
1987 sc = strchr(ssub,'c')!=0;
1988 sf = strchr(ssub,'f')!=0;
1989 sn = strchr(ssub,'n')!=0;
1990 sr = strchr(ssub,'r')!=0;
1991 st = strchr(ssub,'t')!=0;
1992 su = strchr(ssub,'u')!=0;
1993 sw = strchr(ssub,'w')!=0;
1994 sx = strchr(ssub,'x')!=0;
1995 smip = db_column_text(&q, 5);
1996 mtime = db_column_text(&q, 7);
1997 sctime = db_column_text(&q, 8);
@@ -2097,11 +2106,19 @@
2106 @ <label><input type="checkbox" name="st" %s(st?"checked":"")>\
2107 @ Ticket changes</label><br>
2108 }
2109 if( g.perm.RdWiki ){
2110 @ <label><input type="checkbox" name="sw" %s(sw?"checked":"")>\
2111 @ Wiki</label><br>
2112 }
2113 if( g.perm.Admin ){
2114 /* Corner-case bug: if an admin assigns 'u' to a non-admin, that
2115 ** subscription will get removed if the user later edits their
2116 ** subscriptions, as non-admins are not permitted to add that
2117 ** subscription. */
2118 @ <label><input type="checkbox" name="su" %s(su?"checked":"")>\
2119 @ User permission elevation</label>
2120 }
2121 @ </td></tr>
2122 if( strchr(ssub,'k')!=0 ){
2123 @ <tr><td></td><td>&nbsp;&uarr;&nbsp;
2124 @ Note: User did a one-click unsubscribe</td></tr>
@@ -2529,15 +2546,16 @@
2546 ** f An original forum post
2547 ** n New forum threads
2548 ** r Replies to my forum posts
2549 ** x An edit to a prior forum post
2550 ** t A new ticket or a change to an existing ticket
2551 ** u A user was added or received new permissions
2552 ** w A change to a wiki page
2553 ** x Edits to forum posts
2554 */
2555 struct EmailEvent {
2556 int type; /* 'c', 'f', 'n', 'r', 't', 'u', 'w', 'x' */
2557 int needMod; /* Pending moderator approval */
2558 Blob hdr; /* Header content, for forum entries */
2559 Blob txt; /* Text description to appear in an alert */
2560 char *zFromName; /* Human name of the sender */
2561 char *zPriors; /* Upthread sender IDs for forum posts */
@@ -2932,10 +2950,11 @@
2950 );
2951 if( strchr(zSub, 'a') ) blob_appendf(pBody, " * Announcements\n");
2952 if( strchr(zSub, 'c') ) blob_appendf(pBody, " * Check-ins\n");
2953 if( strchr(zSub, 'f') ) blob_appendf(pBody, " * Forum posts\n");
2954 if( strchr(zSub, 't') ) blob_appendf(pBody, " * Ticket changes\n");
2955 if( strchr(zSub, 'u') ) blob_appendf(pBody, " * User permission elevation\n");
2956 if( strchr(zSub, 'w') ) blob_appendf(pBody, " * Wiki changes\n");
2957 blob_appendf(pBody, "\n"
2958 "If you take no action, your subscription will expire and you will be\n"
2959 "unsubscribed in about %d days. To make other changes or to unsubscribe\n"
2960 "immediately, visit the following webpage:\n\n"
@@ -3144,10 +3163,11 @@
3163 switch( p->type ){
3164 case 'x': case 'f':
3165 case 'n': case 'r': xType = '5'; break;
3166 case 't': xType = 'q'; break;
3167 case 'w': xType = 'l'; break;
3168 /* Note: case 'u' is not handled here */
3169 }
3170 if( strchr(zCap,xType)==0 ) continue;
3171 }
3172 }else if( strchr(zCap,'s')!=0 || strchr(zCap,'a')!=0 ){
3173 /* Setup and admin users can get any notification that does not
@@ -3160,10 +3180,11 @@
3180 case 'c': xType = 'o'; break;
3181 case 'x': case 'f':
3182 case 'n': case 'r': xType = '2'; break;
3183 case 't': xType = 'r'; break;
3184 case 'w': xType = 'j'; break;
3185 /* Note: case 'u' is not handled here */
3186 }
3187 if( strchr(zCap,xType)==0 ) continue;
3188 }
3189 if( blob_size(&p->hdr)>0 ){
3190 /* This alert should be sent as a separate email */
3191
+88 -13
--- src/checkin.c
+++ src/checkin.c
@@ -2281,10 +2281,64 @@
22812281
static int tagCmp(const void *a, const void *b){
22822282
char **pA = (char**)a;
22832283
char **pB = (char**)b;
22842284
return fossil_strcmp(pA[0], pB[0]);
22852285
}
2286
+
2287
+/*
2288
+** Check for possible formatting errors in the comment string pComment.
2289
+** If found, write a description of the problem(s) into pSus and return
2290
+** true. If everything looks ok, return false.
2291
+*/
2292
+static int suspicious_comment(Blob *pComment, Blob *pSus){
2293
+ char *zStart = blob_str(pComment);
2294
+ char *z;
2295
+ char *zEnd, *zEnd2;
2296
+ char *zSep;
2297
+ char cSave1;
2298
+ int nIssue = 0;
2299
+
2300
+ z = zStart;
2301
+ while( (z = strchr(z,'['))!=0 ){
2302
+ zEnd = strchr(z,']');
2303
+ if( zEnd==0 ){
2304
+ blob_appendf(pSus,"\n (%d) ", ++nIssue);
2305
+ blob_appendf(pSus, "Unterminated hyperlink \"%.12s...\"", z);
2306
+ break;
2307
+ }
2308
+ if( zEnd[1]=='(' && (zEnd2 = strchr(zEnd,')'))!=0 ){
2309
+ blob_appendf(pSus,"\n (%d) ", ++nIssue);
2310
+ blob_appendf(pSus, "Markdown hyperlink syntax: %.*s",
2311
+ (int)(zEnd2+1-z), z);
2312
+ z = zEnd2;
2313
+ continue;
2314
+ }
2315
+ zSep = strchr(z+1,'|');
2316
+ if( zSep==0 || zSep>zEnd ) zSep = zEnd;
2317
+ cSave1 = zEnd[0];
2318
+ zEnd[0] = 0;
2319
+ if( !wiki_valid_link_target(z+1) ){
2320
+ blob_appendf(pSus,"\n (%d) ", ++nIssue);
2321
+ blob_appendf(pSus, "Broken hyperlink: [%s]", z+1);
2322
+ }
2323
+ zEnd[0] = cSave1;
2324
+ z = zEnd;
2325
+ }
2326
+ if( nIssue ){
2327
+ Blob tmp = *pSus;
2328
+ blob_init(pSus, 0, 0);
2329
+ blob_appendf(pSus,
2330
+ "Possible comment formatting error%s:"
2331
+ "%b\n"
2332
+ "Edit, continue or abort (E/c/a)? ",
2333
+ nIssue>1 ? "s" : "", &tmp);
2334
+ blob_reset(&tmp);
2335
+ return 1;
2336
+ }else{
2337
+ return 0;
2338
+ }
2339
+}
22862340
22872341
/*
22882342
** COMMAND: ci#
22892343
** COMMAND: commit
22902344
**
@@ -2499,11 +2553,11 @@
24992553
sCiInfo.zUserOvrd = find_option("user-override",0,1);
25002554
noSign = db_get_boolean("omitsign", 0)|noSign;
25012555
if( db_get_boolean("clearsign", 0)==0 ){ noSign = 1; }
25022556
useCksum = db_get_boolean("repo-cksum", 1);
25032557
bIgnoreSkew = find_option("ignore-clock-skew",0,0)!=0;
2504
- outputManifest = db_get_manifest_setting();
2558
+ outputManifest = db_get_manifest_setting(0);
25052559
mxSize = db_large_file_size();
25062560
if( find_option("ignore-oversize",0,0)!=0 ) mxSize = 0;
25072561
verify_all_options();
25082562
25092563
/* Get the ID of the parent manifest artifact */
@@ -2801,22 +2855,43 @@
28012855
}else if( zComFile ){
28022856
blob_zero(&comment);
28032857
blob_read_from_file(&comment, zComFile, ExtFILE);
28042858
blob_to_utf8_no_bom(&comment, 1);
28052859
}else if( !noPrompt ){
2806
- char *zInit = db_text(0,"SELECT value FROM vvar WHERE name='ci-comment'");
2807
- prepare_commit_comment(&comment, zInit, &sCiInfo, vid, dryRunFlag);
2808
- if( zInit && zInit[0] && fossil_strcmp(zInit, blob_str(&comment))==0 ){
2809
- prompt_user("unchanged check-in comment. continue (y/N)? ", &ans);
2810
- cReply = blob_str(&ans)[0];
2811
- blob_reset(&ans);
2812
- if( cReply!='y' && cReply!='Y' ){
2813
- fossil_fatal("Commit aborted.");
2814
- }
2815
- }
2816
- free(zInit);
2817
- db_multi_exec("REPLACE INTO vvar VALUES('ci-comment',%B)", &comment);
2860
+ while( 1/*exit-by-break*/ ){
2861
+ char *zInit;
2862
+ Blob sus;
2863
+
2864
+ zInit = db_text(0,"SELECT value FROM vvar WHERE name='ci-comment'");
2865
+ prepare_commit_comment(&comment, zInit, &sCiInfo, vid, dryRunFlag);
2866
+ db_multi_exec("REPLACE INTO vvar VALUES('ci-comment',%B)", &comment);
2867
+ blob_init(&sus, 0, 0);
2868
+ if( suspicious_comment(&comment,&sus) ){
2869
+ prompt_user(blob_str(&sus), &ans);
2870
+ cReply = blob_str(&ans)[0];
2871
+ blob_reset(&ans);
2872
+ blob_reset(&sus);
2873
+ if( cReply=='e' || cReply=='E' ){
2874
+ fossil_free(zInit);
2875
+ continue;
2876
+ }
2877
+ if( cReply!='c' && cReply!='C' ){
2878
+ fossil_fatal("Commit aborted.");
2879
+ }
2880
+ }
2881
+ if( zInit && zInit[0] && fossil_strcmp(zInit, blob_str(&comment))==0 ){
2882
+ prompt_user("unchanged check-in comment. continue (y/N)? ", &ans);
2883
+ cReply = blob_str(&ans)[0];
2884
+ blob_reset(&ans);
2885
+ if( cReply!='y' && cReply!='Y' ){
2886
+ fossil_fatal("Commit aborted.");
2887
+ }
2888
+ }
2889
+ fossil_free(zInit);
2890
+ break;
2891
+ }
2892
+
28182893
db_end_transaction(0);
28192894
db_begin_transaction();
28202895
if( !g.markPrivate && vid!=0 && !allowFork && !forceFlag ){
28212896
/* Do another auto-pull, renewing the check-in lock. Then set
28222897
** bRecheck so that we loop back above to verify that the check-in
28232898
--- src/checkin.c
+++ src/checkin.c
@@ -2281,10 +2281,64 @@
2281 static int tagCmp(const void *a, const void *b){
2282 char **pA = (char**)a;
2283 char **pB = (char**)b;
2284 return fossil_strcmp(pA[0], pB[0]);
2285 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2286
2287 /*
2288 ** COMMAND: ci#
2289 ** COMMAND: commit
2290 **
@@ -2499,11 +2553,11 @@
2499 sCiInfo.zUserOvrd = find_option("user-override",0,1);
2500 noSign = db_get_boolean("omitsign", 0)|noSign;
2501 if( db_get_boolean("clearsign", 0)==0 ){ noSign = 1; }
2502 useCksum = db_get_boolean("repo-cksum", 1);
2503 bIgnoreSkew = find_option("ignore-clock-skew",0,0)!=0;
2504 outputManifest = db_get_manifest_setting();
2505 mxSize = db_large_file_size();
2506 if( find_option("ignore-oversize",0,0)!=0 ) mxSize = 0;
2507 verify_all_options();
2508
2509 /* Get the ID of the parent manifest artifact */
@@ -2801,22 +2855,43 @@
2801 }else if( zComFile ){
2802 blob_zero(&comment);
2803 blob_read_from_file(&comment, zComFile, ExtFILE);
2804 blob_to_utf8_no_bom(&comment, 1);
2805 }else if( !noPrompt ){
2806 char *zInit = db_text(0,"SELECT value FROM vvar WHERE name='ci-comment'");
2807 prepare_commit_comment(&comment, zInit, &sCiInfo, vid, dryRunFlag);
2808 if( zInit && zInit[0] && fossil_strcmp(zInit, blob_str(&comment))==0 ){
2809 prompt_user("unchanged check-in comment. continue (y/N)? ", &ans);
2810 cReply = blob_str(&ans)[0];
2811 blob_reset(&ans);
2812 if( cReply!='y' && cReply!='Y' ){
2813 fossil_fatal("Commit aborted.");
2814 }
2815 }
2816 free(zInit);
2817 db_multi_exec("REPLACE INTO vvar VALUES('ci-comment',%B)", &comment);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2818 db_end_transaction(0);
2819 db_begin_transaction();
2820 if( !g.markPrivate && vid!=0 && !allowFork && !forceFlag ){
2821 /* Do another auto-pull, renewing the check-in lock. Then set
2822 ** bRecheck so that we loop back above to verify that the check-in
2823
--- src/checkin.c
+++ src/checkin.c
@@ -2281,10 +2281,64 @@
2281 static int tagCmp(const void *a, const void *b){
2282 char **pA = (char**)a;
2283 char **pB = (char**)b;
2284 return fossil_strcmp(pA[0], pB[0]);
2285 }
2286
2287 /*
2288 ** Check for possible formatting errors in the comment string pComment.
2289 ** If found, write a description of the problem(s) into pSus and return
2290 ** true. If everything looks ok, return false.
2291 */
2292 static int suspicious_comment(Blob *pComment, Blob *pSus){
2293 char *zStart = blob_str(pComment);
2294 char *z;
2295 char *zEnd, *zEnd2;
2296 char *zSep;
2297 char cSave1;
2298 int nIssue = 0;
2299
2300 z = zStart;
2301 while( (z = strchr(z,'['))!=0 ){
2302 zEnd = strchr(z,']');
2303 if( zEnd==0 ){
2304 blob_appendf(pSus,"\n (%d) ", ++nIssue);
2305 blob_appendf(pSus, "Unterminated hyperlink \"%.12s...\"", z);
2306 break;
2307 }
2308 if( zEnd[1]=='(' && (zEnd2 = strchr(zEnd,')'))!=0 ){
2309 blob_appendf(pSus,"\n (%d) ", ++nIssue);
2310 blob_appendf(pSus, "Markdown hyperlink syntax: %.*s",
2311 (int)(zEnd2+1-z), z);
2312 z = zEnd2;
2313 continue;
2314 }
2315 zSep = strchr(z+1,'|');
2316 if( zSep==0 || zSep>zEnd ) zSep = zEnd;
2317 cSave1 = zEnd[0];
2318 zEnd[0] = 0;
2319 if( !wiki_valid_link_target(z+1) ){
2320 blob_appendf(pSus,"\n (%d) ", ++nIssue);
2321 blob_appendf(pSus, "Broken hyperlink: [%s]", z+1);
2322 }
2323 zEnd[0] = cSave1;
2324 z = zEnd;
2325 }
2326 if( nIssue ){
2327 Blob tmp = *pSus;
2328 blob_init(pSus, 0, 0);
2329 blob_appendf(pSus,
2330 "Possible comment formatting error%s:"
2331 "%b\n"
2332 "Edit, continue or abort (E/c/a)? ",
2333 nIssue>1 ? "s" : "", &tmp);
2334 blob_reset(&tmp);
2335 return 1;
2336 }else{
2337 return 0;
2338 }
2339 }
2340
2341 /*
2342 ** COMMAND: ci#
2343 ** COMMAND: commit
2344 **
@@ -2499,11 +2553,11 @@
2553 sCiInfo.zUserOvrd = find_option("user-override",0,1);
2554 noSign = db_get_boolean("omitsign", 0)|noSign;
2555 if( db_get_boolean("clearsign", 0)==0 ){ noSign = 1; }
2556 useCksum = db_get_boolean("repo-cksum", 1);
2557 bIgnoreSkew = find_option("ignore-clock-skew",0,0)!=0;
2558 outputManifest = db_get_manifest_setting(0);
2559 mxSize = db_large_file_size();
2560 if( find_option("ignore-oversize",0,0)!=0 ) mxSize = 0;
2561 verify_all_options();
2562
2563 /* Get the ID of the parent manifest artifact */
@@ -2801,22 +2855,43 @@
2855 }else if( zComFile ){
2856 blob_zero(&comment);
2857 blob_read_from_file(&comment, zComFile, ExtFILE);
2858 blob_to_utf8_no_bom(&comment, 1);
2859 }else if( !noPrompt ){
2860 while( 1/*exit-by-break*/ ){
2861 char *zInit;
2862 Blob sus;
2863
2864 zInit = db_text(0,"SELECT value FROM vvar WHERE name='ci-comment'");
2865 prepare_commit_comment(&comment, zInit, &sCiInfo, vid, dryRunFlag);
2866 db_multi_exec("REPLACE INTO vvar VALUES('ci-comment',%B)", &comment);
2867 blob_init(&sus, 0, 0);
2868 if( suspicious_comment(&comment,&sus) ){
2869 prompt_user(blob_str(&sus), &ans);
2870 cReply = blob_str(&ans)[0];
2871 blob_reset(&ans);
2872 blob_reset(&sus);
2873 if( cReply=='e' || cReply=='E' ){
2874 fossil_free(zInit);
2875 continue;
2876 }
2877 if( cReply!='c' && cReply!='C' ){
2878 fossil_fatal("Commit aborted.");
2879 }
2880 }
2881 if( zInit && zInit[0] && fossil_strcmp(zInit, blob_str(&comment))==0 ){
2882 prompt_user("unchanged check-in comment. continue (y/N)? ", &ans);
2883 cReply = blob_str(&ans)[0];
2884 blob_reset(&ans);
2885 if( cReply!='y' && cReply!='Y' ){
2886 fossil_fatal("Commit aborted.");
2887 }
2888 }
2889 fossil_free(zInit);
2890 break;
2891 }
2892
2893 db_end_transaction(0);
2894 db_begin_transaction();
2895 if( !g.markPrivate && vid!=0 && !allowFork && !forceFlag ){
2896 /* Do another auto-pull, renewing the check-in lock. Then set
2897 ** bRecheck so that we loop back above to verify that the check-in
2898
+1 -1
--- src/checkout.c
+++ src/checkout.c
@@ -173,11 +173,11 @@
173173
*/
174174
void manifest_to_disk(int vid){
175175
char *zManFile;
176176
int flg;
177177
178
- flg = db_get_manifest_setting();
178
+ flg = db_get_manifest_setting(0);
179179
180180
if( flg & MFESTFLG_RAW ){
181181
Blob manifest = BLOB_INITIALIZER;
182182
content_get(vid, &manifest);
183183
sterilize_manifest(&manifest, CFTYPE_MANIFEST);
184184
--- src/checkout.c
+++ src/checkout.c
@@ -173,11 +173,11 @@
173 */
174 void manifest_to_disk(int vid){
175 char *zManFile;
176 int flg;
177
178 flg = db_get_manifest_setting();
179
180 if( flg & MFESTFLG_RAW ){
181 Blob manifest = BLOB_INITIALIZER;
182 content_get(vid, &manifest);
183 sterilize_manifest(&manifest, CFTYPE_MANIFEST);
184
--- src/checkout.c
+++ src/checkout.c
@@ -173,11 +173,11 @@
173 */
174 void manifest_to_disk(int vid){
175 char *zManFile;
176 int flg;
177
178 flg = db_get_manifest_setting(0);
179
180 if( flg & MFESTFLG_RAW ){
181 Blob manifest = BLOB_INITIALIZER;
182 content_get(vid, &manifest);
183 sterilize_manifest(&manifest, CFTYPE_MANIFEST);
184
+117 -53
--- src/db.c
+++ src/db.c
@@ -3647,66 +3647,79 @@
36473647
**
36483648
** If the zNonVersionedSetting parameter is not NULL then it holds the
36493649
** non-versioned value for this setting. If both a versioned and a
36503650
** non-versioned value exist and are not equal, then a warning message
36513651
** might be generated.
3652
+**
3653
+** zCkin is normally NULL. In that case, the versioned setting is
3654
+** take from the local check-out, if a local checkout exists, or from
3655
+** checkin named by the g.zOpenRevision global variable. If zCkin is
3656
+** not NULL, then zCkin is the name of the specific checkin from which
3657
+** versioned setting value is taken. When zCkin is not NULL, the cache
3658
+** is bypassed.
36523659
*/
3653
-char *db_get_versioned(const char *zName, char *zNonVersionedSetting){
3660
+char *db_get_versioned(
3661
+ const char *zName,
3662
+ char *zNonVersionedSetting,
3663
+ const char *zCkin
3664
+){
36543665
char *zVersionedSetting = 0;
36553666
int noWarn = 0;
36563667
int found = 0;
36573668
struct _cacheEntry {
36583669
struct _cacheEntry *next;
36593670
const char *zName, *zValue;
36603671
} *cacheEntry = 0;
36613672
static struct _cacheEntry *cache = 0;
36623673
3663
- if( !g.localOpen && g.zOpenRevision==0 ) return zNonVersionedSetting;
3674
+ if( !g.localOpen && g.zOpenRevision==0 && zCkin==0 ){
3675
+ return zNonVersionedSetting;
3676
+ }
3677
+
36643678
/* Look up name in cache */
3665
- cacheEntry = cache;
3666
- while( cacheEntry!=0 ){
3667
- if( fossil_strcmp(cacheEntry->zName, zName)==0 ){
3668
- zVersionedSetting = fossil_strdup(cacheEntry->zValue);
3669
- break;
3670
- }
3671
- cacheEntry = cacheEntry->next;
3672
- }
3679
+ if( zCkin==0 ){
3680
+ cacheEntry = cache;
3681
+ while( cacheEntry!=0 ){
3682
+ if( fossil_strcmp(cacheEntry->zName, zName)==0 ){
3683
+ zVersionedSetting = fossil_strdup(cacheEntry->zValue);
3684
+ break;
3685
+ }
3686
+ cacheEntry = cacheEntry->next;
3687
+ }
3688
+ }
3689
+
36733690
/* Attempt to read value from file in check-out if there wasn't a cache hit.*/
36743691
if( cacheEntry==0 ){
36753692
Blob versionedPathname;
36763693
Blob setting;
3677
- blob_zero(&versionedPathname);
3678
- blob_zero(&setting);
3679
- blob_appendf(&versionedPathname, "%s.fossil-settings/%s",
3680
- g.zLocalRoot, zName);
3681
- if( !g.localOpen ){
3682
- /* Repository is in the process of being opened, but files have not been
3683
- * written to disk. Load from the database. */
3684
- Blob noWarnFile;
3685
- if( historical_blob(g.zOpenRevision, blob_str(&versionedPathname),
3686
- &setting, 0) ){
3687
- found = 1;
3688
- }
3689
- /* See if there's a no-warn flag */
3690
- blob_append(&versionedPathname, ".no-warn", -1);
3691
- blob_zero(&noWarnFile);
3692
- if( historical_blob(g.zOpenRevision, blob_str(&versionedPathname),
3693
- &noWarnFile, 0) ){
3694
- noWarn = 1;
3695
- }
3696
- blob_reset(&noWarnFile);
3697
- }else if( file_size(blob_str(&versionedPathname), ExtFILE)>=0 ){
3698
- /* File exists, and contains the value for this setting. Load from
3699
- ** the file. */
3700
- const char *zFile = blob_str(&versionedPathname);
3701
- if( blob_read_from_file(&setting, zFile, ExtFILE)>=0 ){
3702
- found = 1;
3703
- }
3704
- /* See if there's a no-warn flag */
3705
- blob_append(&versionedPathname, ".no-warn", -1);
3706
- if( file_size(blob_str(&versionedPathname), ExtFILE)>=0 ){
3707
- noWarn = 1;
3694
+ blob_init(&versionedPathname, 0, 0);
3695
+ blob_init(&setting, 0, 0);
3696
+ if( !g.localOpen || zCkin!=0 ){
3697
+ /* Repository is in the process of being opened, but files have not been
3698
+ * written to disk. Load from the database. */
3699
+ blob_appendf(&versionedPathname, ".fossil-settings/%s", zName);
3700
+ if( historical_blob(zCkin ? zCkin : g.zOpenRevision,
3701
+ blob_str(&versionedPathname),
3702
+ &setting, 0)
3703
+ ){
3704
+ found = 1;
3705
+ }
3706
+ }else{
3707
+ blob_appendf(&versionedPathname, "%s.fossil-settings/%s",
3708
+ g.zLocalRoot, zName);
3709
+ if( file_size(blob_str(&versionedPathname), ExtFILE)>=0 ){
3710
+ /* File exists, and contains the value for this setting. Load from
3711
+ ** the file. */
3712
+ const char *zFile = blob_str(&versionedPathname);
3713
+ if( blob_read_from_file(&setting, zFile, ExtFILE)>=0 ){
3714
+ found = 1;
3715
+ }
3716
+ /* See if there's a no-warn flag */
3717
+ blob_append(&versionedPathname, ".no-warn", -1);
3718
+ if( file_size(blob_str(&versionedPathname), ExtFILE)>=0 ){
3719
+ noWarn = 1;
3720
+ }
37083721
}
37093722
}
37103723
blob_reset(&versionedPathname);
37113724
if( found ){
37123725
blob_strip_comment_lines(&setting, &setting);
@@ -3713,20 +3726,27 @@
37133726
blob_trim(&setting); /* Avoid non-obvious problems with line endings
37143727
** on boolean properties */
37153728
zVersionedSetting = fossil_strdup(blob_str(&setting));
37163729
}
37173730
blob_reset(&setting);
3731
+
37183732
/* Store result in cache, which can be the value or 0 if not found */
3719
- cacheEntry = (struct _cacheEntry*)fossil_malloc(sizeof(struct _cacheEntry));
3720
- cacheEntry->next = cache;
3721
- cacheEntry->zName = zName;
3722
- cacheEntry->zValue = fossil_strdup(zVersionedSetting);
3723
- cache = cacheEntry;
3733
+ if( zCkin==0 ){
3734
+ cacheEntry = (struct _cacheEntry*)fossil_malloc(sizeof(*cacheEntry));
3735
+ cacheEntry->next = cache;
3736
+ cacheEntry->zName = zName;
3737
+ cacheEntry->zValue = fossil_strdup(zVersionedSetting);
3738
+ cache = cacheEntry;
3739
+ }
37243740
}
3741
+
37253742
/* Display a warning? */
3726
- if( zVersionedSetting!=0 && zNonVersionedSetting!=0
3727
- && zNonVersionedSetting[0]!='\0' && !noWarn
3743
+ if( zVersionedSetting!=0
3744
+ && zNonVersionedSetting!=0
3745
+ && zNonVersionedSetting[0]!='\0'
3746
+ && zCkin==0
3747
+ && !noWarn
37283748
){
37293749
/* There's a versioned setting, and a non-versioned setting. Tell
37303750
** the user about the conflict */
37313751
fossil_warning(
37323752
"setting %s has both versioned and non-versioned values: using "
@@ -3735,10 +3755,11 @@
37353755
"\"%/.fossil-settings/%s.no-warn\" in the check-out root, or delete "
37363756
"the non-versioned setting with \"fossil unset %s\")", zName,
37373757
g.zLocalRoot, zName, g.zLocalRoot, zName, zName
37383758
);
37393759
}
3760
+
37403761
/* Prefer the versioned setting */
37413762
return ( zVersionedSetting!=0 ) ? zVersionedSetting : zNonVersionedSetting;
37423763
}
37433764
37443765
@@ -3778,11 +3799,11 @@
37783799
}
37793800
if( pSetting!=0 && pSetting->versionable ){
37803801
/* This is a versionable setting, try and get the info from a
37813802
** checked-out file */
37823803
char * zZ = z;
3783
- z = db_get_versioned(zName, z);
3804
+ z = db_get_versioned(zName, z, 0);
37843805
if(zZ != z){
37853806
fossil_free(zZ);
37863807
}
37873808
}
37883809
if( z==0 ){
@@ -3919,11 +3940,11 @@
39193940
}
39203941
fossil_free(zVal);
39213942
return dflt;
39223943
}
39233944
int db_get_versioned_boolean(const char *zName, int dflt){
3924
- char *zVal = db_get_versioned(zName, 0);
3945
+ char *zVal = db_get_versioned(zName, 0, 0);
39253946
if( zVal==0 ) return dflt;
39263947
if( is_truth(zVal) ) return 1;
39273948
if( is_false(zVal) ) return 0;
39283949
return dflt;
39293950
}
@@ -4048,14 +4069,30 @@
40484069
** Get the manifest setting. For backwards compatibility first check if the
40494070
** value is a boolean. If it's not a boolean, treat each character as a flag
40504071
** to enable a manifest type. This system puts certain boundary conditions on
40514072
** which letters can be used to represent flags (any permutation of flags must
40524073
** not be able to fully form one of the boolean values).
4074
+**
4075
+** "manifest" is a versionable setting. But we do not issue a warning
4076
+** if there is a conflict. Instead, the value returned is the value for
4077
+** the versioned setting if the versioned setting exists, or the ordinary
4078
+** setting otherwise.
4079
+**
4080
+** The argument zCkin is the specific check-in for which we want the
4081
+** manifest setting.
40534082
*/
4054
-int db_get_manifest_setting(void){
4083
+int db_get_manifest_setting(const char *zCkin){
40554084
int flg;
4056
- char *zVal = db_get("manifest", 0);
4085
+ char *zVal;
4086
+
4087
+ /* Look for the versioned setting first */
4088
+ zVal = db_get_versioned("manifest", 0, zCkin);
4089
+
4090
+ if( zVal==0 && g.repositoryOpen ){
4091
+ /* No versioned setting, look for the repository setting second */
4092
+ zVal = db_text(0, "SELECT value FROM config WHERE name='manifest'");
4093
+ }
40574094
if( zVal==0 || is_false(zVal) ){
40584095
return 0;
40594096
}else if( is_truth(zVal) ){
40604097
return MFESTFLG_RAW|MFESTFLG_UUID;
40614098
}
@@ -4068,10 +4105,37 @@
40684105
}
40694106
zVal++;
40704107
}
40714108
return flg;
40724109
}
4110
+
4111
+/*
4112
+** COMMAND: test-manifest-setting
4113
+**
4114
+** Usage: %fossil test-manifest-setting VERSION VERSION ...
4115
+**
4116
+** Display the value for the "manifest" setting for various versions
4117
+** of the repository.
4118
+*/
4119
+void test_manfest_setting_cmd(void){
4120
+ int i;
4121
+ db_find_and_open_repository(0, 0);
4122
+ for(i=2; i<g.argc; i++){
4123
+ int m = db_get_manifest_setting(g.argv[i]);
4124
+ fossil_print("%s:\n", g.argv[i]);
4125
+ fossil_print(" flags = 0x%02x\n", m);
4126
+ if( m & MFESTFLG_RAW ){
4127
+ fossil_print(" manifest\n");
4128
+ }
4129
+ if( m & MFESTFLG_UUID ){
4130
+ fossil_print(" manifest.uuid\n");
4131
+ }
4132
+ if( m & MFESTFLG_TAGS ){
4133
+ fossil_print(" manifest.tags\n");
4134
+ }
4135
+ }
4136
+}
40734137
40744138
40754139
/*
40764140
** Record the name of a local repository in the global_config() database.
40774141
** The repository filename %s is recorded as an entry with a "name" field
@@ -4391,11 +4455,11 @@
43914455
versioned = 1;
43924456
}
43934457
blob_reset(&versionedPathname);
43944458
}
43954459
if( valueOnly && versioned ){
4396
- fossil_print("%s\n", db_get_versioned(pSetting->name, NULL));
4460
+ fossil_print("%s\n", db_get_versioned(pSetting->name, NULL, NULL));
43974461
return;
43984462
}
43994463
if( g.repositoryOpen ){
44004464
db_prepare(&q,
44014465
"SELECT '(local)', value FROM config WHERE name=%Q"
44024466
--- src/db.c
+++ src/db.c
@@ -3647,66 +3647,79 @@
3647 **
3648 ** If the zNonVersionedSetting parameter is not NULL then it holds the
3649 ** non-versioned value for this setting. If both a versioned and a
3650 ** non-versioned value exist and are not equal, then a warning message
3651 ** might be generated.
 
 
 
 
 
 
 
3652 */
3653 char *db_get_versioned(const char *zName, char *zNonVersionedSetting){
 
 
 
 
3654 char *zVersionedSetting = 0;
3655 int noWarn = 0;
3656 int found = 0;
3657 struct _cacheEntry {
3658 struct _cacheEntry *next;
3659 const char *zName, *zValue;
3660 } *cacheEntry = 0;
3661 static struct _cacheEntry *cache = 0;
3662
3663 if( !g.localOpen && g.zOpenRevision==0 ) return zNonVersionedSetting;
 
 
 
3664 /* Look up name in cache */
3665 cacheEntry = cache;
3666 while( cacheEntry!=0 ){
3667 if( fossil_strcmp(cacheEntry->zName, zName)==0 ){
3668 zVersionedSetting = fossil_strdup(cacheEntry->zValue);
3669 break;
3670 }
3671 cacheEntry = cacheEntry->next;
3672 }
 
 
 
3673 /* Attempt to read value from file in check-out if there wasn't a cache hit.*/
3674 if( cacheEntry==0 ){
3675 Blob versionedPathname;
3676 Blob setting;
3677 blob_zero(&versionedPathname);
3678 blob_zero(&setting);
3679 blob_appendf(&versionedPathname, "%s.fossil-settings/%s",
3680 g.zLocalRoot, zName);
3681 if( !g.localOpen ){
3682 /* Repository is in the process of being opened, but files have not been
3683 * written to disk. Load from the database. */
3684 Blob noWarnFile;
3685 if( historical_blob(g.zOpenRevision, blob_str(&versionedPathname),
3686 &setting, 0) ){
3687 found = 1;
3688 }
3689 /* See if there's a no-warn flag */
3690 blob_append(&versionedPathname, ".no-warn", -1);
3691 blob_zero(&noWarnFile);
3692 if( historical_blob(g.zOpenRevision, blob_str(&versionedPathname),
3693 &noWarnFile, 0) ){
3694 noWarn = 1;
3695 }
3696 blob_reset(&noWarnFile);
3697 }else if( file_size(blob_str(&versionedPathname), ExtFILE)>=0 ){
3698 /* File exists, and contains the value for this setting. Load from
3699 ** the file. */
3700 const char *zFile = blob_str(&versionedPathname);
3701 if( blob_read_from_file(&setting, zFile, ExtFILE)>=0 ){
3702 found = 1;
3703 }
3704 /* See if there's a no-warn flag */
3705 blob_append(&versionedPathname, ".no-warn", -1);
3706 if( file_size(blob_str(&versionedPathname), ExtFILE)>=0 ){
3707 noWarn = 1;
3708 }
3709 }
3710 blob_reset(&versionedPathname);
3711 if( found ){
3712 blob_strip_comment_lines(&setting, &setting);
@@ -3713,20 +3726,27 @@
3713 blob_trim(&setting); /* Avoid non-obvious problems with line endings
3714 ** on boolean properties */
3715 zVersionedSetting = fossil_strdup(blob_str(&setting));
3716 }
3717 blob_reset(&setting);
 
3718 /* Store result in cache, which can be the value or 0 if not found */
3719 cacheEntry = (struct _cacheEntry*)fossil_malloc(sizeof(struct _cacheEntry));
3720 cacheEntry->next = cache;
3721 cacheEntry->zName = zName;
3722 cacheEntry->zValue = fossil_strdup(zVersionedSetting);
3723 cache = cacheEntry;
 
 
3724 }
 
3725 /* Display a warning? */
3726 if( zVersionedSetting!=0 && zNonVersionedSetting!=0
3727 && zNonVersionedSetting[0]!='\0' && !noWarn
 
 
 
3728 ){
3729 /* There's a versioned setting, and a non-versioned setting. Tell
3730 ** the user about the conflict */
3731 fossil_warning(
3732 "setting %s has both versioned and non-versioned values: using "
@@ -3735,10 +3755,11 @@
3735 "\"%/.fossil-settings/%s.no-warn\" in the check-out root, or delete "
3736 "the non-versioned setting with \"fossil unset %s\")", zName,
3737 g.zLocalRoot, zName, g.zLocalRoot, zName, zName
3738 );
3739 }
 
3740 /* Prefer the versioned setting */
3741 return ( zVersionedSetting!=0 ) ? zVersionedSetting : zNonVersionedSetting;
3742 }
3743
3744
@@ -3778,11 +3799,11 @@
3778 }
3779 if( pSetting!=0 && pSetting->versionable ){
3780 /* This is a versionable setting, try and get the info from a
3781 ** checked-out file */
3782 char * zZ = z;
3783 z = db_get_versioned(zName, z);
3784 if(zZ != z){
3785 fossil_free(zZ);
3786 }
3787 }
3788 if( z==0 ){
@@ -3919,11 +3940,11 @@
3919 }
3920 fossil_free(zVal);
3921 return dflt;
3922 }
3923 int db_get_versioned_boolean(const char *zName, int dflt){
3924 char *zVal = db_get_versioned(zName, 0);
3925 if( zVal==0 ) return dflt;
3926 if( is_truth(zVal) ) return 1;
3927 if( is_false(zVal) ) return 0;
3928 return dflt;
3929 }
@@ -4048,14 +4069,30 @@
4048 ** Get the manifest setting. For backwards compatibility first check if the
4049 ** value is a boolean. If it's not a boolean, treat each character as a flag
4050 ** to enable a manifest type. This system puts certain boundary conditions on
4051 ** which letters can be used to represent flags (any permutation of flags must
4052 ** not be able to fully form one of the boolean values).
 
 
 
 
 
 
 
 
4053 */
4054 int db_get_manifest_setting(void){
4055 int flg;
4056 char *zVal = db_get("manifest", 0);
 
 
 
 
 
 
 
 
4057 if( zVal==0 || is_false(zVal) ){
4058 return 0;
4059 }else if( is_truth(zVal) ){
4060 return MFESTFLG_RAW|MFESTFLG_UUID;
4061 }
@@ -4068,10 +4105,37 @@
4068 }
4069 zVal++;
4070 }
4071 return flg;
4072 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4073
4074
4075 /*
4076 ** Record the name of a local repository in the global_config() database.
4077 ** The repository filename %s is recorded as an entry with a "name" field
@@ -4391,11 +4455,11 @@
4391 versioned = 1;
4392 }
4393 blob_reset(&versionedPathname);
4394 }
4395 if( valueOnly && versioned ){
4396 fossil_print("%s\n", db_get_versioned(pSetting->name, NULL));
4397 return;
4398 }
4399 if( g.repositoryOpen ){
4400 db_prepare(&q,
4401 "SELECT '(local)', value FROM config WHERE name=%Q"
4402
--- src/db.c
+++ src/db.c
@@ -3647,66 +3647,79 @@
3647 **
3648 ** If the zNonVersionedSetting parameter is not NULL then it holds the
3649 ** non-versioned value for this setting. If both a versioned and a
3650 ** non-versioned value exist and are not equal, then a warning message
3651 ** might be generated.
3652 **
3653 ** zCkin is normally NULL. In that case, the versioned setting is
3654 ** take from the local check-out, if a local checkout exists, or from
3655 ** checkin named by the g.zOpenRevision global variable. If zCkin is
3656 ** not NULL, then zCkin is the name of the specific checkin from which
3657 ** versioned setting value is taken. When zCkin is not NULL, the cache
3658 ** is bypassed.
3659 */
3660 char *db_get_versioned(
3661 const char *zName,
3662 char *zNonVersionedSetting,
3663 const char *zCkin
3664 ){
3665 char *zVersionedSetting = 0;
3666 int noWarn = 0;
3667 int found = 0;
3668 struct _cacheEntry {
3669 struct _cacheEntry *next;
3670 const char *zName, *zValue;
3671 } *cacheEntry = 0;
3672 static struct _cacheEntry *cache = 0;
3673
3674 if( !g.localOpen && g.zOpenRevision==0 && zCkin==0 ){
3675 return zNonVersionedSetting;
3676 }
3677
3678 /* Look up name in cache */
3679 if( zCkin==0 ){
3680 cacheEntry = cache;
3681 while( cacheEntry!=0 ){
3682 if( fossil_strcmp(cacheEntry->zName, zName)==0 ){
3683 zVersionedSetting = fossil_strdup(cacheEntry->zValue);
3684 break;
3685 }
3686 cacheEntry = cacheEntry->next;
3687 }
3688 }
3689
3690 /* Attempt to read value from file in check-out if there wasn't a cache hit.*/
3691 if( cacheEntry==0 ){
3692 Blob versionedPathname;
3693 Blob setting;
3694 blob_init(&versionedPathname, 0, 0);
3695 blob_init(&setting, 0, 0);
3696 if( !g.localOpen || zCkin!=0 ){
3697 /* Repository is in the process of being opened, but files have not been
3698 * written to disk. Load from the database. */
3699 blob_appendf(&versionedPathname, ".fossil-settings/%s", zName);
3700 if( historical_blob(zCkin ? zCkin : g.zOpenRevision,
3701 blob_str(&versionedPathname),
3702 &setting, 0)
3703 ){
3704 found = 1;
3705 }
3706 }else{
3707 blob_appendf(&versionedPathname, "%s.fossil-settings/%s",
3708 g.zLocalRoot, zName);
3709 if( file_size(blob_str(&versionedPathname), ExtFILE)>=0 ){
3710 /* File exists, and contains the value for this setting. Load from
3711 ** the file. */
3712 const char *zFile = blob_str(&versionedPathname);
3713 if( blob_read_from_file(&setting, zFile, ExtFILE)>=0 ){
3714 found = 1;
3715 }
3716 /* See if there's a no-warn flag */
3717 blob_append(&versionedPathname, ".no-warn", -1);
3718 if( file_size(blob_str(&versionedPathname), ExtFILE)>=0 ){
3719 noWarn = 1;
3720 }
 
 
 
 
3721 }
3722 }
3723 blob_reset(&versionedPathname);
3724 if( found ){
3725 blob_strip_comment_lines(&setting, &setting);
@@ -3713,20 +3726,27 @@
3726 blob_trim(&setting); /* Avoid non-obvious problems with line endings
3727 ** on boolean properties */
3728 zVersionedSetting = fossil_strdup(blob_str(&setting));
3729 }
3730 blob_reset(&setting);
3731
3732 /* Store result in cache, which can be the value or 0 if not found */
3733 if( zCkin==0 ){
3734 cacheEntry = (struct _cacheEntry*)fossil_malloc(sizeof(*cacheEntry));
3735 cacheEntry->next = cache;
3736 cacheEntry->zName = zName;
3737 cacheEntry->zValue = fossil_strdup(zVersionedSetting);
3738 cache = cacheEntry;
3739 }
3740 }
3741
3742 /* Display a warning? */
3743 if( zVersionedSetting!=0
3744 && zNonVersionedSetting!=0
3745 && zNonVersionedSetting[0]!='\0'
3746 && zCkin==0
3747 && !noWarn
3748 ){
3749 /* There's a versioned setting, and a non-versioned setting. Tell
3750 ** the user about the conflict */
3751 fossil_warning(
3752 "setting %s has both versioned and non-versioned values: using "
@@ -3735,10 +3755,11 @@
3755 "\"%/.fossil-settings/%s.no-warn\" in the check-out root, or delete "
3756 "the non-versioned setting with \"fossil unset %s\")", zName,
3757 g.zLocalRoot, zName, g.zLocalRoot, zName, zName
3758 );
3759 }
3760
3761 /* Prefer the versioned setting */
3762 return ( zVersionedSetting!=0 ) ? zVersionedSetting : zNonVersionedSetting;
3763 }
3764
3765
@@ -3778,11 +3799,11 @@
3799 }
3800 if( pSetting!=0 && pSetting->versionable ){
3801 /* This is a versionable setting, try and get the info from a
3802 ** checked-out file */
3803 char * zZ = z;
3804 z = db_get_versioned(zName, z, 0);
3805 if(zZ != z){
3806 fossil_free(zZ);
3807 }
3808 }
3809 if( z==0 ){
@@ -3919,11 +3940,11 @@
3940 }
3941 fossil_free(zVal);
3942 return dflt;
3943 }
3944 int db_get_versioned_boolean(const char *zName, int dflt){
3945 char *zVal = db_get_versioned(zName, 0, 0);
3946 if( zVal==0 ) return dflt;
3947 if( is_truth(zVal) ) return 1;
3948 if( is_false(zVal) ) return 0;
3949 return dflt;
3950 }
@@ -4048,14 +4069,30 @@
4069 ** Get the manifest setting. For backwards compatibility first check if the
4070 ** value is a boolean. If it's not a boolean, treat each character as a flag
4071 ** to enable a manifest type. This system puts certain boundary conditions on
4072 ** which letters can be used to represent flags (any permutation of flags must
4073 ** not be able to fully form one of the boolean values).
4074 **
4075 ** "manifest" is a versionable setting. But we do not issue a warning
4076 ** if there is a conflict. Instead, the value returned is the value for
4077 ** the versioned setting if the versioned setting exists, or the ordinary
4078 ** setting otherwise.
4079 **
4080 ** The argument zCkin is the specific check-in for which we want the
4081 ** manifest setting.
4082 */
4083 int db_get_manifest_setting(const char *zCkin){
4084 int flg;
4085 char *zVal;
4086
4087 /* Look for the versioned setting first */
4088 zVal = db_get_versioned("manifest", 0, zCkin);
4089
4090 if( zVal==0 && g.repositoryOpen ){
4091 /* No versioned setting, look for the repository setting second */
4092 zVal = db_text(0, "SELECT value FROM config WHERE name='manifest'");
4093 }
4094 if( zVal==0 || is_false(zVal) ){
4095 return 0;
4096 }else if( is_truth(zVal) ){
4097 return MFESTFLG_RAW|MFESTFLG_UUID;
4098 }
@@ -4068,10 +4105,37 @@
4105 }
4106 zVal++;
4107 }
4108 return flg;
4109 }
4110
4111 /*
4112 ** COMMAND: test-manifest-setting
4113 **
4114 ** Usage: %fossil test-manifest-setting VERSION VERSION ...
4115 **
4116 ** Display the value for the "manifest" setting for various versions
4117 ** of the repository.
4118 */
4119 void test_manfest_setting_cmd(void){
4120 int i;
4121 db_find_and_open_repository(0, 0);
4122 for(i=2; i<g.argc; i++){
4123 int m = db_get_manifest_setting(g.argv[i]);
4124 fossil_print("%s:\n", g.argv[i]);
4125 fossil_print(" flags = 0x%02x\n", m);
4126 if( m & MFESTFLG_RAW ){
4127 fossil_print(" manifest\n");
4128 }
4129 if( m & MFESTFLG_UUID ){
4130 fossil_print(" manifest.uuid\n");
4131 }
4132 if( m & MFESTFLG_TAGS ){
4133 fossil_print(" manifest.tags\n");
4134 }
4135 }
4136 }
4137
4138
4139 /*
4140 ** Record the name of a local repository in the global_config() database.
4141 ** The repository filename %s is recorded as an entry with a "name" field
@@ -4391,11 +4455,11 @@
4455 versioned = 1;
4456 }
4457 blob_reset(&versionedPathname);
4458 }
4459 if( valueOnly && versioned ){
4460 fossil_print("%s\n", db_get_versioned(pSetting->name, NULL, NULL));
4461 return;
4462 }
4463 if( g.repositoryOpen ){
4464 db_prepare(&q,
4465 "SELECT '(local)', value FROM config WHERE name=%Q"
4466
--- src/default.css
+++ src/default.css
@@ -750,10 +750,11 @@
750750
body.tkt div.content ol.tkt-changes > li:target > ol {
751751
border-left: 1px solid gold;
752752
}
753753
body.cpage-ckout .file-change-line,
754754
body.cpage-info .file-change-line,
755
+body.cpage-vinfo .file-change-line,
755756
body.cpage-vdiff .file-change-line {
756757
margin-top: 16px;
757758
margin-bottom: 16px;
758759
margin-right: 1em /* keep it from nudging right up against the scrollbar-reveal zone */;
759760
display: flex;
760761
--- src/default.css
+++ src/default.css
@@ -750,10 +750,11 @@
750 body.tkt div.content ol.tkt-changes > li:target > ol {
751 border-left: 1px solid gold;
752 }
753 body.cpage-ckout .file-change-line,
754 body.cpage-info .file-change-line,
 
755 body.cpage-vdiff .file-change-line {
756 margin-top: 16px;
757 margin-bottom: 16px;
758 margin-right: 1em /* keep it from nudging right up against the scrollbar-reveal zone */;
759 display: flex;
760
--- src/default.css
+++ src/default.css
@@ -750,10 +750,11 @@
750 body.tkt div.content ol.tkt-changes > li:target > ol {
751 border-left: 1px solid gold;
752 }
753 body.cpage-ckout .file-change-line,
754 body.cpage-info .file-change-line,
755 body.cpage-vinfo .file-change-line,
756 body.cpage-vdiff .file-change-line {
757 margin-top: 16px;
758 margin-bottom: 16px;
759 margin-right: 1em /* keep it from nudging right up against the scrollbar-reveal zone */;
760 display: flex;
761
+3 -1
--- src/diff.c
+++ src/diff.c
@@ -3175,11 +3175,13 @@
31753175
}
31763176
g.diffCnt[1] += nIns;
31773177
g.diffCnt[2] += nDel;
31783178
if( nIns+nDel ){
31793179
g.diffCnt[0]++;
3180
- blob_appendf(pOut, "%10d %10d", nIns, nDel);
3180
+ if( !(pCfg->diffFlags & DIFF_BRIEF) ){
3181
+ blob_appendf(pOut, "%10d %10d", nIns, nDel);
3182
+ }
31813183
}
31823184
}else if( pCfg->diffFlags & (DIFF_RAW|DIFF_BY_TOKEN) ){
31833185
const int *R = c.aEdit;
31843186
unsigned int r;
31853187
for(r=0; R[r] || R[r+1] || R[r+2]; r += 3){
31863188
--- src/diff.c
+++ src/diff.c
@@ -3175,11 +3175,13 @@
3175 }
3176 g.diffCnt[1] += nIns;
3177 g.diffCnt[2] += nDel;
3178 if( nIns+nDel ){
3179 g.diffCnt[0]++;
3180 blob_appendf(pOut, "%10d %10d", nIns, nDel);
 
 
3181 }
3182 }else if( pCfg->diffFlags & (DIFF_RAW|DIFF_BY_TOKEN) ){
3183 const int *R = c.aEdit;
3184 unsigned int r;
3185 for(r=0; R[r] || R[r+1] || R[r+2]; r += 3){
3186
--- src/diff.c
+++ src/diff.c
@@ -3175,11 +3175,13 @@
3175 }
3176 g.diffCnt[1] += nIns;
3177 g.diffCnt[2] += nDel;
3178 if( nIns+nDel ){
3179 g.diffCnt[0]++;
3180 if( !(pCfg->diffFlags & DIFF_BRIEF) ){
3181 blob_appendf(pOut, "%10d %10d", nIns, nDel);
3182 }
3183 }
3184 }else if( pCfg->diffFlags & (DIFF_RAW|DIFF_BY_TOKEN) ){
3185 const int *R = c.aEdit;
3186 unsigned int r;
3187 for(r=0; R[r] || R[r+1] || R[r+2]; r += 3){
3188
+23 -10
--- src/diffcmd.c
+++ src/diffcmd.c
@@ -587,20 +587,22 @@
587587
blob_read_from_file(&file2, zFile2, ExtFILE);
588588
zName2 = zName;
589589
}
590590
591591
/* Compute and output the differences */
592
- if( pCfg->diffFlags & DIFF_BRIEF ){
592
+ if( (pCfg->diffFlags & DIFF_BRIEF) && !(pCfg->diffFlags & DIFF_NUMSTAT) ){
593593
if( blob_compare(pFile1, &file2) ){
594594
fossil_print("CHANGED %s\n", zName);
595595
}
596596
}else{
597597
blob_zero(&out);
598598
text_diff(pFile1, &file2, &out, pCfg);
599599
if( blob_size(&out) ){
600600
if( pCfg->diffFlags & DIFF_NUMSTAT ){
601
- blob_appendf(pOut, "%s %s\n", blob_str(&out), zName);
601
+ if( !(pCfg->diffFlags & DIFF_BRIEF) ){
602
+ blob_appendf(pOut, "%s %s\n", blob_str(&out), zName);
603
+ }
602604
}else{
603605
diff_print_filenames(zName, zName2, pCfg, pOut);
604606
blob_appendf(pOut, "%s\n", blob_str(&out));
605607
}
606608
}
@@ -693,18 +695,22 @@
693695
Blob *pFile1, /* In memory content to compare from */
694696
Blob *pFile2, /* In memory content to compare to */
695697
const char *zName, /* Display name of the file */
696698
DiffConfig *pCfg /* Diff flags */
697699
){
698
- if( pCfg->diffFlags & DIFF_BRIEF ) return;
700
+ if( (pCfg->diffFlags & DIFF_BRIEF) && !(pCfg->diffFlags & DIFF_NUMSTAT) ){
701
+ return;
702
+ }
699703
if( pCfg->zDiffCmd==0 ){
700704
Blob out; /* Diff output text */
701705
702706
blob_zero(&out);
703707
text_diff(pFile1, pFile2, &out, pCfg);
704708
if( pCfg->diffFlags & DIFF_NUMSTAT ){
705
- fossil_print("%s %s\n", blob_str(&out), zName);
709
+ if( !(pCfg->diffFlags & DIFF_BRIEF) ){
710
+ fossil_print("%s %s\n", blob_str(&out), zName);
711
+ }
706712
}else{
707713
diff_print_filenames(zName, zName, pCfg, 0);
708714
fossil_print("%s\n", blob_str(&out));
709715
}
710716
@@ -990,11 +996,13 @@
990996
}else if( pTo ){
991997
zName = pTo->zName;
992998
}else{
993999
zName = DIFF_NO_NAME;
9941000
}
995
- if( pCfg->diffFlags & DIFF_BRIEF ) return;
1001
+ if( (pCfg->diffFlags & DIFF_BRIEF) && !(pCfg->diffFlags & DIFF_NUMSTAT) ){
1002
+ return;
1003
+ }
9961004
diff_print_index(zName, pCfg, 0);
9971005
if( pFrom ){
9981006
rid = uuid_to_rid(pFrom->zUuid, 0);
9991007
content_get(rid, &f1);
10001008
}else{
@@ -1078,11 +1086,11 @@
10781086
(void)file_dir_match(pFileDir, pFromFile->zName); /* Record name usage */
10791087
pFromFile = manifest_file_next(pFrom,0);
10801088
pToFile = manifest_file_next(pTo,0);
10811089
}else{
10821090
if( file_dir_match(pFileDir, pToFile->zName) ){
1083
- if( pCfg->diffFlags & DIFF_BRIEF ){
1091
+ if((pCfg->diffFlags & DIFF_BRIEF) && !(pCfg->diffFlags & DIFF_NUMSTAT)){
10841092
fossil_print("CHANGED %s\n", pFromFile->zName);
10851093
}else{
10861094
diff_manifest_entry(pFromFile, pToFile, pCfg);
10871095
}
10881096
}
@@ -1337,11 +1345,13 @@
13371345
** -i|--internal Use internal diff logic
13381346
** --invert Invert the diff
13391347
** --json Output formatted as JSON
13401348
** -n|--linenum Show line numbers
13411349
** -N|--new-file Alias for --verbose
1342
-** --numstat Show only the number of added and deleted lines
1350
+** --numstat Show the number of added and deleted lines per
1351
+** file, omitting the diff. When combined with
1352
+** --brief, show only the total row.
13431353
** -y|--side-by-side Side-by-side diff
13441354
** --strip-trailing-cr Strip trailing CR
13451355
** --tcl Tcl-formatted output used internally by --tk
13461356
** --tclsh PATH Tcl/Tk shell used for --tk (default: "tclsh")
13471357
** --tk Launch a Tcl/Tk GUI for display
@@ -1429,11 +1439,14 @@
14291439
pFileDir[i-2].nName = blob_size(&fname);
14301440
pFileDir[i-2].nUsed = 0;
14311441
blob_reset(&fname);
14321442
}
14331443
}
1434
- if ( zCheckin!=0 ){
1444
+ if( DCfg.diffFlags & DIFF_NUMSTAT ){
1445
+ fossil_print("%10s %10s\n", "INSERTED", "DELETED");
1446
+ }
1447
+ if( zCheckin!=0 ){
14351448
int ridTo = name_to_typed_rid(zCheckin, "ci");
14361449
zTo = zCheckin;
14371450
zFrom = db_text(0,
14381451
"SELECT uuid FROM blob, plink"
14391452
" WHERE plink.cid=%d AND plink.isprim AND plink.pid=blob.rid",
@@ -1469,12 +1482,12 @@
14691482
}
14701483
fossil_free(pFileDir);
14711484
}
14721485
diff_end(&DCfg, 0);
14731486
if ( DCfg.diffFlags & DIFF_NUMSTAT ){
1474
- fossil_print("%10d %10d TOTAL over %d changed files\n",
1475
- g.diffCnt[1], g.diffCnt[2], g.diffCnt[0]);
1487
+ fossil_print("%10d %10d TOTAL over %d changed file%s\n",
1488
+ g.diffCnt[1], g.diffCnt[2], g.diffCnt[0], g.diffCnt[0]!=1 ? "s": "");
14761489
}
14771490
}
14781491
14791492
/*
14801493
** WEBPAGE: vpatch
14811494
--- src/diffcmd.c
+++ src/diffcmd.c
@@ -587,20 +587,22 @@
587 blob_read_from_file(&file2, zFile2, ExtFILE);
588 zName2 = zName;
589 }
590
591 /* Compute and output the differences */
592 if( pCfg->diffFlags & DIFF_BRIEF ){
593 if( blob_compare(pFile1, &file2) ){
594 fossil_print("CHANGED %s\n", zName);
595 }
596 }else{
597 blob_zero(&out);
598 text_diff(pFile1, &file2, &out, pCfg);
599 if( blob_size(&out) ){
600 if( pCfg->diffFlags & DIFF_NUMSTAT ){
601 blob_appendf(pOut, "%s %s\n", blob_str(&out), zName);
 
 
602 }else{
603 diff_print_filenames(zName, zName2, pCfg, pOut);
604 blob_appendf(pOut, "%s\n", blob_str(&out));
605 }
606 }
@@ -693,18 +695,22 @@
693 Blob *pFile1, /* In memory content to compare from */
694 Blob *pFile2, /* In memory content to compare to */
695 const char *zName, /* Display name of the file */
696 DiffConfig *pCfg /* Diff flags */
697 ){
698 if( pCfg->diffFlags & DIFF_BRIEF ) return;
 
 
699 if( pCfg->zDiffCmd==0 ){
700 Blob out; /* Diff output text */
701
702 blob_zero(&out);
703 text_diff(pFile1, pFile2, &out, pCfg);
704 if( pCfg->diffFlags & DIFF_NUMSTAT ){
705 fossil_print("%s %s\n", blob_str(&out), zName);
 
 
706 }else{
707 diff_print_filenames(zName, zName, pCfg, 0);
708 fossil_print("%s\n", blob_str(&out));
709 }
710
@@ -990,11 +996,13 @@
990 }else if( pTo ){
991 zName = pTo->zName;
992 }else{
993 zName = DIFF_NO_NAME;
994 }
995 if( pCfg->diffFlags & DIFF_BRIEF ) return;
 
 
996 diff_print_index(zName, pCfg, 0);
997 if( pFrom ){
998 rid = uuid_to_rid(pFrom->zUuid, 0);
999 content_get(rid, &f1);
1000 }else{
@@ -1078,11 +1086,11 @@
1078 (void)file_dir_match(pFileDir, pFromFile->zName); /* Record name usage */
1079 pFromFile = manifest_file_next(pFrom,0);
1080 pToFile = manifest_file_next(pTo,0);
1081 }else{
1082 if( file_dir_match(pFileDir, pToFile->zName) ){
1083 if( pCfg->diffFlags & DIFF_BRIEF ){
1084 fossil_print("CHANGED %s\n", pFromFile->zName);
1085 }else{
1086 diff_manifest_entry(pFromFile, pToFile, pCfg);
1087 }
1088 }
@@ -1337,11 +1345,13 @@
1337 ** -i|--internal Use internal diff logic
1338 ** --invert Invert the diff
1339 ** --json Output formatted as JSON
1340 ** -n|--linenum Show line numbers
1341 ** -N|--new-file Alias for --verbose
1342 ** --numstat Show only the number of added and deleted lines
 
 
1343 ** -y|--side-by-side Side-by-side diff
1344 ** --strip-trailing-cr Strip trailing CR
1345 ** --tcl Tcl-formatted output used internally by --tk
1346 ** --tclsh PATH Tcl/Tk shell used for --tk (default: "tclsh")
1347 ** --tk Launch a Tcl/Tk GUI for display
@@ -1429,11 +1439,14 @@
1429 pFileDir[i-2].nName = blob_size(&fname);
1430 pFileDir[i-2].nUsed = 0;
1431 blob_reset(&fname);
1432 }
1433 }
1434 if ( zCheckin!=0 ){
 
 
 
1435 int ridTo = name_to_typed_rid(zCheckin, "ci");
1436 zTo = zCheckin;
1437 zFrom = db_text(0,
1438 "SELECT uuid FROM blob, plink"
1439 " WHERE plink.cid=%d AND plink.isprim AND plink.pid=blob.rid",
@@ -1469,12 +1482,12 @@
1469 }
1470 fossil_free(pFileDir);
1471 }
1472 diff_end(&DCfg, 0);
1473 if ( DCfg.diffFlags & DIFF_NUMSTAT ){
1474 fossil_print("%10d %10d TOTAL over %d changed files\n",
1475 g.diffCnt[1], g.diffCnt[2], g.diffCnt[0]);
1476 }
1477 }
1478
1479 /*
1480 ** WEBPAGE: vpatch
1481
--- src/diffcmd.c
+++ src/diffcmd.c
@@ -587,20 +587,22 @@
587 blob_read_from_file(&file2, zFile2, ExtFILE);
588 zName2 = zName;
589 }
590
591 /* Compute and output the differences */
592 if( (pCfg->diffFlags & DIFF_BRIEF) && !(pCfg->diffFlags & DIFF_NUMSTAT) ){
593 if( blob_compare(pFile1, &file2) ){
594 fossil_print("CHANGED %s\n", zName);
595 }
596 }else{
597 blob_zero(&out);
598 text_diff(pFile1, &file2, &out, pCfg);
599 if( blob_size(&out) ){
600 if( pCfg->diffFlags & DIFF_NUMSTAT ){
601 if( !(pCfg->diffFlags & DIFF_BRIEF) ){
602 blob_appendf(pOut, "%s %s\n", blob_str(&out), zName);
603 }
604 }else{
605 diff_print_filenames(zName, zName2, pCfg, pOut);
606 blob_appendf(pOut, "%s\n", blob_str(&out));
607 }
608 }
@@ -693,18 +695,22 @@
695 Blob *pFile1, /* In memory content to compare from */
696 Blob *pFile2, /* In memory content to compare to */
697 const char *zName, /* Display name of the file */
698 DiffConfig *pCfg /* Diff flags */
699 ){
700 if( (pCfg->diffFlags & DIFF_BRIEF) && !(pCfg->diffFlags & DIFF_NUMSTAT) ){
701 return;
702 }
703 if( pCfg->zDiffCmd==0 ){
704 Blob out; /* Diff output text */
705
706 blob_zero(&out);
707 text_diff(pFile1, pFile2, &out, pCfg);
708 if( pCfg->diffFlags & DIFF_NUMSTAT ){
709 if( !(pCfg->diffFlags & DIFF_BRIEF) ){
710 fossil_print("%s %s\n", blob_str(&out), zName);
711 }
712 }else{
713 diff_print_filenames(zName, zName, pCfg, 0);
714 fossil_print("%s\n", blob_str(&out));
715 }
716
@@ -990,11 +996,13 @@
996 }else if( pTo ){
997 zName = pTo->zName;
998 }else{
999 zName = DIFF_NO_NAME;
1000 }
1001 if( (pCfg->diffFlags & DIFF_BRIEF) && !(pCfg->diffFlags & DIFF_NUMSTAT) ){
1002 return;
1003 }
1004 diff_print_index(zName, pCfg, 0);
1005 if( pFrom ){
1006 rid = uuid_to_rid(pFrom->zUuid, 0);
1007 content_get(rid, &f1);
1008 }else{
@@ -1078,11 +1086,11 @@
1086 (void)file_dir_match(pFileDir, pFromFile->zName); /* Record name usage */
1087 pFromFile = manifest_file_next(pFrom,0);
1088 pToFile = manifest_file_next(pTo,0);
1089 }else{
1090 if( file_dir_match(pFileDir, pToFile->zName) ){
1091 if((pCfg->diffFlags & DIFF_BRIEF) && !(pCfg->diffFlags & DIFF_NUMSTAT)){
1092 fossil_print("CHANGED %s\n", pFromFile->zName);
1093 }else{
1094 diff_manifest_entry(pFromFile, pToFile, pCfg);
1095 }
1096 }
@@ -1337,11 +1345,13 @@
1345 ** -i|--internal Use internal diff logic
1346 ** --invert Invert the diff
1347 ** --json Output formatted as JSON
1348 ** -n|--linenum Show line numbers
1349 ** -N|--new-file Alias for --verbose
1350 ** --numstat Show the number of added and deleted lines per
1351 ** file, omitting the diff. When combined with
1352 ** --brief, show only the total row.
1353 ** -y|--side-by-side Side-by-side diff
1354 ** --strip-trailing-cr Strip trailing CR
1355 ** --tcl Tcl-formatted output used internally by --tk
1356 ** --tclsh PATH Tcl/Tk shell used for --tk (default: "tclsh")
1357 ** --tk Launch a Tcl/Tk GUI for display
@@ -1429,11 +1439,14 @@
1439 pFileDir[i-2].nName = blob_size(&fname);
1440 pFileDir[i-2].nUsed = 0;
1441 blob_reset(&fname);
1442 }
1443 }
1444 if( DCfg.diffFlags & DIFF_NUMSTAT ){
1445 fossil_print("%10s %10s\n", "INSERTED", "DELETED");
1446 }
1447 if( zCheckin!=0 ){
1448 int ridTo = name_to_typed_rid(zCheckin, "ci");
1449 zTo = zCheckin;
1450 zFrom = db_text(0,
1451 "SELECT uuid FROM blob, plink"
1452 " WHERE plink.cid=%d AND plink.isprim AND plink.pid=blob.rid",
@@ -1469,12 +1482,12 @@
1482 }
1483 fossil_free(pFileDir);
1484 }
1485 diff_end(&DCfg, 0);
1486 if ( DCfg.diffFlags & DIFF_NUMSTAT ){
1487 fossil_print("%10d %10d TOTAL over %d changed file%s\n",
1488 g.diffCnt[1], g.diffCnt[2], g.diffCnt[0], g.diffCnt[0]!=1 ? "s": "");
1489 }
1490 }
1491
1492 /*
1493 ** WEBPAGE: vpatch
1494
+13 -8
--- src/export.c
+++ src/export.c
@@ -1074,12 +1074,11 @@
10741074
*/
10751075
static int gitmirror_send_checkin(
10761076
FILE *xCmd, /* Write fast-import text on this pipe */
10771077
int rid, /* BLOB.RID for the check-in to export */
10781078
const char *zUuid, /* BLOB.UUID for the check-in to export */
1079
- int *pnLimit, /* Stop when the counter reaches zero */
1080
- int fManifest /* MFESTFLG_* values */
1079
+ int *pnLimit /* Stop when the counter reaches zero */
10811080
){
10821081
Manifest *pMan; /* The check-in to be output */
10831082
int i; /* Loop counter */
10841083
int iParent; /* Which immediate ancestor is primary. -1 for none */
10851084
Stmt q; /* An SQL query */
@@ -1089,10 +1088,12 @@
10891088
Blob comment; /* The comment text for the check-in */
10901089
int nErr = 0; /* Number of errors */
10911090
int bPhantomOk; /* True if phantom files should be ignored */
10921091
char buf[24];
10931092
char *zEmail; /* Contact info for Git committer field */
1093
+ int fManifest; /* Should the manifest files be included? */
1094
+ int fPManifest = 0; /* OR of the manifest files for all parents */
10941095
10951096
pMan = manifest_get(rid, CFTYPE_MANIFEST, 0);
10961097
if( pMan==0 ){
10971098
/* Must be a phantom. Return without doing anything, and in particular
10981099
** without creating a mark for this check-in. */
@@ -1106,11 +1107,11 @@
11061107
char *zPMark = gitmirror_find_mark(pMan->azParent[i], 0, 0);
11071108
if( zPMark==0 ){
11081109
int prid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q",
11091110
pMan->azParent[i]);
11101111
int rc = gitmirror_send_checkin(xCmd, prid, pMan->azParent[i],
1111
- pnLimit, fManifest);
1112
+ pnLimit);
11121113
if( rc || *pnLimit<=0 ){
11131114
manifest_destroy(pMan);
11141115
return 1;
11151116
}
11161117
}
@@ -1215,10 +1216,11 @@
12151216
blob_reset(&comment);
12161217
iParent = -1; /* Which ancestor is the primary parent */
12171218
for(i=0; i<pMan->nParent; i++){
12181219
char *zOther = gitmirror_find_mark(pMan->azParent[i],0,0);
12191220
if( zOther==0 ) continue;
1221
+ fPManifest |= db_get_manifest_setting(pMan->azParent[i]);
12201222
if( iParent<0 ){
12211223
iParent = i;
12221224
fprintf(xCmd, "from %s\n", zOther);
12231225
}else{
12241226
fprintf(xCmd, "merge %s\n", zOther);
@@ -1271,29 +1273,36 @@
12711273
db_finalize(&q);
12721274
manifest_destroy(pMan);
12731275
pMan = 0;
12741276
12751277
/* Include Fossil-generated auxiliary files in the check-in */
1278
+ fManifest = db_get_manifest_setting(zUuid);
12761279
if( fManifest & MFESTFLG_RAW ){
12771280
Blob manifest;
12781281
content_get(rid, &manifest);
12791282
sterilize_manifest(&manifest, CFTYPE_MANIFEST);
12801283
fprintf(xCmd,"M 100644 inline manifest\ndata %d\n%s\n",
12811284
blob_strlen(&manifest), blob_str(&manifest));
12821285
blob_reset(&manifest);
1286
+ }else if( fPManifest & MFESTFLG_RAW ){
1287
+ fprintf(xCmd, "D manifest\n");
12831288
}
12841289
if( fManifest & MFESTFLG_UUID ){
12851290
int n = (int)strlen(zUuid);
12861291
fprintf(xCmd,"M 100644 inline manifest.uuid\ndata %d\n%s\n\n", n+1, zUuid);
1292
+ }else if( fPManifest & MFESTFLG_UUID ){
1293
+ fprintf(xCmd, "D manifest.uuid\n");
12871294
}
12881295
if( fManifest & MFESTFLG_TAGS ){
12891296
Blob tagslist;
12901297
blob_init(&tagslist, 0, 0);
12911298
get_checkin_taglist(rid, &tagslist);
12921299
fprintf(xCmd,"M 100644 inline manifest.tags\ndata %d\n%s\n",
12931300
blob_strlen(&tagslist), blob_str(&tagslist));
12941301
blob_reset(&tagslist);
1302
+ }else if( fPManifest & MFESTFLG_TAGS ){
1303
+ fprintf(xCmd, "D manifest.tags\n");
12951304
}
12961305
12971306
/* The check-in is finished, so decrement the counter */
12981307
(*pnLimit)--;
12991308
return 0;
@@ -1383,11 +1392,10 @@
13831392
char *zPushUrl; /* URL to sync the mirror to */
13841393
double rEnd; /* time of most recent export */
13851394
int rc; /* Result code */
13861395
int bForce; /* Do the export and sync even if no changes*/
13871396
int bNeedRepack = 0; /* True if we should run repack at the end */
1388
- int fManifest; /* Current "manifest" setting */
13891397
int bIfExists; /* The --if-mirrored flag */
13901398
FILE *xCmd; /* Pipe to the "git fast-import" command */
13911399
FILE *pMarks; /* Git mark files */
13921400
Stmt q; /* Queries */
13931401
char zLine[200]; /* One line of a mark file */
@@ -1521,13 +1529,10 @@
15211529
gitmirror_message(VERB_NORMAL, "no changes\n");
15221530
db_commit_transaction();
15231531
return;
15241532
}
15251533
1526
- /* Do we need to include manifest files in the clone? */
1527
- fManifest = db_get_manifest_setting();
1528
-
15291534
/* Change to the MIRROR directory so that the Git commands will work */
15301535
rc = file_chdir(zMirror, 0);
15311536
if( rc ) fossil_fatal("cannot change the working directory to \"%s\"",
15321537
zMirror);
15331538
@@ -1579,11 +1584,11 @@
15791584
while( nLimit && db_step(&q)==SQLITE_ROW ){
15801585
int rid = db_column_int(&q, 0);
15811586
double rMTime = db_column_double(&q, 1);
15821587
const char *zUuid = db_column_text(&q, 2);
15831588
if( rMTime>rEnd ) rEnd = rMTime;
1584
- rc = gitmirror_send_checkin(xCmd, rid, zUuid, &nLimit, fManifest);
1589
+ rc = gitmirror_send_checkin(xCmd, rid, zUuid, &nLimit);
15851590
if( rc ) break;
15861591
gitmirror_message(VERB_NORMAL,"%d/%d \r", nTotal-nLimit, nTotal);
15871592
fflush(stdout);
15881593
}
15891594
db_finalize(&q);
15901595
--- src/export.c
+++ src/export.c
@@ -1074,12 +1074,11 @@
1074 */
1075 static int gitmirror_send_checkin(
1076 FILE *xCmd, /* Write fast-import text on this pipe */
1077 int rid, /* BLOB.RID for the check-in to export */
1078 const char *zUuid, /* BLOB.UUID for the check-in to export */
1079 int *pnLimit, /* Stop when the counter reaches zero */
1080 int fManifest /* MFESTFLG_* values */
1081 ){
1082 Manifest *pMan; /* The check-in to be output */
1083 int i; /* Loop counter */
1084 int iParent; /* Which immediate ancestor is primary. -1 for none */
1085 Stmt q; /* An SQL query */
@@ -1089,10 +1088,12 @@
1089 Blob comment; /* The comment text for the check-in */
1090 int nErr = 0; /* Number of errors */
1091 int bPhantomOk; /* True if phantom files should be ignored */
1092 char buf[24];
1093 char *zEmail; /* Contact info for Git committer field */
 
 
1094
1095 pMan = manifest_get(rid, CFTYPE_MANIFEST, 0);
1096 if( pMan==0 ){
1097 /* Must be a phantom. Return without doing anything, and in particular
1098 ** without creating a mark for this check-in. */
@@ -1106,11 +1107,11 @@
1106 char *zPMark = gitmirror_find_mark(pMan->azParent[i], 0, 0);
1107 if( zPMark==0 ){
1108 int prid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q",
1109 pMan->azParent[i]);
1110 int rc = gitmirror_send_checkin(xCmd, prid, pMan->azParent[i],
1111 pnLimit, fManifest);
1112 if( rc || *pnLimit<=0 ){
1113 manifest_destroy(pMan);
1114 return 1;
1115 }
1116 }
@@ -1215,10 +1216,11 @@
1215 blob_reset(&comment);
1216 iParent = -1; /* Which ancestor is the primary parent */
1217 for(i=0; i<pMan->nParent; i++){
1218 char *zOther = gitmirror_find_mark(pMan->azParent[i],0,0);
1219 if( zOther==0 ) continue;
 
1220 if( iParent<0 ){
1221 iParent = i;
1222 fprintf(xCmd, "from %s\n", zOther);
1223 }else{
1224 fprintf(xCmd, "merge %s\n", zOther);
@@ -1271,29 +1273,36 @@
1271 db_finalize(&q);
1272 manifest_destroy(pMan);
1273 pMan = 0;
1274
1275 /* Include Fossil-generated auxiliary files in the check-in */
 
1276 if( fManifest & MFESTFLG_RAW ){
1277 Blob manifest;
1278 content_get(rid, &manifest);
1279 sterilize_manifest(&manifest, CFTYPE_MANIFEST);
1280 fprintf(xCmd,"M 100644 inline manifest\ndata %d\n%s\n",
1281 blob_strlen(&manifest), blob_str(&manifest));
1282 blob_reset(&manifest);
 
 
1283 }
1284 if( fManifest & MFESTFLG_UUID ){
1285 int n = (int)strlen(zUuid);
1286 fprintf(xCmd,"M 100644 inline manifest.uuid\ndata %d\n%s\n\n", n+1, zUuid);
 
 
1287 }
1288 if( fManifest & MFESTFLG_TAGS ){
1289 Blob tagslist;
1290 blob_init(&tagslist, 0, 0);
1291 get_checkin_taglist(rid, &tagslist);
1292 fprintf(xCmd,"M 100644 inline manifest.tags\ndata %d\n%s\n",
1293 blob_strlen(&tagslist), blob_str(&tagslist));
1294 blob_reset(&tagslist);
 
 
1295 }
1296
1297 /* The check-in is finished, so decrement the counter */
1298 (*pnLimit)--;
1299 return 0;
@@ -1383,11 +1392,10 @@
1383 char *zPushUrl; /* URL to sync the mirror to */
1384 double rEnd; /* time of most recent export */
1385 int rc; /* Result code */
1386 int bForce; /* Do the export and sync even if no changes*/
1387 int bNeedRepack = 0; /* True if we should run repack at the end */
1388 int fManifest; /* Current "manifest" setting */
1389 int bIfExists; /* The --if-mirrored flag */
1390 FILE *xCmd; /* Pipe to the "git fast-import" command */
1391 FILE *pMarks; /* Git mark files */
1392 Stmt q; /* Queries */
1393 char zLine[200]; /* One line of a mark file */
@@ -1521,13 +1529,10 @@
1521 gitmirror_message(VERB_NORMAL, "no changes\n");
1522 db_commit_transaction();
1523 return;
1524 }
1525
1526 /* Do we need to include manifest files in the clone? */
1527 fManifest = db_get_manifest_setting();
1528
1529 /* Change to the MIRROR directory so that the Git commands will work */
1530 rc = file_chdir(zMirror, 0);
1531 if( rc ) fossil_fatal("cannot change the working directory to \"%s\"",
1532 zMirror);
1533
@@ -1579,11 +1584,11 @@
1579 while( nLimit && db_step(&q)==SQLITE_ROW ){
1580 int rid = db_column_int(&q, 0);
1581 double rMTime = db_column_double(&q, 1);
1582 const char *zUuid = db_column_text(&q, 2);
1583 if( rMTime>rEnd ) rEnd = rMTime;
1584 rc = gitmirror_send_checkin(xCmd, rid, zUuid, &nLimit, fManifest);
1585 if( rc ) break;
1586 gitmirror_message(VERB_NORMAL,"%d/%d \r", nTotal-nLimit, nTotal);
1587 fflush(stdout);
1588 }
1589 db_finalize(&q);
1590
--- src/export.c
+++ src/export.c
@@ -1074,12 +1074,11 @@
1074 */
1075 static int gitmirror_send_checkin(
1076 FILE *xCmd, /* Write fast-import text on this pipe */
1077 int rid, /* BLOB.RID for the check-in to export */
1078 const char *zUuid, /* BLOB.UUID for the check-in to export */
1079 int *pnLimit /* Stop when the counter reaches zero */
 
1080 ){
1081 Manifest *pMan; /* The check-in to be output */
1082 int i; /* Loop counter */
1083 int iParent; /* Which immediate ancestor is primary. -1 for none */
1084 Stmt q; /* An SQL query */
@@ -1089,10 +1088,12 @@
1088 Blob comment; /* The comment text for the check-in */
1089 int nErr = 0; /* Number of errors */
1090 int bPhantomOk; /* True if phantom files should be ignored */
1091 char buf[24];
1092 char *zEmail; /* Contact info for Git committer field */
1093 int fManifest; /* Should the manifest files be included? */
1094 int fPManifest = 0; /* OR of the manifest files for all parents */
1095
1096 pMan = manifest_get(rid, CFTYPE_MANIFEST, 0);
1097 if( pMan==0 ){
1098 /* Must be a phantom. Return without doing anything, and in particular
1099 ** without creating a mark for this check-in. */
@@ -1106,11 +1107,11 @@
1107 char *zPMark = gitmirror_find_mark(pMan->azParent[i], 0, 0);
1108 if( zPMark==0 ){
1109 int prid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q",
1110 pMan->azParent[i]);
1111 int rc = gitmirror_send_checkin(xCmd, prid, pMan->azParent[i],
1112 pnLimit);
1113 if( rc || *pnLimit<=0 ){
1114 manifest_destroy(pMan);
1115 return 1;
1116 }
1117 }
@@ -1215,10 +1216,11 @@
1216 blob_reset(&comment);
1217 iParent = -1; /* Which ancestor is the primary parent */
1218 for(i=0; i<pMan->nParent; i++){
1219 char *zOther = gitmirror_find_mark(pMan->azParent[i],0,0);
1220 if( zOther==0 ) continue;
1221 fPManifest |= db_get_manifest_setting(pMan->azParent[i]);
1222 if( iParent<0 ){
1223 iParent = i;
1224 fprintf(xCmd, "from %s\n", zOther);
1225 }else{
1226 fprintf(xCmd, "merge %s\n", zOther);
@@ -1271,29 +1273,36 @@
1273 db_finalize(&q);
1274 manifest_destroy(pMan);
1275 pMan = 0;
1276
1277 /* Include Fossil-generated auxiliary files in the check-in */
1278 fManifest = db_get_manifest_setting(zUuid);
1279 if( fManifest & MFESTFLG_RAW ){
1280 Blob manifest;
1281 content_get(rid, &manifest);
1282 sterilize_manifest(&manifest, CFTYPE_MANIFEST);
1283 fprintf(xCmd,"M 100644 inline manifest\ndata %d\n%s\n",
1284 blob_strlen(&manifest), blob_str(&manifest));
1285 blob_reset(&manifest);
1286 }else if( fPManifest & MFESTFLG_RAW ){
1287 fprintf(xCmd, "D manifest\n");
1288 }
1289 if( fManifest & MFESTFLG_UUID ){
1290 int n = (int)strlen(zUuid);
1291 fprintf(xCmd,"M 100644 inline manifest.uuid\ndata %d\n%s\n\n", n+1, zUuid);
1292 }else if( fPManifest & MFESTFLG_UUID ){
1293 fprintf(xCmd, "D manifest.uuid\n");
1294 }
1295 if( fManifest & MFESTFLG_TAGS ){
1296 Blob tagslist;
1297 blob_init(&tagslist, 0, 0);
1298 get_checkin_taglist(rid, &tagslist);
1299 fprintf(xCmd,"M 100644 inline manifest.tags\ndata %d\n%s\n",
1300 blob_strlen(&tagslist), blob_str(&tagslist));
1301 blob_reset(&tagslist);
1302 }else if( fPManifest & MFESTFLG_TAGS ){
1303 fprintf(xCmd, "D manifest.tags\n");
1304 }
1305
1306 /* The check-in is finished, so decrement the counter */
1307 (*pnLimit)--;
1308 return 0;
@@ -1383,11 +1392,10 @@
1392 char *zPushUrl; /* URL to sync the mirror to */
1393 double rEnd; /* time of most recent export */
1394 int rc; /* Result code */
1395 int bForce; /* Do the export and sync even if no changes*/
1396 int bNeedRepack = 0; /* True if we should run repack at the end */
 
1397 int bIfExists; /* The --if-mirrored flag */
1398 FILE *xCmd; /* Pipe to the "git fast-import" command */
1399 FILE *pMarks; /* Git mark files */
1400 Stmt q; /* Queries */
1401 char zLine[200]; /* One line of a mark file */
@@ -1521,13 +1529,10 @@
1529 gitmirror_message(VERB_NORMAL, "no changes\n");
1530 db_commit_transaction();
1531 return;
1532 }
1533
 
 
 
1534 /* Change to the MIRROR directory so that the Git commands will work */
1535 rc = file_chdir(zMirror, 0);
1536 if( rc ) fossil_fatal("cannot change the working directory to \"%s\"",
1537 zMirror);
1538
@@ -1579,11 +1584,11 @@
1584 while( nLimit && db_step(&q)==SQLITE_ROW ){
1585 int rid = db_column_int(&q, 0);
1586 double rMTime = db_column_double(&q, 1);
1587 const char *zUuid = db_column_text(&q, 2);
1588 if( rMTime>rEnd ) rEnd = rMTime;
1589 rc = gitmirror_send_checkin(xCmd, rid, zUuid, &nLimit);
1590 if( rc ) break;
1591 gitmirror_message(VERB_NORMAL,"%d/%d \r", nTotal-nLimit, nTotal);
1592 fflush(stdout);
1593 }
1594 db_finalize(&q);
1595
+1 -1
--- src/graph.c
+++ src/graph.c
@@ -84,11 +84,11 @@
8484
8585
GraphRow *pNext; /* Next row down in the list of all rows */
8686
GraphRow *pPrev; /* Previous row */
8787
8888
int idx; /* Row index. Top row is smallest. */
89
- int idxTop; /* Direct descendent highest up on the graph */
89
+ int idxTop; /* Direct descendant highest up on the graph */
9090
GraphRow *pChild; /* Child immediately above this node */
9191
u8 isDup; /* True if this is duplicate of a prior entry */
9292
u8 isLeaf; /* True if this is a leaf node */
9393
u8 isStepParent; /* pChild is actually a step-child. The thick
9494
** arrow up to the child is dashed, not solid */
9595
--- src/graph.c
+++ src/graph.c
@@ -84,11 +84,11 @@
84
85 GraphRow *pNext; /* Next row down in the list of all rows */
86 GraphRow *pPrev; /* Previous row */
87
88 int idx; /* Row index. Top row is smallest. */
89 int idxTop; /* Direct descendent highest up on the graph */
90 GraphRow *pChild; /* Child immediately above this node */
91 u8 isDup; /* True if this is duplicate of a prior entry */
92 u8 isLeaf; /* True if this is a leaf node */
93 u8 isStepParent; /* pChild is actually a step-child. The thick
94 ** arrow up to the child is dashed, not solid */
95
--- src/graph.c
+++ src/graph.c
@@ -84,11 +84,11 @@
84
85 GraphRow *pNext; /* Next row down in the list of all rows */
86 GraphRow *pPrev; /* Previous row */
87
88 int idx; /* Row index. Top row is smallest. */
89 int idxTop; /* Direct descendant highest up on the graph */
90 GraphRow *pChild; /* Child immediately above this node */
91 u8 isDup; /* True if this is duplicate of a prior entry */
92 u8 isLeaf; /* True if this is a leaf node */
93 u8 isStepParent; /* pChild is actually a step-child. The thick
94 ** arrow up to the child is dashed, not solid */
95
+3 -1
--- src/info.c
+++ src/info.c
@@ -257,17 +257,19 @@
257257
}
258258
fossil_print("fossil: %z\n", file_fullexename(g.nameOfExe));
259259
fossil_print("version: %s", z);
260260
blob_reset(&vx);
261261
}
262
- }else{
262
+ }else if( g.repositoryOpen ){
263263
int rid;
264264
rid = name_to_rid(g.argv[2]);
265265
if( rid==0 ){
266266
fossil_fatal("no such object: %s", g.argv[2]);
267267
}
268268
show_common_info(rid, "hash:", 1, 1);
269
+ }else{
270
+ fossil_fatal("Could not find or open a Fossil repository");
269271
}
270272
}
271273
272274
/*
273275
** Show the context graph (immediate parents and children) for
274276
--- src/info.c
+++ src/info.c
@@ -257,17 +257,19 @@
257 }
258 fossil_print("fossil: %z\n", file_fullexename(g.nameOfExe));
259 fossil_print("version: %s", z);
260 blob_reset(&vx);
261 }
262 }else{
263 int rid;
264 rid = name_to_rid(g.argv[2]);
265 if( rid==0 ){
266 fossil_fatal("no such object: %s", g.argv[2]);
267 }
268 show_common_info(rid, "hash:", 1, 1);
 
 
269 }
270 }
271
272 /*
273 ** Show the context graph (immediate parents and children) for
274
--- src/info.c
+++ src/info.c
@@ -257,17 +257,19 @@
257 }
258 fossil_print("fossil: %z\n", file_fullexename(g.nameOfExe));
259 fossil_print("version: %s", z);
260 blob_reset(&vx);
261 }
262 }else if( g.repositoryOpen ){
263 int rid;
264 rid = name_to_rid(g.argv[2]);
265 if( rid==0 ){
266 fossil_fatal("no such object: %s", g.argv[2]);
267 }
268 show_common_info(rid, "hash:", 1, 1);
269 }else{
270 fossil_fatal("Could not find or open a Fossil repository");
271 }
272 }
273
274 /*
275 ** Show the context graph (immediate parents and children) for
276
+5
--- src/main.c
+++ src/main.c
@@ -1849,10 +1849,15 @@
18491849
** not exist.
18501850
*/
18511851
zCleanRepo = file_cleanup_fullpath(zRepo);
18521852
if( szFile==0 && sqlite3_strglob("*/.fossil",zRepo)!=0 ){
18531853
szFile = file_size(zCleanRepo, ExtFILE);
1854
+ if( szFile>0 && !file_isfile(zCleanRepo, ExtFILE) ){
1855
+ /* Only let szFile be non-negative if zCleanRepo really is a file
1856
+ ** and not a directory or some other filesystem object. */
1857
+ szFile = -1;
1858
+ }
18541859
if( g.fHttpTrace ){
18551860
sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", szFile);
18561861
@ <!-- file_size(%h(zCleanRepo)) is %s(zBuf) -->
18571862
fprintf(stderr, "# file_size(%s) = %s\n", zCleanRepo, zBuf);
18581863
}
18591864
--- src/main.c
+++ src/main.c
@@ -1849,10 +1849,15 @@
1849 ** not exist.
1850 */
1851 zCleanRepo = file_cleanup_fullpath(zRepo);
1852 if( szFile==0 && sqlite3_strglob("*/.fossil",zRepo)!=0 ){
1853 szFile = file_size(zCleanRepo, ExtFILE);
 
 
 
 
 
1854 if( g.fHttpTrace ){
1855 sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", szFile);
1856 @ <!-- file_size(%h(zCleanRepo)) is %s(zBuf) -->
1857 fprintf(stderr, "# file_size(%s) = %s\n", zCleanRepo, zBuf);
1858 }
1859
--- src/main.c
+++ src/main.c
@@ -1849,10 +1849,15 @@
1849 ** not exist.
1850 */
1851 zCleanRepo = file_cleanup_fullpath(zRepo);
1852 if( szFile==0 && sqlite3_strglob("*/.fossil",zRepo)!=0 ){
1853 szFile = file_size(zCleanRepo, ExtFILE);
1854 if( szFile>0 && !file_isfile(zCleanRepo, ExtFILE) ){
1855 /* Only let szFile be non-negative if zCleanRepo really is a file
1856 ** and not a directory or some other filesystem object. */
1857 szFile = -1;
1858 }
1859 if( g.fHttpTrace ){
1860 sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", szFile);
1861 @ <!-- file_size(%h(zCleanRepo)) is %s(zBuf) -->
1862 fprintf(stderr, "# file_size(%s) = %s\n", zCleanRepo, zBuf);
1863 }
1864
+8 -8
--- src/search.c
+++ src/search.c
@@ -1497,18 +1497,18 @@
14971497
** Search for check-in comments, documents, tickets, or wiki that
14981498
** match a user-supplied pattern.
14991499
**
15001500
** s=PATTERN Specify the full-text pattern to search for
15011501
** y=TYPE What to search.
1502
-** c -> check-ins
1503
-** d -> documentation
1504
-** t -> tickets
1505
-** w -> wiki
1506
-** e -> tech notes
1507
-** f -> forum
1508
-** h -> built-in help
1509
-** all -> everything
1502
+** c -> check-ins,
1503
+** d -> documentation,
1504
+** t -> tickets,
1505
+** w -> wiki,
1506
+** e -> tech notes,
1507
+** f -> forum,
1508
+** h -> built-in help,
1509
+** all -> everything.
15101510
*/
15111511
void search_page(void){
15121512
const int isSearch = P("s")!=0;
15131513
login_check_credentials();
15141514
style_header("Search%s", isSearch ? " Results" : "");
15151515
--- src/search.c
+++ src/search.c
@@ -1497,18 +1497,18 @@
1497 ** Search for check-in comments, documents, tickets, or wiki that
1498 ** match a user-supplied pattern.
1499 **
1500 ** s=PATTERN Specify the full-text pattern to search for
1501 ** y=TYPE What to search.
1502 ** c -> check-ins
1503 ** d -> documentation
1504 ** t -> tickets
1505 ** w -> wiki
1506 ** e -> tech notes
1507 ** f -> forum
1508 ** h -> built-in help
1509 ** all -> everything
1510 */
1511 void search_page(void){
1512 const int isSearch = P("s")!=0;
1513 login_check_credentials();
1514 style_header("Search%s", isSearch ? " Results" : "");
1515
--- src/search.c
+++ src/search.c
@@ -1497,18 +1497,18 @@
1497 ** Search for check-in comments, documents, tickets, or wiki that
1498 ** match a user-supplied pattern.
1499 **
1500 ** s=PATTERN Specify the full-text pattern to search for
1501 ** y=TYPE What to search.
1502 ** c -> check-ins,
1503 ** d -> documentation,
1504 ** t -> tickets,
1505 ** w -> wiki,
1506 ** e -> tech notes,
1507 ** f -> forum,
1508 ** h -> built-in help,
1509 ** all -> everything.
1510 */
1511 void search_page(void){
1512 const int isSearch = P("s")!=0;
1513 login_check_credentials();
1514 style_header("Search%s", isSearch ? " Results" : "");
1515
+3 -3
--- src/setup.c
+++ src/setup.c
@@ -1178,11 +1178,11 @@
11781178
@ <table border="0"><tr><td valign="top">
11791179
login_insert_csrf_secret();
11801180
for(i=0, pSet=aSetting; i<nSetting; i++, pSet++){
11811181
if( pSet->width==0 ){
11821182
int hasVersionableValue = pSet->versionable &&
1183
- (db_get_versioned(pSet->name, NULL)!=0);
1183
+ (db_get_versioned(pSet->name, NULL, NULL)!=0);
11841184
onoff_attribute("", pSet->name,
11851185
pSet->var!=0 ? pSet->var : pSet->name /*works-like:"x"*/,
11861186
is_truth(pSet->def), hasVersionableValue);
11871187
@ <a href='%R/help?cmd=%s(pSet->name)'>%h(pSet->name)</a>
11881188
if( pSet->versionable ){
@@ -1196,11 +1196,11 @@
11961196
@ </td><td style="width:50px;"></td><td valign="top">
11971197
@ <table>
11981198
for(i=0, pSet=aSetting; i<nSetting; i++, pSet++){
11991199
if( pSet->width>0 && !pSet->forceTextArea ){
12001200
int hasVersionableValue = pSet->versionable &&
1201
- (db_get_versioned(pSet->name, NULL)!=0);
1201
+ (db_get_versioned(pSet->name, NULL, NULL)!=0);
12021202
@ <tr><td>
12031203
@ <a href='%R/help?cmd=%s(pSet->name)'>%h(pSet->name)</a>
12041204
if( pSet->versionable ){
12051205
@ (v)
12061206
} else {
@@ -1215,11 +1215,11 @@
12151215
}
12161216
@</table>
12171217
@ </td><td style="width:50px;"></td><td valign="top">
12181218
for(i=0, pSet=aSetting; i<nSetting; i++, pSet++){
12191219
if( pSet->width>0 && pSet->forceTextArea ){
1220
- int hasVersionableValue = db_get_versioned(pSet->name, NULL)!=0;
1220
+ int hasVersionableValue = db_get_versioned(pSet->name, NULL, NULL)!=0;
12211221
@ <a href='%R/help?cmd=%s(pSet->name)'>%s(pSet->name)</a>
12221222
if( pSet->versionable ){
12231223
@ (v)<br>
12241224
} else {
12251225
@ <br>
12261226
--- src/setup.c
+++ src/setup.c
@@ -1178,11 +1178,11 @@
1178 @ <table border="0"><tr><td valign="top">
1179 login_insert_csrf_secret();
1180 for(i=0, pSet=aSetting; i<nSetting; i++, pSet++){
1181 if( pSet->width==0 ){
1182 int hasVersionableValue = pSet->versionable &&
1183 (db_get_versioned(pSet->name, NULL)!=0);
1184 onoff_attribute("", pSet->name,
1185 pSet->var!=0 ? pSet->var : pSet->name /*works-like:"x"*/,
1186 is_truth(pSet->def), hasVersionableValue);
1187 @ <a href='%R/help?cmd=%s(pSet->name)'>%h(pSet->name)</a>
1188 if( pSet->versionable ){
@@ -1196,11 +1196,11 @@
1196 @ </td><td style="width:50px;"></td><td valign="top">
1197 @ <table>
1198 for(i=0, pSet=aSetting; i<nSetting; i++, pSet++){
1199 if( pSet->width>0 && !pSet->forceTextArea ){
1200 int hasVersionableValue = pSet->versionable &&
1201 (db_get_versioned(pSet->name, NULL)!=0);
1202 @ <tr><td>
1203 @ <a href='%R/help?cmd=%s(pSet->name)'>%h(pSet->name)</a>
1204 if( pSet->versionable ){
1205 @ (v)
1206 } else {
@@ -1215,11 +1215,11 @@
1215 }
1216 @</table>
1217 @ </td><td style="width:50px;"></td><td valign="top">
1218 for(i=0, pSet=aSetting; i<nSetting; i++, pSet++){
1219 if( pSet->width>0 && pSet->forceTextArea ){
1220 int hasVersionableValue = db_get_versioned(pSet->name, NULL)!=0;
1221 @ <a href='%R/help?cmd=%s(pSet->name)'>%s(pSet->name)</a>
1222 if( pSet->versionable ){
1223 @ (v)<br>
1224 } else {
1225 @ <br>
1226
--- src/setup.c
+++ src/setup.c
@@ -1178,11 +1178,11 @@
1178 @ <table border="0"><tr><td valign="top">
1179 login_insert_csrf_secret();
1180 for(i=0, pSet=aSetting; i<nSetting; i++, pSet++){
1181 if( pSet->width==0 ){
1182 int hasVersionableValue = pSet->versionable &&
1183 (db_get_versioned(pSet->name, NULL, NULL)!=0);
1184 onoff_attribute("", pSet->name,
1185 pSet->var!=0 ? pSet->var : pSet->name /*works-like:"x"*/,
1186 is_truth(pSet->def), hasVersionableValue);
1187 @ <a href='%R/help?cmd=%s(pSet->name)'>%h(pSet->name)</a>
1188 if( pSet->versionable ){
@@ -1196,11 +1196,11 @@
1196 @ </td><td style="width:50px;"></td><td valign="top">
1197 @ <table>
1198 for(i=0, pSet=aSetting; i<nSetting; i++, pSet++){
1199 if( pSet->width>0 && !pSet->forceTextArea ){
1200 int hasVersionableValue = pSet->versionable &&
1201 (db_get_versioned(pSet->name, NULL, NULL)!=0);
1202 @ <tr><td>
1203 @ <a href='%R/help?cmd=%s(pSet->name)'>%h(pSet->name)</a>
1204 if( pSet->versionable ){
1205 @ (v)
1206 } else {
@@ -1215,11 +1215,11 @@
1215 }
1216 @</table>
1217 @ </td><td style="width:50px;"></td><td valign="top">
1218 for(i=0, pSet=aSetting; i<nSetting; i++, pSet++){
1219 if( pSet->width>0 && pSet->forceTextArea ){
1220 int hasVersionableValue = db_get_versioned(pSet->name, NULL, NULL)!=0;
1221 @ <a href='%R/help?cmd=%s(pSet->name)'>%s(pSet->name)</a>
1222 if( pSet->versionable ){
1223 @ (v)<br>
1224 } else {
1225 @ <br>
1226
+3 -3
--- src/setup.c
+++ src/setup.c
@@ -1178,11 +1178,11 @@
11781178
@ <table border="0"><tr><td valign="top">
11791179
login_insert_csrf_secret();
11801180
for(i=0, pSet=aSetting; i<nSetting; i++, pSet++){
11811181
if( pSet->width==0 ){
11821182
int hasVersionableValue = pSet->versionable &&
1183
- (db_get_versioned(pSet->name, NULL)!=0);
1183
+ (db_get_versioned(pSet->name, NULL, NULL)!=0);
11841184
onoff_attribute("", pSet->name,
11851185
pSet->var!=0 ? pSet->var : pSet->name /*works-like:"x"*/,
11861186
is_truth(pSet->def), hasVersionableValue);
11871187
@ <a href='%R/help?cmd=%s(pSet->name)'>%h(pSet->name)</a>
11881188
if( pSet->versionable ){
@@ -1196,11 +1196,11 @@
11961196
@ </td><td style="width:50px;"></td><td valign="top">
11971197
@ <table>
11981198
for(i=0, pSet=aSetting; i<nSetting; i++, pSet++){
11991199
if( pSet->width>0 && !pSet->forceTextArea ){
12001200
int hasVersionableValue = pSet->versionable &&
1201
- (db_get_versioned(pSet->name, NULL)!=0);
1201
+ (db_get_versioned(pSet->name, NULL, NULL)!=0);
12021202
@ <tr><td>
12031203
@ <a href='%R/help?cmd=%s(pSet->name)'>%h(pSet->name)</a>
12041204
if( pSet->versionable ){
12051205
@ (v)
12061206
} else {
@@ -1215,11 +1215,11 @@
12151215
}
12161216
@</table>
12171217
@ </td><td style="width:50px;"></td><td valign="top">
12181218
for(i=0, pSet=aSetting; i<nSetting; i++, pSet++){
12191219
if( pSet->width>0 && pSet->forceTextArea ){
1220
- int hasVersionableValue = db_get_versioned(pSet->name, NULL)!=0;
1220
+ int hasVersionableValue = db_get_versioned(pSet->name, NULL, NULL)!=0;
12211221
@ <a href='%R/help?cmd=%s(pSet->name)'>%s(pSet->name)</a>
12221222
if( pSet->versionable ){
12231223
@ (v)<br>
12241224
} else {
12251225
@ <br>
12261226
--- src/setup.c
+++ src/setup.c
@@ -1178,11 +1178,11 @@
1178 @ <table border="0"><tr><td valign="top">
1179 login_insert_csrf_secret();
1180 for(i=0, pSet=aSetting; i<nSetting; i++, pSet++){
1181 if( pSet->width==0 ){
1182 int hasVersionableValue = pSet->versionable &&
1183 (db_get_versioned(pSet->name, NULL)!=0);
1184 onoff_attribute("", pSet->name,
1185 pSet->var!=0 ? pSet->var : pSet->name /*works-like:"x"*/,
1186 is_truth(pSet->def), hasVersionableValue);
1187 @ <a href='%R/help?cmd=%s(pSet->name)'>%h(pSet->name)</a>
1188 if( pSet->versionable ){
@@ -1196,11 +1196,11 @@
1196 @ </td><td style="width:50px;"></td><td valign="top">
1197 @ <table>
1198 for(i=0, pSet=aSetting; i<nSetting; i++, pSet++){
1199 if( pSet->width>0 && !pSet->forceTextArea ){
1200 int hasVersionableValue = pSet->versionable &&
1201 (db_get_versioned(pSet->name, NULL)!=0);
1202 @ <tr><td>
1203 @ <a href='%R/help?cmd=%s(pSet->name)'>%h(pSet->name)</a>
1204 if( pSet->versionable ){
1205 @ (v)
1206 } else {
@@ -1215,11 +1215,11 @@
1215 }
1216 @</table>
1217 @ </td><td style="width:50px;"></td><td valign="top">
1218 for(i=0, pSet=aSetting; i<nSetting; i++, pSet++){
1219 if( pSet->width>0 && pSet->forceTextArea ){
1220 int hasVersionableValue = db_get_versioned(pSet->name, NULL)!=0;
1221 @ <a href='%R/help?cmd=%s(pSet->name)'>%s(pSet->name)</a>
1222 if( pSet->versionable ){
1223 @ (v)<br>
1224 } else {
1225 @ <br>
1226
--- src/setup.c
+++ src/setup.c
@@ -1178,11 +1178,11 @@
1178 @ <table border="0"><tr><td valign="top">
1179 login_insert_csrf_secret();
1180 for(i=0, pSet=aSetting; i<nSetting; i++, pSet++){
1181 if( pSet->width==0 ){
1182 int hasVersionableValue = pSet->versionable &&
1183 (db_get_versioned(pSet->name, NULL, NULL)!=0);
1184 onoff_attribute("", pSet->name,
1185 pSet->var!=0 ? pSet->var : pSet->name /*works-like:"x"*/,
1186 is_truth(pSet->def), hasVersionableValue);
1187 @ <a href='%R/help?cmd=%s(pSet->name)'>%h(pSet->name)</a>
1188 if( pSet->versionable ){
@@ -1196,11 +1196,11 @@
1196 @ </td><td style="width:50px;"></td><td valign="top">
1197 @ <table>
1198 for(i=0, pSet=aSetting; i<nSetting; i++, pSet++){
1199 if( pSet->width>0 && !pSet->forceTextArea ){
1200 int hasVersionableValue = pSet->versionable &&
1201 (db_get_versioned(pSet->name, NULL, NULL)!=0);
1202 @ <tr><td>
1203 @ <a href='%R/help?cmd=%s(pSet->name)'>%h(pSet->name)</a>
1204 if( pSet->versionable ){
1205 @ (v)
1206 } else {
@@ -1215,11 +1215,11 @@
1215 }
1216 @</table>
1217 @ </td><td style="width:50px;"></td><td valign="top">
1218 for(i=0, pSet=aSetting; i<nSetting; i++, pSet++){
1219 if( pSet->width>0 && pSet->forceTextArea ){
1220 int hasVersionableValue = db_get_versioned(pSet->name, NULL, NULL)!=0;
1221 @ <a href='%R/help?cmd=%s(pSet->name)'>%s(pSet->name)</a>
1222 if( pSet->versionable ){
1223 @ (v)<br>
1224 } else {
1225 @ <br>
1226
+116 -12
--- src/setupuser.c
+++ src/setupuser.c
@@ -302,10 +302,101 @@
302302
if( zPw==0 ) return 0;
303303
if( zPw[0]==0 ) return 1;
304304
while( zPw[0]=='*' ){ zPw++; }
305305
return zPw[0]!=0;
306306
}
307
+
308
+/*
309
+** Return true if user capability string zNew contains any capability
310
+** letter which is not in user capability string zOrig, else 0. This
311
+** does not take inherited permissions into account. Either argument
312
+** may be NULL.
313
+*/
314
+static int userHasNewCaps(const char *zOrig, const char *zNew){
315
+ for( ; zNew && *zNew; ++zNew ){
316
+ if( !zOrig || strchr(zOrig,*zNew)==0 ){
317
+ return *zNew;
318
+ }
319
+ }
320
+ return 0;
321
+}
322
+
323
+/*
324
+** Sends notification of user permission elevation changes to all
325
+** subscribers with a "u" subscription. This is a no-op if alerts are
326
+** not enabled.
327
+**
328
+** These subscriptions differ from most, in that:
329
+**
330
+** - They currently lack an "unsubscribe" link.
331
+**
332
+** - Only an admin can assign this subscription, but if a non-admin
333
+** edits their subscriptions after an admin assigns them this one,
334
+** this particular one will be lost. "Feature or bug?" is unclear,
335
+** but it would be odd for a non-admin to be assigned this
336
+** capability.
337
+*/
338
+static void alert_user_elevation(const char *zLogin, /*Affected user*/
339
+ int uid, /*[user].uid*/
340
+ int bIsNew, /*true if new user*/
341
+ const char *zOrigCaps,/*Old caps*/
342
+ const char *zNewCaps /*New caps*/){
343
+ Blob hdr, body;
344
+ Stmt q;
345
+ int nBody;
346
+ AlertSender *pSender;
347
+ char *zSubname;
348
+ char *zURL;
349
+ char * zSubject;
350
+
351
+ if( !alert_enabled() ) return;
352
+ zSubject = bIsNew
353
+ ? mprintf("New user created: [%q]", zLogin)
354
+ : mprintf("User [%q] permissions elevated", zLogin);
355
+ zURL = db_get("email-url",0);
356
+ zSubname = db_get("email-subname", "[Fossil Repo]");
357
+ blob_init(&body, 0, 0);
358
+ blob_init(&hdr, 0, 0);
359
+ if( bIsNew ){
360
+ blob_appendf(&body, "User [%q] was created by with "
361
+ "permissions [%q] by user [%q].\n",
362
+ zLogin, zNewCaps, g.zLogin);
363
+ } else {
364
+ blob_appendf(&body, "Permissions for user [%q] where elevated "
365
+ "from [%q] to [%q] by user [%q].\n",
366
+ zLogin, zOrigCaps, zNewCaps, g.zLogin);
367
+ }
368
+ if( zURL ){
369
+ blob_appendf(&body, "\nUser editor: %s/setup_uedit?uid=%d\n", zURL, uid);
370
+ }
371
+ nBody = blob_size(&body);
372
+ pSender = alert_sender_new(0, 0);
373
+ db_prepare(&q,
374
+ "SELECT semail, hex(subscriberCode)"
375
+ " FROM subscriber, user "
376
+ " WHERE sverified AND NOT sdonotcall"
377
+ " AND suname=login"
378
+ " AND ssub GLOB '*u*'");
379
+ while( !pSender->zErr && db_step(&q)==SQLITE_ROW ){
380
+ const char *zTo = db_column_text(&q, 0);
381
+ blob_truncate(&hdr, 0);
382
+ blob_appendf(&hdr, "To: <%s>\r\nSubject: %s %s\r\n",
383
+ zTo, zSubname, zSubject);
384
+ if( zURL ){
385
+ const char *zCode = db_column_text(&q, 1);
386
+ blob_truncate(&body, nBody);
387
+ blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n",
388
+ zURL, zCode);
389
+ }
390
+ alert_send(pSender, &hdr, &body, 0);
391
+ }
392
+ db_finalize(&q);
393
+ alert_sender_free(pSender);
394
+ fossil_free(zURL);
395
+ fossil_free(zSubname);
396
+ fossil_free(zSubject);
397
+}
307398
308399
/*
309400
** WEBPAGE: setup_uedit
310401
**
311402
** Edit information about a user or create a new user.
@@ -314,10 +405,11 @@
314405
void user_edit(void){
315406
const char *zId, *zLogin, *zInfo, *zCap, *zPw;
316407
const char *zGroup;
317408
const char *zOldLogin;
318409
int uid, i;
410
+ char *zOldCaps = 0; /* Capabilities before edit */
319411
char *zDeleteVerify = 0; /* Delete user verification text */
320412
int higherUser = 0; /* True if user being edited is SETUP and the */
321413
/* user doing the editing is ADMIN. Disallow editing */
322414
const char *inherit[128];
323415
int a[128];
@@ -331,14 +423,15 @@
331423
/* Check to see if an ADMIN user is trying to edit a SETUP account.
332424
** Don't allow that.
333425
*/
334426
zId = PD("id", "0");
335427
uid = atoi(zId);
336
- if( zId && !g.perm.Setup && uid>0 ){
337
- char *zOldCaps;
338
- zOldCaps = db_text(0, "SELECT cap FROM user WHERE uid=%d",uid);
339
- higherUser = zOldCaps && strchr(zOldCaps,'s');
428
+ if( uid>0 ){
429
+ zOldCaps = db_text("", "SELECT cap FROM user WHERE uid=%d",uid);
430
+ if( zId && !g.perm.Setup ){
431
+ higherUser = zOldCaps && strchr(zOldCaps,'s');
432
+ }
340433
}
341434
342435
if( P("can") ){
343436
/* User pressed the cancel button */
344437
cgi_redirect(cgi_referer("setup_ulist"));
@@ -393,10 +486,12 @@
393486
}else if( !cgi_csrf_safe(2) ){
394487
/* This might be a cross-site request forgery, so ignore it */
395488
}else{
396489
/* We have all the information we need to make the change to the user */
397490
char c;
491
+ int bHasNewCaps = 0 /* 1 if user's permissions are increased */;
492
+ const int bIsNew = uid<=0;
398493
char aCap[70], zNm[4];
399494
zNm[0] = 'a';
400495
zNm[2] = 0;
401496
for(i=0, c='a'; c<='z'; c++){
402497
zNm[1] = c;
@@ -413,10 +508,11 @@
413508
a[c&0x7f] = P(zNm)!=0;
414509
if( a[c&0x7f] ) aCap[i++] = c;
415510
}
416511
417512
aCap[i] = 0;
513
+ bHasNewCaps = bIsNew || userHasNewCaps(zOldCaps, &aCap[0]);
418514
zPw = P("pw");
419515
zLogin = P("login");
420516
if( strlen(zLogin)==0 ){
421517
const char *zRef = cgi_referer("setup_ulist");
422518
style_header("User Creation Error");
@@ -444,15 +540,16 @@
444540
style_finish_page();
445541
return;
446542
}
447543
cgi_csrf_verify();
448544
db_unprotect(PROTECT_USER);
449
- db_multi_exec(
450
- "REPLACE INTO user(uid,login,info,pw,cap,mtime) "
451
- "VALUES(nullif(%d,0),%Q,%Q,%Q,%Q,now())",
452
- uid, zLogin, P("info"), zPw, &aCap[0]
453
- );
545
+ uid = db_int(0,
546
+ "REPLACE INTO user(uid,login,info,pw,cap,mtime) "
547
+ "VALUES(nullif(%d,0),%Q,%Q,%Q,%Q,now()) "
548
+ "RETURNING uid",
549
+ uid, zLogin, P("info"), zPw, &aCap[0]);
550
+ assert( uid>0 );
454551
if( zOldLogin && fossil_strcmp(zLogin, zOldLogin)!=0 ){
455552
if( alert_tables_exist() ){
456553
/* Rename matching subscriber entry, else the user cannot
457554
re-subscribe with their same email address. */
458555
db_multi_exec("UPDATE subscriber SET suname=%Q WHERE suname=%Q",
@@ -460,11 +557,12 @@
460557
}
461558
admin_log( "Renamed user [%q] to [%q].", zOldLogin, zLogin );
462559
}
463560
db_protect_pop();
464561
setup_incr_cfgcnt();
465
- admin_log( "Updated user [%q] with capabilities [%q].",
562
+ admin_log( "%s user [%q] with capabilities [%q].",
563
+ bIsNew ? "Added" : "Updated",
466564
zLogin, &aCap[0] );
467565
if( atoi(PD("all","0"))>0 ){
468566
Blob sql;
469567
char *zErr = 0;
470568
blob_zero(&sql);
@@ -515,30 +613,36 @@
515613
@ <span class="loginError">%h(zErr)</span>
516614
@
517615
@ <p><a href="setup_uedit?id=%d(uid)&referer=%T(zRef)">
518616
@ [Bummer]</a></p>
519617
style_finish_page();
618
+ if( bHasNewCaps ){
619
+ alert_user_elevation(zLogin, uid, bIsNew, zOldCaps, &aCap[0]);
620
+ }
520621
return;
521622
}
522623
}
624
+ if( bHasNewCaps ){
625
+ alert_user_elevation(zLogin, uid, bIsNew, zOldCaps, &aCap[0]);
626
+ }
523627
cgi_redirect(cgi_referer("setup_ulist"));
524628
return;
525629
}
526630
527631
/* Load the existing information about the user, if any
528632
*/
529633
zLogin = "";
530634
zInfo = "";
531
- zCap = "";
635
+ zCap = zOldCaps;
532636
zPw = "";
533637
for(i='a'; i<='z'; i++) oa[i] = "";
534638
for(i='0'; i<='9'; i++) oa[i] = "";
535639
for(i='A'; i<='Z'; i++) oa[i] = "";
536640
if( uid ){
641
+ assert( zCap );
537642
zLogin = db_text("", "SELECT login FROM user WHERE uid=%d", uid);
538643
zInfo = db_text("", "SELECT info FROM user WHERE uid=%d", uid);
539
- zCap = db_text("", "SELECT cap FROM user WHERE uid=%d", uid);
540644
zPw = db_text("", "SELECT pw FROM user WHERE uid=%d", uid);
541645
for(i=0; zCap[i]; i++){
542646
char c = zCap[i];
543647
if( (c>='a' && c<='z') || (c>='0' && c<='9') || (c>='A' && c<='Z') ){
544648
oa[c&0x7f] = " checked=\"checked\"";
545649
--- src/setupuser.c
+++ src/setupuser.c
@@ -302,10 +302,101 @@
302 if( zPw==0 ) return 0;
303 if( zPw[0]==0 ) return 1;
304 while( zPw[0]=='*' ){ zPw++; }
305 return zPw[0]!=0;
306 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
307
308 /*
309 ** WEBPAGE: setup_uedit
310 **
311 ** Edit information about a user or create a new user.
@@ -314,10 +405,11 @@
314 void user_edit(void){
315 const char *zId, *zLogin, *zInfo, *zCap, *zPw;
316 const char *zGroup;
317 const char *zOldLogin;
318 int uid, i;
 
319 char *zDeleteVerify = 0; /* Delete user verification text */
320 int higherUser = 0; /* True if user being edited is SETUP and the */
321 /* user doing the editing is ADMIN. Disallow editing */
322 const char *inherit[128];
323 int a[128];
@@ -331,14 +423,15 @@
331 /* Check to see if an ADMIN user is trying to edit a SETUP account.
332 ** Don't allow that.
333 */
334 zId = PD("id", "0");
335 uid = atoi(zId);
336 if( zId && !g.perm.Setup && uid>0 ){
337 char *zOldCaps;
338 zOldCaps = db_text(0, "SELECT cap FROM user WHERE uid=%d",uid);
339 higherUser = zOldCaps && strchr(zOldCaps,'s');
 
340 }
341
342 if( P("can") ){
343 /* User pressed the cancel button */
344 cgi_redirect(cgi_referer("setup_ulist"));
@@ -393,10 +486,12 @@
393 }else if( !cgi_csrf_safe(2) ){
394 /* This might be a cross-site request forgery, so ignore it */
395 }else{
396 /* We have all the information we need to make the change to the user */
397 char c;
 
 
398 char aCap[70], zNm[4];
399 zNm[0] = 'a';
400 zNm[2] = 0;
401 for(i=0, c='a'; c<='z'; c++){
402 zNm[1] = c;
@@ -413,10 +508,11 @@
413 a[c&0x7f] = P(zNm)!=0;
414 if( a[c&0x7f] ) aCap[i++] = c;
415 }
416
417 aCap[i] = 0;
 
418 zPw = P("pw");
419 zLogin = P("login");
420 if( strlen(zLogin)==0 ){
421 const char *zRef = cgi_referer("setup_ulist");
422 style_header("User Creation Error");
@@ -444,15 +540,16 @@
444 style_finish_page();
445 return;
446 }
447 cgi_csrf_verify();
448 db_unprotect(PROTECT_USER);
449 db_multi_exec(
450 "REPLACE INTO user(uid,login,info,pw,cap,mtime) "
451 "VALUES(nullif(%d,0),%Q,%Q,%Q,%Q,now())",
452 uid, zLogin, P("info"), zPw, &aCap[0]
453 );
 
454 if( zOldLogin && fossil_strcmp(zLogin, zOldLogin)!=0 ){
455 if( alert_tables_exist() ){
456 /* Rename matching subscriber entry, else the user cannot
457 re-subscribe with their same email address. */
458 db_multi_exec("UPDATE subscriber SET suname=%Q WHERE suname=%Q",
@@ -460,11 +557,12 @@
460 }
461 admin_log( "Renamed user [%q] to [%q].", zOldLogin, zLogin );
462 }
463 db_protect_pop();
464 setup_incr_cfgcnt();
465 admin_log( "Updated user [%q] with capabilities [%q].",
 
466 zLogin, &aCap[0] );
467 if( atoi(PD("all","0"))>0 ){
468 Blob sql;
469 char *zErr = 0;
470 blob_zero(&sql);
@@ -515,30 +613,36 @@
515 @ <span class="loginError">%h(zErr)</span>
516 @
517 @ <p><a href="setup_uedit?id=%d(uid)&referer=%T(zRef)">
518 @ [Bummer]</a></p>
519 style_finish_page();
 
 
 
520 return;
521 }
522 }
 
 
 
523 cgi_redirect(cgi_referer("setup_ulist"));
524 return;
525 }
526
527 /* Load the existing information about the user, if any
528 */
529 zLogin = "";
530 zInfo = "";
531 zCap = "";
532 zPw = "";
533 for(i='a'; i<='z'; i++) oa[i] = "";
534 for(i='0'; i<='9'; i++) oa[i] = "";
535 for(i='A'; i<='Z'; i++) oa[i] = "";
536 if( uid ){
 
537 zLogin = db_text("", "SELECT login FROM user WHERE uid=%d", uid);
538 zInfo = db_text("", "SELECT info FROM user WHERE uid=%d", uid);
539 zCap = db_text("", "SELECT cap FROM user WHERE uid=%d", uid);
540 zPw = db_text("", "SELECT pw FROM user WHERE uid=%d", uid);
541 for(i=0; zCap[i]; i++){
542 char c = zCap[i];
543 if( (c>='a' && c<='z') || (c>='0' && c<='9') || (c>='A' && c<='Z') ){
544 oa[c&0x7f] = " checked=\"checked\"";
545
--- src/setupuser.c
+++ src/setupuser.c
@@ -302,10 +302,101 @@
302 if( zPw==0 ) return 0;
303 if( zPw[0]==0 ) return 1;
304 while( zPw[0]=='*' ){ zPw++; }
305 return zPw[0]!=0;
306 }
307
308 /*
309 ** Return true if user capability string zNew contains any capability
310 ** letter which is not in user capability string zOrig, else 0. This
311 ** does not take inherited permissions into account. Either argument
312 ** may be NULL.
313 */
314 static int userHasNewCaps(const char *zOrig, const char *zNew){
315 for( ; zNew && *zNew; ++zNew ){
316 if( !zOrig || strchr(zOrig,*zNew)==0 ){
317 return *zNew;
318 }
319 }
320 return 0;
321 }
322
323 /*
324 ** Sends notification of user permission elevation changes to all
325 ** subscribers with a "u" subscription. This is a no-op if alerts are
326 ** not enabled.
327 **
328 ** These subscriptions differ from most, in that:
329 **
330 ** - They currently lack an "unsubscribe" link.
331 **
332 ** - Only an admin can assign this subscription, but if a non-admin
333 ** edits their subscriptions after an admin assigns them this one,
334 ** this particular one will be lost. "Feature or bug?" is unclear,
335 ** but it would be odd for a non-admin to be assigned this
336 ** capability.
337 */
338 static void alert_user_elevation(const char *zLogin, /*Affected user*/
339 int uid, /*[user].uid*/
340 int bIsNew, /*true if new user*/
341 const char *zOrigCaps,/*Old caps*/
342 const char *zNewCaps /*New caps*/){
343 Blob hdr, body;
344 Stmt q;
345 int nBody;
346 AlertSender *pSender;
347 char *zSubname;
348 char *zURL;
349 char * zSubject;
350
351 if( !alert_enabled() ) return;
352 zSubject = bIsNew
353 ? mprintf("New user created: [%q]", zLogin)
354 : mprintf("User [%q] permissions elevated", zLogin);
355 zURL = db_get("email-url",0);
356 zSubname = db_get("email-subname", "[Fossil Repo]");
357 blob_init(&body, 0, 0);
358 blob_init(&hdr, 0, 0);
359 if( bIsNew ){
360 blob_appendf(&body, "User [%q] was created by with "
361 "permissions [%q] by user [%q].\n",
362 zLogin, zNewCaps, g.zLogin);
363 } else {
364 blob_appendf(&body, "Permissions for user [%q] where elevated "
365 "from [%q] to [%q] by user [%q].\n",
366 zLogin, zOrigCaps, zNewCaps, g.zLogin);
367 }
368 if( zURL ){
369 blob_appendf(&body, "\nUser editor: %s/setup_uedit?uid=%d\n", zURL, uid);
370 }
371 nBody = blob_size(&body);
372 pSender = alert_sender_new(0, 0);
373 db_prepare(&q,
374 "SELECT semail, hex(subscriberCode)"
375 " FROM subscriber, user "
376 " WHERE sverified AND NOT sdonotcall"
377 " AND suname=login"
378 " AND ssub GLOB '*u*'");
379 while( !pSender->zErr && db_step(&q)==SQLITE_ROW ){
380 const char *zTo = db_column_text(&q, 0);
381 blob_truncate(&hdr, 0);
382 blob_appendf(&hdr, "To: <%s>\r\nSubject: %s %s\r\n",
383 zTo, zSubname, zSubject);
384 if( zURL ){
385 const char *zCode = db_column_text(&q, 1);
386 blob_truncate(&body, nBody);
387 blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n",
388 zURL, zCode);
389 }
390 alert_send(pSender, &hdr, &body, 0);
391 }
392 db_finalize(&q);
393 alert_sender_free(pSender);
394 fossil_free(zURL);
395 fossil_free(zSubname);
396 fossil_free(zSubject);
397 }
398
399 /*
400 ** WEBPAGE: setup_uedit
401 **
402 ** Edit information about a user or create a new user.
@@ -314,10 +405,11 @@
405 void user_edit(void){
406 const char *zId, *zLogin, *zInfo, *zCap, *zPw;
407 const char *zGroup;
408 const char *zOldLogin;
409 int uid, i;
410 char *zOldCaps = 0; /* Capabilities before edit */
411 char *zDeleteVerify = 0; /* Delete user verification text */
412 int higherUser = 0; /* True if user being edited is SETUP and the */
413 /* user doing the editing is ADMIN. Disallow editing */
414 const char *inherit[128];
415 int a[128];
@@ -331,14 +423,15 @@
423 /* Check to see if an ADMIN user is trying to edit a SETUP account.
424 ** Don't allow that.
425 */
426 zId = PD("id", "0");
427 uid = atoi(zId);
428 if( uid>0 ){
429 zOldCaps = db_text("", "SELECT cap FROM user WHERE uid=%d",uid);
430 if( zId && !g.perm.Setup ){
431 higherUser = zOldCaps && strchr(zOldCaps,'s');
432 }
433 }
434
435 if( P("can") ){
436 /* User pressed the cancel button */
437 cgi_redirect(cgi_referer("setup_ulist"));
@@ -393,10 +486,12 @@
486 }else if( !cgi_csrf_safe(2) ){
487 /* This might be a cross-site request forgery, so ignore it */
488 }else{
489 /* We have all the information we need to make the change to the user */
490 char c;
491 int bHasNewCaps = 0 /* 1 if user's permissions are increased */;
492 const int bIsNew = uid<=0;
493 char aCap[70], zNm[4];
494 zNm[0] = 'a';
495 zNm[2] = 0;
496 for(i=0, c='a'; c<='z'; c++){
497 zNm[1] = c;
@@ -413,10 +508,11 @@
508 a[c&0x7f] = P(zNm)!=0;
509 if( a[c&0x7f] ) aCap[i++] = c;
510 }
511
512 aCap[i] = 0;
513 bHasNewCaps = bIsNew || userHasNewCaps(zOldCaps, &aCap[0]);
514 zPw = P("pw");
515 zLogin = P("login");
516 if( strlen(zLogin)==0 ){
517 const char *zRef = cgi_referer("setup_ulist");
518 style_header("User Creation Error");
@@ -444,15 +540,16 @@
540 style_finish_page();
541 return;
542 }
543 cgi_csrf_verify();
544 db_unprotect(PROTECT_USER);
545 uid = db_int(0,
546 "REPLACE INTO user(uid,login,info,pw,cap,mtime) "
547 "VALUES(nullif(%d,0),%Q,%Q,%Q,%Q,now()) "
548 "RETURNING uid",
549 uid, zLogin, P("info"), zPw, &aCap[0]);
550 assert( uid>0 );
551 if( zOldLogin && fossil_strcmp(zLogin, zOldLogin)!=0 ){
552 if( alert_tables_exist() ){
553 /* Rename matching subscriber entry, else the user cannot
554 re-subscribe with their same email address. */
555 db_multi_exec("UPDATE subscriber SET suname=%Q WHERE suname=%Q",
@@ -460,11 +557,12 @@
557 }
558 admin_log( "Renamed user [%q] to [%q].", zOldLogin, zLogin );
559 }
560 db_protect_pop();
561 setup_incr_cfgcnt();
562 admin_log( "%s user [%q] with capabilities [%q].",
563 bIsNew ? "Added" : "Updated",
564 zLogin, &aCap[0] );
565 if( atoi(PD("all","0"))>0 ){
566 Blob sql;
567 char *zErr = 0;
568 blob_zero(&sql);
@@ -515,30 +613,36 @@
613 @ <span class="loginError">%h(zErr)</span>
614 @
615 @ <p><a href="setup_uedit?id=%d(uid)&referer=%T(zRef)">
616 @ [Bummer]</a></p>
617 style_finish_page();
618 if( bHasNewCaps ){
619 alert_user_elevation(zLogin, uid, bIsNew, zOldCaps, &aCap[0]);
620 }
621 return;
622 }
623 }
624 if( bHasNewCaps ){
625 alert_user_elevation(zLogin, uid, bIsNew, zOldCaps, &aCap[0]);
626 }
627 cgi_redirect(cgi_referer("setup_ulist"));
628 return;
629 }
630
631 /* Load the existing information about the user, if any
632 */
633 zLogin = "";
634 zInfo = "";
635 zCap = zOldCaps;
636 zPw = "";
637 for(i='a'; i<='z'; i++) oa[i] = "";
638 for(i='0'; i<='9'; i++) oa[i] = "";
639 for(i='A'; i<='Z'; i++) oa[i] = "";
640 if( uid ){
641 assert( zCap );
642 zLogin = db_text("", "SELECT login FROM user WHERE uid=%d", uid);
643 zInfo = db_text("", "SELECT info FROM user WHERE uid=%d", uid);
 
644 zPw = db_text("", "SELECT pw FROM user WHERE uid=%d", uid);
645 for(i=0; zCap[i]; i++){
646 char c = zCap[i];
647 if( (c>='a' && c<='z') || (c>='0' && c<='9') || (c>='A' && c<='Z') ){
648 oa[c&0x7f] = " checked=\"checked\"";
649
--- src/sqlcmd.c
+++ src/sqlcmd.c
@@ -235,10 +235,11 @@
235235
char *zSql = sqlite3_mprintf("ATTACH %Q AS 'configdb' KEY ''",
236236
g.zConfigDbName);
237237
sqlite3_exec(db, zSql, 0, 0, 0);
238238
sqlite3_free(zSql);
239239
}
240
+ (void)timeline_query_for_tty(); /* Registers wiki_to_text() as side-effect */
240241
/* Arrange to trace close operations so that static prepared statements
241242
** will get cleaned up when the shell closes the database connection */
242243
if( g.fSqlTrace ) mTrace |= SQLITE_TRACE_PROFILE;
243244
sqlite3_trace_v2(db, mTrace, db_sql_trace, 0);
244245
db_protect_only(PROTECT_NONE);
245246
--- src/sqlcmd.c
+++ src/sqlcmd.c
@@ -235,10 +235,11 @@
235 char *zSql = sqlite3_mprintf("ATTACH %Q AS 'configdb' KEY ''",
236 g.zConfigDbName);
237 sqlite3_exec(db, zSql, 0, 0, 0);
238 sqlite3_free(zSql);
239 }
 
240 /* Arrange to trace close operations so that static prepared statements
241 ** will get cleaned up when the shell closes the database connection */
242 if( g.fSqlTrace ) mTrace |= SQLITE_TRACE_PROFILE;
243 sqlite3_trace_v2(db, mTrace, db_sql_trace, 0);
244 db_protect_only(PROTECT_NONE);
245
--- src/sqlcmd.c
+++ src/sqlcmd.c
@@ -235,10 +235,11 @@
235 char *zSql = sqlite3_mprintf("ATTACH %Q AS 'configdb' KEY ''",
236 g.zConfigDbName);
237 sqlite3_exec(db, zSql, 0, 0, 0);
238 sqlite3_free(zSql);
239 }
240 (void)timeline_query_for_tty(); /* Registers wiki_to_text() as side-effect */
241 /* Arrange to trace close operations so that static prepared statements
242 ** will get cleaned up when the shell closes the database connection */
243 if( g.fSqlTrace ) mTrace |= SQLITE_TRACE_PROFILE;
244 sqlite3_trace_v2(db, mTrace, db_sql_trace, 0);
245 db_protect_only(PROTECT_NONE);
246
+1 -1
--- src/tar.c
+++ src/tar.c
@@ -499,11 +499,11 @@
499499
pManifest = manifest_get(rid, CFTYPE_MANIFEST, 0);
500500
if( pManifest ){
501501
int flg, eflg = 0;
502502
mTime = (unsigned)((pManifest->rDate - 2440587.5)*86400.0);
503503
if( pTar ) tar_begin(mTime);
504
- flg = db_get_manifest_setting();
504
+ flg = db_get_manifest_setting(blob_str(&hash));
505505
if( flg ){
506506
/* eflg is the effective flags, taking include/exclude into account */
507507
if( (pInclude==0 || glob_match(pInclude, "manifest"))
508508
&& !glob_match(pExclude, "manifest")
509509
&& (flg & MFESTFLG_RAW) ){
510510
--- src/tar.c
+++ src/tar.c
@@ -499,11 +499,11 @@
499 pManifest = manifest_get(rid, CFTYPE_MANIFEST, 0);
500 if( pManifest ){
501 int flg, eflg = 0;
502 mTime = (unsigned)((pManifest->rDate - 2440587.5)*86400.0);
503 if( pTar ) tar_begin(mTime);
504 flg = db_get_manifest_setting();
505 if( flg ){
506 /* eflg is the effective flags, taking include/exclude into account */
507 if( (pInclude==0 || glob_match(pInclude, "manifest"))
508 && !glob_match(pExclude, "manifest")
509 && (flg & MFESTFLG_RAW) ){
510
--- src/tar.c
+++ src/tar.c
@@ -499,11 +499,11 @@
499 pManifest = manifest_get(rid, CFTYPE_MANIFEST, 0);
500 if( pManifest ){
501 int flg, eflg = 0;
502 mTime = (unsigned)((pManifest->rDate - 2440587.5)*86400.0);
503 if( pTar ) tar_begin(mTime);
504 flg = db_get_manifest_setting(blob_str(&hash));
505 if( flg ){
506 /* eflg is the effective flags, taking include/exclude into account */
507 if( (pInclude==0 || glob_match(pInclude, "manifest"))
508 && !glob_match(pExclude, "manifest")
509 && (flg & MFESTFLG_RAW) ){
510
+47 -10
--- src/timeline.c
+++ src/timeline.c
@@ -1381,11 +1381,11 @@
13811381
** return 0.
13821382
*/
13831383
static int timeline_endpoint(
13841384
int iFrom, /* Starting point */
13851385
const char *zEnd, /* Tag we are searching for */
1386
- int bForward /* 1: forwards in time (descendents) 0: backwards */
1386
+ int bForward /* 1: forwards in time (descendants) 0: backwards */
13871387
){
13881388
int tagId;
13891389
int endId = 0;
13901390
Stmt q;
13911391
int ans = 0;
@@ -1513,17 +1513,17 @@
15131513
/*
15141514
** COMMAND: test-endpoint
15151515
**
15161516
** Usage: fossil test-endpoint BASE TAG ?OPTIONS?
15171517
**
1518
-** Show the first check-in with TAG that is a descendent or ancestor
1519
-** of BASE. The first descendent checkin is shown by default. Use
1518
+** Show the first check-in with TAG that is a descendant or ancestor
1519
+** of BASE. The first descendant checkin is shown by default. Use
15201520
** the --backto to see the first ancestor checkin.
15211521
**
15221522
** Options:
15231523
**
1524
-** --backto Show ancestor. Others defaults to descendents.
1524
+** --backto Show ancestor. Others defaults to descendants.
15251525
*/
15261526
void timeline_test_endpoint(void){
15271527
int bForward = find_option("backto",0,0)==0;
15281528
int from_rid;
15291529
int ans;
@@ -2229,11 +2229,11 @@
22292229
}
22302230
addFileGlobDescription(zChng, &desc);
22312231
}else if( (p_rid || d_rid) && g.perm.Read && zTagSql==0 ){
22322232
/* If either p= or d= or both are present, ignore all other parameters
22332233
** other than n=, ft=, and bt= */
2234
- const char *zBaseName;
2234
+ const char *zBaseName = 0;
22352235
int np = 0, nd;
22362236
const char *zBackTo = 0;
22372237
const char *zFwdTo = 0;
22382238
int ridBackTo = 0;
22392239
int ridFwdTo = 0;
@@ -2355,14 +2355,14 @@
23552355
if( bSeparateDandP ){
23562356
int n = db_int(0, "SELECT count(*) FROM ok");
23572357
blob_reset(&desc);
23582358
blob_appendf(&desc,
23592359
"%d check-ins that are both ancestors of %z%h</a>"
2360
- " and descendents of %z%h</a>",
2360
+ " and descendants of %z%h</a>",
23612361
n,
23622362
href("%R/info?name=%h",zDPNameP),zDPNameP,
2363
- href("%R/info/name=%h",zDPNameD),zDPNameD
2363
+ href("%R/info?name=%h",zDPNameD),zDPNameD
23642364
);
23652365
ridBackTo = 0;
23662366
ridFwdTo = 0;
23672367
}else{
23682368
blob_appendf(&desc, " of %z%h</a>",
@@ -2380,11 +2380,11 @@
23802380
href("%R/info?name=%h",zBackTo), zBackTo,
23812381
bBackAdded ? " (not a direct anscestor)" : "");
23822382
if( ridFwdTo && zFwdTo ){
23832383
blob_appendf(&desc, " and up to %z%h</a>%s",
23842384
href("%R/info?name=%h",zFwdTo), zFwdTo,
2385
- bFwdAdded ? " (not a direct descendent)" : "");
2385
+ bFwdAdded ? " (not a direct descendant)" : "");
23862386
}
23872387
}
23882388
}else if( ridFwdTo ){
23892389
if( nd==0 ){
23902390
blob_reset(&desc);
@@ -2393,11 +2393,11 @@
23932393
href("%R/info?name=%h",zBaseName), zBaseName,
23942394
href("%R/info?name=%h",zFwdTo), zFwdTo);
23952395
}else{
23962396
blob_appendf(&desc, " up to %z%h</a>%s",
23972397
href("%R/info?name=%h",zFwdTo), zFwdTo,
2398
- bFwdAdded ? " (not a direct descendent)":"");
2398
+ bFwdAdded ? " (not a direct descendant)":"");
23992399
}
24002400
}
24012401
if( advancedMenu ){
24022402
style_submenu_checkbox("v", "Files", (zType[0]!='a' && zType[0]!='c'),0);
24032403
}
@@ -3435,22 +3435,54 @@
34353435
fossil_print("+++ no more data (%d) +++\n", nEntry);
34363436
}
34373437
}
34383438
if( fchngQueryInit ) db_finalize(&fchngQuery);
34393439
}
3440
+
3441
+/*
3442
+** wiki_to_text(TEXT)
3443
+**
3444
+** Return a plain-text rendering of Fossil-Wiki TEXT.
3445
+*/
3446
+static void wiki_to_text_sqlfunc(
3447
+ sqlite3_context *context,
3448
+ int argc,
3449
+ sqlite3_value **argv
3450
+){
3451
+ const char *zIn, *zOut;
3452
+ int nIn, nOut;
3453
+ Blob in, html, txt;
3454
+ zIn = (const char*)sqlite3_value_text(argv[0]);
3455
+ if( zIn==0 ) return;
3456
+ nIn = sqlite3_value_bytes(argv[0]);
3457
+ blob_init(&in, zIn, nIn);
3458
+ blob_init(&html, 0, 0);
3459
+ wiki_convert(&in, &html, WIKI_INLINE);
3460
+ blob_reset(&in);
3461
+ blob_init(&txt, 0, 0);
3462
+ html_to_plaintext(blob_str(&html), &txt);
3463
+ blob_reset(&html);
3464
+ nOut = blob_size(&txt);
3465
+ zOut = blob_str(&txt);
3466
+ while( fossil_isspace(zOut[0]) ){ zOut++; nOut--; }
3467
+ while( nOut>0 && fossil_isspace(zOut[nOut-1]) ){ nOut--; }
3468
+ sqlite3_result_text(context, zOut, nOut, SQLITE_TRANSIENT);
3469
+ blob_reset(&txt);
3470
+}
34403471
34413472
/*
34423473
** Return a pointer to a static string that forms the basis for
34433474
** a timeline query for display on a TTY.
34443475
*/
34453476
const char *timeline_query_for_tty(void){
3477
+ static int once = 0;
34463478
static const char zBaseSql[] =
34473479
@ SELECT
34483480
@ blob.rid AS rid,
34493481
@ uuid,
34503482
@ datetime(event.mtime,toLocal()) AS mDateTime,
3451
- @ coalesce(ecomment,comment)
3483
+ @ wiki_to_text(coalesce(ecomment,comment))
34523484
@ || ' (user: ' || coalesce(euser,user,'?')
34533485
@ || (SELECT case when length(x)>0 then ' tags: ' || x else '' end
34543486
@ FROM (SELECT group_concat(substr(tagname,5), ', ') AS x
34553487
@ FROM tag, tagxref
34563488
@ WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid
@@ -3474,10 +3506,15 @@
34743506
@ AND tagxref.tagtype>0
34753507
@ AND tagxref.rid=blob.rid
34763508
@ WHERE blob.rid=event.objid
34773509
@ AND tag.tagname='branch'
34783510
;
3511
+ if( !once && g.db ){
3512
+ once = 1;
3513
+ sqlite3_create_function(g.db, "wiki_to_text", 1, SQLITE_UTF8, 0,
3514
+ wiki_to_text_sqlfunc, 0, 0);
3515
+ }
34793516
return zBaseSql;
34803517
}
34813518
34823519
/*
34833520
** Return true if the input string is a date in the ISO 8601 format:
34843521
--- src/timeline.c
+++ src/timeline.c
@@ -1381,11 +1381,11 @@
1381 ** return 0.
1382 */
1383 static int timeline_endpoint(
1384 int iFrom, /* Starting point */
1385 const char *zEnd, /* Tag we are searching for */
1386 int bForward /* 1: forwards in time (descendents) 0: backwards */
1387 ){
1388 int tagId;
1389 int endId = 0;
1390 Stmt q;
1391 int ans = 0;
@@ -1513,17 +1513,17 @@
1513 /*
1514 ** COMMAND: test-endpoint
1515 **
1516 ** Usage: fossil test-endpoint BASE TAG ?OPTIONS?
1517 **
1518 ** Show the first check-in with TAG that is a descendent or ancestor
1519 ** of BASE. The first descendent checkin is shown by default. Use
1520 ** the --backto to see the first ancestor checkin.
1521 **
1522 ** Options:
1523 **
1524 ** --backto Show ancestor. Others defaults to descendents.
1525 */
1526 void timeline_test_endpoint(void){
1527 int bForward = find_option("backto",0,0)==0;
1528 int from_rid;
1529 int ans;
@@ -2229,11 +2229,11 @@
2229 }
2230 addFileGlobDescription(zChng, &desc);
2231 }else if( (p_rid || d_rid) && g.perm.Read && zTagSql==0 ){
2232 /* If either p= or d= or both are present, ignore all other parameters
2233 ** other than n=, ft=, and bt= */
2234 const char *zBaseName;
2235 int np = 0, nd;
2236 const char *zBackTo = 0;
2237 const char *zFwdTo = 0;
2238 int ridBackTo = 0;
2239 int ridFwdTo = 0;
@@ -2355,14 +2355,14 @@
2355 if( bSeparateDandP ){
2356 int n = db_int(0, "SELECT count(*) FROM ok");
2357 blob_reset(&desc);
2358 blob_appendf(&desc,
2359 "%d check-ins that are both ancestors of %z%h</a>"
2360 " and descendents of %z%h</a>",
2361 n,
2362 href("%R/info?name=%h",zDPNameP),zDPNameP,
2363 href("%R/info/name=%h",zDPNameD),zDPNameD
2364 );
2365 ridBackTo = 0;
2366 ridFwdTo = 0;
2367 }else{
2368 blob_appendf(&desc, " of %z%h</a>",
@@ -2380,11 +2380,11 @@
2380 href("%R/info?name=%h",zBackTo), zBackTo,
2381 bBackAdded ? " (not a direct anscestor)" : "");
2382 if( ridFwdTo && zFwdTo ){
2383 blob_appendf(&desc, " and up to %z%h</a>%s",
2384 href("%R/info?name=%h",zFwdTo), zFwdTo,
2385 bFwdAdded ? " (not a direct descendent)" : "");
2386 }
2387 }
2388 }else if( ridFwdTo ){
2389 if( nd==0 ){
2390 blob_reset(&desc);
@@ -2393,11 +2393,11 @@
2393 href("%R/info?name=%h",zBaseName), zBaseName,
2394 href("%R/info?name=%h",zFwdTo), zFwdTo);
2395 }else{
2396 blob_appendf(&desc, " up to %z%h</a>%s",
2397 href("%R/info?name=%h",zFwdTo), zFwdTo,
2398 bFwdAdded ? " (not a direct descendent)":"");
2399 }
2400 }
2401 if( advancedMenu ){
2402 style_submenu_checkbox("v", "Files", (zType[0]!='a' && zType[0]!='c'),0);
2403 }
@@ -3435,22 +3435,54 @@
3435 fossil_print("+++ no more data (%d) +++\n", nEntry);
3436 }
3437 }
3438 if( fchngQueryInit ) db_finalize(&fchngQuery);
3439 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3440
3441 /*
3442 ** Return a pointer to a static string that forms the basis for
3443 ** a timeline query for display on a TTY.
3444 */
3445 const char *timeline_query_for_tty(void){
 
3446 static const char zBaseSql[] =
3447 @ SELECT
3448 @ blob.rid AS rid,
3449 @ uuid,
3450 @ datetime(event.mtime,toLocal()) AS mDateTime,
3451 @ coalesce(ecomment,comment)
3452 @ || ' (user: ' || coalesce(euser,user,'?')
3453 @ || (SELECT case when length(x)>0 then ' tags: ' || x else '' end
3454 @ FROM (SELECT group_concat(substr(tagname,5), ', ') AS x
3455 @ FROM tag, tagxref
3456 @ WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid
@@ -3474,10 +3506,15 @@
3474 @ AND tagxref.tagtype>0
3475 @ AND tagxref.rid=blob.rid
3476 @ WHERE blob.rid=event.objid
3477 @ AND tag.tagname='branch'
3478 ;
 
 
 
 
 
3479 return zBaseSql;
3480 }
3481
3482 /*
3483 ** Return true if the input string is a date in the ISO 8601 format:
3484
--- src/timeline.c
+++ src/timeline.c
@@ -1381,11 +1381,11 @@
1381 ** return 0.
1382 */
1383 static int timeline_endpoint(
1384 int iFrom, /* Starting point */
1385 const char *zEnd, /* Tag we are searching for */
1386 int bForward /* 1: forwards in time (descendants) 0: backwards */
1387 ){
1388 int tagId;
1389 int endId = 0;
1390 Stmt q;
1391 int ans = 0;
@@ -1513,17 +1513,17 @@
1513 /*
1514 ** COMMAND: test-endpoint
1515 **
1516 ** Usage: fossil test-endpoint BASE TAG ?OPTIONS?
1517 **
1518 ** Show the first check-in with TAG that is a descendant or ancestor
1519 ** of BASE. The first descendant checkin is shown by default. Use
1520 ** the --backto to see the first ancestor checkin.
1521 **
1522 ** Options:
1523 **
1524 ** --backto Show ancestor. Others defaults to descendants.
1525 */
1526 void timeline_test_endpoint(void){
1527 int bForward = find_option("backto",0,0)==0;
1528 int from_rid;
1529 int ans;
@@ -2229,11 +2229,11 @@
2229 }
2230 addFileGlobDescription(zChng, &desc);
2231 }else if( (p_rid || d_rid) && g.perm.Read && zTagSql==0 ){
2232 /* If either p= or d= or both are present, ignore all other parameters
2233 ** other than n=, ft=, and bt= */
2234 const char *zBaseName = 0;
2235 int np = 0, nd;
2236 const char *zBackTo = 0;
2237 const char *zFwdTo = 0;
2238 int ridBackTo = 0;
2239 int ridFwdTo = 0;
@@ -2355,14 +2355,14 @@
2355 if( bSeparateDandP ){
2356 int n = db_int(0, "SELECT count(*) FROM ok");
2357 blob_reset(&desc);
2358 blob_appendf(&desc,
2359 "%d check-ins that are both ancestors of %z%h</a>"
2360 " and descendants of %z%h</a>",
2361 n,
2362 href("%R/info?name=%h",zDPNameP),zDPNameP,
2363 href("%R/info?name=%h",zDPNameD),zDPNameD
2364 );
2365 ridBackTo = 0;
2366 ridFwdTo = 0;
2367 }else{
2368 blob_appendf(&desc, " of %z%h</a>",
@@ -2380,11 +2380,11 @@
2380 href("%R/info?name=%h",zBackTo), zBackTo,
2381 bBackAdded ? " (not a direct anscestor)" : "");
2382 if( ridFwdTo && zFwdTo ){
2383 blob_appendf(&desc, " and up to %z%h</a>%s",
2384 href("%R/info?name=%h",zFwdTo), zFwdTo,
2385 bFwdAdded ? " (not a direct descendant)" : "");
2386 }
2387 }
2388 }else if( ridFwdTo ){
2389 if( nd==0 ){
2390 blob_reset(&desc);
@@ -2393,11 +2393,11 @@
2393 href("%R/info?name=%h",zBaseName), zBaseName,
2394 href("%R/info?name=%h",zFwdTo), zFwdTo);
2395 }else{
2396 blob_appendf(&desc, " up to %z%h</a>%s",
2397 href("%R/info?name=%h",zFwdTo), zFwdTo,
2398 bFwdAdded ? " (not a direct descendant)":"");
2399 }
2400 }
2401 if( advancedMenu ){
2402 style_submenu_checkbox("v", "Files", (zType[0]!='a' && zType[0]!='c'),0);
2403 }
@@ -3435,22 +3435,54 @@
3435 fossil_print("+++ no more data (%d) +++\n", nEntry);
3436 }
3437 }
3438 if( fchngQueryInit ) db_finalize(&fchngQuery);
3439 }
3440
3441 /*
3442 ** wiki_to_text(TEXT)
3443 **
3444 ** Return a plain-text rendering of Fossil-Wiki TEXT.
3445 */
3446 static void wiki_to_text_sqlfunc(
3447 sqlite3_context *context,
3448 int argc,
3449 sqlite3_value **argv
3450 ){
3451 const char *zIn, *zOut;
3452 int nIn, nOut;
3453 Blob in, html, txt;
3454 zIn = (const char*)sqlite3_value_text(argv[0]);
3455 if( zIn==0 ) return;
3456 nIn = sqlite3_value_bytes(argv[0]);
3457 blob_init(&in, zIn, nIn);
3458 blob_init(&html, 0, 0);
3459 wiki_convert(&in, &html, WIKI_INLINE);
3460 blob_reset(&in);
3461 blob_init(&txt, 0, 0);
3462 html_to_plaintext(blob_str(&html), &txt);
3463 blob_reset(&html);
3464 nOut = blob_size(&txt);
3465 zOut = blob_str(&txt);
3466 while( fossil_isspace(zOut[0]) ){ zOut++; nOut--; }
3467 while( nOut>0 && fossil_isspace(zOut[nOut-1]) ){ nOut--; }
3468 sqlite3_result_text(context, zOut, nOut, SQLITE_TRANSIENT);
3469 blob_reset(&txt);
3470 }
3471
3472 /*
3473 ** Return a pointer to a static string that forms the basis for
3474 ** a timeline query for display on a TTY.
3475 */
3476 const char *timeline_query_for_tty(void){
3477 static int once = 0;
3478 static const char zBaseSql[] =
3479 @ SELECT
3480 @ blob.rid AS rid,
3481 @ uuid,
3482 @ datetime(event.mtime,toLocal()) AS mDateTime,
3483 @ wiki_to_text(coalesce(ecomment,comment))
3484 @ || ' (user: ' || coalesce(euser,user,'?')
3485 @ || (SELECT case when length(x)>0 then ' tags: ' || x else '' end
3486 @ FROM (SELECT group_concat(substr(tagname,5), ', ') AS x
3487 @ FROM tag, tagxref
3488 @ WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid
@@ -3474,10 +3506,15 @@
3506 @ AND tagxref.tagtype>0
3507 @ AND tagxref.rid=blob.rid
3508 @ WHERE blob.rid=event.objid
3509 @ AND tag.tagname='branch'
3510 ;
3511 if( !once && g.db ){
3512 once = 1;
3513 sqlite3_create_function(g.db, "wiki_to_text", 1, SQLITE_UTF8, 0,
3514 wiki_to_text_sqlfunc, 0, 0);
3515 }
3516 return zBaseSql;
3517 }
3518
3519 /*
3520 ** Return true if the input string is a date in the ISO 8601 format:
3521
+1 -1
--- src/update.c
+++ src/update.c
@@ -199,11 +199,11 @@
199199
}
200200
}
201201
}
202202
203203
/* If no VERSION is specified on the command-line, then look for a
204
- ** descendent of the current version. If there are multiple descendants,
204
+ ** descendant of the current version. If there are multiple descendants,
205205
** look for one from the same branch as the current version. If there
206206
** are still multiple descendants, show them all and refuse to update
207207
** until the user selects one.
208208
*/
209209
if( tid==0 ){
210210
--- src/update.c
+++ src/update.c
@@ -199,11 +199,11 @@
199 }
200 }
201 }
202
203 /* If no VERSION is specified on the command-line, then look for a
204 ** descendent of the current version. If there are multiple descendants,
205 ** look for one from the same branch as the current version. If there
206 ** are still multiple descendants, show them all and refuse to update
207 ** until the user selects one.
208 */
209 if( tid==0 ){
210
--- src/update.c
+++ src/update.c
@@ -199,11 +199,11 @@
199 }
200 }
201 }
202
203 /* If no VERSION is specified on the command-line, then look for a
204 ** descendant of the current version. If there are multiple descendants,
205 ** look for one from the same branch as the current version. If there
206 ** are still multiple descendants, show them all and refuse to update
207 ** until the user selects one.
208 */
209 if( tid==0 ){
210
+19 -13
--- src/wiki.c
+++ src/wiki.c
@@ -21,10 +21,13 @@
2121
#include "config.h"
2222
#include <assert.h>
2323
#include <ctype.h>
2424
#include "wiki.h"
2525
26
+#define has_prefix(literal_prfx, zStr) \
27
+ (fossil_strncmp((zStr), "" literal_prfx, (sizeof literal_prfx)-1)==0)
28
+
2629
/*
2730
** Return true if the input string is a well-formed wiki page name.
2831
**
2932
** Well-formed wiki page names do not begin or end with whitespace,
3033
** and do not contain tabs or other control characters and do not
@@ -420,22 +423,22 @@
420423
*/
421424
int wiki_page_type(const char *zPageName){
422425
if( db_get_boolean("wiki-about",1)==0 ){
423426
return WIKITYPE_NORMAL;
424427
}else
425
- if( sqlite3_strglob("checkin/*", zPageName)==0
428
+ if( has_prefix("checkin/", zPageName)
426429
&& db_exists("SELECT 1 FROM blob WHERE uuid=%Q",zPageName+8)
427430
){
428431
return WIKITYPE_CHECKIN;
429432
}else
430
- if( sqlite3_strglob("branch/*", zPageName)==0 ){
433
+ if( has_prefix("branch/", zPageName) ){
431434
return WIKITYPE_BRANCH;
432435
}else
433
- if( sqlite3_strglob("tag/*", zPageName)==0 ){
436
+ if( has_prefix("tag/", zPageName) ){
434437
return WIKITYPE_TAG;
435438
}else
436
- if( sqlite3_strglob("ticket/*", zPageName)==0 ){
439
+ if( has_prefix("ticket/", zPageName) ){
437440
return WIKITYPE_TICKET;
438441
}
439442
return WIKITYPE_NORMAL;
440443
}
441444
@@ -1987,11 +1990,12 @@
19871990
style_submenu_element("All", "%R/wcontent?all=1");
19881991
}
19891992
cgi_check_for_malice();
19901993
showCkBr = db_exists(
19911994
"SELECT tag.tagname AS tn FROM tag JOIN tagxref USING(tagid) "
1992
- "WHERE ( tn GLOB 'wiki-checkin/*' OR tn GLOB 'wiki-branch/*' ) "
1995
+ "WHERE ( tn GLOB 'wiki-checkin/*' OR tn GLOB 'wiki-branch/*' OR "
1996
+ " tn GLOB 'wiki-tag/*' OR tn GLOB 'wiki-ticket/*' ) "
19931997
" AND TYPEOF(tagxref.value+0)='integer'" );
19941998
if( showCkBr ){
19951999
showCkBr = P("showckbr")!=0;
19962000
style_submenu_checkbox("showckbr", "Show associated wikis", 0, 0);
19972001
}
@@ -2017,20 +2021,20 @@
20172021
char *zAge;
20182022
int wcnt = db_column_int(&q, 4);
20192023
char *zWDisplayName;
20202024
20212025
if( !showCkBr &&
2022
- (sqlite3_strglob("checkin/*", zWName)==0 ||
2023
- sqlite3_strglob("branch/*", zWName)==0 ||
2024
- sqlite3_strglob("tag/*", zWName)==0 ||
2025
- sqlite3_strglob("ticket/*", zWName)==0) ){
2026
+ (has_prefix("checkin/", zWName) ||
2027
+ has_prefix("branch/", zWName) ||
2028
+ has_prefix("tag/", zWName) ||
2029
+ has_prefix("ticket/", zWName) )){
20262030
continue;
20272031
}
2028
- if( sqlite3_strglob("checkin/*", zWName)==0 ){
2032
+ if( has_prefix("checkin/",zWName) || has_prefix("ticket/",zWName) ){
20292033
zWDisplayName = mprintf("%.25s...", zWName);
20302034
}else{
2031
- zWDisplayName = mprintf("%s", zWName);
2035
+ zWDisplayName = fossil_strdup(zWName);
20322036
}
20332037
if( wrid==0 ){
20342038
if( !showAll ) continue;
20352039
@ <tr><td data-sortkey="%h(zSort)">\
20362040
@ %z(href("%R/whistory?name=%T",zWName))<s>%h(zWDisplayName)</s></a></td>
@@ -2522,12 +2526,14 @@
25222526
const int wrid = db_column_int(&q, 2);
25232527
if(!showAll && !wrid){
25242528
continue;
25252529
}
25262530
if( !showCkBr &&
2527
- (sqlite3_strglob("checkin/*", zName)==0 ||
2528
- sqlite3_strglob("branch/*", zName)==0) ){
2531
+ (has_prefix("checkin/", zName) ||
2532
+ has_prefix("branch/", zName) ||
2533
+ has_prefix("tag/", zName) ||
2534
+ has_prefix("ticket/", zName) ) ){
25292535
continue;
25302536
}
25312537
if( showIds ){
25322538
const char *zUuid = db_column_text(&q, 1);
25332539
fossil_print("%s ",zUuid);
25342540
--- src/wiki.c
+++ src/wiki.c
@@ -21,10 +21,13 @@
21 #include "config.h"
22 #include <assert.h>
23 #include <ctype.h>
24 #include "wiki.h"
25
 
 
 
26 /*
27 ** Return true if the input string is a well-formed wiki page name.
28 **
29 ** Well-formed wiki page names do not begin or end with whitespace,
30 ** and do not contain tabs or other control characters and do not
@@ -420,22 +423,22 @@
420 */
421 int wiki_page_type(const char *zPageName){
422 if( db_get_boolean("wiki-about",1)==0 ){
423 return WIKITYPE_NORMAL;
424 }else
425 if( sqlite3_strglob("checkin/*", zPageName)==0
426 && db_exists("SELECT 1 FROM blob WHERE uuid=%Q",zPageName+8)
427 ){
428 return WIKITYPE_CHECKIN;
429 }else
430 if( sqlite3_strglob("branch/*", zPageName)==0 ){
431 return WIKITYPE_BRANCH;
432 }else
433 if( sqlite3_strglob("tag/*", zPageName)==0 ){
434 return WIKITYPE_TAG;
435 }else
436 if( sqlite3_strglob("ticket/*", zPageName)==0 ){
437 return WIKITYPE_TICKET;
438 }
439 return WIKITYPE_NORMAL;
440 }
441
@@ -1987,11 +1990,12 @@
1987 style_submenu_element("All", "%R/wcontent?all=1");
1988 }
1989 cgi_check_for_malice();
1990 showCkBr = db_exists(
1991 "SELECT tag.tagname AS tn FROM tag JOIN tagxref USING(tagid) "
1992 "WHERE ( tn GLOB 'wiki-checkin/*' OR tn GLOB 'wiki-branch/*' ) "
 
1993 " AND TYPEOF(tagxref.value+0)='integer'" );
1994 if( showCkBr ){
1995 showCkBr = P("showckbr")!=0;
1996 style_submenu_checkbox("showckbr", "Show associated wikis", 0, 0);
1997 }
@@ -2017,20 +2021,20 @@
2017 char *zAge;
2018 int wcnt = db_column_int(&q, 4);
2019 char *zWDisplayName;
2020
2021 if( !showCkBr &&
2022 (sqlite3_strglob("checkin/*", zWName)==0 ||
2023 sqlite3_strglob("branch/*", zWName)==0 ||
2024 sqlite3_strglob("tag/*", zWName)==0 ||
2025 sqlite3_strglob("ticket/*", zWName)==0) ){
2026 continue;
2027 }
2028 if( sqlite3_strglob("checkin/*", zWName)==0 ){
2029 zWDisplayName = mprintf("%.25s...", zWName);
2030 }else{
2031 zWDisplayName = mprintf("%s", zWName);
2032 }
2033 if( wrid==0 ){
2034 if( !showAll ) continue;
2035 @ <tr><td data-sortkey="%h(zSort)">\
2036 @ %z(href("%R/whistory?name=%T",zWName))<s>%h(zWDisplayName)</s></a></td>
@@ -2522,12 +2526,14 @@
2522 const int wrid = db_column_int(&q, 2);
2523 if(!showAll && !wrid){
2524 continue;
2525 }
2526 if( !showCkBr &&
2527 (sqlite3_strglob("checkin/*", zName)==0 ||
2528 sqlite3_strglob("branch/*", zName)==0) ){
 
 
2529 continue;
2530 }
2531 if( showIds ){
2532 const char *zUuid = db_column_text(&q, 1);
2533 fossil_print("%s ",zUuid);
2534
--- src/wiki.c
+++ src/wiki.c
@@ -21,10 +21,13 @@
21 #include "config.h"
22 #include <assert.h>
23 #include <ctype.h>
24 #include "wiki.h"
25
26 #define has_prefix(literal_prfx, zStr) \
27 (fossil_strncmp((zStr), "" literal_prfx, (sizeof literal_prfx)-1)==0)
28
29 /*
30 ** Return true if the input string is a well-formed wiki page name.
31 **
32 ** Well-formed wiki page names do not begin or end with whitespace,
33 ** and do not contain tabs or other control characters and do not
@@ -420,22 +423,22 @@
423 */
424 int wiki_page_type(const char *zPageName){
425 if( db_get_boolean("wiki-about",1)==0 ){
426 return WIKITYPE_NORMAL;
427 }else
428 if( has_prefix("checkin/", zPageName)
429 && db_exists("SELECT 1 FROM blob WHERE uuid=%Q",zPageName+8)
430 ){
431 return WIKITYPE_CHECKIN;
432 }else
433 if( has_prefix("branch/", zPageName) ){
434 return WIKITYPE_BRANCH;
435 }else
436 if( has_prefix("tag/", zPageName) ){
437 return WIKITYPE_TAG;
438 }else
439 if( has_prefix("ticket/", zPageName) ){
440 return WIKITYPE_TICKET;
441 }
442 return WIKITYPE_NORMAL;
443 }
444
@@ -1987,11 +1990,12 @@
1990 style_submenu_element("All", "%R/wcontent?all=1");
1991 }
1992 cgi_check_for_malice();
1993 showCkBr = db_exists(
1994 "SELECT tag.tagname AS tn FROM tag JOIN tagxref USING(tagid) "
1995 "WHERE ( tn GLOB 'wiki-checkin/*' OR tn GLOB 'wiki-branch/*' OR "
1996 " tn GLOB 'wiki-tag/*' OR tn GLOB 'wiki-ticket/*' ) "
1997 " AND TYPEOF(tagxref.value+0)='integer'" );
1998 if( showCkBr ){
1999 showCkBr = P("showckbr")!=0;
2000 style_submenu_checkbox("showckbr", "Show associated wikis", 0, 0);
2001 }
@@ -2017,20 +2021,20 @@
2021 char *zAge;
2022 int wcnt = db_column_int(&q, 4);
2023 char *zWDisplayName;
2024
2025 if( !showCkBr &&
2026 (has_prefix("checkin/", zWName) ||
2027 has_prefix("branch/", zWName) ||
2028 has_prefix("tag/", zWName) ||
2029 has_prefix("ticket/", zWName) )){
2030 continue;
2031 }
2032 if( has_prefix("checkin/",zWName) || has_prefix("ticket/",zWName) ){
2033 zWDisplayName = mprintf("%.25s...", zWName);
2034 }else{
2035 zWDisplayName = fossil_strdup(zWName);
2036 }
2037 if( wrid==0 ){
2038 if( !showAll ) continue;
2039 @ <tr><td data-sortkey="%h(zSort)">\
2040 @ %z(href("%R/whistory?name=%T",zWName))<s>%h(zWDisplayName)</s></a></td>
@@ -2522,12 +2526,14 @@
2526 const int wrid = db_column_int(&q, 2);
2527 if(!showAll && !wrid){
2528 continue;
2529 }
2530 if( !showCkBr &&
2531 (has_prefix("checkin/", zName) ||
2532 has_prefix("branch/", zName) ||
2533 has_prefix("tag/", zName) ||
2534 has_prefix("ticket/", zName) ) ){
2535 continue;
2536 }
2537 if( showIds ){
2538 const char *zUuid = db_column_text(&q, 1);
2539 fossil_print("%s ",zUuid);
2540
+17 -2
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -37,10 +37,11 @@
3737
#define WIKI_NOBRACKET 0x0200 /* Omit extra [..] around hyperlinks */
3838
#define WIKI_MARKDOWN_URL 0x0400 /* Process link targets as in markdown */
3939
#define WIKI_MARKDOWN_FONT 0x0800 /* Accept markdown font/style markup */
4040
#define WIKI_MARKDOWN_LINK 0x1000 /* Accept markdown hyperlinks */
4141
#define WIKI_MARKDOWN_INLINE 0x1800 /* Combo of _FONT and _LINK */
42
+#define WIKI_ADMIN 0x800 /* Ignore g.perm.Hyperlink */
4243
#endif
4344
4445
4546
/*
4647
** These are the only markup attributes allowed.
@@ -1395,11 +1396,11 @@
13951396
zTerm = "";
13961397
}else{
13971398
blob_appendf(pOut, "<span class=\"brokenlink\">%s", zLB);
13981399
zTerm = "]</span>";
13991400
}
1400
- }else if( g.perm.Hyperlink ){
1401
+ }else if( g.perm.Hyperlink || (mFlags & WIKI_ADMIN)!=0 ){
14011402
blob_appendf(pOut, "%z%s",xhref(zExtraNS, "%R/info/%s", zTarget), zLB);
14021403
zTerm = "]</a>";
14031404
}else{
14041405
zTerm = "";
14051406
}
@@ -1439,10 +1440,24 @@
14391440
}
14401441
if( zExtra ) fossil_free(zExtra);
14411442
assert( (int)strlen(zTerm)<nClose );
14421443
sqlite3_snprintf(nClose, zClose, "%s", zTerm);
14431444
}
1445
+
1446
+/*
1447
+** Check zTarget to see if it looks like a valid hyperlink target.
1448
+** Return true if it does seem valid and false if not.
1449
+*/
1450
+int wiki_valid_link_target(char *zTarget){
1451
+ char zClose[30];
1452
+ Blob notUsed;
1453
+ blob_init(&notUsed, 0, 0);
1454
+ wiki_resolve_hyperlink(&notUsed, WIKI_NOBADLINKS|WIKI_ADMIN,
1455
+ zTarget, zClose, sizeof(zClose)-1, 0, 0);
1456
+ blob_reset(&notUsed);
1457
+ return zClose[0]!=0;
1458
+}
14441459
14451460
/*
14461461
** Check to see if the given parsed markup is the correct
14471462
** </verbatim> tag.
14481463
*/
@@ -2121,11 +2136,11 @@
21212136
** Options:
21222137
** --buttons Set the WIKI_BUTTONS flag
21232138
** --dark-pikchr Render pikchrs in dark mode
21242139
** --htmlonly Set the WIKI_HTMLONLY flag
21252140
** --inline Set the WIKI_INLINE flag
2126
-** --linksonly Set the WIKI_LINKSONLY flag
2141
+** --linksonly Set the WIKI_LINKSONLY flag
21272142
** --markdown Allow all in-line markdown syntax
21282143
** --markdown-link Allow markdown hyperlink syntax
21292144
** --markdown-style Allow markdown font and style markup
21302145
** --nobadlinks Set the WIKI_NOBADLINKS flag
21312146
** --noblock Set the WIKI_NOBLOCK flag
21322147
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -37,10 +37,11 @@
37 #define WIKI_NOBRACKET 0x0200 /* Omit extra [..] around hyperlinks */
38 #define WIKI_MARKDOWN_URL 0x0400 /* Process link targets as in markdown */
39 #define WIKI_MARKDOWN_FONT 0x0800 /* Accept markdown font/style markup */
40 #define WIKI_MARKDOWN_LINK 0x1000 /* Accept markdown hyperlinks */
41 #define WIKI_MARKDOWN_INLINE 0x1800 /* Combo of _FONT and _LINK */
 
42 #endif
43
44
45 /*
46 ** These are the only markup attributes allowed.
@@ -1395,11 +1396,11 @@
1395 zTerm = "";
1396 }else{
1397 blob_appendf(pOut, "<span class=\"brokenlink\">%s", zLB);
1398 zTerm = "]</span>";
1399 }
1400 }else if( g.perm.Hyperlink ){
1401 blob_appendf(pOut, "%z%s",xhref(zExtraNS, "%R/info/%s", zTarget), zLB);
1402 zTerm = "]</a>";
1403 }else{
1404 zTerm = "";
1405 }
@@ -1439,10 +1440,24 @@
1439 }
1440 if( zExtra ) fossil_free(zExtra);
1441 assert( (int)strlen(zTerm)<nClose );
1442 sqlite3_snprintf(nClose, zClose, "%s", zTerm);
1443 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1444
1445 /*
1446 ** Check to see if the given parsed markup is the correct
1447 ** </verbatim> tag.
1448 */
@@ -2121,11 +2136,11 @@
2121 ** Options:
2122 ** --buttons Set the WIKI_BUTTONS flag
2123 ** --dark-pikchr Render pikchrs in dark mode
2124 ** --htmlonly Set the WIKI_HTMLONLY flag
2125 ** --inline Set the WIKI_INLINE flag
2126 ** --linksonly Set the WIKI_LINKSONLY flag
2127 ** --markdown Allow all in-line markdown syntax
2128 ** --markdown-link Allow markdown hyperlink syntax
2129 ** --markdown-style Allow markdown font and style markup
2130 ** --nobadlinks Set the WIKI_NOBADLINKS flag
2131 ** --noblock Set the WIKI_NOBLOCK flag
2132
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -37,10 +37,11 @@
37 #define WIKI_NOBRACKET 0x0200 /* Omit extra [..] around hyperlinks */
38 #define WIKI_MARKDOWN_URL 0x0400 /* Process link targets as in markdown */
39 #define WIKI_MARKDOWN_FONT 0x0800 /* Accept markdown font/style markup */
40 #define WIKI_MARKDOWN_LINK 0x1000 /* Accept markdown hyperlinks */
41 #define WIKI_MARKDOWN_INLINE 0x1800 /* Combo of _FONT and _LINK */
42 #define WIKI_ADMIN 0x800 /* Ignore g.perm.Hyperlink */
43 #endif
44
45
46 /*
47 ** These are the only markup attributes allowed.
@@ -1395,11 +1396,11 @@
1396 zTerm = "";
1397 }else{
1398 blob_appendf(pOut, "<span class=\"brokenlink\">%s", zLB);
1399 zTerm = "]</span>";
1400 }
1401 }else if( g.perm.Hyperlink || (mFlags & WIKI_ADMIN)!=0 ){
1402 blob_appendf(pOut, "%z%s",xhref(zExtraNS, "%R/info/%s", zTarget), zLB);
1403 zTerm = "]</a>";
1404 }else{
1405 zTerm = "";
1406 }
@@ -1439,10 +1440,24 @@
1440 }
1441 if( zExtra ) fossil_free(zExtra);
1442 assert( (int)strlen(zTerm)<nClose );
1443 sqlite3_snprintf(nClose, zClose, "%s", zTerm);
1444 }
1445
1446 /*
1447 ** Check zTarget to see if it looks like a valid hyperlink target.
1448 ** Return true if it does seem valid and false if not.
1449 */
1450 int wiki_valid_link_target(char *zTarget){
1451 char zClose[30];
1452 Blob notUsed;
1453 blob_init(&notUsed, 0, 0);
1454 wiki_resolve_hyperlink(&notUsed, WIKI_NOBADLINKS|WIKI_ADMIN,
1455 zTarget, zClose, sizeof(zClose)-1, 0, 0);
1456 blob_reset(&notUsed);
1457 return zClose[0]!=0;
1458 }
1459
1460 /*
1461 ** Check to see if the given parsed markup is the correct
1462 ** </verbatim> tag.
1463 */
@@ -2121,11 +2136,11 @@
2136 ** Options:
2137 ** --buttons Set the WIKI_BUTTONS flag
2138 ** --dark-pikchr Render pikchrs in dark mode
2139 ** --htmlonly Set the WIKI_HTMLONLY flag
2140 ** --inline Set the WIKI_INLINE flag
2141 ** --linksonly Set the WIKI_LINKSONLY flag
2142 ** --markdown Allow all in-line markdown syntax
2143 ** --markdown-link Allow markdown hyperlink syntax
2144 ** --markdown-style Allow markdown font and style markup
2145 ** --nobadlinks Set the WIKI_NOBADLINKS flag
2146 ** --noblock Set the WIKI_NOBLOCK flag
2147
+17 -2
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -37,10 +37,11 @@
3737
#define WIKI_NOBRACKET 0x0200 /* Omit extra [..] around hyperlinks */
3838
#define WIKI_MARKDOWN_URL 0x0400 /* Process link targets as in markdown */
3939
#define WIKI_MARKDOWN_FONT 0x0800 /* Accept markdown font/style markup */
4040
#define WIKI_MARKDOWN_LINK 0x1000 /* Accept markdown hyperlinks */
4141
#define WIKI_MARKDOWN_INLINE 0x1800 /* Combo of _FONT and _LINK */
42
+#define WIKI_ADMIN 0x800 /* Ignore g.perm.Hyperlink */
4243
#endif
4344
4445
4546
/*
4647
** These are the only markup attributes allowed.
@@ -1395,11 +1396,11 @@
13951396
zTerm = "";
13961397
}else{
13971398
blob_appendf(pOut, "<span class=\"brokenlink\">%s", zLB);
13981399
zTerm = "]</span>";
13991400
}
1400
- }else if( g.perm.Hyperlink ){
1401
+ }else if( g.perm.Hyperlink || (mFlags & WIKI_ADMIN)!=0 ){
14011402
blob_appendf(pOut, "%z%s",xhref(zExtraNS, "%R/info/%s", zTarget), zLB);
14021403
zTerm = "]</a>";
14031404
}else{
14041405
zTerm = "";
14051406
}
@@ -1439,10 +1440,24 @@
14391440
}
14401441
if( zExtra ) fossil_free(zExtra);
14411442
assert( (int)strlen(zTerm)<nClose );
14421443
sqlite3_snprintf(nClose, zClose, "%s", zTerm);
14431444
}
1445
+
1446
+/*
1447
+** Check zTarget to see if it looks like a valid hyperlink target.
1448
+** Return true if it does seem valid and false if not.
1449
+*/
1450
+int wiki_valid_link_target(char *zTarget){
1451
+ char zClose[30];
1452
+ Blob notUsed;
1453
+ blob_init(&notUsed, 0, 0);
1454
+ wiki_resolve_hyperlink(&notUsed, WIKI_NOBADLINKS|WIKI_ADMIN,
1455
+ zTarget, zClose, sizeof(zClose)-1, 0, 0);
1456
+ blob_reset(&notUsed);
1457
+ return zClose[0]!=0;
1458
+}
14441459
14451460
/*
14461461
** Check to see if the given parsed markup is the correct
14471462
** </verbatim> tag.
14481463
*/
@@ -2121,11 +2136,11 @@
21212136
** Options:
21222137
** --buttons Set the WIKI_BUTTONS flag
21232138
** --dark-pikchr Render pikchrs in dark mode
21242139
** --htmlonly Set the WIKI_HTMLONLY flag
21252140
** --inline Set the WIKI_INLINE flag
2126
-** --linksonly Set the WIKI_LINKSONLY flag
2141
+** --linksonly Set the WIKI_LINKSONLY flag
21272142
** --markdown Allow all in-line markdown syntax
21282143
** --markdown-link Allow markdown hyperlink syntax
21292144
** --markdown-style Allow markdown font and style markup
21302145
** --nobadlinks Set the WIKI_NOBADLINKS flag
21312146
** --noblock Set the WIKI_NOBLOCK flag
21322147
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -37,10 +37,11 @@
37 #define WIKI_NOBRACKET 0x0200 /* Omit extra [..] around hyperlinks */
38 #define WIKI_MARKDOWN_URL 0x0400 /* Process link targets as in markdown */
39 #define WIKI_MARKDOWN_FONT 0x0800 /* Accept markdown font/style markup */
40 #define WIKI_MARKDOWN_LINK 0x1000 /* Accept markdown hyperlinks */
41 #define WIKI_MARKDOWN_INLINE 0x1800 /* Combo of _FONT and _LINK */
 
42 #endif
43
44
45 /*
46 ** These are the only markup attributes allowed.
@@ -1395,11 +1396,11 @@
1395 zTerm = "";
1396 }else{
1397 blob_appendf(pOut, "<span class=\"brokenlink\">%s", zLB);
1398 zTerm = "]</span>";
1399 }
1400 }else if( g.perm.Hyperlink ){
1401 blob_appendf(pOut, "%z%s",xhref(zExtraNS, "%R/info/%s", zTarget), zLB);
1402 zTerm = "]</a>";
1403 }else{
1404 zTerm = "";
1405 }
@@ -1439,10 +1440,24 @@
1439 }
1440 if( zExtra ) fossil_free(zExtra);
1441 assert( (int)strlen(zTerm)<nClose );
1442 sqlite3_snprintf(nClose, zClose, "%s", zTerm);
1443 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1444
1445 /*
1446 ** Check to see if the given parsed markup is the correct
1447 ** </verbatim> tag.
1448 */
@@ -2121,11 +2136,11 @@
2121 ** Options:
2122 ** --buttons Set the WIKI_BUTTONS flag
2123 ** --dark-pikchr Render pikchrs in dark mode
2124 ** --htmlonly Set the WIKI_HTMLONLY flag
2125 ** --inline Set the WIKI_INLINE flag
2126 ** --linksonly Set the WIKI_LINKSONLY flag
2127 ** --markdown Allow all in-line markdown syntax
2128 ** --markdown-link Allow markdown hyperlink syntax
2129 ** --markdown-style Allow markdown font and style markup
2130 ** --nobadlinks Set the WIKI_NOBADLINKS flag
2131 ** --noblock Set the WIKI_NOBLOCK flag
2132
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -37,10 +37,11 @@
37 #define WIKI_NOBRACKET 0x0200 /* Omit extra [..] around hyperlinks */
38 #define WIKI_MARKDOWN_URL 0x0400 /* Process link targets as in markdown */
39 #define WIKI_MARKDOWN_FONT 0x0800 /* Accept markdown font/style markup */
40 #define WIKI_MARKDOWN_LINK 0x1000 /* Accept markdown hyperlinks */
41 #define WIKI_MARKDOWN_INLINE 0x1800 /* Combo of _FONT and _LINK */
42 #define WIKI_ADMIN 0x800 /* Ignore g.perm.Hyperlink */
43 #endif
44
45
46 /*
47 ** These are the only markup attributes allowed.
@@ -1395,11 +1396,11 @@
1396 zTerm = "";
1397 }else{
1398 blob_appendf(pOut, "<span class=\"brokenlink\">%s", zLB);
1399 zTerm = "]</span>";
1400 }
1401 }else if( g.perm.Hyperlink || (mFlags & WIKI_ADMIN)!=0 ){
1402 blob_appendf(pOut, "%z%s",xhref(zExtraNS, "%R/info/%s", zTarget), zLB);
1403 zTerm = "]</a>";
1404 }else{
1405 zTerm = "";
1406 }
@@ -1439,10 +1440,24 @@
1440 }
1441 if( zExtra ) fossil_free(zExtra);
1442 assert( (int)strlen(zTerm)<nClose );
1443 sqlite3_snprintf(nClose, zClose, "%s", zTerm);
1444 }
1445
1446 /*
1447 ** Check zTarget to see if it looks like a valid hyperlink target.
1448 ** Return true if it does seem valid and false if not.
1449 */
1450 int wiki_valid_link_target(char *zTarget){
1451 char zClose[30];
1452 Blob notUsed;
1453 blob_init(&notUsed, 0, 0);
1454 wiki_resolve_hyperlink(&notUsed, WIKI_NOBADLINKS|WIKI_ADMIN,
1455 zTarget, zClose, sizeof(zClose)-1, 0, 0);
1456 blob_reset(&notUsed);
1457 return zClose[0]!=0;
1458 }
1459
1460 /*
1461 ** Check to see if the given parsed markup is the correct
1462 ** </verbatim> tag.
1463 */
@@ -2121,11 +2136,11 @@
2136 ** Options:
2137 ** --buttons Set the WIKI_BUTTONS flag
2138 ** --dark-pikchr Render pikchrs in dark mode
2139 ** --htmlonly Set the WIKI_HTMLONLY flag
2140 ** --inline Set the WIKI_INLINE flag
2141 ** --linksonly Set the WIKI_LINKSONLY flag
2142 ** --markdown Allow all in-line markdown syntax
2143 ** --markdown-link Allow markdown hyperlink syntax
2144 ** --markdown-style Allow markdown font and style markup
2145 ** --nobadlinks Set the WIKI_NOBADLINKS flag
2146 ** --noblock Set the WIKI_NOBLOCK flag
2147
+1 -1
--- src/zip.c
+++ src/zip.c
@@ -652,11 +652,11 @@
652652
pManifest = manifest_get(rid, CFTYPE_MANIFEST, 0);
653653
if( pManifest ){
654654
int flg, eflg = 0;
655655
char *zName = 0;
656656
zip_set_timedate(pManifest->rDate);
657
- flg = db_get_manifest_setting();
657
+ flg = db_get_manifest_setting(blob_str(&hash));
658658
if( flg ){
659659
/* eflg is the effective flags, taking include/exclude into account */
660660
if( (pInclude==0 || glob_match(pInclude, "manifest"))
661661
&& !glob_match(pExclude, "manifest")
662662
&& (flg & MFESTFLG_RAW) ){
663663
--- src/zip.c
+++ src/zip.c
@@ -652,11 +652,11 @@
652 pManifest = manifest_get(rid, CFTYPE_MANIFEST, 0);
653 if( pManifest ){
654 int flg, eflg = 0;
655 char *zName = 0;
656 zip_set_timedate(pManifest->rDate);
657 flg = db_get_manifest_setting();
658 if( flg ){
659 /* eflg is the effective flags, taking include/exclude into account */
660 if( (pInclude==0 || glob_match(pInclude, "manifest"))
661 && !glob_match(pExclude, "manifest")
662 && (flg & MFESTFLG_RAW) ){
663
--- src/zip.c
+++ src/zip.c
@@ -652,11 +652,11 @@
652 pManifest = manifest_get(rid, CFTYPE_MANIFEST, 0);
653 if( pManifest ){
654 int flg, eflg = 0;
655 char *zName = 0;
656 zip_set_timedate(pManifest->rDate);
657 flg = db_get_manifest_setting(blob_str(&hash));
658 if( flg ){
659 /* eflg is the effective flags, taking include/exclude into account */
660 if( (pInclude==0 || glob_match(pInclude, "manifest"))
661 && !glob_match(pExclude, "manifest")
662 && (flg & MFESTFLG_RAW) ){
663
--- www/alerts.md
+++ www/alerts.md
@@ -7,10 +7,11 @@
77
88
* New [checkins](/help?cmd=ci)
99
* [Ticket](./tickets.wiki) changes
1010
* [Wiki](./wikitheory.wiki) page changes
1111
* New and edited [forum](./forum.wiki) posts
12
+ * Users receiving [new permissions](./caps/index.md) (admins only)
1213
* Announcements
1314
1415
Subscribers can elect to receive emails as soon as these events happen,
1516
or they can receive a daily digest of the events instead.
1617
@@ -515,10 +516,14 @@
515516
email addresses until the user clicks the link in the verification
516517
email. This checkbox lets the Fossil Admin user manually verify the
517518
user, such as in the case where the verification email message got
518519
lost. Unchecking this box does not cause another verification email
519520
to be sent.
521
+
522
+* Admin users (only) may activate the "user elevation" subscription,
523
+ which sends a notification when a user is created or is explicitly
524
+ assigned permission they did not formerly have.
520525
521526
This screen also allows a Fossil Admin user to perform other activities
522527
on behalf of a subscriber which they could do themselves, such as to
523528
[unsubscribe](#unsub) them.
524529
525530
--- www/alerts.md
+++ www/alerts.md
@@ -7,10 +7,11 @@
7
8 * New [checkins](/help?cmd=ci)
9 * [Ticket](./tickets.wiki) changes
10 * [Wiki](./wikitheory.wiki) page changes
11 * New and edited [forum](./forum.wiki) posts
 
12 * Announcements
13
14 Subscribers can elect to receive emails as soon as these events happen,
15 or they can receive a daily digest of the events instead.
16
@@ -515,10 +516,14 @@
515 email addresses until the user clicks the link in the verification
516 email. This checkbox lets the Fossil Admin user manually verify the
517 user, such as in the case where the verification email message got
518 lost. Unchecking this box does not cause another verification email
519 to be sent.
 
 
 
 
520
521 This screen also allows a Fossil Admin user to perform other activities
522 on behalf of a subscriber which they could do themselves, such as to
523 [unsubscribe](#unsub) them.
524
525
--- www/alerts.md
+++ www/alerts.md
@@ -7,10 +7,11 @@
7
8 * New [checkins](/help?cmd=ci)
9 * [Ticket](./tickets.wiki) changes
10 * [Wiki](./wikitheory.wiki) page changes
11 * New and edited [forum](./forum.wiki) posts
12 * Users receiving [new permissions](./caps/index.md) (admins only)
13 * Announcements
14
15 Subscribers can elect to receive emails as soon as these events happen,
16 or they can receive a daily digest of the events instead.
17
@@ -515,10 +516,14 @@
516 email addresses until the user clicks the link in the verification
517 email. This checkbox lets the Fossil Admin user manually verify the
518 user, such as in the case where the verification email message got
519 lost. Unchecking this box does not cause another verification email
520 to be sent.
521
522 * Admin users (only) may activate the "user elevation" subscription,
523 which sends a notification when a user is created or is explicitly
524 assigned permission they did not formerly have.
525
526 This screen also allows a Fossil Admin user to perform other activities
527 on behalf of a subscriber which they could do themselves, such as to
528 [unsubscribe](#unsub) them.
529
530
+28 -7
--- www/changes.wiki
+++ www/changes.wiki
@@ -12,17 +12,25 @@
1212
"--from PATH" option is present, the default start page becomes
1313
"/ckout?exbase=PATH".
1414
* Added the [/help?cmd=merge-info|fossil merge-info] command and especially
1515
the --tk option to that command, to provide analysis of the most recent
1616
merge or update operation.
17
- * Added the ability to sign check-ins with SSH keys.
18
- * Issue a warning if a user tries to commit on a check-in where the
19
- branch has been changed.
2017
* When a merge conflict occurs, a new section is added to the conflict
2118
text that shows Fossil's suggested resolution to the conflict.
2219
* Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show
2320
diffs of multiple files.
21
+ * Enhancements to [/help?cmd=commit|fossil commit]:
22
+ <ol type="a">
23
+ <li> If Fossil sees potential formatting mistakes (bad hyperlinks)
24
+ in the check-in comment, it will alert the developer and give
25
+ him or her the opportunity to edit the comment before continuing.
26
+ <li> The new "--if-changes" option causes the commit to become
27
+ a quiet no-op if there are no pending changes.
28
+ <li> Added the ability to sign check-ins with SSH keys.
29
+ <li> Issue a warning if a user tries to commit on a check-in where the
30
+ branch has been changed.
31
+ </ol>
2432
* Deprecate the --comfmtflags and --comment-format global options and
2533
no longer list them in the built-in help, but keep them working for
2634
backwards compatibility.
2735
Alternative TTY comment formatting can still be specified using the
2836
[/help?cmd=comment-format|comment-format setting], if desired. The
@@ -49,30 +57,43 @@
4957
<li> Enhance the "ymd" query parameter so that when used like
5058
"ymd=YYYYMMDD-YYYYMMDD" it shows all events in the range of
5159
dates specified.
5260
<li> Accept the "Z" (Zulu-time) suffix on date arguments for the
5361
"ymd" and "yw" query parameters.
62
+ <li> The new "min" query parameter, when added to a from=,to= query,
63
+ collapses long runs of check-ins on the same branch into just
64
+ end-points.
65
+ <li> The p= and d= parameters an reference different check-ins, which
66
+ case the timeline shows those check-ins that are both ancestors
67
+ of p= and descendants of d=.
5468
</ol>
55
- * Add the "--if-changes" option to the [/help?cmd=commit|fossil commit]
56
- command that causes the command to become a quiet no-op if there are
57
- no pending changes.
5869
* Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis
5970
and debugging
6071
* Fix a bug in [/help?cmd=patch|fossil patch create] that causes
6172
[/help?cmd=revert|fossil revert] operations that happened on individual
6273
files after a [/help?cmd=merge|fossil merge] to be omitted from the
6374
patch.
6475
* Added the [/help?cmd=patch|patch alias] command for managing aliases
6576
for remote checkout names.
66
- * Enhance the [/help?cmd=help|fossil help] command:
77
+ * Enhancements to on-line help and the [/help?cmd=help|fossil help] command:
6778
<ol type="a">
79
+ <li> Add the ability to search the help text, either in the UI
80
+ (on the [/help?cmd=/search|/search page]) or from the command-line
81
+ (using the "[/help?cmd=search|fossil search -h PATTERN]" command.)
6882
<li> Accepts an optional SUBCOMMAND argument following the
6983
COMMAND argument and only shows results for the specified
7084
subcommand, not the entire command.
7185
<li> The -u (--usage) option shows only the command-line syntax
7286
<li> The -o (--options) option shows only the command-line options
7387
</ol>
88
+ * Added the ability to attach wiki pages to a ticket for extended
89
+ descriptions.
90
+ * Add a "user elevation" [/doc/trunk/www/alerts.md|subscription]
91
+ which alerts subscribers when an admin creates a new user or
92
+ adds new permissions to one.
93
+ * Diverse minor fixes and additions.
94
+
7495
7596
<h2 id='v2_25'>Changes for version 2.25 (2024-11-06)</h2>
7697
7798
* The "[/help?cmd=ui|fossil ui /]" command now works even for repositories
7899
that have non-ASCII filenames
79100
--- www/changes.wiki
+++ www/changes.wiki
@@ -12,17 +12,25 @@
12 "--from PATH" option is present, the default start page becomes
13 "/ckout?exbase=PATH".
14 * Added the [/help?cmd=merge-info|fossil merge-info] command and especially
15 the --tk option to that command, to provide analysis of the most recent
16 merge or update operation.
17 * Added the ability to sign check-ins with SSH keys.
18 * Issue a warning if a user tries to commit on a check-in where the
19 branch has been changed.
20 * When a merge conflict occurs, a new section is added to the conflict
21 text that shows Fossil's suggested resolution to the conflict.
22 * Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show
23 diffs of multiple files.
 
 
 
 
 
 
 
 
 
 
 
24 * Deprecate the --comfmtflags and --comment-format global options and
25 no longer list them in the built-in help, but keep them working for
26 backwards compatibility.
27 Alternative TTY comment formatting can still be specified using the
28 [/help?cmd=comment-format|comment-format setting], if desired. The
@@ -49,30 +57,43 @@
49 <li> Enhance the "ymd" query parameter so that when used like
50 "ymd=YYYYMMDD-YYYYMMDD" it shows all events in the range of
51 dates specified.
52 <li> Accept the "Z" (Zulu-time) suffix on date arguments for the
53 "ymd" and "yw" query parameters.
 
 
 
 
 
 
54 </ol>
55 * Add the "--if-changes" option to the [/help?cmd=commit|fossil commit]
56 command that causes the command to become a quiet no-op if there are
57 no pending changes.
58 * Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis
59 and debugging
60 * Fix a bug in [/help?cmd=patch|fossil patch create] that causes
61 [/help?cmd=revert|fossil revert] operations that happened on individual
62 files after a [/help?cmd=merge|fossil merge] to be omitted from the
63 patch.
64 * Added the [/help?cmd=patch|patch alias] command for managing aliases
65 for remote checkout names.
66 * Enhance the [/help?cmd=help|fossil help] command:
67 <ol type="a">
 
 
 
68 <li> Accepts an optional SUBCOMMAND argument following the
69 COMMAND argument and only shows results for the specified
70 subcommand, not the entire command.
71 <li> The -u (--usage) option shows only the command-line syntax
72 <li> The -o (--options) option shows only the command-line options
73 </ol>
 
 
 
 
 
 
 
74
75 <h2 id='v2_25'>Changes for version 2.25 (2024-11-06)</h2>
76
77 * The "[/help?cmd=ui|fossil ui /]" command now works even for repositories
78 that have non-ASCII filenames
79
--- www/changes.wiki
+++ www/changes.wiki
@@ -12,17 +12,25 @@
12 "--from PATH" option is present, the default start page becomes
13 "/ckout?exbase=PATH".
14 * Added the [/help?cmd=merge-info|fossil merge-info] command and especially
15 the --tk option to that command, to provide analysis of the most recent
16 merge or update operation.
 
 
 
17 * When a merge conflict occurs, a new section is added to the conflict
18 text that shows Fossil's suggested resolution to the conflict.
19 * Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show
20 diffs of multiple files.
21 * Enhancements to [/help?cmd=commit|fossil commit]:
22 <ol type="a">
23 <li> If Fossil sees potential formatting mistakes (bad hyperlinks)
24 in the check-in comment, it will alert the developer and give
25 him or her the opportunity to edit the comment before continuing.
26 <li> The new "--if-changes" option causes the commit to become
27 a quiet no-op if there are no pending changes.
28 <li> Added the ability to sign check-ins with SSH keys.
29 <li> Issue a warning if a user tries to commit on a check-in where the
30 branch has been changed.
31 </ol>
32 * Deprecate the --comfmtflags and --comment-format global options and
33 no longer list them in the built-in help, but keep them working for
34 backwards compatibility.
35 Alternative TTY comment formatting can still be specified using the
36 [/help?cmd=comment-format|comment-format setting], if desired. The
@@ -49,30 +57,43 @@
57 <li> Enhance the "ymd" query parameter so that when used like
58 "ymd=YYYYMMDD-YYYYMMDD" it shows all events in the range of
59 dates specified.
60 <li> Accept the "Z" (Zulu-time) suffix on date arguments for the
61 "ymd" and "yw" query parameters.
62 <li> The new "min" query parameter, when added to a from=,to= query,
63 collapses long runs of check-ins on the same branch into just
64 end-points.
65 <li> The p= and d= parameters an reference different check-ins, which
66 case the timeline shows those check-ins that are both ancestors
67 of p= and descendants of d=.
68 </ol>
 
 
 
69 * Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis
70 and debugging
71 * Fix a bug in [/help?cmd=patch|fossil patch create] that causes
72 [/help?cmd=revert|fossil revert] operations that happened on individual
73 files after a [/help?cmd=merge|fossil merge] to be omitted from the
74 patch.
75 * Added the [/help?cmd=patch|patch alias] command for managing aliases
76 for remote checkout names.
77 * Enhancements to on-line help and the [/help?cmd=help|fossil help] command:
78 <ol type="a">
79 <li> Add the ability to search the help text, either in the UI
80 (on the [/help?cmd=/search|/search page]) or from the command-line
81 (using the "[/help?cmd=search|fossil search -h PATTERN]" command.)
82 <li> Accepts an optional SUBCOMMAND argument following the
83 COMMAND argument and only shows results for the specified
84 subcommand, not the entire command.
85 <li> The -u (--usage) option shows only the command-line syntax
86 <li> The -o (--options) option shows only the command-line options
87 </ol>
88 * Added the ability to attach wiki pages to a ticket for extended
89 descriptions.
90 * Add a "user elevation" [/doc/trunk/www/alerts.md|subscription]
91 which alerts subscribers when an admin creates a new user or
92 adds new permissions to one.
93 * Diverse minor fixes and additions.
94
95
96 <h2 id='v2_25'>Changes for version 2.25 (2024-11-06)</h2>
97
98 * The "[/help?cmd=ui|fossil ui /]" command now works even for repositories
99 that have non-ASCII filenames
100
--- www/settings.wiki
+++ www/settings.wiki
@@ -1,18 +1,18 @@
11
<title>Fossil Settings</title>
22
3
-<h2>Using Fossil Settings</h2>
3
+<h1>Using Fossil Settings</h1>
44
55
Settings control the behaviour of fossil. They are set with the
66
<tt>fossil settings</tt> command, or through the web interface in
77
the Settings page in the Admin section.
88
99
For a list of all settings, view the Settings page, or type
1010
<tt>fossil help settings</tt> from the command line.
1111
1212
13
-<h3 id="repo">Repository settings</h3>
13
+<h2 id="repo">1.0 Repository settings</h2>
1414
1515
Settings are set on a per-repository basis. When you clone a repository,
1616
a subset of settings are copied to your local repository.
1717
1818
If you make a change to a setting on your local repository, it is not
@@ -24,11 +24,11 @@
2424
will be used for all repositories cloned to your machine, unless
2525
overridden explicitly in a particular repository. Global settings can be
2626
set by using the <tt>-global</tt> option on the <tt>fossil settings</tt>
2727
command.
2828
29
-<h3 id="versionable">"Versionable" settings</h3>
29
+<h2 id="versionable">2.0 "Versionable" settings</h2>
3030
3131
Most of the settings control the behaviour of fossil on your local
3232
machine, largely acting to reflect your preference on how you want to
3333
use Fossil, how you communicate with the server, or options for hosting
3434
a repository on the web.
3535
--- www/settings.wiki
+++ www/settings.wiki
@@ -1,18 +1,18 @@
1 <title>Fossil Settings</title>
2
3 <h2>Using Fossil Settings</h2>
4
5 Settings control the behaviour of fossil. They are set with the
6 <tt>fossil settings</tt> command, or through the web interface in
7 the Settings page in the Admin section.
8
9 For a list of all settings, view the Settings page, or type
10 <tt>fossil help settings</tt> from the command line.
11
12
13 <h3 id="repo">Repository settings</h3>
14
15 Settings are set on a per-repository basis. When you clone a repository,
16 a subset of settings are copied to your local repository.
17
18 If you make a change to a setting on your local repository, it is not
@@ -24,11 +24,11 @@
24 will be used for all repositories cloned to your machine, unless
25 overridden explicitly in a particular repository. Global settings can be
26 set by using the <tt>-global</tt> option on the <tt>fossil settings</tt>
27 command.
28
29 <h3 id="versionable">"Versionable" settings</h3>
30
31 Most of the settings control the behaviour of fossil on your local
32 machine, largely acting to reflect your preference on how you want to
33 use Fossil, how you communicate with the server, or options for hosting
34 a repository on the web.
35
--- www/settings.wiki
+++ www/settings.wiki
@@ -1,18 +1,18 @@
1 <title>Fossil Settings</title>
2
3 <h1>Using Fossil Settings</h1>
4
5 Settings control the behaviour of fossil. They are set with the
6 <tt>fossil settings</tt> command, or through the web interface in
7 the Settings page in the Admin section.
8
9 For a list of all settings, view the Settings page, or type
10 <tt>fossil help settings</tt> from the command line.
11
12
13 <h2 id="repo">1.0 Repository settings</h2>
14
15 Settings are set on a per-repository basis. When you clone a repository,
16 a subset of settings are copied to your local repository.
17
18 If you make a change to a setting on your local repository, it is not
@@ -24,11 +24,11 @@
24 will be used for all repositories cloned to your machine, unless
25 overridden explicitly in a particular repository. Global settings can be
26 set by using the <tt>-global</tt> option on the <tt>fossil settings</tt>
27 command.
28
29 <h2 id="versionable">2.0 "Versionable" settings</h2>
30
31 Most of the settings control the behaviour of fossil on your local
32 machine, largely acting to reflect your preference on how you want to
33 use Fossil, how you communicate with the server, or options for hosting
34 a repository on the web.
35

Keyboard Shortcuts

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