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.

drh 2024-12-15 00:17 trunk merge
Commit 3807891e6278559bc89bd80db7d45fb16c0435da351caf6cf70be88c2cd14e64
2 files changed +199 -58 +6
+199 -58
--- src/info.c
+++ src/info.c
@@ -609,54 +609,21 @@
609609
db_finalize(&q);
610610
style_finish_page();
611611
}
612612
613613
/*
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
619615
*/
620
-void ckout_page(void){
621
- int vid;
622
- char *zHostname;
623
- char *zCwd;
616
+static void ckout_normal_diff(int vid){
624617
int diffType; /* 0: no diff, 1: unified, 2: side-by-side */
625618
DiffConfig DCfg,*pCfg; /* Diff details */
626
- const char *zHome; /* Home directory */
627619
const char *zW; /* The "w" query parameter */
628620
int nChng; /* Number of changes */
629621
Stmt q;
630622
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);
636623
diffType = preferred_diff_type();
637624
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);
658625
nChng = db_int(0, "SELECT count(*) FROM vfile"
659626
" WHERE vid=%d AND (deleted OR chnged OR rid==0)", vid);
660627
if( nChng==0 ){
661628
@ <p>No uncommitted changes</p>
662629
style_finish_page();
@@ -674,11 +641,10 @@
674641
if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){
675642
DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG;
676643
}else{
677644
DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG;
678645
}
679
- @ <hr>
680646
@ <div class="sectionmenu info-changes-menu">
681647
zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
682648
if( diffType!=1 ){
683649
@ %z(chref("button","%R?diff=1%s",zW))Unified&nbsp;Diff</a>
684650
}
@@ -750,12 +716,159 @@
750716
blob_reset(&old);
751717
blob_reset(&new);
752718
}
753719
}
754720
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&nbsp;Diff</a>
749
+ }
750
+ if( diffType!=2 ){
751
+ @ %z(chref("button","%R?diff=2&exbase=%h%s",zExBase,zW))\
752
+ @ Side-by-Side&nbsp;Diff</a>
753
+ }
754
+ if( diffType!=0 ){
755
+ if( *zW ){
756
+ @ %z(chref("button","%R?diff=%d&exbase=%h",diffType,zExBase))\
757
+ @ Show&nbsp;Whitespace&nbsp;Changes</a>
758
+ }else{
759
+ @ %z(chref("button","%R?diff=%d&exbase=%h&w",diffType,zExBase))\
760
+ @ Ignore&nbsp;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);
756811
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
+ }
757870
style_finish_page();
758871
}
759872
760873
/*
761874
** WEBPAGE: vinfo
@@ -2077,10 +2190,16 @@
20772190
** WEBPAGE: jchunk hidden
20782191
** URL: /jchunk/HASH?from=N&to=M
20792192
**
20802193
** Return lines of text from a file as a JSON array - one entry in the
20812194
** 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.
20822201
**
20832202
** **Warning:** This is an internal-use-only interface that is subject to
20842203
** change at any moment. External application should not use this interface
20852204
** since the application will break when this interface changes, and this
20862205
** interface will undoubtedly change.
@@ -2092,10 +2211,11 @@
20922211
** ajax_route_error().
20932212
*/
20942213
void jchunk_page(void){
20952214
int rid = 0;
20962215
const char *zName = PD("name", "");
2216
+ int nName = (int)(strlen(zName)&0x7fffffff);
20972217
int iFrom = atoi(PD("from","0"));
20982218
int iTo = atoi(PD("to","0"));
20992219
int ln;
21002220
int go = 1;
21012221
const char *zSep;
@@ -2112,36 +2232,57 @@
21122232
cgi_check_for_malice();
21132233
if( !g.perm.Read ){
21142234
ajax_route_error(403, "Access requires Read permissions.");
21152235
return;
21162236
}
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
21372237
if( iFrom<1 || iTo<iFrom ){
21382238
ajax_route_error(500, "Invalid line range from=%d, to=%d.",
21392239
iFrom, iTo);
21402240
return;
21412241
}
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
+ }
21432284
g.isConst = 1;
21442285
cgi_set_content_type("application/json");
21452286
ln = 0;
21462287
while( go && ln<iFrom ){
21472288
go = blob_line(&content, &line);
21482289
--- 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&nbsp;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&nbsp;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&nbsp;Diff</a>
749 }
750 if( diffType!=2 ){
751 @ %z(chref("button","%R?diff=2&exbase=%h%s",zExBase,zW))\
752 @ Side-by-Side&nbsp;Diff</a>
753 }
754 if( diffType!=0 ){
755 if( *zW ){
756 @ %z(chref("button","%R?diff=%d&exbase=%h",diffType,zExBase))\
757 @ Show&nbsp;Whitespace&nbsp;Changes</a>
758 }else{
759 @ %z(chref("button","%R?diff=%d&exbase=%h&w",diffType,zExBase))\
760 @ Ignore&nbsp;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 @@
31753175
** --ckout-alias NAME Treat URIs of the form /doc/NAME/... as if they were
31763176
** /doc/ckout/...
31773177
** --create Create a new REPOSITORY if it does not already exist
31783178
** --errorlog FILE Append HTTP error messages to FILE
31793179
** --extroot DIR Document root for the /ext extension mechanism
3180
+** --external-baseline DIR External baseline for the initial /ckout page.
31803181
** --files GLOBLIST Comma-separated list of glob patterns for static files
31813182
** --fossilcmd PATH The pathname of the "fossil" executable on the remote
31823183
** system when REPOSITORY is remote.
31833184
** --localauth Enable automatic login for requests from localhost
31843185
** --localhost Listen on 127.0.0.1 only (always true for "ui")
@@ -3250,10 +3251,11 @@
32503251
const char *zInitPage = 0; /* Start on this page. --page option */
32513252
int findServerArg = 2; /* argv index for find_server_repository() */
32523253
char *zRemote = 0; /* Remote host on which to run "fossil ui" */
32533254
const char *zJsMode; /* The --jsmode parameter */
32543255
const char *zFossilCmd =0; /* Name of "fossil" binary on remote system */
3256
+ const char *zExBase; /* Value for --external-baseline */
32553257
32563258
32573259
#if USE_SEE
32583260
db_setup_for_saved_encryption_key();
32593261
#endif
@@ -3286,13 +3288,17 @@
32863288
g.useLocalauth = find_option("localauth", 0, 0)!=0;
32873289
Th_InitTraceLog();
32883290
zPort = find_option("port", "P", 1);
32893291
isUiCmd = g.argv[1][0]=='u';
32903292
if( isUiCmd ){
3293
+ zExBase = find_option("external-baseline", 0, 1);
32913294
zInitPage = find_option("page", "p", 1);
32923295
if( zInitPage && zInitPage[0]=='/' ) zInitPage++;
32933296
zFossilCmd = find_option("fossilcmd", 0, 1);
3297
+ if( zExBase && zInitPage==0 ){
3298
+ zInitPage = mprintf("ckout?exbase=%T", zExBase);
3299
+ }
32943300
}
32953301
zNotFound = find_option("notfound", 0, 1);
32963302
allowRepoList = find_option("repolist",0,0)!=0;
32973303
if( find_option("nocompress",0,0)!=0 ) g.fNoHttpCompress = 1;
32983304
zAltBase = find_option("baseurl", 0, 1);
32993305
--- 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

Keyboard Shortcuts

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