Fossil SCM
Add the exbase=PATH query parameter to the /ckout page so that the diff uses an external baseline. Add the --external-baseline option to the "fossil ui" command to force the initial page to be /ckout with the exbase query parameter.
Commit
3807891e6278559bc89bd80db7d45fb16c0435da351caf6cf70be88c2cd14e64
Parent
36f354927b013b1…
2 files changed
+199
-58
+6
+199
-58
| --- src/info.c | ||
| +++ src/info.c | ||
| @@ -609,54 +609,21 @@ | ||
| 609 | 609 | db_finalize(&q); |
| 610 | 610 | style_finish_page(); |
| 611 | 611 | } |
| 612 | 612 | |
| 613 | 613 | /* |
| 614 | -** WEBPAGE: ckout | |
| 615 | -** | |
| 616 | -** Show information about the current checkout. This page only functions | |
| 617 | -** if the web server is run on a loopback interface (in other words, was | |
| 618 | -** started using "fossil ui" or similar) from with on open check-out. | |
| 614 | +** Render a web-page diff of the changes in the working check-out | |
| 619 | 615 | */ |
| 620 | -void ckout_page(void){ | |
| 621 | - int vid; | |
| 622 | - char *zHostname; | |
| 623 | - char *zCwd; | |
| 616 | +static void ckout_normal_diff(int vid){ | |
| 624 | 617 | int diffType; /* 0: no diff, 1: unified, 2: side-by-side */ |
| 625 | 618 | DiffConfig DCfg,*pCfg; /* Diff details */ |
| 626 | - const char *zHome; /* Home directory */ | |
| 627 | 619 | const char *zW; /* The "w" query parameter */ |
| 628 | 620 | int nChng; /* Number of changes */ |
| 629 | 621 | Stmt q; |
| 630 | 622 | |
| 631 | - if( !db_open_local(0) || !cgi_is_loopback(g.zIpAddr) ){ | |
| 632 | - cgi_redirectf("%R/home"); | |
| 633 | - return; | |
| 634 | - } | |
| 635 | - file_chdir(g.zLocalRoot, 0); | |
| 636 | 623 | diffType = preferred_diff_type(); |
| 637 | 624 | pCfg = construct_diff_flags(diffType, &DCfg); |
| 638 | - vid = db_lget_int("checkout", 0); | |
| 639 | - db_unprotect(PROTECT_ALL); | |
| 640 | - vfile_check_signature(vid, CKSIG_ENOTFILE); | |
| 641 | - db_protect_pop(); | |
| 642 | - style_set_current_feature("vinfo"); | |
| 643 | - zHostname = fossil_hostname(); | |
| 644 | - zCwd = file_getcwd(0,0); | |
| 645 | - zHome = fossil_getenv("HOME"); | |
| 646 | - if( zHome ){ | |
| 647 | - int nHome = (int)strlen(zHome); | |
| 648 | - if( strncmp(zCwd, zHome, nHome)==0 && zCwd[nHome]=='/' ){ | |
| 649 | - zCwd = mprintf("~%s", zCwd+nHome); | |
| 650 | - } | |
| 651 | - } | |
| 652 | - if( zHostname ){ | |
| 653 | - style_header("Checkout Status: %h on %h", zCwd, zHostname); | |
| 654 | - }else{ | |
| 655 | - style_header("Checkout Status: %h", zCwd); | |
| 656 | - } | |
| 657 | - render_checkin_context(vid, 0, 0, 0); | |
| 658 | 625 | nChng = db_int(0, "SELECT count(*) FROM vfile" |
| 659 | 626 | " WHERE vid=%d AND (deleted OR chnged OR rid==0)", vid); |
| 660 | 627 | if( nChng==0 ){ |
| 661 | 628 | @ <p>No uncommitted changes</p> |
| 662 | 629 | style_finish_page(); |
| @@ -674,11 +641,10 @@ | ||
| 674 | 641 | if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){ |
| 675 | 642 | DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG; |
| 676 | 643 | }else{ |
| 677 | 644 | DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG; |
| 678 | 645 | } |
| 679 | - @ <hr> | |
| 680 | 646 | @ <div class="sectionmenu info-changes-menu"> |
| 681 | 647 | zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":""; |
| 682 | 648 | if( diffType!=1 ){ |
| 683 | 649 | @ %z(chref("button","%R?diff=1%s",zW))Unified Diff</a> |
| 684 | 650 | } |
| @@ -750,12 +716,159 @@ | ||
| 750 | 716 | blob_reset(&old); |
| 751 | 717 | blob_reset(&new); |
| 752 | 718 | } |
| 753 | 719 | } |
| 754 | 720 | db_finalize(&q); |
| 755 | - // @ </div> <!-- ap-002 --> | |
| 721 | + append_diff_javascript(diffType); | |
| 722 | +} | |
| 723 | + | |
| 724 | +/* | |
| 725 | +** Render a web-page diff of the changes in the working check-out to | |
| 726 | +** an external reference. | |
| 727 | +*/ | |
| 728 | +static void ckout_external_base_diff(int vid, const char *zExBase){ | |
| 729 | + int diffType; /* 0: no diff, 1: unified, 2: side-by-side */ | |
| 730 | + DiffConfig DCfg,*pCfg; /* Diff details */ | |
| 731 | + const char *zW; /* The "w" query parameter */ | |
| 732 | + Stmt q; | |
| 733 | + | |
| 734 | + diffType = preferred_diff_type(); | |
| 735 | + pCfg = construct_diff_flags(diffType, &DCfg); | |
| 736 | + db_prepare(&q, | |
| 737 | + "SELECT pathname FROM vfile WHERE vid=%d ORDER BY pathname", vid | |
| 738 | + ); | |
| 739 | + if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){ | |
| 740 | + DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG; | |
| 741 | + }else{ | |
| 742 | + DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG; | |
| 743 | + } | |
| 744 | + @ <div class="sectionmenu info-changes-menu"> | |
| 745 | + zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":""; | |
| 746 | + if( diffType!=1 ){ | |
| 747 | + @ %z(chref("button","%R?diff=1&exbase=%h%s",zExBase,zW))\ | |
| 748 | + @ Unified Diff</a> | |
| 749 | + } | |
| 750 | + if( diffType!=2 ){ | |
| 751 | + @ %z(chref("button","%R?diff=2&exbase=%h%s",zExBase,zW))\ | |
| 752 | + @ Side-by-Side Diff</a> | |
| 753 | + } | |
| 754 | + if( diffType!=0 ){ | |
| 755 | + if( *zW ){ | |
| 756 | + @ %z(chref("button","%R?diff=%d&exbase=%h",diffType,zExBase))\ | |
| 757 | + @ Show Whitespace Changes</a> | |
| 758 | + }else{ | |
| 759 | + @ %z(chref("button","%R?diff=%d&exbase=%h&w",diffType,zExBase))\ | |
| 760 | + @ Ignore Whitespace</a> | |
| 761 | + } | |
| 762 | + } | |
| 763 | + @ </div> | |
| 764 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 765 | + const char *zFile; /* Name of file in the repository */ | |
| 766 | + char *zLhs; /* Full name of left-hand side file */ | |
| 767 | + char *zRhs; /* Full name of right-hand side file */ | |
| 768 | + Blob rhs; /* Full text of RHS */ | |
| 769 | + Blob lhs; /* Full text of LHS */ | |
| 770 | + | |
| 771 | + zFile = db_column_text(&q,0); | |
| 772 | + zLhs = mprintf("%s/%s", zExBase, zFile); | |
| 773 | + zRhs = mprintf("%s%s", g.zLocalRoot, zFile); | |
| 774 | + if( file_size(zLhs, ExtFILE)<0 ){ | |
| 775 | + @ <div class='file-change-line'><span> | |
| 776 | + @ Missing from external baseline: %h(zFile) | |
| 777 | + @ </span></div> | |
| 778 | + }else{ | |
| 779 | + blob_read_from_file(&lhs, zLhs, ExtFILE); | |
| 780 | + blob_read_from_file(&rhs, zRhs, ExtFILE); | |
| 781 | + if( blob_size(&lhs)!=blob_size(&rhs) | |
| 782 | + || memcmp(blob_buffer(&lhs), blob_buffer(&rhs), blob_size(&lhs))!=0 | |
| 783 | + ){ | |
| 784 | + @ <div class='file-change-line'><span> | |
| 785 | + @ Changes to %h(zFile) | |
| 786 | + @ </span></div> | |
| 787 | + if( pCfg ){ | |
| 788 | + char *zFullFN; | |
| 789 | + char *zHexFN; | |
| 790 | + int nFullFN; | |
| 791 | + zFullFN = file_canonical_name_dup(zLhs); | |
| 792 | + nFullFN = (int)strlen(zFullFN); | |
| 793 | + zHexFN = fossil_malloc( nFullFN*2 + 5 ); | |
| 794 | + zHexFN[0] = 'x'; | |
| 795 | + encode16((const u8*)zFullFN, (u8*)(zHexFN+1), nFullFN); | |
| 796 | + zHexFN[1+nFullFN*2] = 0; | |
| 797 | + fossil_free(zFullFN); | |
| 798 | + pCfg->zLeftHash = zHexFN; | |
| 799 | + text_diff(&lhs, &rhs, cgi_output_blob(), pCfg); | |
| 800 | + pCfg->zLeftHash = 0; | |
| 801 | + fossil_free(zHexFN); | |
| 802 | + } | |
| 803 | + } | |
| 804 | + blob_reset(&lhs); | |
| 805 | + blob_reset(&rhs); | |
| 806 | + } | |
| 807 | + fossil_free(zLhs); | |
| 808 | + fossil_free(zRhs); | |
| 809 | + } | |
| 810 | + db_finalize(&q); | |
| 756 | 811 | append_diff_javascript(diffType); |
| 812 | +} | |
| 813 | + | |
| 814 | +/* | |
| 815 | +** WEBPAGE: ckout | |
| 816 | +** | |
| 817 | +** Show information about the current checkout. This page only functions | |
| 818 | +** if the web server is run on a loopback interface (in other words, was | |
| 819 | +** started using "fossil ui" or similar) from with on open check-out. | |
| 820 | +*/ | |
| 821 | +void ckout_page(void){ | |
| 822 | + int vid; | |
| 823 | + const char *zHome; /* Home directory */ | |
| 824 | + int nHome; | |
| 825 | + const char *zExBase; | |
| 826 | + char *zHostname; | |
| 827 | + char *zCwd; | |
| 828 | + | |
| 829 | + if( !db_open_local(0) || !cgi_is_loopback(g.zIpAddr) ){ | |
| 830 | + cgi_redirectf("%R/home"); | |
| 831 | + return; | |
| 832 | + } | |
| 833 | + file_chdir(g.zLocalRoot, 0); | |
| 834 | + vid = db_lget_int("checkout", 0); | |
| 835 | + db_unprotect(PROTECT_ALL); | |
| 836 | + vfile_check_signature(vid, CKSIG_ENOTFILE); | |
| 837 | + db_protect_pop(); | |
| 838 | + style_set_current_feature("vinfo"); | |
| 839 | + zHostname = fossil_hostname(); | |
| 840 | + zCwd = file_getcwd(0,0); | |
| 841 | + zHome = fossil_getenv("HOME"); | |
| 842 | + if( zHome ){ | |
| 843 | + nHome = (int)strlen(zHome); | |
| 844 | + if( strncmp(zCwd, zHome, nHome)==0 && zCwd[nHome]=='/' ){ | |
| 845 | + zCwd = mprintf("~%s", zCwd+nHome); | |
| 846 | + } | |
| 847 | + }else{ | |
| 848 | + nHome = 0; | |
| 849 | + } | |
| 850 | + if( zHostname ){ | |
| 851 | + style_header("Checkout Status: %h on %h", zCwd, zHostname); | |
| 852 | + }else{ | |
| 853 | + style_header("Checkout Status: %h", zCwd); | |
| 854 | + } | |
| 855 | + render_checkin_context(vid, 0, 0, 0); | |
| 856 | + @ <hr> | |
| 857 | + zExBase = P("exbase"); | |
| 858 | + if( zExBase && zExBase[0] ){ | |
| 859 | + char *zCBase = file_canonical_name_dup(zExBase); | |
| 860 | + if( nHome && strncmp(zCBase, zHome, nHome)==0 && zCBase[nHome]=='/' ){ | |
| 861 | + @ <p>Using external baseline: ~%h(zCBase+nHome)</p> | |
| 862 | + }else{ | |
| 863 | + @ <p>Using external baseline: %h(zCBase)</p> | |
| 864 | + } | |
| 865 | + ckout_external_base_diff(vid, zCBase); | |
| 866 | + fossil_free(zCBase); | |
| 867 | + }else{ | |
| 868 | + ckout_normal_diff(vid); | |
| 869 | + } | |
| 757 | 870 | style_finish_page(); |
| 758 | 871 | } |
| 759 | 872 | |
| 760 | 873 | /* |
| 761 | 874 | ** WEBPAGE: vinfo |
| @@ -2077,10 +2190,16 @@ | ||
| 2077 | 2190 | ** WEBPAGE: jchunk hidden |
| 2078 | 2191 | ** URL: /jchunk/HASH?from=N&to=M |
| 2079 | 2192 | ** |
| 2080 | 2193 | ** Return lines of text from a file as a JSON array - one entry in the |
| 2081 | 2194 | ** array for each line of text. |
| 2195 | +** | |
| 2196 | +** The HASH is normally a sha1 or sha3 hash that identifies an artifact | |
| 2197 | +** in the BLOB table of the database. However, if HASH starts with an "x" | |
| 2198 | +** and is followed by valid hexadecimal, and if we are running in a | |
| 2199 | +** "fossil ui" situation (locally and with privilege), then decode the hex | |
| 2200 | +** into a filename and read the file content from that name. | |
| 2082 | 2201 | ** |
| 2083 | 2202 | ** **Warning:** This is an internal-use-only interface that is subject to |
| 2084 | 2203 | ** change at any moment. External application should not use this interface |
| 2085 | 2204 | ** since the application will break when this interface changes, and this |
| 2086 | 2205 | ** interface will undoubtedly change. |
| @@ -2092,10 +2211,11 @@ | ||
| 2092 | 2211 | ** ajax_route_error(). |
| 2093 | 2212 | */ |
| 2094 | 2213 | void jchunk_page(void){ |
| 2095 | 2214 | int rid = 0; |
| 2096 | 2215 | const char *zName = PD("name", ""); |
| 2216 | + int nName = (int)(strlen(zName)&0x7fffffff); | |
| 2097 | 2217 | int iFrom = atoi(PD("from","0")); |
| 2098 | 2218 | int iTo = atoi(PD("to","0")); |
| 2099 | 2219 | int ln; |
| 2100 | 2220 | int go = 1; |
| 2101 | 2221 | const char *zSep; |
| @@ -2112,36 +2232,57 @@ | ||
| 2112 | 2232 | cgi_check_for_malice(); |
| 2113 | 2233 | if( !g.perm.Read ){ |
| 2114 | 2234 | ajax_route_error(403, "Access requires Read permissions."); |
| 2115 | 2235 | return; |
| 2116 | 2236 | } |
| 2117 | -#if 1 | |
| 2118 | - /* Re-enable this block once this code is integrated somewhere into | |
| 2119 | - the UI. */ | |
| 2120 | - rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zName); | |
| 2121 | - if( rid==0 ){ | |
| 2122 | - ajax_route_error(404, "Unknown artifact: %h", zName); | |
| 2123 | - return; | |
| 2124 | - } | |
| 2125 | -#else | |
| 2126 | - /* This impl is only to simplify "manual" testing via the JS | |
| 2127 | - console. */ | |
| 2128 | - rid = symbolic_name_to_rid(zName, "*"); | |
| 2129 | - if( rid==0 ){ | |
| 2130 | - ajax_route_error(404, "Unknown artifact: %h", zName); | |
| 2131 | - return; | |
| 2132 | - }else if( rid<0 ){ | |
| 2133 | - ajax_route_error(418, "Ambiguous artifact name: %h", zName); | |
| 2134 | - return; | |
| 2135 | - } | |
| 2136 | -#endif | |
| 2137 | 2237 | if( iFrom<1 || iTo<iFrom ){ |
| 2138 | 2238 | ajax_route_error(500, "Invalid line range from=%d, to=%d.", |
| 2139 | 2239 | iFrom, iTo); |
| 2140 | 2240 | return; |
| 2141 | 2241 | } |
| 2142 | - content_get(rid, &content); | |
| 2242 | + if( zName[0]=='x' | |
| 2243 | + && ((nName-1)&1)==0 | |
| 2244 | + && validate16(&zName[1],nName-1) | |
| 2245 | + && g.perm.Admin | |
| 2246 | + && db_open_local(0) | |
| 2247 | + && cgi_is_loopback(g.zIpAddr) | |
| 2248 | + ){ | |
| 2249 | + /* Treat the HASH as a hex-encoded filename */ | |
| 2250 | + int n = (nName-1)/2; | |
| 2251 | + char *zFN = fossil_malloc(n+1); | |
| 2252 | + decode16((const u8*)&zName[1], (u8*)zFN, nName-1); | |
| 2253 | + zFN[n] = 0; | |
| 2254 | + if( file_size(zFN, ExtFILE)<0 ){ | |
| 2255 | + blob_zero(&content); | |
| 2256 | + }else{ | |
| 2257 | + blob_read_from_file(&content, zFN, ExtFILE); | |
| 2258 | + } | |
| 2259 | + fossil_free(zFN); | |
| 2260 | + }else{ | |
| 2261 | + /* Treat the HASH as an artifact hash matching BLOB.UUID */ | |
| 2262 | +#if 1 | |
| 2263 | + /* Re-enable this block once this code is integrated somewhere into | |
| 2264 | + the UI. */ | |
| 2265 | + rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zName); | |
| 2266 | + if( rid==0 ){ | |
| 2267 | + ajax_route_error(404, "Unknown artifact: %h", zName); | |
| 2268 | + return; | |
| 2269 | + } | |
| 2270 | +#else | |
| 2271 | + /* This impl is only to simplify "manual" testing via the JS | |
| 2272 | + console. */ | |
| 2273 | + rid = symbolic_name_to_rid(zName, "*"); | |
| 2274 | + if( rid==0 ){ | |
| 2275 | + ajax_route_error(404, "Unknown artifact: %h", zName); | |
| 2276 | + return; | |
| 2277 | + }else if( rid<0 ){ | |
| 2278 | + ajax_route_error(418, "Ambiguous artifact name: %h", zName); | |
| 2279 | + return; | |
| 2280 | + } | |
| 2281 | +#endif | |
| 2282 | + content_get(rid, &content); | |
| 2283 | + } | |
| 2143 | 2284 | g.isConst = 1; |
| 2144 | 2285 | cgi_set_content_type("application/json"); |
| 2145 | 2286 | ln = 0; |
| 2146 | 2287 | while( go && ln<iFrom ){ |
| 2147 | 2288 | go = blob_line(&content, &line); |
| 2148 | 2289 |
| --- src/info.c | |
| +++ src/info.c | |
| @@ -609,54 +609,21 @@ | |
| 609 | db_finalize(&q); |
| 610 | style_finish_page(); |
| 611 | } |
| 612 | |
| 613 | /* |
| 614 | ** WEBPAGE: ckout |
| 615 | ** |
| 616 | ** Show information about the current checkout. This page only functions |
| 617 | ** if the web server is run on a loopback interface (in other words, was |
| 618 | ** started using "fossil ui" or similar) from with on open check-out. |
| 619 | */ |
| 620 | void ckout_page(void){ |
| 621 | int vid; |
| 622 | char *zHostname; |
| 623 | char *zCwd; |
| 624 | int diffType; /* 0: no diff, 1: unified, 2: side-by-side */ |
| 625 | DiffConfig DCfg,*pCfg; /* Diff details */ |
| 626 | const char *zHome; /* Home directory */ |
| 627 | const char *zW; /* The "w" query parameter */ |
| 628 | int nChng; /* Number of changes */ |
| 629 | Stmt q; |
| 630 | |
| 631 | if( !db_open_local(0) || !cgi_is_loopback(g.zIpAddr) ){ |
| 632 | cgi_redirectf("%R/home"); |
| 633 | return; |
| 634 | } |
| 635 | file_chdir(g.zLocalRoot, 0); |
| 636 | diffType = preferred_diff_type(); |
| 637 | pCfg = construct_diff_flags(diffType, &DCfg); |
| 638 | vid = db_lget_int("checkout", 0); |
| 639 | db_unprotect(PROTECT_ALL); |
| 640 | vfile_check_signature(vid, CKSIG_ENOTFILE); |
| 641 | db_protect_pop(); |
| 642 | style_set_current_feature("vinfo"); |
| 643 | zHostname = fossil_hostname(); |
| 644 | zCwd = file_getcwd(0,0); |
| 645 | zHome = fossil_getenv("HOME"); |
| 646 | if( zHome ){ |
| 647 | int nHome = (int)strlen(zHome); |
| 648 | if( strncmp(zCwd, zHome, nHome)==0 && zCwd[nHome]=='/' ){ |
| 649 | zCwd = mprintf("~%s", zCwd+nHome); |
| 650 | } |
| 651 | } |
| 652 | if( zHostname ){ |
| 653 | style_header("Checkout Status: %h on %h", zCwd, zHostname); |
| 654 | }else{ |
| 655 | style_header("Checkout Status: %h", zCwd); |
| 656 | } |
| 657 | render_checkin_context(vid, 0, 0, 0); |
| 658 | nChng = db_int(0, "SELECT count(*) FROM vfile" |
| 659 | " WHERE vid=%d AND (deleted OR chnged OR rid==0)", vid); |
| 660 | if( nChng==0 ){ |
| 661 | @ <p>No uncommitted changes</p> |
| 662 | style_finish_page(); |
| @@ -674,11 +641,10 @@ | |
| 674 | if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){ |
| 675 | DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG; |
| 676 | }else{ |
| 677 | DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG; |
| 678 | } |
| 679 | @ <hr> |
| 680 | @ <div class="sectionmenu info-changes-menu"> |
| 681 | zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":""; |
| 682 | if( diffType!=1 ){ |
| 683 | @ %z(chref("button","%R?diff=1%s",zW))Unified Diff</a> |
| 684 | } |
| @@ -750,12 +716,159 @@ | |
| 750 | blob_reset(&old); |
| 751 | blob_reset(&new); |
| 752 | } |
| 753 | } |
| 754 | db_finalize(&q); |
| 755 | // @ </div> <!-- ap-002 --> |
| 756 | append_diff_javascript(diffType); |
| 757 | style_finish_page(); |
| 758 | } |
| 759 | |
| 760 | /* |
| 761 | ** WEBPAGE: vinfo |
| @@ -2077,10 +2190,16 @@ | |
| 2077 | ** WEBPAGE: jchunk hidden |
| 2078 | ** URL: /jchunk/HASH?from=N&to=M |
| 2079 | ** |
| 2080 | ** Return lines of text from a file as a JSON array - one entry in the |
| 2081 | ** array for each line of text. |
| 2082 | ** |
| 2083 | ** **Warning:** This is an internal-use-only interface that is subject to |
| 2084 | ** change at any moment. External application should not use this interface |
| 2085 | ** since the application will break when this interface changes, and this |
| 2086 | ** interface will undoubtedly change. |
| @@ -2092,10 +2211,11 @@ | |
| 2092 | ** ajax_route_error(). |
| 2093 | */ |
| 2094 | void jchunk_page(void){ |
| 2095 | int rid = 0; |
| 2096 | const char *zName = PD("name", ""); |
| 2097 | int iFrom = atoi(PD("from","0")); |
| 2098 | int iTo = atoi(PD("to","0")); |
| 2099 | int ln; |
| 2100 | int go = 1; |
| 2101 | const char *zSep; |
| @@ -2112,36 +2232,57 @@ | |
| 2112 | cgi_check_for_malice(); |
| 2113 | if( !g.perm.Read ){ |
| 2114 | ajax_route_error(403, "Access requires Read permissions."); |
| 2115 | return; |
| 2116 | } |
| 2117 | #if 1 |
| 2118 | /* Re-enable this block once this code is integrated somewhere into |
| 2119 | the UI. */ |
| 2120 | rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zName); |
| 2121 | if( rid==0 ){ |
| 2122 | ajax_route_error(404, "Unknown artifact: %h", zName); |
| 2123 | return; |
| 2124 | } |
| 2125 | #else |
| 2126 | /* This impl is only to simplify "manual" testing via the JS |
| 2127 | console. */ |
| 2128 | rid = symbolic_name_to_rid(zName, "*"); |
| 2129 | if( rid==0 ){ |
| 2130 | ajax_route_error(404, "Unknown artifact: %h", zName); |
| 2131 | return; |
| 2132 | }else if( rid<0 ){ |
| 2133 | ajax_route_error(418, "Ambiguous artifact name: %h", zName); |
| 2134 | return; |
| 2135 | } |
| 2136 | #endif |
| 2137 | if( iFrom<1 || iTo<iFrom ){ |
| 2138 | ajax_route_error(500, "Invalid line range from=%d, to=%d.", |
| 2139 | iFrom, iTo); |
| 2140 | return; |
| 2141 | } |
| 2142 | content_get(rid, &content); |
| 2143 | g.isConst = 1; |
| 2144 | cgi_set_content_type("application/json"); |
| 2145 | ln = 0; |
| 2146 | while( go && ln<iFrom ){ |
| 2147 | go = blob_line(&content, &line); |
| 2148 |
| --- src/info.c | |
| +++ src/info.c | |
| @@ -609,54 +609,21 @@ | |
| 609 | db_finalize(&q); |
| 610 | style_finish_page(); |
| 611 | } |
| 612 | |
| 613 | /* |
| 614 | ** Render a web-page diff of the changes in the working check-out |
| 615 | */ |
| 616 | static void ckout_normal_diff(int vid){ |
| 617 | int diffType; /* 0: no diff, 1: unified, 2: side-by-side */ |
| 618 | DiffConfig DCfg,*pCfg; /* Diff details */ |
| 619 | const char *zW; /* The "w" query parameter */ |
| 620 | int nChng; /* Number of changes */ |
| 621 | Stmt q; |
| 622 | |
| 623 | diffType = preferred_diff_type(); |
| 624 | pCfg = construct_diff_flags(diffType, &DCfg); |
| 625 | nChng = db_int(0, "SELECT count(*) FROM vfile" |
| 626 | " WHERE vid=%d AND (deleted OR chnged OR rid==0)", vid); |
| 627 | if( nChng==0 ){ |
| 628 | @ <p>No uncommitted changes</p> |
| 629 | style_finish_page(); |
| @@ -674,11 +641,10 @@ | |
| 641 | if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){ |
| 642 | DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG; |
| 643 | }else{ |
| 644 | DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG; |
| 645 | } |
| 646 | @ <div class="sectionmenu info-changes-menu"> |
| 647 | zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":""; |
| 648 | if( diffType!=1 ){ |
| 649 | @ %z(chref("button","%R?diff=1%s",zW))Unified Diff</a> |
| 650 | } |
| @@ -750,12 +716,159 @@ | |
| 716 | blob_reset(&old); |
| 717 | blob_reset(&new); |
| 718 | } |
| 719 | } |
| 720 | db_finalize(&q); |
| 721 | append_diff_javascript(diffType); |
| 722 | } |
| 723 | |
| 724 | /* |
| 725 | ** Render a web-page diff of the changes in the working check-out to |
| 726 | ** an external reference. |
| 727 | */ |
| 728 | static void ckout_external_base_diff(int vid, const char *zExBase){ |
| 729 | int diffType; /* 0: no diff, 1: unified, 2: side-by-side */ |
| 730 | DiffConfig DCfg,*pCfg; /* Diff details */ |
| 731 | const char *zW; /* The "w" query parameter */ |
| 732 | Stmt q; |
| 733 | |
| 734 | diffType = preferred_diff_type(); |
| 735 | pCfg = construct_diff_flags(diffType, &DCfg); |
| 736 | db_prepare(&q, |
| 737 | "SELECT pathname FROM vfile WHERE vid=%d ORDER BY pathname", vid |
| 738 | ); |
| 739 | if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){ |
| 740 | DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG; |
| 741 | }else{ |
| 742 | DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG; |
| 743 | } |
| 744 | @ <div class="sectionmenu info-changes-menu"> |
| 745 | zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":""; |
| 746 | if( diffType!=1 ){ |
| 747 | @ %z(chref("button","%R?diff=1&exbase=%h%s",zExBase,zW))\ |
| 748 | @ Unified Diff</a> |
| 749 | } |
| 750 | if( diffType!=2 ){ |
| 751 | @ %z(chref("button","%R?diff=2&exbase=%h%s",zExBase,zW))\ |
| 752 | @ Side-by-Side Diff</a> |
| 753 | } |
| 754 | if( diffType!=0 ){ |
| 755 | if( *zW ){ |
| 756 | @ %z(chref("button","%R?diff=%d&exbase=%h",diffType,zExBase))\ |
| 757 | @ Show Whitespace Changes</a> |
| 758 | }else{ |
| 759 | @ %z(chref("button","%R?diff=%d&exbase=%h&w",diffType,zExBase))\ |
| 760 | @ Ignore Whitespace</a> |
| 761 | } |
| 762 | } |
| 763 | @ </div> |
| 764 | while( db_step(&q)==SQLITE_ROW ){ |
| 765 | const char *zFile; /* Name of file in the repository */ |
| 766 | char *zLhs; /* Full name of left-hand side file */ |
| 767 | char *zRhs; /* Full name of right-hand side file */ |
| 768 | Blob rhs; /* Full text of RHS */ |
| 769 | Blob lhs; /* Full text of LHS */ |
| 770 | |
| 771 | zFile = db_column_text(&q,0); |
| 772 | zLhs = mprintf("%s/%s", zExBase, zFile); |
| 773 | zRhs = mprintf("%s%s", g.zLocalRoot, zFile); |
| 774 | if( file_size(zLhs, ExtFILE)<0 ){ |
| 775 | @ <div class='file-change-line'><span> |
| 776 | @ Missing from external baseline: %h(zFile) |
| 777 | @ </span></div> |
| 778 | }else{ |
| 779 | blob_read_from_file(&lhs, zLhs, ExtFILE); |
| 780 | blob_read_from_file(&rhs, zRhs, ExtFILE); |
| 781 | if( blob_size(&lhs)!=blob_size(&rhs) |
| 782 | || memcmp(blob_buffer(&lhs), blob_buffer(&rhs), blob_size(&lhs))!=0 |
| 783 | ){ |
| 784 | @ <div class='file-change-line'><span> |
| 785 | @ Changes to %h(zFile) |
| 786 | @ </span></div> |
| 787 | if( pCfg ){ |
| 788 | char *zFullFN; |
| 789 | char *zHexFN; |
| 790 | int nFullFN; |
| 791 | zFullFN = file_canonical_name_dup(zLhs); |
| 792 | nFullFN = (int)strlen(zFullFN); |
| 793 | zHexFN = fossil_malloc( nFullFN*2 + 5 ); |
| 794 | zHexFN[0] = 'x'; |
| 795 | encode16((const u8*)zFullFN, (u8*)(zHexFN+1), nFullFN); |
| 796 | zHexFN[1+nFullFN*2] = 0; |
| 797 | fossil_free(zFullFN); |
| 798 | pCfg->zLeftHash = zHexFN; |
| 799 | text_diff(&lhs, &rhs, cgi_output_blob(), pCfg); |
| 800 | pCfg->zLeftHash = 0; |
| 801 | fossil_free(zHexFN); |
| 802 | } |
| 803 | } |
| 804 | blob_reset(&lhs); |
| 805 | blob_reset(&rhs); |
| 806 | } |
| 807 | fossil_free(zLhs); |
| 808 | fossil_free(zRhs); |
| 809 | } |
| 810 | db_finalize(&q); |
| 811 | append_diff_javascript(diffType); |
| 812 | } |
| 813 | |
| 814 | /* |
| 815 | ** WEBPAGE: ckout |
| 816 | ** |
| 817 | ** Show information about the current checkout. This page only functions |
| 818 | ** if the web server is run on a loopback interface (in other words, was |
| 819 | ** started using "fossil ui" or similar) from with on open check-out. |
| 820 | */ |
| 821 | void ckout_page(void){ |
| 822 | int vid; |
| 823 | const char *zHome; /* Home directory */ |
| 824 | int nHome; |
| 825 | const char *zExBase; |
| 826 | char *zHostname; |
| 827 | char *zCwd; |
| 828 | |
| 829 | if( !db_open_local(0) || !cgi_is_loopback(g.zIpAddr) ){ |
| 830 | cgi_redirectf("%R/home"); |
| 831 | return; |
| 832 | } |
| 833 | file_chdir(g.zLocalRoot, 0); |
| 834 | vid = db_lget_int("checkout", 0); |
| 835 | db_unprotect(PROTECT_ALL); |
| 836 | vfile_check_signature(vid, CKSIG_ENOTFILE); |
| 837 | db_protect_pop(); |
| 838 | style_set_current_feature("vinfo"); |
| 839 | zHostname = fossil_hostname(); |
| 840 | zCwd = file_getcwd(0,0); |
| 841 | zHome = fossil_getenv("HOME"); |
| 842 | if( zHome ){ |
| 843 | nHome = (int)strlen(zHome); |
| 844 | if( strncmp(zCwd, zHome, nHome)==0 && zCwd[nHome]=='/' ){ |
| 845 | zCwd = mprintf("~%s", zCwd+nHome); |
| 846 | } |
| 847 | }else{ |
| 848 | nHome = 0; |
| 849 | } |
| 850 | if( zHostname ){ |
| 851 | style_header("Checkout Status: %h on %h", zCwd, zHostname); |
| 852 | }else{ |
| 853 | style_header("Checkout Status: %h", zCwd); |
| 854 | } |
| 855 | render_checkin_context(vid, 0, 0, 0); |
| 856 | @ <hr> |
| 857 | zExBase = P("exbase"); |
| 858 | if( zExBase && zExBase[0] ){ |
| 859 | char *zCBase = file_canonical_name_dup(zExBase); |
| 860 | if( nHome && strncmp(zCBase, zHome, nHome)==0 && zCBase[nHome]=='/' ){ |
| 861 | @ <p>Using external baseline: ~%h(zCBase+nHome)</p> |
| 862 | }else{ |
| 863 | @ <p>Using external baseline: %h(zCBase)</p> |
| 864 | } |
| 865 | ckout_external_base_diff(vid, zCBase); |
| 866 | fossil_free(zCBase); |
| 867 | }else{ |
| 868 | ckout_normal_diff(vid); |
| 869 | } |
| 870 | style_finish_page(); |
| 871 | } |
| 872 | |
| 873 | /* |
| 874 | ** WEBPAGE: vinfo |
| @@ -2077,10 +2190,16 @@ | |
| 2190 | ** WEBPAGE: jchunk hidden |
| 2191 | ** URL: /jchunk/HASH?from=N&to=M |
| 2192 | ** |
| 2193 | ** Return lines of text from a file as a JSON array - one entry in the |
| 2194 | ** array for each line of text. |
| 2195 | ** |
| 2196 | ** The HASH is normally a sha1 or sha3 hash that identifies an artifact |
| 2197 | ** in the BLOB table of the database. However, if HASH starts with an "x" |
| 2198 | ** and is followed by valid hexadecimal, and if we are running in a |
| 2199 | ** "fossil ui" situation (locally and with privilege), then decode the hex |
| 2200 | ** into a filename and read the file content from that name. |
| 2201 | ** |
| 2202 | ** **Warning:** This is an internal-use-only interface that is subject to |
| 2203 | ** change at any moment. External application should not use this interface |
| 2204 | ** since the application will break when this interface changes, and this |
| 2205 | ** interface will undoubtedly change. |
| @@ -2092,10 +2211,11 @@ | |
| 2211 | ** ajax_route_error(). |
| 2212 | */ |
| 2213 | void jchunk_page(void){ |
| 2214 | int rid = 0; |
| 2215 | const char *zName = PD("name", ""); |
| 2216 | int nName = (int)(strlen(zName)&0x7fffffff); |
| 2217 | int iFrom = atoi(PD("from","0")); |
| 2218 | int iTo = atoi(PD("to","0")); |
| 2219 | int ln; |
| 2220 | int go = 1; |
| 2221 | const char *zSep; |
| @@ -2112,36 +2232,57 @@ | |
| 2232 | cgi_check_for_malice(); |
| 2233 | if( !g.perm.Read ){ |
| 2234 | ajax_route_error(403, "Access requires Read permissions."); |
| 2235 | return; |
| 2236 | } |
| 2237 | if( iFrom<1 || iTo<iFrom ){ |
| 2238 | ajax_route_error(500, "Invalid line range from=%d, to=%d.", |
| 2239 | iFrom, iTo); |
| 2240 | return; |
| 2241 | } |
| 2242 | if( zName[0]=='x' |
| 2243 | && ((nName-1)&1)==0 |
| 2244 | && validate16(&zName[1],nName-1) |
| 2245 | && g.perm.Admin |
| 2246 | && db_open_local(0) |
| 2247 | && cgi_is_loopback(g.zIpAddr) |
| 2248 | ){ |
| 2249 | /* Treat the HASH as a hex-encoded filename */ |
| 2250 | int n = (nName-1)/2; |
| 2251 | char *zFN = fossil_malloc(n+1); |
| 2252 | decode16((const u8*)&zName[1], (u8*)zFN, nName-1); |
| 2253 | zFN[n] = 0; |
| 2254 | if( file_size(zFN, ExtFILE)<0 ){ |
| 2255 | blob_zero(&content); |
| 2256 | }else{ |
| 2257 | blob_read_from_file(&content, zFN, ExtFILE); |
| 2258 | } |
| 2259 | fossil_free(zFN); |
| 2260 | }else{ |
| 2261 | /* Treat the HASH as an artifact hash matching BLOB.UUID */ |
| 2262 | #if 1 |
| 2263 | /* Re-enable this block once this code is integrated somewhere into |
| 2264 | the UI. */ |
| 2265 | rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zName); |
| 2266 | if( rid==0 ){ |
| 2267 | ajax_route_error(404, "Unknown artifact: %h", zName); |
| 2268 | return; |
| 2269 | } |
| 2270 | #else |
| 2271 | /* This impl is only to simplify "manual" testing via the JS |
| 2272 | console. */ |
| 2273 | rid = symbolic_name_to_rid(zName, "*"); |
| 2274 | if( rid==0 ){ |
| 2275 | ajax_route_error(404, "Unknown artifact: %h", zName); |
| 2276 | return; |
| 2277 | }else if( rid<0 ){ |
| 2278 | ajax_route_error(418, "Ambiguous artifact name: %h", zName); |
| 2279 | return; |
| 2280 | } |
| 2281 | #endif |
| 2282 | content_get(rid, &content); |
| 2283 | } |
| 2284 | g.isConst = 1; |
| 2285 | cgi_set_content_type("application/json"); |
| 2286 | ln = 0; |
| 2287 | while( go && ln<iFrom ){ |
| 2288 | go = blob_line(&content, &line); |
| 2289 |
+6
| --- src/main.c | ||
| +++ src/main.c | ||
| @@ -3175,10 +3175,11 @@ | ||
| 3175 | 3175 | ** --ckout-alias NAME Treat URIs of the form /doc/NAME/... as if they were |
| 3176 | 3176 | ** /doc/ckout/... |
| 3177 | 3177 | ** --create Create a new REPOSITORY if it does not already exist |
| 3178 | 3178 | ** --errorlog FILE Append HTTP error messages to FILE |
| 3179 | 3179 | ** --extroot DIR Document root for the /ext extension mechanism |
| 3180 | +** --external-baseline DIR External baseline for the initial /ckout page. | |
| 3180 | 3181 | ** --files GLOBLIST Comma-separated list of glob patterns for static files |
| 3181 | 3182 | ** --fossilcmd PATH The pathname of the "fossil" executable on the remote |
| 3182 | 3183 | ** system when REPOSITORY is remote. |
| 3183 | 3184 | ** --localauth Enable automatic login for requests from localhost |
| 3184 | 3185 | ** --localhost Listen on 127.0.0.1 only (always true for "ui") |
| @@ -3250,10 +3251,11 @@ | ||
| 3250 | 3251 | const char *zInitPage = 0; /* Start on this page. --page option */ |
| 3251 | 3252 | int findServerArg = 2; /* argv index for find_server_repository() */ |
| 3252 | 3253 | char *zRemote = 0; /* Remote host on which to run "fossil ui" */ |
| 3253 | 3254 | const char *zJsMode; /* The --jsmode parameter */ |
| 3254 | 3255 | const char *zFossilCmd =0; /* Name of "fossil" binary on remote system */ |
| 3256 | + const char *zExBase; /* Value for --external-baseline */ | |
| 3255 | 3257 | |
| 3256 | 3258 | |
| 3257 | 3259 | #if USE_SEE |
| 3258 | 3260 | db_setup_for_saved_encryption_key(); |
| 3259 | 3261 | #endif |
| @@ -3286,13 +3288,17 @@ | ||
| 3286 | 3288 | g.useLocalauth = find_option("localauth", 0, 0)!=0; |
| 3287 | 3289 | Th_InitTraceLog(); |
| 3288 | 3290 | zPort = find_option("port", "P", 1); |
| 3289 | 3291 | isUiCmd = g.argv[1][0]=='u'; |
| 3290 | 3292 | if( isUiCmd ){ |
| 3293 | + zExBase = find_option("external-baseline", 0, 1); | |
| 3291 | 3294 | zInitPage = find_option("page", "p", 1); |
| 3292 | 3295 | if( zInitPage && zInitPage[0]=='/' ) zInitPage++; |
| 3293 | 3296 | zFossilCmd = find_option("fossilcmd", 0, 1); |
| 3297 | + if( zExBase && zInitPage==0 ){ | |
| 3298 | + zInitPage = mprintf("ckout?exbase=%T", zExBase); | |
| 3299 | + } | |
| 3294 | 3300 | } |
| 3295 | 3301 | zNotFound = find_option("notfound", 0, 1); |
| 3296 | 3302 | allowRepoList = find_option("repolist",0,0)!=0; |
| 3297 | 3303 | if( find_option("nocompress",0,0)!=0 ) g.fNoHttpCompress = 1; |
| 3298 | 3304 | zAltBase = find_option("baseurl", 0, 1); |
| 3299 | 3305 |
| --- src/main.c | |
| +++ src/main.c | |
| @@ -3175,10 +3175,11 @@ | |
| 3175 | ** --ckout-alias NAME Treat URIs of the form /doc/NAME/... as if they were |
| 3176 | ** /doc/ckout/... |
| 3177 | ** --create Create a new REPOSITORY if it does not already exist |
| 3178 | ** --errorlog FILE Append HTTP error messages to FILE |
| 3179 | ** --extroot DIR Document root for the /ext extension mechanism |
| 3180 | ** --files GLOBLIST Comma-separated list of glob patterns for static files |
| 3181 | ** --fossilcmd PATH The pathname of the "fossil" executable on the remote |
| 3182 | ** system when REPOSITORY is remote. |
| 3183 | ** --localauth Enable automatic login for requests from localhost |
| 3184 | ** --localhost Listen on 127.0.0.1 only (always true for "ui") |
| @@ -3250,10 +3251,11 @@ | |
| 3250 | const char *zInitPage = 0; /* Start on this page. --page option */ |
| 3251 | int findServerArg = 2; /* argv index for find_server_repository() */ |
| 3252 | char *zRemote = 0; /* Remote host on which to run "fossil ui" */ |
| 3253 | const char *zJsMode; /* The --jsmode parameter */ |
| 3254 | const char *zFossilCmd =0; /* Name of "fossil" binary on remote system */ |
| 3255 | |
| 3256 | |
| 3257 | #if USE_SEE |
| 3258 | db_setup_for_saved_encryption_key(); |
| 3259 | #endif |
| @@ -3286,13 +3288,17 @@ | |
| 3286 | g.useLocalauth = find_option("localauth", 0, 0)!=0; |
| 3287 | Th_InitTraceLog(); |
| 3288 | zPort = find_option("port", "P", 1); |
| 3289 | isUiCmd = g.argv[1][0]=='u'; |
| 3290 | if( isUiCmd ){ |
| 3291 | zInitPage = find_option("page", "p", 1); |
| 3292 | if( zInitPage && zInitPage[0]=='/' ) zInitPage++; |
| 3293 | zFossilCmd = find_option("fossilcmd", 0, 1); |
| 3294 | } |
| 3295 | zNotFound = find_option("notfound", 0, 1); |
| 3296 | allowRepoList = find_option("repolist",0,0)!=0; |
| 3297 | if( find_option("nocompress",0,0)!=0 ) g.fNoHttpCompress = 1; |
| 3298 | zAltBase = find_option("baseurl", 0, 1); |
| 3299 |
| --- src/main.c | |
| +++ src/main.c | |
| @@ -3175,10 +3175,11 @@ | |
| 3175 | ** --ckout-alias NAME Treat URIs of the form /doc/NAME/... as if they were |
| 3176 | ** /doc/ckout/... |
| 3177 | ** --create Create a new REPOSITORY if it does not already exist |
| 3178 | ** --errorlog FILE Append HTTP error messages to FILE |
| 3179 | ** --extroot DIR Document root for the /ext extension mechanism |
| 3180 | ** --external-baseline DIR External baseline for the initial /ckout page. |
| 3181 | ** --files GLOBLIST Comma-separated list of glob patterns for static files |
| 3182 | ** --fossilcmd PATH The pathname of the "fossil" executable on the remote |
| 3183 | ** system when REPOSITORY is remote. |
| 3184 | ** --localauth Enable automatic login for requests from localhost |
| 3185 | ** --localhost Listen on 127.0.0.1 only (always true for "ui") |
| @@ -3250,10 +3251,11 @@ | |
| 3251 | const char *zInitPage = 0; /* Start on this page. --page option */ |
| 3252 | int findServerArg = 2; /* argv index for find_server_repository() */ |
| 3253 | char *zRemote = 0; /* Remote host on which to run "fossil ui" */ |
| 3254 | const char *zJsMode; /* The --jsmode parameter */ |
| 3255 | const char *zFossilCmd =0; /* Name of "fossil" binary on remote system */ |
| 3256 | const char *zExBase; /* Value for --external-baseline */ |
| 3257 | |
| 3258 | |
| 3259 | #if USE_SEE |
| 3260 | db_setup_for_saved_encryption_key(); |
| 3261 | #endif |
| @@ -3286,13 +3288,17 @@ | |
| 3288 | g.useLocalauth = find_option("localauth", 0, 0)!=0; |
| 3289 | Th_InitTraceLog(); |
| 3290 | zPort = find_option("port", "P", 1); |
| 3291 | isUiCmd = g.argv[1][0]=='u'; |
| 3292 | if( isUiCmd ){ |
| 3293 | zExBase = find_option("external-baseline", 0, 1); |
| 3294 | zInitPage = find_option("page", "p", 1); |
| 3295 | if( zInitPage && zInitPage[0]=='/' ) zInitPage++; |
| 3296 | zFossilCmd = find_option("fossilcmd", 0, 1); |
| 3297 | if( zExBase && zInitPage==0 ){ |
| 3298 | zInitPage = mprintf("ckout?exbase=%T", zExBase); |
| 3299 | } |
| 3300 | } |
| 3301 | zNotFound = find_option("notfound", 0, 1); |
| 3302 | allowRepoList = find_option("repolist",0,0)!=0; |
| 3303 | if( find_option("nocompress",0,0)!=0 ) g.fNoHttpCompress = 1; |
| 3304 | zAltBase = find_option("baseurl", 0, 1); |
| 3305 |