Fossil SCM

The "fossil commit" command looks for suspicious and/or broken hyperlinks in comments and gives the user an opportunity to continue editing the comment.

drh 2025-03-10 18:42 trunk
Commit 3e87f78e6019f2ebf21ff79f4f1ef8bd8afe82f4717ad02d8a893c781c2c5ad5
2 files changed +87 -12 +16 -1
+87 -12
--- 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
**
@@ -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 **
@@ -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 **
@@ -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
+16 -1
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -34,10 +34,11 @@
3434
#define WIKI_NEWLINE 0x040 /* Honor \n - break lines at each \n */
3535
#define WIKI_MARKDOWNLINKS 0x080 /* Resolve hyperlinks as in markdown */
3636
#define WIKI_SAFE 0x100 /* Make the result safe for embedding */
3737
#define WIKI_TARGET_BLANK 0x200 /* Hyperlinks go to a new window */
3838
#define WIKI_NOBRACKET 0x400 /* Omit extra [..] around hyperlinks */
39
+#define WIKI_ADMIN 0x800 /* Ignore g.perm.Hyperlink */
3940
#endif
4041
4142
4243
/*
4344
** These are the only markup attributes allowed.
@@ -1320,11 +1321,11 @@
13201321
zTerm = "";
13211322
}else{
13221323
blob_appendf(pOut, "<span class=\"brokenlink\">%s", zLB);
13231324
zTerm = "]</span>";
13241325
}
1325
- }else if( g.perm.Hyperlink ){
1326
+ }else if( g.perm.Hyperlink || (mFlags & WIKI_ADMIN)!=0 ){
13261327
blob_appendf(pOut, "%z%s",xhref(zExtraNS, "%R/info/%s", zTarget), zLB);
13271328
zTerm = "]</a>";
13281329
}else{
13291330
zTerm = "";
13301331
}
@@ -1364,10 +1365,24 @@
13641365
}
13651366
if( zExtra ) fossil_free(zExtra);
13661367
assert( (int)strlen(zTerm)<nClose );
13671368
sqlite3_snprintf(nClose, zClose, "%s", zTerm);
13681369
}
1370
+
1371
+/*
1372
+** Check zTarget to see if it looks like a valid hyperlink target.
1373
+** Return true if it does seem valid and false if not.
1374
+*/
1375
+int wiki_valid_link_target(char *zTarget){
1376
+ char zClose[30];
1377
+ Blob notUsed;
1378
+ blob_init(&notUsed, 0, 0);
1379
+ wiki_resolve_hyperlink(&notUsed, WIKI_NOBADLINKS|WIKI_ADMIN,
1380
+ zTarget, zClose, sizeof(zClose)-1, 0, 0);
1381
+ blob_reset(&notUsed);
1382
+ return zClose[0]!=0;
1383
+}
13691384
13701385
/*
13711386
** Check to see if the given parsed markup is the correct
13721387
** </verbatim> tag.
13731388
*/
13741389
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -34,10 +34,11 @@
34 #define WIKI_NEWLINE 0x040 /* Honor \n - break lines at each \n */
35 #define WIKI_MARKDOWNLINKS 0x080 /* Resolve hyperlinks as in markdown */
36 #define WIKI_SAFE 0x100 /* Make the result safe for embedding */
37 #define WIKI_TARGET_BLANK 0x200 /* Hyperlinks go to a new window */
38 #define WIKI_NOBRACKET 0x400 /* Omit extra [..] around hyperlinks */
 
39 #endif
40
41
42 /*
43 ** These are the only markup attributes allowed.
@@ -1320,11 +1321,11 @@
1320 zTerm = "";
1321 }else{
1322 blob_appendf(pOut, "<span class=\"brokenlink\">%s", zLB);
1323 zTerm = "]</span>";
1324 }
1325 }else if( g.perm.Hyperlink ){
1326 blob_appendf(pOut, "%z%s",xhref(zExtraNS, "%R/info/%s", zTarget), zLB);
1327 zTerm = "]</a>";
1328 }else{
1329 zTerm = "";
1330 }
@@ -1364,10 +1365,24 @@
1364 }
1365 if( zExtra ) fossil_free(zExtra);
1366 assert( (int)strlen(zTerm)<nClose );
1367 sqlite3_snprintf(nClose, zClose, "%s", zTerm);
1368 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1369
1370 /*
1371 ** Check to see if the given parsed markup is the correct
1372 ** </verbatim> tag.
1373 */
1374
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -34,10 +34,11 @@
34 #define WIKI_NEWLINE 0x040 /* Honor \n - break lines at each \n */
35 #define WIKI_MARKDOWNLINKS 0x080 /* Resolve hyperlinks as in markdown */
36 #define WIKI_SAFE 0x100 /* Make the result safe for embedding */
37 #define WIKI_TARGET_BLANK 0x200 /* Hyperlinks go to a new window */
38 #define WIKI_NOBRACKET 0x400 /* Omit extra [..] around hyperlinks */
39 #define WIKI_ADMIN 0x800 /* Ignore g.perm.Hyperlink */
40 #endif
41
42
43 /*
44 ** These are the only markup attributes allowed.
@@ -1320,11 +1321,11 @@
1321 zTerm = "";
1322 }else{
1323 blob_appendf(pOut, "<span class=\"brokenlink\">%s", zLB);
1324 zTerm = "]</span>";
1325 }
1326 }else if( g.perm.Hyperlink || (mFlags & WIKI_ADMIN)!=0 ){
1327 blob_appendf(pOut, "%z%s",xhref(zExtraNS, "%R/info/%s", zTarget), zLB);
1328 zTerm = "]</a>";
1329 }else{
1330 zTerm = "";
1331 }
@@ -1364,10 +1365,24 @@
1365 }
1366 if( zExtra ) fossil_free(zExtra);
1367 assert( (int)strlen(zTerm)<nClose );
1368 sqlite3_snprintf(nClose, zClose, "%s", zTerm);
1369 }
1370
1371 /*
1372 ** Check zTarget to see if it looks like a valid hyperlink target.
1373 ** Return true if it does seem valid and false if not.
1374 */
1375 int wiki_valid_link_target(char *zTarget){
1376 char zClose[30];
1377 Blob notUsed;
1378 blob_init(&notUsed, 0, 0);
1379 wiki_resolve_hyperlink(&notUsed, WIKI_NOBADLINKS|WIKI_ADMIN,
1380 zTarget, zClose, sizeof(zClose)-1, 0, 0);
1381 blob_reset(&notUsed);
1382 return zClose[0]!=0;
1383 }
1384
1385 /*
1386 ** Check to see if the given parsed markup is the correct
1387 ** </verbatim> tag.
1388 */
1389

Keyboard Shortcuts

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