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.
Commit
3e87f78e6019f2ebf21ff79f4f1ef8bd8afe82f4717ad02d8a893c781c2c5ad5
Parent
d1d4cc7529eefa9…
2 files changed
+87
-12
+16
-1
+87
-12
| --- src/checkin.c | ||
| +++ src/checkin.c | ||
| @@ -2281,10 +2281,64 @@ | ||
| 2281 | 2281 | static int tagCmp(const void *a, const void *b){ |
| 2282 | 2282 | char **pA = (char**)a; |
| 2283 | 2283 | char **pB = (char**)b; |
| 2284 | 2284 | return fossil_strcmp(pA[0], pB[0]); |
| 2285 | 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 | +} | |
| 2286 | 2340 | |
| 2287 | 2341 | /* |
| 2288 | 2342 | ** COMMAND: ci# |
| 2289 | 2343 | ** COMMAND: commit |
| 2290 | 2344 | ** |
| @@ -2801,22 +2855,43 @@ | ||
| 2801 | 2855 | }else if( zComFile ){ |
| 2802 | 2856 | blob_zero(&comment); |
| 2803 | 2857 | blob_read_from_file(&comment, zComFile, ExtFILE); |
| 2804 | 2858 | blob_to_utf8_no_bom(&comment, 1); |
| 2805 | 2859 | }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 | + | |
| 2818 | 2893 | db_end_transaction(0); |
| 2819 | 2894 | db_begin_transaction(); |
| 2820 | 2895 | if( !g.markPrivate && vid!=0 && !allowFork && !forceFlag ){ |
| 2821 | 2896 | /* Do another auto-pull, renewing the check-in lock. Then set |
| 2822 | 2897 | ** bRecheck so that we loop back above to verify that the check-in |
| 2823 | 2898 |
| --- 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 @@ | ||
| 34 | 34 | #define WIKI_NEWLINE 0x040 /* Honor \n - break lines at each \n */ |
| 35 | 35 | #define WIKI_MARKDOWNLINKS 0x080 /* Resolve hyperlinks as in markdown */ |
| 36 | 36 | #define WIKI_SAFE 0x100 /* Make the result safe for embedding */ |
| 37 | 37 | #define WIKI_TARGET_BLANK 0x200 /* Hyperlinks go to a new window */ |
| 38 | 38 | #define WIKI_NOBRACKET 0x400 /* Omit extra [..] around hyperlinks */ |
| 39 | +#define WIKI_ADMIN 0x800 /* Ignore g.perm.Hyperlink */ | |
| 39 | 40 | #endif |
| 40 | 41 | |
| 41 | 42 | |
| 42 | 43 | /* |
| 43 | 44 | ** These are the only markup attributes allowed. |
| @@ -1320,11 +1321,11 @@ | ||
| 1320 | 1321 | zTerm = ""; |
| 1321 | 1322 | }else{ |
| 1322 | 1323 | blob_appendf(pOut, "<span class=\"brokenlink\">%s", zLB); |
| 1323 | 1324 | zTerm = "]</span>"; |
| 1324 | 1325 | } |
| 1325 | - }else if( g.perm.Hyperlink ){ | |
| 1326 | + }else if( g.perm.Hyperlink || (mFlags & WIKI_ADMIN)!=0 ){ | |
| 1326 | 1327 | blob_appendf(pOut, "%z%s",xhref(zExtraNS, "%R/info/%s", zTarget), zLB); |
| 1327 | 1328 | zTerm = "]</a>"; |
| 1328 | 1329 | }else{ |
| 1329 | 1330 | zTerm = ""; |
| 1330 | 1331 | } |
| @@ -1364,10 +1365,24 @@ | ||
| 1364 | 1365 | } |
| 1365 | 1366 | if( zExtra ) fossil_free(zExtra); |
| 1366 | 1367 | assert( (int)strlen(zTerm)<nClose ); |
| 1367 | 1368 | sqlite3_snprintf(nClose, zClose, "%s", zTerm); |
| 1368 | 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(¬Used, 0, 0); | |
| 1379 | + wiki_resolve_hyperlink(¬Used, WIKI_NOBADLINKS|WIKI_ADMIN, | |
| 1380 | + zTarget, zClose, sizeof(zClose)-1, 0, 0); | |
| 1381 | + blob_reset(¬Used); | |
| 1382 | + return zClose[0]!=0; | |
| 1383 | +} | |
| 1369 | 1384 | |
| 1370 | 1385 | /* |
| 1371 | 1386 | ** Check to see if the given parsed markup is the correct |
| 1372 | 1387 | ** </verbatim> tag. |
| 1373 | 1388 | */ |
| 1374 | 1389 |
| --- 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(¬Used, 0, 0); |
| 1379 | wiki_resolve_hyperlink(¬Used, WIKI_NOBADLINKS|WIKI_ADMIN, |
| 1380 | zTarget, zClose, sizeof(zClose)-1, 0, 0); |
| 1381 | blob_reset(¬Used); |
| 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 |