Fossil SCM

Sync with trunk.

florian 2024-12-17 06:36 diff-keyboard-navigation merge
Commit 361fc76769c0e976629fba0e519e95cef803aa2cc9450faa56bb17a74aab0be5
+2 -2
--- src/checkin.c
+++ src/checkin.c
@@ -1563,17 +1563,17 @@
15631563
break;
15641564
}
15651565
diffFiles[i].nName = strlen(diffFiles[i].zName);
15661566
diffFiles[i].nUsed = 0;
15671567
}
1568
- diff_against_disk(0, &DCfg, diffFiles, &prompt);
1568
+ diff_version_to_checkout(0, &DCfg, diffFiles, &prompt);
15691569
for( i=0; diffFiles[i].zName; ++i ){
15701570
fossil_free(diffFiles[i].zName);
15711571
}
15721572
fossil_free(diffFiles);
15731573
}else{
1574
- diff_against_disk(0, &DCfg, 0, &prompt);
1574
+ diff_version_to_checkout(0, &DCfg, 0, &prompt);
15751575
}
15761576
}
15771577
prompt_for_user_comment(pComment, &prompt);
15781578
blob_reset(&prompt);
15791579
}
15801580
--- src/checkin.c
+++ src/checkin.c
@@ -1563,17 +1563,17 @@
1563 break;
1564 }
1565 diffFiles[i].nName = strlen(diffFiles[i].zName);
1566 diffFiles[i].nUsed = 0;
1567 }
1568 diff_against_disk(0, &DCfg, diffFiles, &prompt);
1569 for( i=0; diffFiles[i].zName; ++i ){
1570 fossil_free(diffFiles[i].zName);
1571 }
1572 fossil_free(diffFiles);
1573 }else{
1574 diff_against_disk(0, &DCfg, 0, &prompt);
1575 }
1576 }
1577 prompt_for_user_comment(pComment, &prompt);
1578 blob_reset(&prompt);
1579 }
1580
--- src/checkin.c
+++ src/checkin.c
@@ -1563,17 +1563,17 @@
1563 break;
1564 }
1565 diffFiles[i].nName = strlen(diffFiles[i].zName);
1566 diffFiles[i].nUsed = 0;
1567 }
1568 diff_version_to_checkout(0, &DCfg, diffFiles, &prompt);
1569 for( i=0; diffFiles[i].zName; ++i ){
1570 fossil_free(diffFiles[i].zName);
1571 }
1572 fossil_free(diffFiles);
1573 }else{
1574 diff_version_to_checkout(0, &DCfg, 0, &prompt);
1575 }
1576 }
1577 prompt_for_user_comment(pComment, &prompt);
1578 blob_reset(&prompt);
1579 }
1580
+5 -1
--- src/diff.tcl
+++ src/diff.tcl
@@ -110,11 +110,15 @@
110110
111111
set fromIndex [lsearch -glob $fossilcmd *-from]
112112
set toIndex [lsearch -glob $fossilcmd *-to]
113113
set branchIndex [lsearch -glob $fossilcmd *-branch]
114114
set checkinIndex [lsearch -glob $fossilcmd *-checkin]
115
- set fA {base check-in}
115
+ if {[string match *?--external-baseline* $fossilcmd]} {
116
+ set fA {external baseline}
117
+ } else {
118
+ set fA {base check-in}
119
+ }
116120
set fB {current check-out}
117121
if {$fromIndex > -1} {set fA [lindex $fossilcmd $fromIndex+1]}
118122
if {$toIndex > -1} {set fB [lindex $fossilcmd $toIndex+1]}
119123
if {$branchIndex > -1} {set fA "branch point"; set fB "leaf of branch '[lindex $fossilcmd $branchIndex+1]'"}
120124
if {$checkinIndex > -1} {set fA "primary parent"; set fB [lindex $fossilcmd $checkinIndex+1]}
121125
--- src/diff.tcl
+++ src/diff.tcl
@@ -110,11 +110,15 @@
110
111 set fromIndex [lsearch -glob $fossilcmd *-from]
112 set toIndex [lsearch -glob $fossilcmd *-to]
113 set branchIndex [lsearch -glob $fossilcmd *-branch]
114 set checkinIndex [lsearch -glob $fossilcmd *-checkin]
115 set fA {base check-in}
 
 
 
 
116 set fB {current check-out}
117 if {$fromIndex > -1} {set fA [lindex $fossilcmd $fromIndex+1]}
118 if {$toIndex > -1} {set fB [lindex $fossilcmd $toIndex+1]}
119 if {$branchIndex > -1} {set fA "branch point"; set fB "leaf of branch '[lindex $fossilcmd $branchIndex+1]'"}
120 if {$checkinIndex > -1} {set fA "primary parent"; set fB [lindex $fossilcmd $checkinIndex+1]}
121
--- src/diff.tcl
+++ src/diff.tcl
@@ -110,11 +110,15 @@
110
111 set fromIndex [lsearch -glob $fossilcmd *-from]
112 set toIndex [lsearch -glob $fossilcmd *-to]
113 set branchIndex [lsearch -glob $fossilcmd *-branch]
114 set checkinIndex [lsearch -glob $fossilcmd *-checkin]
115 if {[string match *?--external-baseline* $fossilcmd]} {
116 set fA {external baseline}
117 } else {
118 set fA {base check-in}
119 }
120 set fB {current check-out}
121 if {$fromIndex > -1} {set fA [lindex $fossilcmd $fromIndex+1]}
122 if {$toIndex > -1} {set fB [lindex $fossilcmd $toIndex+1]}
123 if {$branchIndex > -1} {set fA "branch point"; set fB "leaf of branch '[lindex $fossilcmd $branchIndex+1]'"}
124 if {$checkinIndex > -1} {set fA "primary parent"; set fB [lindex $fossilcmd $checkinIndex+1]}
125
+92 -16
--- src/diffcmd.c
+++ src/diffcmd.c
@@ -784,26 +784,27 @@
784784
blob_reset(&file);
785785
return rc;
786786
}
787787
788788
/*
789
-** Run a diff between the version zFrom and files on disk. zFrom might
790
-** be NULL which means to simply show the difference between the edited
791
-** files on disk and the check-out on which they are based.
789
+** Run a diff between the version zFrom and files on disk in the current
790
+** working checkout. zFrom might be NULL which means to simply show the
791
+** difference between the edited files on disk and the check-out on which
792
+** they are based.
792793
**
793794
** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the
794795
** command zDiffCmd to do the diffing.
795796
**
796797
** When using an external diff program, zBinGlob contains the GLOB patterns
797798
** for file names to treat as binary. If fIncludeBinary is zero, these files
798799
** will be skipped in addition to files that may contain binary content.
799800
*/
800
-void diff_against_disk(
801
+void diff_version_to_checkout(
801802
const char *zFrom, /* Version to difference from */
802803
DiffConfig *pCfg, /* Flags controlling diff output */
803804
FileDirList *pFileDir, /* Which files to diff */
804
- Blob *pOut /* Blob to output diff instead of stdout */
805
+ Blob *pOut /* Blob to output diff instead of stdout */
805806
){
806807
int vid;
807808
Blob sql;
808809
Stmt q;
809810
int asNewFile; /* Treat non-existant files as empty files */
@@ -928,20 +929,20 @@
928929
db_finalize(&q);
929930
db_end_transaction(1); /* ROLLBACK */
930931
}
931932
932933
/*
933
-** Run a diff between the undo buffer and files on disk.
934
+** Run a diff from the undo buffer to files on disk.
934935
**
935936
** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the
936937
** command zDiffCmd to do the diffing.
937938
**
938939
** When using an external diff program, zBinGlob contains the GLOB patterns
939940
** for file names to treat as binary. If fIncludeBinary is zero, these files
940941
** will be skipped in addition to files that may contain binary content.
941942
*/
942
-static void diff_against_undo(
943
+static void diff_undo_to_checkout(
943944
DiffConfig *pCfg, /* Flags controlling diff output */
944945
FileDirList *pFileDir /* List of files and directories to diff */
945946
){
946947
Stmt q;
947948
Blob content;
@@ -1088,10 +1089,67 @@
10881089
}
10891090
}
10901091
manifest_destroy(pFrom);
10911092
manifest_destroy(pTo);
10921093
}
1094
+
1095
+/*
1096
+** Compute the difference from an external tree of files to the current
1097
+** working checkout with its edits.
1098
+**
1099
+** To put it another way: Every managed file in the current working
1100
+** checkout is compared to the file with same name under zExternBase. The
1101
+** zExternBase files are on the left and the files in the current working
1102
+** directory are on the right.
1103
+*/
1104
+void diff_externbase_to_checkout(
1105
+ const char *zExternBase, /* Remote tree to use as the baseline */
1106
+ DiffConfig *pCfg, /* Diff settings */
1107
+ FileDirList *pFileDir /* Only look at these files */
1108
+){
1109
+ int vid;
1110
+ Stmt q;
1111
+
1112
+ vid = db_lget_int("checkout",0);
1113
+ if( file_isdir(zExternBase, ExtFILE)!=1 ){
1114
+ fossil_fatal("\"%s\" is not a directory", zExternBase);
1115
+ }
1116
+ db_prepare(&q,
1117
+ "SELECT pathname FROM vfile WHERE vid=%d ORDER BY pathname",
1118
+ vid
1119
+ );
1120
+ while( db_step(&q)==SQLITE_ROW ){
1121
+ const char *zFile; /* Name of file in the repository */
1122
+ char *zLhs; /* Full name of left-hand side file */
1123
+ char *zRhs; /* Full name of right-hand side file */
1124
+ Blob rhs; /* Full text of RHS */
1125
+ Blob lhs; /* Full text of LHS */
1126
+
1127
+ zFile = db_column_text(&q,0);
1128
+ if( !file_dir_match(pFileDir, zFile) ) continue;
1129
+ zLhs = mprintf("%s/%s", zExternBase, zFile);
1130
+ zRhs = mprintf("%s%s", g.zLocalRoot, zFile);
1131
+ if( file_size(zLhs, ExtFILE)<0 ){
1132
+ blob_zero(&lhs);
1133
+ }else{
1134
+ blob_read_from_file(&lhs, zLhs, ExtFILE);
1135
+ }
1136
+ blob_read_from_file(&rhs, zRhs, ExtFILE);
1137
+ if( blob_size(&lhs)!=blob_size(&rhs)
1138
+ || memcmp(blob_buffer(&lhs), blob_buffer(&rhs), blob_size(&lhs))!=0
1139
+ ){
1140
+ diff_print_index(zFile, pCfg, 0);
1141
+ diff_file_mem(&lhs, &rhs, zFile, pCfg);
1142
+ }
1143
+ blob_reset(&lhs);
1144
+ blob_reset(&rhs);
1145
+ fossil_free(zLhs);
1146
+ fossil_free(zRhs);
1147
+ }
1148
+ db_finalize(&q);
1149
+}
1150
+
10931151
10941152
/*
10951153
** Return the name of the external diff command, or return NULL if
10961154
** no external diff command is defined.
10971155
*/
@@ -1224,10 +1282,14 @@
12241282
** option specifies the check-in from which the second version of the file
12251283
** or files is taken. If there is no "--to" option then the (possibly edited)
12261284
** files in the current check-out are used. The "--checkin VERSION" option
12271285
** shows the changes made by check-in VERSION relative to its primary parent.
12281286
** The "--branch BRANCHNAME" shows all the changes on the branch BRANCHNAME.
1287
+**
1288
+** With the "--from VERSION" option, if VERSION is actually a directory name
1289
+** (not a tag or check-in hash) then the files under that directory are used
1290
+** as the baseline for the diff.
12291291
**
12301292
** The "-i" command-line option forces the use of Fossil's own internal
12311293
** diff logic rather than any external diff program that might be configured
12321294
** using the "setting" command. If no external diff program is configured,
12331295
** then the "-i" option is a no-op. The "-i" option converts "gdiff" into
@@ -1256,11 +1318,13 @@
12561318
** with negative N meaning show all content
12571319
** --dark Use dark mode for the Tcl/Tk-based GUI and HTML
12581320
** --diff-binary BOOL Include binary files with external commands
12591321
** --exec-abs-paths Force absolute path names on external commands
12601322
** --exec-rel-paths Force relative path names on external commands
1261
-** -r|--from VERSION Select VERSION as source for the diff
1323
+** -r|--from VERSION Use VERSION as the baseline for the diff, or
1324
+** if VERSION is a directory name, use files in
1325
+** that directory as the baseline.
12621326
** -w|--ignore-all-space Ignore white space when comparing lines
12631327
** -i|--internal Use internal diff logic
12641328
** --invert Invert the diff
12651329
** --json Output formatted as JSON
12661330
** -n|--linenum Show line numbers
@@ -1270,11 +1334,11 @@
12701334
** --strip-trailing-cr Strip trailing CR
12711335
** --tcl Tcl-formatted output used internally by --tk
12721336
** --tclsh PATH Tcl/Tk shell used for --tk (default: "tclsh")
12731337
** --tk Launch a Tcl/Tk GUI for display
12741338
** --to VERSION Select VERSION as target for the diff
1275
-** --undo Diff against the "undo" buffer
1339
+** --undo Use the undo buffer as the baseline
12761340
** --unified Unified diff
12771341
** -v|--verbose Output complete text of added or deleted files
12781342
** -h|--versions Show compared versions in the diff header
12791343
** --webpage Format output as a stand-alone HTML webpage
12801344
** -W|--width N Width of lines in side-by-side diff
@@ -1287,10 +1351,11 @@
12871351
const char *zCheckin; /* Check-in version number */
12881352
const char *zBranch; /* Branch to diff */
12891353
int againstUndo = 0; /* Diff against files in the undo buffer */
12901354
FileDirList *pFileDir = 0; /* Restrict the diff to these files */
12911355
DiffConfig DCfg; /* Diff configuration object */
1356
+ int bFromIsDir = 0; /* True if zFrom is a directory name */
12921357
12931358
if( find_option("tk",0,0)!=0 || has_option("tclsh") ){
12941359
diff_tk("diff", 2);
12951360
return;
12961361
}
@@ -1298,11 +1363,11 @@
12981363
zFrom = find_option("from", "r", 1);
12991364
zTo = find_option("to", 0, 1);
13001365
zCheckin = find_option("checkin", "ci", 1);
13011366
zBranch = find_option("branch", 0, 1);
13021367
againstUndo = find_option("undo",0,0)!=0;
1303
- if( againstUndo && ( zFrom!=0 || zTo!=0 || zCheckin!=0 || zBranch!=0) ){
1368
+ if( againstUndo && (zFrom!=0 || zTo!=0 || zCheckin!=0 || zBranch!=0) ){
13041369
fossil_fatal("cannot use --undo together with --from, --to, --checkin,"
13051370
" or --branch");
13061371
}
13071372
if( zBranch ){
13081373
if( zTo || zFrom || zCheckin ){
@@ -1309,15 +1374,13 @@
13091374
fossil_fatal("cannot use --from, --to, or --checkin with --branch");
13101375
}
13111376
zTo = zBranch;
13121377
zFrom = mprintf("root:%s", zBranch);
13131378
}
1314
- if( zCheckin!=0 && ( zFrom!=0 || zTo!=0 ) ){
1379
+ if( zCheckin!=0 && (zFrom!=0 || zTo!=0) ){
13151380
fossil_fatal("cannot use --checkin together with --from or --to");
13161381
}
1317
- diff_options(&DCfg, isGDiff, 0);
1318
- determine_exec_relative_option(1);
13191382
if( 0==zCheckin ){
13201383
if( zTo==0 || againstUndo ){
13211384
db_must_be_within_tree();
13221385
}else if( zFrom==0 ){
13231386
fossil_fatal("must use --from if --to is present");
@@ -1325,10 +1388,21 @@
13251388
db_find_and_open_repository(0, 0);
13261389
}
13271390
}else{
13281391
db_find_and_open_repository(0, 0);
13291392
}
1393
+ determine_exec_relative_option(1);
1394
+ if( zFrom!=file_tail(zFrom)
1395
+ && file_isdir(zFrom, ExtFILE)==1
1396
+ && !db_exists("SELECT 1 FROM tag WHERE tagname='sym-%q'", zFrom)
1397
+ ){
1398
+ bFromIsDir = 1;
1399
+ if( zTo ){
1400
+ fossil_fatal("cannot use --to together with \"--from PATH\"");
1401
+ }
1402
+ }
1403
+ diff_options(&DCfg, isGDiff, 0);
13301404
verify_all_options();
13311405
g.diffCnt[0] = g.diffCnt[1] = g.diffCnt[2] = 0;
13321406
if( g.argc>=3 ){
13331407
int i;
13341408
Blob fname;
@@ -1357,18 +1431,20 @@
13571431
if( zFrom==0 ){
13581432
fossil_fatal("check-in %s has no parent", zTo);
13591433
}
13601434
}
13611435
diff_begin(&DCfg);
1362
- if( againstUndo ){
1436
+ if( bFromIsDir ){
1437
+ diff_externbase_to_checkout(zFrom, &DCfg, pFileDir);
1438
+ }else if( againstUndo ){
13631439
if( db_lget_int("undo_available",0)==0 ){
13641440
fossil_print("No undo or redo is available\n");
13651441
return;
13661442
}
1367
- diff_against_undo(&DCfg, pFileDir);
1443
+ diff_undo_to_checkout(&DCfg, pFileDir);
13681444
}else if( zTo==0 ){
1369
- diff_against_disk(zFrom, &DCfg, pFileDir, 0);
1445
+ diff_version_to_checkout(zFrom, &DCfg, pFileDir, 0);
13701446
}else{
13711447
diff_two_versions(zFrom, zTo, &DCfg, pFileDir);
13721448
}
13731449
if( pFileDir ){
13741450
int i;
13751451
--- src/diffcmd.c
+++ src/diffcmd.c
@@ -784,26 +784,27 @@
784 blob_reset(&file);
785 return rc;
786 }
787
788 /*
789 ** Run a diff between the version zFrom and files on disk. zFrom might
790 ** be NULL which means to simply show the difference between the edited
791 ** files on disk and the check-out on which they are based.
 
792 **
793 ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the
794 ** command zDiffCmd to do the diffing.
795 **
796 ** When using an external diff program, zBinGlob contains the GLOB patterns
797 ** for file names to treat as binary. If fIncludeBinary is zero, these files
798 ** will be skipped in addition to files that may contain binary content.
799 */
800 void diff_against_disk(
801 const char *zFrom, /* Version to difference from */
802 DiffConfig *pCfg, /* Flags controlling diff output */
803 FileDirList *pFileDir, /* Which files to diff */
804 Blob *pOut /* Blob to output diff instead of stdout */
805 ){
806 int vid;
807 Blob sql;
808 Stmt q;
809 int asNewFile; /* Treat non-existant files as empty files */
@@ -928,20 +929,20 @@
928 db_finalize(&q);
929 db_end_transaction(1); /* ROLLBACK */
930 }
931
932 /*
933 ** Run a diff between the undo buffer and files on disk.
934 **
935 ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the
936 ** command zDiffCmd to do the diffing.
937 **
938 ** When using an external diff program, zBinGlob contains the GLOB patterns
939 ** for file names to treat as binary. If fIncludeBinary is zero, these files
940 ** will be skipped in addition to files that may contain binary content.
941 */
942 static void diff_against_undo(
943 DiffConfig *pCfg, /* Flags controlling diff output */
944 FileDirList *pFileDir /* List of files and directories to diff */
945 ){
946 Stmt q;
947 Blob content;
@@ -1088,10 +1089,67 @@
1088 }
1089 }
1090 manifest_destroy(pFrom);
1091 manifest_destroy(pTo);
1092 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1093
1094 /*
1095 ** Return the name of the external diff command, or return NULL if
1096 ** no external diff command is defined.
1097 */
@@ -1224,10 +1282,14 @@
1224 ** option specifies the check-in from which the second version of the file
1225 ** or files is taken. If there is no "--to" option then the (possibly edited)
1226 ** files in the current check-out are used. The "--checkin VERSION" option
1227 ** shows the changes made by check-in VERSION relative to its primary parent.
1228 ** The "--branch BRANCHNAME" shows all the changes on the branch BRANCHNAME.
 
 
 
 
1229 **
1230 ** The "-i" command-line option forces the use of Fossil's own internal
1231 ** diff logic rather than any external diff program that might be configured
1232 ** using the "setting" command. If no external diff program is configured,
1233 ** then the "-i" option is a no-op. The "-i" option converts "gdiff" into
@@ -1256,11 +1318,13 @@
1256 ** with negative N meaning show all content
1257 ** --dark Use dark mode for the Tcl/Tk-based GUI and HTML
1258 ** --diff-binary BOOL Include binary files with external commands
1259 ** --exec-abs-paths Force absolute path names on external commands
1260 ** --exec-rel-paths Force relative path names on external commands
1261 ** -r|--from VERSION Select VERSION as source for the diff
 
 
1262 ** -w|--ignore-all-space Ignore white space when comparing lines
1263 ** -i|--internal Use internal diff logic
1264 ** --invert Invert the diff
1265 ** --json Output formatted as JSON
1266 ** -n|--linenum Show line numbers
@@ -1270,11 +1334,11 @@
1270 ** --strip-trailing-cr Strip trailing CR
1271 ** --tcl Tcl-formatted output used internally by --tk
1272 ** --tclsh PATH Tcl/Tk shell used for --tk (default: "tclsh")
1273 ** --tk Launch a Tcl/Tk GUI for display
1274 ** --to VERSION Select VERSION as target for the diff
1275 ** --undo Diff against the "undo" buffer
1276 ** --unified Unified diff
1277 ** -v|--verbose Output complete text of added or deleted files
1278 ** -h|--versions Show compared versions in the diff header
1279 ** --webpage Format output as a stand-alone HTML webpage
1280 ** -W|--width N Width of lines in side-by-side diff
@@ -1287,10 +1351,11 @@
1287 const char *zCheckin; /* Check-in version number */
1288 const char *zBranch; /* Branch to diff */
1289 int againstUndo = 0; /* Diff against files in the undo buffer */
1290 FileDirList *pFileDir = 0; /* Restrict the diff to these files */
1291 DiffConfig DCfg; /* Diff configuration object */
 
1292
1293 if( find_option("tk",0,0)!=0 || has_option("tclsh") ){
1294 diff_tk("diff", 2);
1295 return;
1296 }
@@ -1298,11 +1363,11 @@
1298 zFrom = find_option("from", "r", 1);
1299 zTo = find_option("to", 0, 1);
1300 zCheckin = find_option("checkin", "ci", 1);
1301 zBranch = find_option("branch", 0, 1);
1302 againstUndo = find_option("undo",0,0)!=0;
1303 if( againstUndo && ( zFrom!=0 || zTo!=0 || zCheckin!=0 || zBranch!=0) ){
1304 fossil_fatal("cannot use --undo together with --from, --to, --checkin,"
1305 " or --branch");
1306 }
1307 if( zBranch ){
1308 if( zTo || zFrom || zCheckin ){
@@ -1309,15 +1374,13 @@
1309 fossil_fatal("cannot use --from, --to, or --checkin with --branch");
1310 }
1311 zTo = zBranch;
1312 zFrom = mprintf("root:%s", zBranch);
1313 }
1314 if( zCheckin!=0 && ( zFrom!=0 || zTo!=0 ) ){
1315 fossil_fatal("cannot use --checkin together with --from or --to");
1316 }
1317 diff_options(&DCfg, isGDiff, 0);
1318 determine_exec_relative_option(1);
1319 if( 0==zCheckin ){
1320 if( zTo==0 || againstUndo ){
1321 db_must_be_within_tree();
1322 }else if( zFrom==0 ){
1323 fossil_fatal("must use --from if --to is present");
@@ -1325,10 +1388,21 @@
1325 db_find_and_open_repository(0, 0);
1326 }
1327 }else{
1328 db_find_and_open_repository(0, 0);
1329 }
 
 
 
 
 
 
 
 
 
 
 
1330 verify_all_options();
1331 g.diffCnt[0] = g.diffCnt[1] = g.diffCnt[2] = 0;
1332 if( g.argc>=3 ){
1333 int i;
1334 Blob fname;
@@ -1357,18 +1431,20 @@
1357 if( zFrom==0 ){
1358 fossil_fatal("check-in %s has no parent", zTo);
1359 }
1360 }
1361 diff_begin(&DCfg);
1362 if( againstUndo ){
 
 
1363 if( db_lget_int("undo_available",0)==0 ){
1364 fossil_print("No undo or redo is available\n");
1365 return;
1366 }
1367 diff_against_undo(&DCfg, pFileDir);
1368 }else if( zTo==0 ){
1369 diff_against_disk(zFrom, &DCfg, pFileDir, 0);
1370 }else{
1371 diff_two_versions(zFrom, zTo, &DCfg, pFileDir);
1372 }
1373 if( pFileDir ){
1374 int i;
1375
--- src/diffcmd.c
+++ src/diffcmd.c
@@ -784,26 +784,27 @@
784 blob_reset(&file);
785 return rc;
786 }
787
788 /*
789 ** Run a diff between the version zFrom and files on disk in the current
790 ** working checkout. zFrom might be NULL which means to simply show the
791 ** difference between the edited files on disk and the check-out on which
792 ** they are based.
793 **
794 ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the
795 ** command zDiffCmd to do the diffing.
796 **
797 ** When using an external diff program, zBinGlob contains the GLOB patterns
798 ** for file names to treat as binary. If fIncludeBinary is zero, these files
799 ** will be skipped in addition to files that may contain binary content.
800 */
801 void diff_version_to_checkout(
802 const char *zFrom, /* Version to difference from */
803 DiffConfig *pCfg, /* Flags controlling diff output */
804 FileDirList *pFileDir, /* Which files to diff */
805 Blob *pOut /* Blob to output diff instead of stdout */
806 ){
807 int vid;
808 Blob sql;
809 Stmt q;
810 int asNewFile; /* Treat non-existant files as empty files */
@@ -928,20 +929,20 @@
929 db_finalize(&q);
930 db_end_transaction(1); /* ROLLBACK */
931 }
932
933 /*
934 ** Run a diff from the undo buffer to files on disk.
935 **
936 ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the
937 ** command zDiffCmd to do the diffing.
938 **
939 ** When using an external diff program, zBinGlob contains the GLOB patterns
940 ** for file names to treat as binary. If fIncludeBinary is zero, these files
941 ** will be skipped in addition to files that may contain binary content.
942 */
943 static void diff_undo_to_checkout(
944 DiffConfig *pCfg, /* Flags controlling diff output */
945 FileDirList *pFileDir /* List of files and directories to diff */
946 ){
947 Stmt q;
948 Blob content;
@@ -1088,10 +1089,67 @@
1089 }
1090 }
1091 manifest_destroy(pFrom);
1092 manifest_destroy(pTo);
1093 }
1094
1095 /*
1096 ** Compute the difference from an external tree of files to the current
1097 ** working checkout with its edits.
1098 **
1099 ** To put it another way: Every managed file in the current working
1100 ** checkout is compared to the file with same name under zExternBase. The
1101 ** zExternBase files are on the left and the files in the current working
1102 ** directory are on the right.
1103 */
1104 void diff_externbase_to_checkout(
1105 const char *zExternBase, /* Remote tree to use as the baseline */
1106 DiffConfig *pCfg, /* Diff settings */
1107 FileDirList *pFileDir /* Only look at these files */
1108 ){
1109 int vid;
1110 Stmt q;
1111
1112 vid = db_lget_int("checkout",0);
1113 if( file_isdir(zExternBase, ExtFILE)!=1 ){
1114 fossil_fatal("\"%s\" is not a directory", zExternBase);
1115 }
1116 db_prepare(&q,
1117 "SELECT pathname FROM vfile WHERE vid=%d ORDER BY pathname",
1118 vid
1119 );
1120 while( db_step(&q)==SQLITE_ROW ){
1121 const char *zFile; /* Name of file in the repository */
1122 char *zLhs; /* Full name of left-hand side file */
1123 char *zRhs; /* Full name of right-hand side file */
1124 Blob rhs; /* Full text of RHS */
1125 Blob lhs; /* Full text of LHS */
1126
1127 zFile = db_column_text(&q,0);
1128 if( !file_dir_match(pFileDir, zFile) ) continue;
1129 zLhs = mprintf("%s/%s", zExternBase, zFile);
1130 zRhs = mprintf("%s%s", g.zLocalRoot, zFile);
1131 if( file_size(zLhs, ExtFILE)<0 ){
1132 blob_zero(&lhs);
1133 }else{
1134 blob_read_from_file(&lhs, zLhs, ExtFILE);
1135 }
1136 blob_read_from_file(&rhs, zRhs, ExtFILE);
1137 if( blob_size(&lhs)!=blob_size(&rhs)
1138 || memcmp(blob_buffer(&lhs), blob_buffer(&rhs), blob_size(&lhs))!=0
1139 ){
1140 diff_print_index(zFile, pCfg, 0);
1141 diff_file_mem(&lhs, &rhs, zFile, pCfg);
1142 }
1143 blob_reset(&lhs);
1144 blob_reset(&rhs);
1145 fossil_free(zLhs);
1146 fossil_free(zRhs);
1147 }
1148 db_finalize(&q);
1149 }
1150
1151
1152 /*
1153 ** Return the name of the external diff command, or return NULL if
1154 ** no external diff command is defined.
1155 */
@@ -1224,10 +1282,14 @@
1282 ** option specifies the check-in from which the second version of the file
1283 ** or files is taken. If there is no "--to" option then the (possibly edited)
1284 ** files in the current check-out are used. The "--checkin VERSION" option
1285 ** shows the changes made by check-in VERSION relative to its primary parent.
1286 ** The "--branch BRANCHNAME" shows all the changes on the branch BRANCHNAME.
1287 **
1288 ** With the "--from VERSION" option, if VERSION is actually a directory name
1289 ** (not a tag or check-in hash) then the files under that directory are used
1290 ** as the baseline for the diff.
1291 **
1292 ** The "-i" command-line option forces the use of Fossil's own internal
1293 ** diff logic rather than any external diff program that might be configured
1294 ** using the "setting" command. If no external diff program is configured,
1295 ** then the "-i" option is a no-op. The "-i" option converts "gdiff" into
@@ -1256,11 +1318,13 @@
1318 ** with negative N meaning show all content
1319 ** --dark Use dark mode for the Tcl/Tk-based GUI and HTML
1320 ** --diff-binary BOOL Include binary files with external commands
1321 ** --exec-abs-paths Force absolute path names on external commands
1322 ** --exec-rel-paths Force relative path names on external commands
1323 ** -r|--from VERSION Use VERSION as the baseline for the diff, or
1324 ** if VERSION is a directory name, use files in
1325 ** that directory as the baseline.
1326 ** -w|--ignore-all-space Ignore white space when comparing lines
1327 ** -i|--internal Use internal diff logic
1328 ** --invert Invert the diff
1329 ** --json Output formatted as JSON
1330 ** -n|--linenum Show line numbers
@@ -1270,11 +1334,11 @@
1334 ** --strip-trailing-cr Strip trailing CR
1335 ** --tcl Tcl-formatted output used internally by --tk
1336 ** --tclsh PATH Tcl/Tk shell used for --tk (default: "tclsh")
1337 ** --tk Launch a Tcl/Tk GUI for display
1338 ** --to VERSION Select VERSION as target for the diff
1339 ** --undo Use the undo buffer as the baseline
1340 ** --unified Unified diff
1341 ** -v|--verbose Output complete text of added or deleted files
1342 ** -h|--versions Show compared versions in the diff header
1343 ** --webpage Format output as a stand-alone HTML webpage
1344 ** -W|--width N Width of lines in side-by-side diff
@@ -1287,10 +1351,11 @@
1351 const char *zCheckin; /* Check-in version number */
1352 const char *zBranch; /* Branch to diff */
1353 int againstUndo = 0; /* Diff against files in the undo buffer */
1354 FileDirList *pFileDir = 0; /* Restrict the diff to these files */
1355 DiffConfig DCfg; /* Diff configuration object */
1356 int bFromIsDir = 0; /* True if zFrom is a directory name */
1357
1358 if( find_option("tk",0,0)!=0 || has_option("tclsh") ){
1359 diff_tk("diff", 2);
1360 return;
1361 }
@@ -1298,11 +1363,11 @@
1363 zFrom = find_option("from", "r", 1);
1364 zTo = find_option("to", 0, 1);
1365 zCheckin = find_option("checkin", "ci", 1);
1366 zBranch = find_option("branch", 0, 1);
1367 againstUndo = find_option("undo",0,0)!=0;
1368 if( againstUndo && (zFrom!=0 || zTo!=0 || zCheckin!=0 || zBranch!=0) ){
1369 fossil_fatal("cannot use --undo together with --from, --to, --checkin,"
1370 " or --branch");
1371 }
1372 if( zBranch ){
1373 if( zTo || zFrom || zCheckin ){
@@ -1309,15 +1374,13 @@
1374 fossil_fatal("cannot use --from, --to, or --checkin with --branch");
1375 }
1376 zTo = zBranch;
1377 zFrom = mprintf("root:%s", zBranch);
1378 }
1379 if( zCheckin!=0 && (zFrom!=0 || zTo!=0) ){
1380 fossil_fatal("cannot use --checkin together with --from or --to");
1381 }
 
 
1382 if( 0==zCheckin ){
1383 if( zTo==0 || againstUndo ){
1384 db_must_be_within_tree();
1385 }else if( zFrom==0 ){
1386 fossil_fatal("must use --from if --to is present");
@@ -1325,10 +1388,21 @@
1388 db_find_and_open_repository(0, 0);
1389 }
1390 }else{
1391 db_find_and_open_repository(0, 0);
1392 }
1393 determine_exec_relative_option(1);
1394 if( zFrom!=file_tail(zFrom)
1395 && file_isdir(zFrom, ExtFILE)==1
1396 && !db_exists("SELECT 1 FROM tag WHERE tagname='sym-%q'", zFrom)
1397 ){
1398 bFromIsDir = 1;
1399 if( zTo ){
1400 fossil_fatal("cannot use --to together with \"--from PATH\"");
1401 }
1402 }
1403 diff_options(&DCfg, isGDiff, 0);
1404 verify_all_options();
1405 g.diffCnt[0] = g.diffCnt[1] = g.diffCnt[2] = 0;
1406 if( g.argc>=3 ){
1407 int i;
1408 Blob fname;
@@ -1357,18 +1431,20 @@
1431 if( zFrom==0 ){
1432 fossil_fatal("check-in %s has no parent", zTo);
1433 }
1434 }
1435 diff_begin(&DCfg);
1436 if( bFromIsDir ){
1437 diff_externbase_to_checkout(zFrom, &DCfg, pFileDir);
1438 }else if( againstUndo ){
1439 if( db_lget_int("undo_available",0)==0 ){
1440 fossil_print("No undo or redo is available\n");
1441 return;
1442 }
1443 diff_undo_to_checkout(&DCfg, pFileDir);
1444 }else if( zTo==0 ){
1445 diff_version_to_checkout(zFrom, &DCfg, pFileDir, 0);
1446 }else{
1447 diff_two_versions(zFrom, zTo, &DCfg, pFileDir);
1448 }
1449 if( pFileDir ){
1450 int i;
1451
+24 -13
--- src/fossil.diff.js
+++ src/fossil.diff.js
@@ -28,48 +28,59 @@
2828
window.fossil.onPageLoad(function(){
2929
/**
3030
Adds toggle checkboxes to each file entry in the diff views for
3131
/info and similar pages.
3232
*/
33
+ if( !window.fossil.page.diffControlContainer ){
34
+ return;
35
+ }
3336
const D = window.fossil.dom;
34
- const allToggles = [/*collection of all diff-toggle checkboxes */];
37
+ const allToggles = [/*collection of all diff-toggle checkboxes*/];
38
+ let checkedCount =
39
+ 0 /* When showing more than one diff, keep track of how many
40
+ "show/hide" checkboxes are are checked so we can update the
41
+ "show/hide all" label dynamically. */;
42
+ let btnAll /* UI control to show/hide all diffs */;
43
+ /* Install a diff-toggle button for the given diff table element. */
3544
const addToggle = function(diffElem){
3645
const sib = diffElem.previousElementSibling,
3746
ckbox = sib ? D.addClass(D.checkbox(true), 'diff-toggle') : 0;
3847
if(!sib) return;
3948
const lblToggle = D.label();
4049
D.append(lblToggle, ckbox, D.text(" show/hide "));
4150
const wrapper = D.append(D.span(), lblToggle);
4251
allToggles.push(ckbox);
52
+ ++checkedCount;
4353
D.append(sib, D.append(wrapper, lblToggle));
4454
ckbox.addEventListener('change', function(){
4555
diffElem.classList[this.checked ? 'remove' : 'add']('hidden');
56
+ if(btnAll){
57
+ checkedCount += (this.checked ? 1 : -1);
58
+ btnAll.innerText = (checkedCount < allToggles.length)
59
+ ? "Show diffs" : "Hide diffs";
60
+ }
4661
}, false);
4762
};
4863
if( !document.querySelector('body.fdiff') ){
4964
/* Don't show the diff toggle button for /fdiff because it only
5065
has a single file to show (and also a different DOM layout). */
5166
document.querySelectorAll('table.diff').forEach(addToggle);
5267
}
68
+ /**
69
+ Set up a "toggle all diffs" button which toggles all of the
70
+ above-installed checkboxes, but only if more than one diff is
71
+ rendered.
72
+ */
5373
const icm = allToggles.length>1 ? window.fossil.page.diffControlContainer : 0;
5474
if(icm) {
55
- const btnAll = D.addClass(D.a("#", "Show/Hide"), "button");
75
+ btnAll = D.addClass(D.a("#", "Hide diffs"), "button");
5676
D.append( icm, btnAll );
5777
btnAll.addEventListener('click', function(ev){
5878
ev.preventDefault();
5979
ev.stopPropagation();
60
- /* Figure out whether we want to show all or hide all: if any diffs are
61
- toggled off, show all, else hide all. */
62
- let show = false;
63
- let ckbox;
64
- for( ckbox of allToggles ){
65
- if( !ckbox.checked ){
66
- show = true;
67
- break;
68
- }
69
- }
70
- for( ckbox of allToggles ){
80
+ const show = checkedCount < allToggles.length;
81
+ for( const ckbox of allToggles ){
7182
/* Toggle all entries to match this new state. We use click()
7283
instead of ckbox.checked=... so that the on-change event handler
7384
fires. */
7485
if(ckbox.checked!==show) ckbox.click();
7586
}
7687
--- src/fossil.diff.js
+++ src/fossil.diff.js
@@ -28,48 +28,59 @@
28 window.fossil.onPageLoad(function(){
29 /**
30 Adds toggle checkboxes to each file entry in the diff views for
31 /info and similar pages.
32 */
 
 
 
33 const D = window.fossil.dom;
34 const allToggles = [/*collection of all diff-toggle checkboxes */];
 
 
 
 
 
 
35 const addToggle = function(diffElem){
36 const sib = diffElem.previousElementSibling,
37 ckbox = sib ? D.addClass(D.checkbox(true), 'diff-toggle') : 0;
38 if(!sib) return;
39 const lblToggle = D.label();
40 D.append(lblToggle, ckbox, D.text(" show/hide "));
41 const wrapper = D.append(D.span(), lblToggle);
42 allToggles.push(ckbox);
 
43 D.append(sib, D.append(wrapper, lblToggle));
44 ckbox.addEventListener('change', function(){
45 diffElem.classList[this.checked ? 'remove' : 'add']('hidden');
 
 
 
 
 
46 }, false);
47 };
48 if( !document.querySelector('body.fdiff') ){
49 /* Don't show the diff toggle button for /fdiff because it only
50 has a single file to show (and also a different DOM layout). */
51 document.querySelectorAll('table.diff').forEach(addToggle);
52 }
 
 
 
 
 
53 const icm = allToggles.length>1 ? window.fossil.page.diffControlContainer : 0;
54 if(icm) {
55 const btnAll = D.addClass(D.a("#", "Show/Hide"), "button");
56 D.append( icm, btnAll );
57 btnAll.addEventListener('click', function(ev){
58 ev.preventDefault();
59 ev.stopPropagation();
60 /* Figure out whether we want to show all or hide all: if any diffs are
61 toggled off, show all, else hide all. */
62 let show = false;
63 let ckbox;
64 for( ckbox of allToggles ){
65 if( !ckbox.checked ){
66 show = true;
67 break;
68 }
69 }
70 for( ckbox of allToggles ){
71 /* Toggle all entries to match this new state. We use click()
72 instead of ckbox.checked=... so that the on-change event handler
73 fires. */
74 if(ckbox.checked!==show) ckbox.click();
75 }
76
--- src/fossil.diff.js
+++ src/fossil.diff.js
@@ -28,48 +28,59 @@
28 window.fossil.onPageLoad(function(){
29 /**
30 Adds toggle checkboxes to each file entry in the diff views for
31 /info and similar pages.
32 */
33 if( !window.fossil.page.diffControlContainer ){
34 return;
35 }
36 const D = window.fossil.dom;
37 const allToggles = [/*collection of all diff-toggle checkboxes*/];
38 let checkedCount =
39 0 /* When showing more than one diff, keep track of how many
40 "show/hide" checkboxes are are checked so we can update the
41 "show/hide all" label dynamically. */;
42 let btnAll /* UI control to show/hide all diffs */;
43 /* Install a diff-toggle button for the given diff table element. */
44 const addToggle = function(diffElem){
45 const sib = diffElem.previousElementSibling,
46 ckbox = sib ? D.addClass(D.checkbox(true), 'diff-toggle') : 0;
47 if(!sib) return;
48 const lblToggle = D.label();
49 D.append(lblToggle, ckbox, D.text(" show/hide "));
50 const wrapper = D.append(D.span(), lblToggle);
51 allToggles.push(ckbox);
52 ++checkedCount;
53 D.append(sib, D.append(wrapper, lblToggle));
54 ckbox.addEventListener('change', function(){
55 diffElem.classList[this.checked ? 'remove' : 'add']('hidden');
56 if(btnAll){
57 checkedCount += (this.checked ? 1 : -1);
58 btnAll.innerText = (checkedCount < allToggles.length)
59 ? "Show diffs" : "Hide diffs";
60 }
61 }, false);
62 };
63 if( !document.querySelector('body.fdiff') ){
64 /* Don't show the diff toggle button for /fdiff because it only
65 has a single file to show (and also a different DOM layout). */
66 document.querySelectorAll('table.diff').forEach(addToggle);
67 }
68 /**
69 Set up a "toggle all diffs" button which toggles all of the
70 above-installed checkboxes, but only if more than one diff is
71 rendered.
72 */
73 const icm = allToggles.length>1 ? window.fossil.page.diffControlContainer : 0;
74 if(icm) {
75 btnAll = D.addClass(D.a("#", "Hide diffs"), "button");
76 D.append( icm, btnAll );
77 btnAll.addEventListener('click', function(ev){
78 ev.preventDefault();
79 ev.stopPropagation();
80 const show = checkedCount < allToggles.length;
81 for( const ckbox of allToggles ){
 
 
 
 
 
 
 
 
 
82 /* Toggle all entries to match this new state. We use click()
83 instead of ckbox.checked=... so that the on-change event handler
84 fires. */
85 if(ckbox.checked!==show) ckbox.click();
86 }
87
+24 -13
--- src/fossil.diff.js
+++ src/fossil.diff.js
@@ -28,48 +28,59 @@
2828
window.fossil.onPageLoad(function(){
2929
/**
3030
Adds toggle checkboxes to each file entry in the diff views for
3131
/info and similar pages.
3232
*/
33
+ if( !window.fossil.page.diffControlContainer ){
34
+ return;
35
+ }
3336
const D = window.fossil.dom;
34
- const allToggles = [/*collection of all diff-toggle checkboxes */];
37
+ const allToggles = [/*collection of all diff-toggle checkboxes*/];
38
+ let checkedCount =
39
+ 0 /* When showing more than one diff, keep track of how many
40
+ "show/hide" checkboxes are are checked so we can update the
41
+ "show/hide all" label dynamically. */;
42
+ let btnAll /* UI control to show/hide all diffs */;
43
+ /* Install a diff-toggle button for the given diff table element. */
3544
const addToggle = function(diffElem){
3645
const sib = diffElem.previousElementSibling,
3746
ckbox = sib ? D.addClass(D.checkbox(true), 'diff-toggle') : 0;
3847
if(!sib) return;
3948
const lblToggle = D.label();
4049
D.append(lblToggle, ckbox, D.text(" show/hide "));
4150
const wrapper = D.append(D.span(), lblToggle);
4251
allToggles.push(ckbox);
52
+ ++checkedCount;
4353
D.append(sib, D.append(wrapper, lblToggle));
4454
ckbox.addEventListener('change', function(){
4555
diffElem.classList[this.checked ? 'remove' : 'add']('hidden');
56
+ if(btnAll){
57
+ checkedCount += (this.checked ? 1 : -1);
58
+ btnAll.innerText = (checkedCount < allToggles.length)
59
+ ? "Show diffs" : "Hide diffs";
60
+ }
4661
}, false);
4762
};
4863
if( !document.querySelector('body.fdiff') ){
4964
/* Don't show the diff toggle button for /fdiff because it only
5065
has a single file to show (and also a different DOM layout). */
5166
document.querySelectorAll('table.diff').forEach(addToggle);
5267
}
68
+ /**
69
+ Set up a "toggle all diffs" button which toggles all of the
70
+ above-installed checkboxes, but only if more than one diff is
71
+ rendered.
72
+ */
5373
const icm = allToggles.length>1 ? window.fossil.page.diffControlContainer : 0;
5474
if(icm) {
55
- const btnAll = D.addClass(D.a("#", "Show/Hide"), "button");
75
+ btnAll = D.addClass(D.a("#", "Hide diffs"), "button");
5676
D.append( icm, btnAll );
5777
btnAll.addEventListener('click', function(ev){
5878
ev.preventDefault();
5979
ev.stopPropagation();
60
- /* Figure out whether we want to show all or hide all: if any diffs are
61
- toggled off, show all, else hide all. */
62
- let show = false;
63
- let ckbox;
64
- for( ckbox of allToggles ){
65
- if( !ckbox.checked ){
66
- show = true;
67
- break;
68
- }
69
- }
70
- for( ckbox of allToggles ){
80
+ const show = checkedCount < allToggles.length;
81
+ for( const ckbox of allToggles ){
7182
/* Toggle all entries to match this new state. We use click()
7283
instead of ckbox.checked=... so that the on-change event handler
7384
fires. */
7485
if(ckbox.checked!==show) ckbox.click();
7586
}
7687
--- src/fossil.diff.js
+++ src/fossil.diff.js
@@ -28,48 +28,59 @@
28 window.fossil.onPageLoad(function(){
29 /**
30 Adds toggle checkboxes to each file entry in the diff views for
31 /info and similar pages.
32 */
 
 
 
33 const D = window.fossil.dom;
34 const allToggles = [/*collection of all diff-toggle checkboxes */];
 
 
 
 
 
 
35 const addToggle = function(diffElem){
36 const sib = diffElem.previousElementSibling,
37 ckbox = sib ? D.addClass(D.checkbox(true), 'diff-toggle') : 0;
38 if(!sib) return;
39 const lblToggle = D.label();
40 D.append(lblToggle, ckbox, D.text(" show/hide "));
41 const wrapper = D.append(D.span(), lblToggle);
42 allToggles.push(ckbox);
 
43 D.append(sib, D.append(wrapper, lblToggle));
44 ckbox.addEventListener('change', function(){
45 diffElem.classList[this.checked ? 'remove' : 'add']('hidden');
 
 
 
 
 
46 }, false);
47 };
48 if( !document.querySelector('body.fdiff') ){
49 /* Don't show the diff toggle button for /fdiff because it only
50 has a single file to show (and also a different DOM layout). */
51 document.querySelectorAll('table.diff').forEach(addToggle);
52 }
 
 
 
 
 
53 const icm = allToggles.length>1 ? window.fossil.page.diffControlContainer : 0;
54 if(icm) {
55 const btnAll = D.addClass(D.a("#", "Show/Hide"), "button");
56 D.append( icm, btnAll );
57 btnAll.addEventListener('click', function(ev){
58 ev.preventDefault();
59 ev.stopPropagation();
60 /* Figure out whether we want to show all or hide all: if any diffs are
61 toggled off, show all, else hide all. */
62 let show = false;
63 let ckbox;
64 for( ckbox of allToggles ){
65 if( !ckbox.checked ){
66 show = true;
67 break;
68 }
69 }
70 for( ckbox of allToggles ){
71 /* Toggle all entries to match this new state. We use click()
72 instead of ckbox.checked=... so that the on-change event handler
73 fires. */
74 if(ckbox.checked!==show) ckbox.click();
75 }
76
--- src/fossil.diff.js
+++ src/fossil.diff.js
@@ -28,48 +28,59 @@
28 window.fossil.onPageLoad(function(){
29 /**
30 Adds toggle checkboxes to each file entry in the diff views for
31 /info and similar pages.
32 */
33 if( !window.fossil.page.diffControlContainer ){
34 return;
35 }
36 const D = window.fossil.dom;
37 const allToggles = [/*collection of all diff-toggle checkboxes*/];
38 let checkedCount =
39 0 /* When showing more than one diff, keep track of how many
40 "show/hide" checkboxes are are checked so we can update the
41 "show/hide all" label dynamically. */;
42 let btnAll /* UI control to show/hide all diffs */;
43 /* Install a diff-toggle button for the given diff table element. */
44 const addToggle = function(diffElem){
45 const sib = diffElem.previousElementSibling,
46 ckbox = sib ? D.addClass(D.checkbox(true), 'diff-toggle') : 0;
47 if(!sib) return;
48 const lblToggle = D.label();
49 D.append(lblToggle, ckbox, D.text(" show/hide "));
50 const wrapper = D.append(D.span(), lblToggle);
51 allToggles.push(ckbox);
52 ++checkedCount;
53 D.append(sib, D.append(wrapper, lblToggle));
54 ckbox.addEventListener('change', function(){
55 diffElem.classList[this.checked ? 'remove' : 'add']('hidden');
56 if(btnAll){
57 checkedCount += (this.checked ? 1 : -1);
58 btnAll.innerText = (checkedCount < allToggles.length)
59 ? "Show diffs" : "Hide diffs";
60 }
61 }, false);
62 };
63 if( !document.querySelector('body.fdiff') ){
64 /* Don't show the diff toggle button for /fdiff because it only
65 has a single file to show (and also a different DOM layout). */
66 document.querySelectorAll('table.diff').forEach(addToggle);
67 }
68 /**
69 Set up a "toggle all diffs" button which toggles all of the
70 above-installed checkboxes, but only if more than one diff is
71 rendered.
72 */
73 const icm = allToggles.length>1 ? window.fossil.page.diffControlContainer : 0;
74 if(icm) {
75 btnAll = D.addClass(D.a("#", "Hide diffs"), "button");
76 D.append( icm, btnAll );
77 btnAll.addEventListener('click', function(ev){
78 ev.preventDefault();
79 ev.stopPropagation();
80 const show = checkedCount < allToggles.length;
81 for( const ckbox of allToggles ){
 
 
 
 
 
 
 
 
 
82 /* Toggle all entries to match this new state. We use click()
83 instead of ckbox.checked=... so that the on-change event handler
84 fires. */
85 if(ckbox.checked!==show) ckbox.click();
86 }
87
+210 -62
--- src/info.c
+++ src/info.c
@@ -609,62 +609,25 @@
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.
619
-**
620
-** See the help screen for the /vdiff web page for a list of available
621
-** keyboard shortcuts.
614
+** Render a web-page diff of the changes in the working check-out
622615
*/
623
-void ckout_page(void){
624
- int vid;
625
- char *zHostname;
626
- char *zCwd;
616
+static void ckout_normal_diff(int vid){
627617
int diffType; /* 0: no diff, 1: unified, 2: side-by-side */
628618
DiffConfig DCfg,*pCfg; /* Diff details */
629
- const char *zHome; /* Home directory */
630619
const char *zW; /* The "w" query parameter */
631620
int nChng; /* Number of changes */
632621
Stmt q;
633622
634
- if( !db_open_local(0) || !cgi_is_loopback(g.zIpAddr) ){
635
- cgi_redirectf("%R/home");
636
- return;
637
- }
638
- file_chdir(g.zLocalRoot, 0);
639623
diffType = preferred_diff_type();
640624
pCfg = construct_diff_flags(diffType, &DCfg);
641
- vid = db_lget_int("checkout", 0);
642
- db_unprotect(PROTECT_ALL);
643
- vfile_check_signature(vid, CKSIG_ENOTFILE);
644
- db_protect_pop();
645
- style_set_current_feature("vinfo");
646
- zHostname = fossil_hostname();
647
- zCwd = file_getcwd(0,0);
648
- zHome = fossil_getenv("HOME");
649
- if( zHome ){
650
- int nHome = (int)strlen(zHome);
651
- if( strncmp(zCwd, zHome, nHome)==0 && zCwd[nHome]=='/' ){
652
- zCwd = mprintf("~%s", zCwd+nHome);
653
- }
654
- }
655
- if( zHostname ){
656
- style_header("Checkout Status: %h on %h", zCwd, zHostname);
657
- }else{
658
- style_header("Checkout Status: %h", zCwd);
659
- }
660
- render_checkin_context(vid, 0, 0, 0);
661625
nChng = db_int(0, "SELECT count(*) FROM vfile"
662626
" WHERE vid=%d AND (deleted OR chnged OR rid==0)", vid);
663627
if( nChng==0 ){
664628
@ <p>No uncommitted changes</p>
665
- style_finish_page();
666629
return;
667630
}
668631
db_prepare(&q,
669632
/* 0 1 2 3 4 5 6 */
670633
"SELECT pathname, deleted, chnged , rid==0, rid, islink, uuid"
@@ -677,11 +640,10 @@
677640
if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){
678641
DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG;
679642
}else{
680643
DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG;
681644
}
682
- @ <hr>
683645
@ <div class="sectionmenu info-changes-menu">
684646
zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
685647
if( diffType!=0 ){
686648
/* Class "smb-hide-diff" required by the fossil.diff.js script. */
687649
const char *zBtnClass = "button smb-hide-diff";
@@ -762,12 +724,170 @@
762724
blob_reset(&old);
763725
blob_reset(&new);
764726
}
765727
}
766728
db_finalize(&q);
767
- // @ </div> <!-- ap-002 -->
729
+ append_diff_javascript(diffType);
730
+}
731
+
732
+/*
733
+** Render a web-page diff of the changes in the working check-out to
734
+** an external reference.
735
+*/
736
+static void ckout_external_base_diff(int vid, const char *zExBase){
737
+ int diffType; /* 0: no diff, 1: unified, 2: side-by-side */
738
+ DiffConfig DCfg,*pCfg; /* Diff details */
739
+ const char *zW; /* The "w" query parameter */
740
+ Stmt q;
741
+
742
+ diffType = preferred_diff_type();
743
+ pCfg = construct_diff_flags(diffType, &DCfg);
744
+ db_prepare(&q,
745
+ "SELECT pathname FROM vfile WHERE vid=%d ORDER BY pathname", vid
746
+ );
747
+ if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){
748
+ DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG;
749
+ }else{
750
+ DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG;
751
+ }
752
+ @ <div class="sectionmenu info-changes-menu">
753
+ zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
754
+ if( diffType!=1 ){
755
+ @ %z(chref("button","%R?diff=1&exbase=%h%s",zExBase,zW))\
756
+ @ Unified&nbsp;Diff</a>
757
+ }
758
+ if( diffType!=2 ){
759
+ @ %z(chref("button","%R?diff=2&exbase=%h%s",zExBase,zW))\
760
+ @ Side-by-Side&nbsp;Diff</a>
761
+ }
762
+ if( diffType!=0 ){
763
+ if( *zW ){
764
+ @ %z(chref("button","%R?diff=%d&exbase=%h",diffType,zExBase))\
765
+ @ Show&nbsp;Whitespace&nbsp;Changes</a>
766
+ }else{
767
+ @ %z(chref("button","%R?diff=%d&exbase=%h&w",diffType,zExBase))\
768
+ @ Ignore&nbsp;Whitespace</a>
769
+ }
770
+ }
771
+ @ </div>
772
+ while( db_step(&q)==SQLITE_ROW ){
773
+ const char *zFile; /* Name of file in the repository */
774
+ char *zLhs; /* Full name of left-hand side file */
775
+ char *zRhs; /* Full name of right-hand side file */
776
+ Blob rhs; /* Full text of RHS */
777
+ Blob lhs; /* Full text of LHS */
778
+
779
+ zFile = db_column_text(&q,0);
780
+ zLhs = mprintf("%s/%s", zExBase, zFile);
781
+ zRhs = mprintf("%s%s", g.zLocalRoot, zFile);
782
+ if( file_size(zLhs, ExtFILE)<0 ){
783
+ @ <div class='file-change-line'><span>
784
+ @ Missing from external baseline: %h(zFile)
785
+ @ </span></div>
786
+ }else{
787
+ blob_read_from_file(&lhs, zLhs, ExtFILE);
788
+ blob_read_from_file(&rhs, zRhs, ExtFILE);
789
+ if( blob_size(&lhs)!=blob_size(&rhs)
790
+ || memcmp(blob_buffer(&lhs), blob_buffer(&rhs), blob_size(&lhs))!=0
791
+ ){
792
+ @ <div class='file-change-line'><span>
793
+ @ Changes to %h(zFile)
794
+ @ </span></div>
795
+ if( pCfg ){
796
+ char *zFullFN;
797
+ char *zHexFN;
798
+ int nFullFN;
799
+ zFullFN = file_canonical_name_dup(zLhs);
800
+ nFullFN = (int)strlen(zFullFN);
801
+ zHexFN = fossil_malloc( nFullFN*2 + 5 );
802
+ zHexFN[0] = 'x';
803
+ encode16((const u8*)zFullFN, (u8*)(zHexFN+1), nFullFN);
804
+ zHexFN[1+nFullFN*2] = 0;
805
+ fossil_free(zFullFN);
806
+ pCfg->zLeftHash = zHexFN;
807
+ text_diff(&lhs, &rhs, cgi_output_blob(), pCfg);
808
+ pCfg->zLeftHash = 0;
809
+ fossil_free(zHexFN);
810
+ }
811
+ }
812
+ blob_reset(&lhs);
813
+ blob_reset(&rhs);
814
+ }
815
+ fossil_free(zLhs);
816
+ fossil_free(zRhs);
817
+ }
818
+ db_finalize(&q);
768819
append_diff_javascript(diffType);
820
+}
821
+
822
+/*
823
+** WEBPAGE: ckout
824
+**
825
+** Show information about the current checkout. This page only functions
826
+** if the web server is run on a loopback interface (in other words, was
827
+** started using "fossil ui" or similar) from within an open check-out.
828
+**
829
+** If the "exbase=PATH" query parameter is provided, then the diff shown
830
+** uses the files in PATH as the baseline. This is the same as using
831
+** the "--from PATH" argument to the "fossil diff" command-line. In fact,
832
+** when using "fossil ui --from PATH", the --from argument becomes the value
833
+** of the exbase query parameter for the start page.
834
+**
835
+** Other query parameters related to diffs are also accepted.
836
+**
837
+** See the help screen for the /vdiff web page for a list of available
838
+** keyboard shortcuts.
839
+*/
840
+void ckout_page(void){
841
+ int vid;
842
+ const char *zHome; /* Home directory */
843
+ int nHome;
844
+ const char *zExBase;
845
+ char *zHostname;
846
+ char *zCwd;
847
+
848
+ if( !db_open_local(0) || !cgi_is_loopback(g.zIpAddr) ){
849
+ cgi_redirectf("%R/home");
850
+ return;
851
+ }
852
+ file_chdir(g.zLocalRoot, 0);
853
+ vid = db_lget_int("checkout", 0);
854
+ db_unprotect(PROTECT_ALL);
855
+ vfile_check_signature(vid, CKSIG_ENOTFILE);
856
+ db_protect_pop();
857
+ style_set_current_feature("vinfo");
858
+ zHostname = fossil_hostname();
859
+ zCwd = file_getcwd(0,0);
860
+ zHome = fossil_getenv("HOME");
861
+ if( zHome ){
862
+ nHome = (int)strlen(zHome);
863
+ if( strncmp(zCwd, zHome, nHome)==0 && zCwd[nHome]=='/' ){
864
+ zCwd = mprintf("~%s", zCwd+nHome);
865
+ }
866
+ }else{
867
+ nHome = 0;
868
+ }
869
+ if( zHostname ){
870
+ style_header("Checkout Status: %h on %h", zCwd, zHostname);
871
+ }else{
872
+ style_header("Checkout Status: %h", zCwd);
873
+ }
874
+ render_checkin_context(vid, 0, 0, 0);
875
+ @ <hr>
876
+ zExBase = P("exbase");
877
+ if( zExBase && zExBase[0] ){
878
+ char *zCBase = file_canonical_name_dup(zExBase);
879
+ if( nHome && strncmp(zCBase, zHome, nHome)==0 && zCBase[nHome]=='/' ){
880
+ @ <p>Using external baseline: ~%h(zCBase+nHome)</p>
881
+ }else{
882
+ @ <p>Using external baseline: %h(zCBase)</p>
883
+ }
884
+ ckout_external_base_diff(vid, zCBase);
885
+ fossil_free(zCBase);
886
+ }else{
887
+ ckout_normal_diff(vid);
888
+ }
769889
style_finish_page();
770890
}
771891
772892
/*
773893
** WEBPAGE: vinfo
@@ -2123,10 +2243,16 @@
21232243
** WEBPAGE: jchunk hidden
21242244
** URL: /jchunk/HASH?from=N&to=M
21252245
**
21262246
** Return lines of text from a file as a JSON array - one entry in the
21272247
** array for each line of text.
2248
+**
2249
+** The HASH is normally a sha1 or sha3 hash that identifies an artifact
2250
+** in the BLOB table of the database. However, if HASH starts with an "x"
2251
+** and is followed by valid hexadecimal, and if we are running in a
2252
+** "fossil ui" situation (locally and with privilege), then decode the hex
2253
+** into a filename and read the file content from that name.
21282254
**
21292255
** **Warning:** This is an internal-use-only interface that is subject to
21302256
** change at any moment. External application should not use this interface
21312257
** since the application will break when this interface changes, and this
21322258
** interface will undoubtedly change.
@@ -2138,10 +2264,11 @@
21382264
** ajax_route_error().
21392265
*/
21402266
void jchunk_page(void){
21412267
int rid = 0;
21422268
const char *zName = PD("name", "");
2269
+ int nName = (int)(strlen(zName)&0x7fffffff);
21432270
int iFrom = atoi(PD("from","0"));
21442271
int iTo = atoi(PD("to","0"));
21452272
int ln;
21462273
int go = 1;
21472274
const char *zSep;
@@ -2158,36 +2285,57 @@
21582285
cgi_check_for_malice();
21592286
if( !g.perm.Read ){
21602287
ajax_route_error(403, "Access requires Read permissions.");
21612288
return;
21622289
}
2163
-#if 1
2164
- /* Re-enable this block once this code is integrated somewhere into
2165
- the UI. */
2166
- rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zName);
2167
- if( rid==0 ){
2168
- ajax_route_error(404, "Unknown artifact: %h", zName);
2169
- return;
2170
- }
2171
-#else
2172
- /* This impl is only to simplify "manual" testing via the JS
2173
- console. */
2174
- rid = symbolic_name_to_rid(zName, "*");
2175
- if( rid==0 ){
2176
- ajax_route_error(404, "Unknown artifact: %h", zName);
2177
- return;
2178
- }else if( rid<0 ){
2179
- ajax_route_error(418, "Ambiguous artifact name: %h", zName);
2180
- return;
2181
- }
2182
-#endif
21832290
if( iFrom<1 || iTo<iFrom ){
21842291
ajax_route_error(500, "Invalid line range from=%d, to=%d.",
21852292
iFrom, iTo);
21862293
return;
21872294
}
2188
- content_get(rid, &content);
2295
+ if( zName[0]=='x'
2296
+ && ((nName-1)&1)==0
2297
+ && validate16(&zName[1],nName-1)
2298
+ && g.perm.Admin
2299
+ && db_open_local(0)
2300
+ && cgi_is_loopback(g.zIpAddr)
2301
+ ){
2302
+ /* Treat the HASH as a hex-encoded filename */
2303
+ int n = (nName-1)/2;
2304
+ char *zFN = fossil_malloc(n+1);
2305
+ decode16((const u8*)&zName[1], (u8*)zFN, nName-1);
2306
+ zFN[n] = 0;
2307
+ if( file_size(zFN, ExtFILE)<0 ){
2308
+ blob_zero(&content);
2309
+ }else{
2310
+ blob_read_from_file(&content, zFN, ExtFILE);
2311
+ }
2312
+ fossil_free(zFN);
2313
+ }else{
2314
+ /* Treat the HASH as an artifact hash matching BLOB.UUID */
2315
+#if 1
2316
+ /* Re-enable this block once this code is integrated somewhere into
2317
+ the UI. */
2318
+ rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zName);
2319
+ if( rid==0 ){
2320
+ ajax_route_error(404, "Unknown artifact: %h", zName);
2321
+ return;
2322
+ }
2323
+#else
2324
+ /* This impl is only to simplify "manual" testing via the JS
2325
+ console. */
2326
+ rid = symbolic_name_to_rid(zName, "*");
2327
+ if( rid==0 ){
2328
+ ajax_route_error(404, "Unknown artifact: %h", zName);
2329
+ return;
2330
+ }else if( rid<0 ){
2331
+ ajax_route_error(418, "Ambiguous artifact name: %h", zName);
2332
+ return;
2333
+ }
2334
+#endif
2335
+ content_get(rid, &content);
2336
+ }
21892337
g.isConst = 1;
21902338
cgi_set_content_type("application/json");
21912339
ln = 0;
21922340
while( go && ln<iFrom ){
21932341
go = blob_line(&content, &line);
21942342
--- src/info.c
+++ src/info.c
@@ -609,62 +609,25 @@
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 ** See the help screen for the /vdiff web page for a list of available
621 ** keyboard shortcuts.
622 */
623 void ckout_page(void){
624 int vid;
625 char *zHostname;
626 char *zCwd;
627 int diffType; /* 0: no diff, 1: unified, 2: side-by-side */
628 DiffConfig DCfg,*pCfg; /* Diff details */
629 const char *zHome; /* Home directory */
630 const char *zW; /* The "w" query parameter */
631 int nChng; /* Number of changes */
632 Stmt q;
633
634 if( !db_open_local(0) || !cgi_is_loopback(g.zIpAddr) ){
635 cgi_redirectf("%R/home");
636 return;
637 }
638 file_chdir(g.zLocalRoot, 0);
639 diffType = preferred_diff_type();
640 pCfg = construct_diff_flags(diffType, &DCfg);
641 vid = db_lget_int("checkout", 0);
642 db_unprotect(PROTECT_ALL);
643 vfile_check_signature(vid, CKSIG_ENOTFILE);
644 db_protect_pop();
645 style_set_current_feature("vinfo");
646 zHostname = fossil_hostname();
647 zCwd = file_getcwd(0,0);
648 zHome = fossil_getenv("HOME");
649 if( zHome ){
650 int nHome = (int)strlen(zHome);
651 if( strncmp(zCwd, zHome, nHome)==0 && zCwd[nHome]=='/' ){
652 zCwd = mprintf("~%s", zCwd+nHome);
653 }
654 }
655 if( zHostname ){
656 style_header("Checkout Status: %h on %h", zCwd, zHostname);
657 }else{
658 style_header("Checkout Status: %h", zCwd);
659 }
660 render_checkin_context(vid, 0, 0, 0);
661 nChng = db_int(0, "SELECT count(*) FROM vfile"
662 " WHERE vid=%d AND (deleted OR chnged OR rid==0)", vid);
663 if( nChng==0 ){
664 @ <p>No uncommitted changes</p>
665 style_finish_page();
666 return;
667 }
668 db_prepare(&q,
669 /* 0 1 2 3 4 5 6 */
670 "SELECT pathname, deleted, chnged , rid==0, rid, islink, uuid"
@@ -677,11 +640,10 @@
677 if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){
678 DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG;
679 }else{
680 DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG;
681 }
682 @ <hr>
683 @ <div class="sectionmenu info-changes-menu">
684 zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
685 if( diffType!=0 ){
686 /* Class "smb-hide-diff" required by the fossil.diff.js script. */
687 const char *zBtnClass = "button smb-hide-diff";
@@ -762,12 +724,170 @@
762 blob_reset(&old);
763 blob_reset(&new);
764 }
765 }
766 db_finalize(&q);
767 // @ </div> <!-- ap-002 -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
768 append_diff_javascript(diffType);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
769 style_finish_page();
770 }
771
772 /*
773 ** WEBPAGE: vinfo
@@ -2123,10 +2243,16 @@
2123 ** WEBPAGE: jchunk hidden
2124 ** URL: /jchunk/HASH?from=N&to=M
2125 **
2126 ** Return lines of text from a file as a JSON array - one entry in the
2127 ** array for each line of text.
 
 
 
 
 
 
2128 **
2129 ** **Warning:** This is an internal-use-only interface that is subject to
2130 ** change at any moment. External application should not use this interface
2131 ** since the application will break when this interface changes, and this
2132 ** interface will undoubtedly change.
@@ -2138,10 +2264,11 @@
2138 ** ajax_route_error().
2139 */
2140 void jchunk_page(void){
2141 int rid = 0;
2142 const char *zName = PD("name", "");
 
2143 int iFrom = atoi(PD("from","0"));
2144 int iTo = atoi(PD("to","0"));
2145 int ln;
2146 int go = 1;
2147 const char *zSep;
@@ -2158,36 +2285,57 @@
2158 cgi_check_for_malice();
2159 if( !g.perm.Read ){
2160 ajax_route_error(403, "Access requires Read permissions.");
2161 return;
2162 }
2163 #if 1
2164 /* Re-enable this block once this code is integrated somewhere into
2165 the UI. */
2166 rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zName);
2167 if( rid==0 ){
2168 ajax_route_error(404, "Unknown artifact: %h", zName);
2169 return;
2170 }
2171 #else
2172 /* This impl is only to simplify "manual" testing via the JS
2173 console. */
2174 rid = symbolic_name_to_rid(zName, "*");
2175 if( rid==0 ){
2176 ajax_route_error(404, "Unknown artifact: %h", zName);
2177 return;
2178 }else if( rid<0 ){
2179 ajax_route_error(418, "Ambiguous artifact name: %h", zName);
2180 return;
2181 }
2182 #endif
2183 if( iFrom<1 || iTo<iFrom ){
2184 ajax_route_error(500, "Invalid line range from=%d, to=%d.",
2185 iFrom, iTo);
2186 return;
2187 }
2188 content_get(rid, &content);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2189 g.isConst = 1;
2190 cgi_set_content_type("application/json");
2191 ln = 0;
2192 while( go && ln<iFrom ){
2193 go = blob_line(&content, &line);
2194
--- src/info.c
+++ src/info.c
@@ -609,62 +609,25 @@
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 return;
630 }
631 db_prepare(&q,
632 /* 0 1 2 3 4 5 6 */
633 "SELECT pathname, deleted, chnged , rid==0, rid, islink, uuid"
@@ -677,11 +640,10 @@
640 if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){
641 DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG;
642 }else{
643 DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG;
644 }
 
645 @ <div class="sectionmenu info-changes-menu">
646 zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
647 if( diffType!=0 ){
648 /* Class "smb-hide-diff" required by the fossil.diff.js script. */
649 const char *zBtnClass = "button smb-hide-diff";
@@ -762,12 +724,170 @@
724 blob_reset(&old);
725 blob_reset(&new);
726 }
727 }
728 db_finalize(&q);
729 append_diff_javascript(diffType);
730 }
731
732 /*
733 ** Render a web-page diff of the changes in the working check-out to
734 ** an external reference.
735 */
736 static void ckout_external_base_diff(int vid, const char *zExBase){
737 int diffType; /* 0: no diff, 1: unified, 2: side-by-side */
738 DiffConfig DCfg,*pCfg; /* Diff details */
739 const char *zW; /* The "w" query parameter */
740 Stmt q;
741
742 diffType = preferred_diff_type();
743 pCfg = construct_diff_flags(diffType, &DCfg);
744 db_prepare(&q,
745 "SELECT pathname FROM vfile WHERE vid=%d ORDER BY pathname", vid
746 );
747 if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){
748 DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG;
749 }else{
750 DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG;
751 }
752 @ <div class="sectionmenu info-changes-menu">
753 zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
754 if( diffType!=1 ){
755 @ %z(chref("button","%R?diff=1&exbase=%h%s",zExBase,zW))\
756 @ Unified&nbsp;Diff</a>
757 }
758 if( diffType!=2 ){
759 @ %z(chref("button","%R?diff=2&exbase=%h%s",zExBase,zW))\
760 @ Side-by-Side&nbsp;Diff</a>
761 }
762 if( diffType!=0 ){
763 if( *zW ){
764 @ %z(chref("button","%R?diff=%d&exbase=%h",diffType,zExBase))\
765 @ Show&nbsp;Whitespace&nbsp;Changes</a>
766 }else{
767 @ %z(chref("button","%R?diff=%d&exbase=%h&w",diffType,zExBase))\
768 @ Ignore&nbsp;Whitespace</a>
769 }
770 }
771 @ </div>
772 while( db_step(&q)==SQLITE_ROW ){
773 const char *zFile; /* Name of file in the repository */
774 char *zLhs; /* Full name of left-hand side file */
775 char *zRhs; /* Full name of right-hand side file */
776 Blob rhs; /* Full text of RHS */
777 Blob lhs; /* Full text of LHS */
778
779 zFile = db_column_text(&q,0);
780 zLhs = mprintf("%s/%s", zExBase, zFile);
781 zRhs = mprintf("%s%s", g.zLocalRoot, zFile);
782 if( file_size(zLhs, ExtFILE)<0 ){
783 @ <div class='file-change-line'><span>
784 @ Missing from external baseline: %h(zFile)
785 @ </span></div>
786 }else{
787 blob_read_from_file(&lhs, zLhs, ExtFILE);
788 blob_read_from_file(&rhs, zRhs, ExtFILE);
789 if( blob_size(&lhs)!=blob_size(&rhs)
790 || memcmp(blob_buffer(&lhs), blob_buffer(&rhs), blob_size(&lhs))!=0
791 ){
792 @ <div class='file-change-line'><span>
793 @ Changes to %h(zFile)
794 @ </span></div>
795 if( pCfg ){
796 char *zFullFN;
797 char *zHexFN;
798 int nFullFN;
799 zFullFN = file_canonical_name_dup(zLhs);
800 nFullFN = (int)strlen(zFullFN);
801 zHexFN = fossil_malloc( nFullFN*2 + 5 );
802 zHexFN[0] = 'x';
803 encode16((const u8*)zFullFN, (u8*)(zHexFN+1), nFullFN);
804 zHexFN[1+nFullFN*2] = 0;
805 fossil_free(zFullFN);
806 pCfg->zLeftHash = zHexFN;
807 text_diff(&lhs, &rhs, cgi_output_blob(), pCfg);
808 pCfg->zLeftHash = 0;
809 fossil_free(zHexFN);
810 }
811 }
812 blob_reset(&lhs);
813 blob_reset(&rhs);
814 }
815 fossil_free(zLhs);
816 fossil_free(zRhs);
817 }
818 db_finalize(&q);
819 append_diff_javascript(diffType);
820 }
821
822 /*
823 ** WEBPAGE: ckout
824 **
825 ** Show information about the current checkout. This page only functions
826 ** if the web server is run on a loopback interface (in other words, was
827 ** started using "fossil ui" or similar) from within an open check-out.
828 **
829 ** If the "exbase=PATH" query parameter is provided, then the diff shown
830 ** uses the files in PATH as the baseline. This is the same as using
831 ** the "--from PATH" argument to the "fossil diff" command-line. In fact,
832 ** when using "fossil ui --from PATH", the --from argument becomes the value
833 ** of the exbase query parameter for the start page.
834 **
835 ** Other query parameters related to diffs are also accepted.
836 **
837 ** See the help screen for the /vdiff web page for a list of available
838 ** keyboard shortcuts.
839 */
840 void ckout_page(void){
841 int vid;
842 const char *zHome; /* Home directory */
843 int nHome;
844 const char *zExBase;
845 char *zHostname;
846 char *zCwd;
847
848 if( !db_open_local(0) || !cgi_is_loopback(g.zIpAddr) ){
849 cgi_redirectf("%R/home");
850 return;
851 }
852 file_chdir(g.zLocalRoot, 0);
853 vid = db_lget_int("checkout", 0);
854 db_unprotect(PROTECT_ALL);
855 vfile_check_signature(vid, CKSIG_ENOTFILE);
856 db_protect_pop();
857 style_set_current_feature("vinfo");
858 zHostname = fossil_hostname();
859 zCwd = file_getcwd(0,0);
860 zHome = fossil_getenv("HOME");
861 if( zHome ){
862 nHome = (int)strlen(zHome);
863 if( strncmp(zCwd, zHome, nHome)==0 && zCwd[nHome]=='/' ){
864 zCwd = mprintf("~%s", zCwd+nHome);
865 }
866 }else{
867 nHome = 0;
868 }
869 if( zHostname ){
870 style_header("Checkout Status: %h on %h", zCwd, zHostname);
871 }else{
872 style_header("Checkout Status: %h", zCwd);
873 }
874 render_checkin_context(vid, 0, 0, 0);
875 @ <hr>
876 zExBase = P("exbase");
877 if( zExBase && zExBase[0] ){
878 char *zCBase = file_canonical_name_dup(zExBase);
879 if( nHome && strncmp(zCBase, zHome, nHome)==0 && zCBase[nHome]=='/' ){
880 @ <p>Using external baseline: ~%h(zCBase+nHome)</p>
881 }else{
882 @ <p>Using external baseline: %h(zCBase)</p>
883 }
884 ckout_external_base_diff(vid, zCBase);
885 fossil_free(zCBase);
886 }else{
887 ckout_normal_diff(vid);
888 }
889 style_finish_page();
890 }
891
892 /*
893 ** WEBPAGE: vinfo
@@ -2123,10 +2243,16 @@
2243 ** WEBPAGE: jchunk hidden
2244 ** URL: /jchunk/HASH?from=N&to=M
2245 **
2246 ** Return lines of text from a file as a JSON array - one entry in the
2247 ** array for each line of text.
2248 **
2249 ** The HASH is normally a sha1 or sha3 hash that identifies an artifact
2250 ** in the BLOB table of the database. However, if HASH starts with an "x"
2251 ** and is followed by valid hexadecimal, and if we are running in a
2252 ** "fossil ui" situation (locally and with privilege), then decode the hex
2253 ** into a filename and read the file content from that name.
2254 **
2255 ** **Warning:** This is an internal-use-only interface that is subject to
2256 ** change at any moment. External application should not use this interface
2257 ** since the application will break when this interface changes, and this
2258 ** interface will undoubtedly change.
@@ -2138,10 +2264,11 @@
2264 ** ajax_route_error().
2265 */
2266 void jchunk_page(void){
2267 int rid = 0;
2268 const char *zName = PD("name", "");
2269 int nName = (int)(strlen(zName)&0x7fffffff);
2270 int iFrom = atoi(PD("from","0"));
2271 int iTo = atoi(PD("to","0"));
2272 int ln;
2273 int go = 1;
2274 const char *zSep;
@@ -2158,36 +2285,57 @@
2285 cgi_check_for_malice();
2286 if( !g.perm.Read ){
2287 ajax_route_error(403, "Access requires Read permissions.");
2288 return;
2289 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2290 if( iFrom<1 || iTo<iFrom ){
2291 ajax_route_error(500, "Invalid line range from=%d, to=%d.",
2292 iFrom, iTo);
2293 return;
2294 }
2295 if( zName[0]=='x'
2296 && ((nName-1)&1)==0
2297 && validate16(&zName[1],nName-1)
2298 && g.perm.Admin
2299 && db_open_local(0)
2300 && cgi_is_loopback(g.zIpAddr)
2301 ){
2302 /* Treat the HASH as a hex-encoded filename */
2303 int n = (nName-1)/2;
2304 char *zFN = fossil_malloc(n+1);
2305 decode16((const u8*)&zName[1], (u8*)zFN, nName-1);
2306 zFN[n] = 0;
2307 if( file_size(zFN, ExtFILE)<0 ){
2308 blob_zero(&content);
2309 }else{
2310 blob_read_from_file(&content, zFN, ExtFILE);
2311 }
2312 fossil_free(zFN);
2313 }else{
2314 /* Treat the HASH as an artifact hash matching BLOB.UUID */
2315 #if 1
2316 /* Re-enable this block once this code is integrated somewhere into
2317 the UI. */
2318 rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zName);
2319 if( rid==0 ){
2320 ajax_route_error(404, "Unknown artifact: %h", zName);
2321 return;
2322 }
2323 #else
2324 /* This impl is only to simplify "manual" testing via the JS
2325 console. */
2326 rid = symbolic_name_to_rid(zName, "*");
2327 if( rid==0 ){
2328 ajax_route_error(404, "Unknown artifact: %h", zName);
2329 return;
2330 }else if( rid<0 ){
2331 ajax_route_error(418, "Ambiguous artifact name: %h", zName);
2332 return;
2333 }
2334 #endif
2335 content_get(rid, &content);
2336 }
2337 g.isConst = 1;
2338 cgi_set_content_type("application/json");
2339 ln = 0;
2340 while( go && ln<iFrom ){
2341 go = blob_line(&content, &line);
2342
+210 -62
--- src/info.c
+++ src/info.c
@@ -609,62 +609,25 @@
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.
619
-**
620
-** See the help screen for the /vdiff web page for a list of available
621
-** keyboard shortcuts.
614
+** Render a web-page diff of the changes in the working check-out
622615
*/
623
-void ckout_page(void){
624
- int vid;
625
- char *zHostname;
626
- char *zCwd;
616
+static void ckout_normal_diff(int vid){
627617
int diffType; /* 0: no diff, 1: unified, 2: side-by-side */
628618
DiffConfig DCfg,*pCfg; /* Diff details */
629
- const char *zHome; /* Home directory */
630619
const char *zW; /* The "w" query parameter */
631620
int nChng; /* Number of changes */
632621
Stmt q;
633622
634
- if( !db_open_local(0) || !cgi_is_loopback(g.zIpAddr) ){
635
- cgi_redirectf("%R/home");
636
- return;
637
- }
638
- file_chdir(g.zLocalRoot, 0);
639623
diffType = preferred_diff_type();
640624
pCfg = construct_diff_flags(diffType, &DCfg);
641
- vid = db_lget_int("checkout", 0);
642
- db_unprotect(PROTECT_ALL);
643
- vfile_check_signature(vid, CKSIG_ENOTFILE);
644
- db_protect_pop();
645
- style_set_current_feature("vinfo");
646
- zHostname = fossil_hostname();
647
- zCwd = file_getcwd(0,0);
648
- zHome = fossil_getenv("HOME");
649
- if( zHome ){
650
- int nHome = (int)strlen(zHome);
651
- if( strncmp(zCwd, zHome, nHome)==0 && zCwd[nHome]=='/' ){
652
- zCwd = mprintf("~%s", zCwd+nHome);
653
- }
654
- }
655
- if( zHostname ){
656
- style_header("Checkout Status: %h on %h", zCwd, zHostname);
657
- }else{
658
- style_header("Checkout Status: %h", zCwd);
659
- }
660
- render_checkin_context(vid, 0, 0, 0);
661625
nChng = db_int(0, "SELECT count(*) FROM vfile"
662626
" WHERE vid=%d AND (deleted OR chnged OR rid==0)", vid);
663627
if( nChng==0 ){
664628
@ <p>No uncommitted changes</p>
665
- style_finish_page();
666629
return;
667630
}
668631
db_prepare(&q,
669632
/* 0 1 2 3 4 5 6 */
670633
"SELECT pathname, deleted, chnged , rid==0, rid, islink, uuid"
@@ -677,11 +640,10 @@
677640
if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){
678641
DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG;
679642
}else{
680643
DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG;
681644
}
682
- @ <hr>
683645
@ <div class="sectionmenu info-changes-menu">
684646
zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
685647
if( diffType!=0 ){
686648
/* Class "smb-hide-diff" required by the fossil.diff.js script. */
687649
const char *zBtnClass = "button smb-hide-diff";
@@ -762,12 +724,170 @@
762724
blob_reset(&old);
763725
blob_reset(&new);
764726
}
765727
}
766728
db_finalize(&q);
767
- // @ </div> <!-- ap-002 -->
729
+ append_diff_javascript(diffType);
730
+}
731
+
732
+/*
733
+** Render a web-page diff of the changes in the working check-out to
734
+** an external reference.
735
+*/
736
+static void ckout_external_base_diff(int vid, const char *zExBase){
737
+ int diffType; /* 0: no diff, 1: unified, 2: side-by-side */
738
+ DiffConfig DCfg,*pCfg; /* Diff details */
739
+ const char *zW; /* The "w" query parameter */
740
+ Stmt q;
741
+
742
+ diffType = preferred_diff_type();
743
+ pCfg = construct_diff_flags(diffType, &DCfg);
744
+ db_prepare(&q,
745
+ "SELECT pathname FROM vfile WHERE vid=%d ORDER BY pathname", vid
746
+ );
747
+ if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){
748
+ DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG;
749
+ }else{
750
+ DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG;
751
+ }
752
+ @ <div class="sectionmenu info-changes-menu">
753
+ zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
754
+ if( diffType!=1 ){
755
+ @ %z(chref("button","%R?diff=1&exbase=%h%s",zExBase,zW))\
756
+ @ Unified&nbsp;Diff</a>
757
+ }
758
+ if( diffType!=2 ){
759
+ @ %z(chref("button","%R?diff=2&exbase=%h%s",zExBase,zW))\
760
+ @ Side-by-Side&nbsp;Diff</a>
761
+ }
762
+ if( diffType!=0 ){
763
+ if( *zW ){
764
+ @ %z(chref("button","%R?diff=%d&exbase=%h",diffType,zExBase))\
765
+ @ Show&nbsp;Whitespace&nbsp;Changes</a>
766
+ }else{
767
+ @ %z(chref("button","%R?diff=%d&exbase=%h&w",diffType,zExBase))\
768
+ @ Ignore&nbsp;Whitespace</a>
769
+ }
770
+ }
771
+ @ </div>
772
+ while( db_step(&q)==SQLITE_ROW ){
773
+ const char *zFile; /* Name of file in the repository */
774
+ char *zLhs; /* Full name of left-hand side file */
775
+ char *zRhs; /* Full name of right-hand side file */
776
+ Blob rhs; /* Full text of RHS */
777
+ Blob lhs; /* Full text of LHS */
778
+
779
+ zFile = db_column_text(&q,0);
780
+ zLhs = mprintf("%s/%s", zExBase, zFile);
781
+ zRhs = mprintf("%s%s", g.zLocalRoot, zFile);
782
+ if( file_size(zLhs, ExtFILE)<0 ){
783
+ @ <div class='file-change-line'><span>
784
+ @ Missing from external baseline: %h(zFile)
785
+ @ </span></div>
786
+ }else{
787
+ blob_read_from_file(&lhs, zLhs, ExtFILE);
788
+ blob_read_from_file(&rhs, zRhs, ExtFILE);
789
+ if( blob_size(&lhs)!=blob_size(&rhs)
790
+ || memcmp(blob_buffer(&lhs), blob_buffer(&rhs), blob_size(&lhs))!=0
791
+ ){
792
+ @ <div class='file-change-line'><span>
793
+ @ Changes to %h(zFile)
794
+ @ </span></div>
795
+ if( pCfg ){
796
+ char *zFullFN;
797
+ char *zHexFN;
798
+ int nFullFN;
799
+ zFullFN = file_canonical_name_dup(zLhs);
800
+ nFullFN = (int)strlen(zFullFN);
801
+ zHexFN = fossil_malloc( nFullFN*2 + 5 );
802
+ zHexFN[0] = 'x';
803
+ encode16((const u8*)zFullFN, (u8*)(zHexFN+1), nFullFN);
804
+ zHexFN[1+nFullFN*2] = 0;
805
+ fossil_free(zFullFN);
806
+ pCfg->zLeftHash = zHexFN;
807
+ text_diff(&lhs, &rhs, cgi_output_blob(), pCfg);
808
+ pCfg->zLeftHash = 0;
809
+ fossil_free(zHexFN);
810
+ }
811
+ }
812
+ blob_reset(&lhs);
813
+ blob_reset(&rhs);
814
+ }
815
+ fossil_free(zLhs);
816
+ fossil_free(zRhs);
817
+ }
818
+ db_finalize(&q);
768819
append_diff_javascript(diffType);
820
+}
821
+
822
+/*
823
+** WEBPAGE: ckout
824
+**
825
+** Show information about the current checkout. This page only functions
826
+** if the web server is run on a loopback interface (in other words, was
827
+** started using "fossil ui" or similar) from within an open check-out.
828
+**
829
+** If the "exbase=PATH" query parameter is provided, then the diff shown
830
+** uses the files in PATH as the baseline. This is the same as using
831
+** the "--from PATH" argument to the "fossil diff" command-line. In fact,
832
+** when using "fossil ui --from PATH", the --from argument becomes the value
833
+** of the exbase query parameter for the start page.
834
+**
835
+** Other query parameters related to diffs are also accepted.
836
+**
837
+** See the help screen for the /vdiff web page for a list of available
838
+** keyboard shortcuts.
839
+*/
840
+void ckout_page(void){
841
+ int vid;
842
+ const char *zHome; /* Home directory */
843
+ int nHome;
844
+ const char *zExBase;
845
+ char *zHostname;
846
+ char *zCwd;
847
+
848
+ if( !db_open_local(0) || !cgi_is_loopback(g.zIpAddr) ){
849
+ cgi_redirectf("%R/home");
850
+ return;
851
+ }
852
+ file_chdir(g.zLocalRoot, 0);
853
+ vid = db_lget_int("checkout", 0);
854
+ db_unprotect(PROTECT_ALL);
855
+ vfile_check_signature(vid, CKSIG_ENOTFILE);
856
+ db_protect_pop();
857
+ style_set_current_feature("vinfo");
858
+ zHostname = fossil_hostname();
859
+ zCwd = file_getcwd(0,0);
860
+ zHome = fossil_getenv("HOME");
861
+ if( zHome ){
862
+ nHome = (int)strlen(zHome);
863
+ if( strncmp(zCwd, zHome, nHome)==0 && zCwd[nHome]=='/' ){
864
+ zCwd = mprintf("~%s", zCwd+nHome);
865
+ }
866
+ }else{
867
+ nHome = 0;
868
+ }
869
+ if( zHostname ){
870
+ style_header("Checkout Status: %h on %h", zCwd, zHostname);
871
+ }else{
872
+ style_header("Checkout Status: %h", zCwd);
873
+ }
874
+ render_checkin_context(vid, 0, 0, 0);
875
+ @ <hr>
876
+ zExBase = P("exbase");
877
+ if( zExBase && zExBase[0] ){
878
+ char *zCBase = file_canonical_name_dup(zExBase);
879
+ if( nHome && strncmp(zCBase, zHome, nHome)==0 && zCBase[nHome]=='/' ){
880
+ @ <p>Using external baseline: ~%h(zCBase+nHome)</p>
881
+ }else{
882
+ @ <p>Using external baseline: %h(zCBase)</p>
883
+ }
884
+ ckout_external_base_diff(vid, zCBase);
885
+ fossil_free(zCBase);
886
+ }else{
887
+ ckout_normal_diff(vid);
888
+ }
769889
style_finish_page();
770890
}
771891
772892
/*
773893
** WEBPAGE: vinfo
@@ -2123,10 +2243,16 @@
21232243
** WEBPAGE: jchunk hidden
21242244
** URL: /jchunk/HASH?from=N&to=M
21252245
**
21262246
** Return lines of text from a file as a JSON array - one entry in the
21272247
** array for each line of text.
2248
+**
2249
+** The HASH is normally a sha1 or sha3 hash that identifies an artifact
2250
+** in the BLOB table of the database. However, if HASH starts with an "x"
2251
+** and is followed by valid hexadecimal, and if we are running in a
2252
+** "fossil ui" situation (locally and with privilege), then decode the hex
2253
+** into a filename and read the file content from that name.
21282254
**
21292255
** **Warning:** This is an internal-use-only interface that is subject to
21302256
** change at any moment. External application should not use this interface
21312257
** since the application will break when this interface changes, and this
21322258
** interface will undoubtedly change.
@@ -2138,10 +2264,11 @@
21382264
** ajax_route_error().
21392265
*/
21402266
void jchunk_page(void){
21412267
int rid = 0;
21422268
const char *zName = PD("name", "");
2269
+ int nName = (int)(strlen(zName)&0x7fffffff);
21432270
int iFrom = atoi(PD("from","0"));
21442271
int iTo = atoi(PD("to","0"));
21452272
int ln;
21462273
int go = 1;
21472274
const char *zSep;
@@ -2158,36 +2285,57 @@
21582285
cgi_check_for_malice();
21592286
if( !g.perm.Read ){
21602287
ajax_route_error(403, "Access requires Read permissions.");
21612288
return;
21622289
}
2163
-#if 1
2164
- /* Re-enable this block once this code is integrated somewhere into
2165
- the UI. */
2166
- rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zName);
2167
- if( rid==0 ){
2168
- ajax_route_error(404, "Unknown artifact: %h", zName);
2169
- return;
2170
- }
2171
-#else
2172
- /* This impl is only to simplify "manual" testing via the JS
2173
- console. */
2174
- rid = symbolic_name_to_rid(zName, "*");
2175
- if( rid==0 ){
2176
- ajax_route_error(404, "Unknown artifact: %h", zName);
2177
- return;
2178
- }else if( rid<0 ){
2179
- ajax_route_error(418, "Ambiguous artifact name: %h", zName);
2180
- return;
2181
- }
2182
-#endif
21832290
if( iFrom<1 || iTo<iFrom ){
21842291
ajax_route_error(500, "Invalid line range from=%d, to=%d.",
21852292
iFrom, iTo);
21862293
return;
21872294
}
2188
- content_get(rid, &content);
2295
+ if( zName[0]=='x'
2296
+ && ((nName-1)&1)==0
2297
+ && validate16(&zName[1],nName-1)
2298
+ && g.perm.Admin
2299
+ && db_open_local(0)
2300
+ && cgi_is_loopback(g.zIpAddr)
2301
+ ){
2302
+ /* Treat the HASH as a hex-encoded filename */
2303
+ int n = (nName-1)/2;
2304
+ char *zFN = fossil_malloc(n+1);
2305
+ decode16((const u8*)&zName[1], (u8*)zFN, nName-1);
2306
+ zFN[n] = 0;
2307
+ if( file_size(zFN, ExtFILE)<0 ){
2308
+ blob_zero(&content);
2309
+ }else{
2310
+ blob_read_from_file(&content, zFN, ExtFILE);
2311
+ }
2312
+ fossil_free(zFN);
2313
+ }else{
2314
+ /* Treat the HASH as an artifact hash matching BLOB.UUID */
2315
+#if 1
2316
+ /* Re-enable this block once this code is integrated somewhere into
2317
+ the UI. */
2318
+ rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zName);
2319
+ if( rid==0 ){
2320
+ ajax_route_error(404, "Unknown artifact: %h", zName);
2321
+ return;
2322
+ }
2323
+#else
2324
+ /* This impl is only to simplify "manual" testing via the JS
2325
+ console. */
2326
+ rid = symbolic_name_to_rid(zName, "*");
2327
+ if( rid==0 ){
2328
+ ajax_route_error(404, "Unknown artifact: %h", zName);
2329
+ return;
2330
+ }else if( rid<0 ){
2331
+ ajax_route_error(418, "Ambiguous artifact name: %h", zName);
2332
+ return;
2333
+ }
2334
+#endif
2335
+ content_get(rid, &content);
2336
+ }
21892337
g.isConst = 1;
21902338
cgi_set_content_type("application/json");
21912339
ln = 0;
21922340
while( go && ln<iFrom ){
21932341
go = blob_line(&content, &line);
21942342
--- src/info.c
+++ src/info.c
@@ -609,62 +609,25 @@
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 ** See the help screen for the /vdiff web page for a list of available
621 ** keyboard shortcuts.
622 */
623 void ckout_page(void){
624 int vid;
625 char *zHostname;
626 char *zCwd;
627 int diffType; /* 0: no diff, 1: unified, 2: side-by-side */
628 DiffConfig DCfg,*pCfg; /* Diff details */
629 const char *zHome; /* Home directory */
630 const char *zW; /* The "w" query parameter */
631 int nChng; /* Number of changes */
632 Stmt q;
633
634 if( !db_open_local(0) || !cgi_is_loopback(g.zIpAddr) ){
635 cgi_redirectf("%R/home");
636 return;
637 }
638 file_chdir(g.zLocalRoot, 0);
639 diffType = preferred_diff_type();
640 pCfg = construct_diff_flags(diffType, &DCfg);
641 vid = db_lget_int("checkout", 0);
642 db_unprotect(PROTECT_ALL);
643 vfile_check_signature(vid, CKSIG_ENOTFILE);
644 db_protect_pop();
645 style_set_current_feature("vinfo");
646 zHostname = fossil_hostname();
647 zCwd = file_getcwd(0,0);
648 zHome = fossil_getenv("HOME");
649 if( zHome ){
650 int nHome = (int)strlen(zHome);
651 if( strncmp(zCwd, zHome, nHome)==0 && zCwd[nHome]=='/' ){
652 zCwd = mprintf("~%s", zCwd+nHome);
653 }
654 }
655 if( zHostname ){
656 style_header("Checkout Status: %h on %h", zCwd, zHostname);
657 }else{
658 style_header("Checkout Status: %h", zCwd);
659 }
660 render_checkin_context(vid, 0, 0, 0);
661 nChng = db_int(0, "SELECT count(*) FROM vfile"
662 " WHERE vid=%d AND (deleted OR chnged OR rid==0)", vid);
663 if( nChng==0 ){
664 @ <p>No uncommitted changes</p>
665 style_finish_page();
666 return;
667 }
668 db_prepare(&q,
669 /* 0 1 2 3 4 5 6 */
670 "SELECT pathname, deleted, chnged , rid==0, rid, islink, uuid"
@@ -677,11 +640,10 @@
677 if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){
678 DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG;
679 }else{
680 DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG;
681 }
682 @ <hr>
683 @ <div class="sectionmenu info-changes-menu">
684 zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
685 if( diffType!=0 ){
686 /* Class "smb-hide-diff" required by the fossil.diff.js script. */
687 const char *zBtnClass = "button smb-hide-diff";
@@ -762,12 +724,170 @@
762 blob_reset(&old);
763 blob_reset(&new);
764 }
765 }
766 db_finalize(&q);
767 // @ </div> <!-- ap-002 -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
768 append_diff_javascript(diffType);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
769 style_finish_page();
770 }
771
772 /*
773 ** WEBPAGE: vinfo
@@ -2123,10 +2243,16 @@
2123 ** WEBPAGE: jchunk hidden
2124 ** URL: /jchunk/HASH?from=N&to=M
2125 **
2126 ** Return lines of text from a file as a JSON array - one entry in the
2127 ** array for each line of text.
 
 
 
 
 
 
2128 **
2129 ** **Warning:** This is an internal-use-only interface that is subject to
2130 ** change at any moment. External application should not use this interface
2131 ** since the application will break when this interface changes, and this
2132 ** interface will undoubtedly change.
@@ -2138,10 +2264,11 @@
2138 ** ajax_route_error().
2139 */
2140 void jchunk_page(void){
2141 int rid = 0;
2142 const char *zName = PD("name", "");
 
2143 int iFrom = atoi(PD("from","0"));
2144 int iTo = atoi(PD("to","0"));
2145 int ln;
2146 int go = 1;
2147 const char *zSep;
@@ -2158,36 +2285,57 @@
2158 cgi_check_for_malice();
2159 if( !g.perm.Read ){
2160 ajax_route_error(403, "Access requires Read permissions.");
2161 return;
2162 }
2163 #if 1
2164 /* Re-enable this block once this code is integrated somewhere into
2165 the UI. */
2166 rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zName);
2167 if( rid==0 ){
2168 ajax_route_error(404, "Unknown artifact: %h", zName);
2169 return;
2170 }
2171 #else
2172 /* This impl is only to simplify "manual" testing via the JS
2173 console. */
2174 rid = symbolic_name_to_rid(zName, "*");
2175 if( rid==0 ){
2176 ajax_route_error(404, "Unknown artifact: %h", zName);
2177 return;
2178 }else if( rid<0 ){
2179 ajax_route_error(418, "Ambiguous artifact name: %h", zName);
2180 return;
2181 }
2182 #endif
2183 if( iFrom<1 || iTo<iFrom ){
2184 ajax_route_error(500, "Invalid line range from=%d, to=%d.",
2185 iFrom, iTo);
2186 return;
2187 }
2188 content_get(rid, &content);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2189 g.isConst = 1;
2190 cgi_set_content_type("application/json");
2191 ln = 0;
2192 while( go && ln<iFrom ){
2193 go = blob_line(&content, &line);
2194
--- src/info.c
+++ src/info.c
@@ -609,62 +609,25 @@
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 return;
630 }
631 db_prepare(&q,
632 /* 0 1 2 3 4 5 6 */
633 "SELECT pathname, deleted, chnged , rid==0, rid, islink, uuid"
@@ -677,11 +640,10 @@
640 if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){
641 DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG;
642 }else{
643 DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG;
644 }
 
645 @ <div class="sectionmenu info-changes-menu">
646 zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
647 if( diffType!=0 ){
648 /* Class "smb-hide-diff" required by the fossil.diff.js script. */
649 const char *zBtnClass = "button smb-hide-diff";
@@ -762,12 +724,170 @@
724 blob_reset(&old);
725 blob_reset(&new);
726 }
727 }
728 db_finalize(&q);
729 append_diff_javascript(diffType);
730 }
731
732 /*
733 ** Render a web-page diff of the changes in the working check-out to
734 ** an external reference.
735 */
736 static void ckout_external_base_diff(int vid, const char *zExBase){
737 int diffType; /* 0: no diff, 1: unified, 2: side-by-side */
738 DiffConfig DCfg,*pCfg; /* Diff details */
739 const char *zW; /* The "w" query parameter */
740 Stmt q;
741
742 diffType = preferred_diff_type();
743 pCfg = construct_diff_flags(diffType, &DCfg);
744 db_prepare(&q,
745 "SELECT pathname FROM vfile WHERE vid=%d ORDER BY pathname", vid
746 );
747 if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){
748 DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG;
749 }else{
750 DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG;
751 }
752 @ <div class="sectionmenu info-changes-menu">
753 zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
754 if( diffType!=1 ){
755 @ %z(chref("button","%R?diff=1&exbase=%h%s",zExBase,zW))\
756 @ Unified&nbsp;Diff</a>
757 }
758 if( diffType!=2 ){
759 @ %z(chref("button","%R?diff=2&exbase=%h%s",zExBase,zW))\
760 @ Side-by-Side&nbsp;Diff</a>
761 }
762 if( diffType!=0 ){
763 if( *zW ){
764 @ %z(chref("button","%R?diff=%d&exbase=%h",diffType,zExBase))\
765 @ Show&nbsp;Whitespace&nbsp;Changes</a>
766 }else{
767 @ %z(chref("button","%R?diff=%d&exbase=%h&w",diffType,zExBase))\
768 @ Ignore&nbsp;Whitespace</a>
769 }
770 }
771 @ </div>
772 while( db_step(&q)==SQLITE_ROW ){
773 const char *zFile; /* Name of file in the repository */
774 char *zLhs; /* Full name of left-hand side file */
775 char *zRhs; /* Full name of right-hand side file */
776 Blob rhs; /* Full text of RHS */
777 Blob lhs; /* Full text of LHS */
778
779 zFile = db_column_text(&q,0);
780 zLhs = mprintf("%s/%s", zExBase, zFile);
781 zRhs = mprintf("%s%s", g.zLocalRoot, zFile);
782 if( file_size(zLhs, ExtFILE)<0 ){
783 @ <div class='file-change-line'><span>
784 @ Missing from external baseline: %h(zFile)
785 @ </span></div>
786 }else{
787 blob_read_from_file(&lhs, zLhs, ExtFILE);
788 blob_read_from_file(&rhs, zRhs, ExtFILE);
789 if( blob_size(&lhs)!=blob_size(&rhs)
790 || memcmp(blob_buffer(&lhs), blob_buffer(&rhs), blob_size(&lhs))!=0
791 ){
792 @ <div class='file-change-line'><span>
793 @ Changes to %h(zFile)
794 @ </span></div>
795 if( pCfg ){
796 char *zFullFN;
797 char *zHexFN;
798 int nFullFN;
799 zFullFN = file_canonical_name_dup(zLhs);
800 nFullFN = (int)strlen(zFullFN);
801 zHexFN = fossil_malloc( nFullFN*2 + 5 );
802 zHexFN[0] = 'x';
803 encode16((const u8*)zFullFN, (u8*)(zHexFN+1), nFullFN);
804 zHexFN[1+nFullFN*2] = 0;
805 fossil_free(zFullFN);
806 pCfg->zLeftHash = zHexFN;
807 text_diff(&lhs, &rhs, cgi_output_blob(), pCfg);
808 pCfg->zLeftHash = 0;
809 fossil_free(zHexFN);
810 }
811 }
812 blob_reset(&lhs);
813 blob_reset(&rhs);
814 }
815 fossil_free(zLhs);
816 fossil_free(zRhs);
817 }
818 db_finalize(&q);
819 append_diff_javascript(diffType);
820 }
821
822 /*
823 ** WEBPAGE: ckout
824 **
825 ** Show information about the current checkout. This page only functions
826 ** if the web server is run on a loopback interface (in other words, was
827 ** started using "fossil ui" or similar) from within an open check-out.
828 **
829 ** If the "exbase=PATH" query parameter is provided, then the diff shown
830 ** uses the files in PATH as the baseline. This is the same as using
831 ** the "--from PATH" argument to the "fossil diff" command-line. In fact,
832 ** when using "fossil ui --from PATH", the --from argument becomes the value
833 ** of the exbase query parameter for the start page.
834 **
835 ** Other query parameters related to diffs are also accepted.
836 **
837 ** See the help screen for the /vdiff web page for a list of available
838 ** keyboard shortcuts.
839 */
840 void ckout_page(void){
841 int vid;
842 const char *zHome; /* Home directory */
843 int nHome;
844 const char *zExBase;
845 char *zHostname;
846 char *zCwd;
847
848 if( !db_open_local(0) || !cgi_is_loopback(g.zIpAddr) ){
849 cgi_redirectf("%R/home");
850 return;
851 }
852 file_chdir(g.zLocalRoot, 0);
853 vid = db_lget_int("checkout", 0);
854 db_unprotect(PROTECT_ALL);
855 vfile_check_signature(vid, CKSIG_ENOTFILE);
856 db_protect_pop();
857 style_set_current_feature("vinfo");
858 zHostname = fossil_hostname();
859 zCwd = file_getcwd(0,0);
860 zHome = fossil_getenv("HOME");
861 if( zHome ){
862 nHome = (int)strlen(zHome);
863 if( strncmp(zCwd, zHome, nHome)==0 && zCwd[nHome]=='/' ){
864 zCwd = mprintf("~%s", zCwd+nHome);
865 }
866 }else{
867 nHome = 0;
868 }
869 if( zHostname ){
870 style_header("Checkout Status: %h on %h", zCwd, zHostname);
871 }else{
872 style_header("Checkout Status: %h", zCwd);
873 }
874 render_checkin_context(vid, 0, 0, 0);
875 @ <hr>
876 zExBase = P("exbase");
877 if( zExBase && zExBase[0] ){
878 char *zCBase = file_canonical_name_dup(zExBase);
879 if( nHome && strncmp(zCBase, zHome, nHome)==0 && zCBase[nHome]=='/' ){
880 @ <p>Using external baseline: ~%h(zCBase+nHome)</p>
881 }else{
882 @ <p>Using external baseline: %h(zCBase)</p>
883 }
884 ckout_external_base_diff(vid, zCBase);
885 fossil_free(zCBase);
886 }else{
887 ckout_normal_diff(vid);
888 }
889 style_finish_page();
890 }
891
892 /*
893 ** WEBPAGE: vinfo
@@ -2123,10 +2243,16 @@
2243 ** WEBPAGE: jchunk hidden
2244 ** URL: /jchunk/HASH?from=N&to=M
2245 **
2246 ** Return lines of text from a file as a JSON array - one entry in the
2247 ** array for each line of text.
2248 **
2249 ** The HASH is normally a sha1 or sha3 hash that identifies an artifact
2250 ** in the BLOB table of the database. However, if HASH starts with an "x"
2251 ** and is followed by valid hexadecimal, and if we are running in a
2252 ** "fossil ui" situation (locally and with privilege), then decode the hex
2253 ** into a filename and read the file content from that name.
2254 **
2255 ** **Warning:** This is an internal-use-only interface that is subject to
2256 ** change at any moment. External application should not use this interface
2257 ** since the application will break when this interface changes, and this
2258 ** interface will undoubtedly change.
@@ -2138,10 +2264,11 @@
2264 ** ajax_route_error().
2265 */
2266 void jchunk_page(void){
2267 int rid = 0;
2268 const char *zName = PD("name", "");
2269 int nName = (int)(strlen(zName)&0x7fffffff);
2270 int iFrom = atoi(PD("from","0"));
2271 int iTo = atoi(PD("to","0"));
2272 int ln;
2273 int go = 1;
2274 const char *zSep;
@@ -2158,36 +2285,57 @@
2285 cgi_check_for_malice();
2286 if( !g.perm.Read ){
2287 ajax_route_error(403, "Access requires Read permissions.");
2288 return;
2289 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2290 if( iFrom<1 || iTo<iFrom ){
2291 ajax_route_error(500, "Invalid line range from=%d, to=%d.",
2292 iFrom, iTo);
2293 return;
2294 }
2295 if( zName[0]=='x'
2296 && ((nName-1)&1)==0
2297 && validate16(&zName[1],nName-1)
2298 && g.perm.Admin
2299 && db_open_local(0)
2300 && cgi_is_loopback(g.zIpAddr)
2301 ){
2302 /* Treat the HASH as a hex-encoded filename */
2303 int n = (nName-1)/2;
2304 char *zFN = fossil_malloc(n+1);
2305 decode16((const u8*)&zName[1], (u8*)zFN, nName-1);
2306 zFN[n] = 0;
2307 if( file_size(zFN, ExtFILE)<0 ){
2308 blob_zero(&content);
2309 }else{
2310 blob_read_from_file(&content, zFN, ExtFILE);
2311 }
2312 fossil_free(zFN);
2313 }else{
2314 /* Treat the HASH as an artifact hash matching BLOB.UUID */
2315 #if 1
2316 /* Re-enable this block once this code is integrated somewhere into
2317 the UI. */
2318 rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zName);
2319 if( rid==0 ){
2320 ajax_route_error(404, "Unknown artifact: %h", zName);
2321 return;
2322 }
2323 #else
2324 /* This impl is only to simplify "manual" testing via the JS
2325 console. */
2326 rid = symbolic_name_to_rid(zName, "*");
2327 if( rid==0 ){
2328 ajax_route_error(404, "Unknown artifact: %h", zName);
2329 return;
2330 }else if( rid<0 ){
2331 ajax_route_error(418, "Ambiguous artifact name: %h", zName);
2332 return;
2333 }
2334 #endif
2335 content_get(rid, &content);
2336 }
2337 g.isConst = 1;
2338 cgi_set_content_type("application/json");
2339 ln = 0;
2340 while( go && ln<iFrom ){
2341 go = blob_line(&content, &line);
2342
+10
--- src/main.c
+++ src/main.c
@@ -3178,10 +3178,11 @@
31783178
** --errorlog FILE Append HTTP error messages to FILE
31793179
** --extroot DIR Document root for the /ext extension mechanism
31803180
** --files GLOBLIST Comma-separated list of glob patterns for static files
31813181
** --fossilcmd PATH The pathname of the "fossil" executable on the remote
31823182
** system when REPOSITORY is remote.
3183
+** --from PATH Use PATH as the diff baseline for the /ckout page
31833184
** --localauth Enable automatic login for requests from localhost
31843185
** --localhost Listen on 127.0.0.1 only (always true for "ui")
31853186
** --https Indicates that the input is coming through a reverse
31863187
** proxy that has already translated HTTPS into HTTP.
31873188
** --jsmode MODE Determine how JavaScript is delivered with pages.
@@ -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 *zFrom; /* Value for --from */
32553257
32563258
32573259
#if USE_SEE
32583260
db_setup_for_saved_encryption_key();
32593261
#endif
@@ -3286,13 +3288,21 @@
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
+ zFrom = find_option("from", 0, 1);
3294
+ if( zFrom && zFrom==file_tail(zFrom) ){
3295
+ fossil_fatal("the argument to --from must be a pathname for"
3296
+ " the \"ui\" command");
3297
+ }
32913298
zInitPage = find_option("page", "p", 1);
32923299
if( zInitPage && zInitPage[0]=='/' ) zInitPage++;
32933300
zFossilCmd = find_option("fossilcmd", 0, 1);
3301
+ if( zFrom && zInitPage==0 ){
3302
+ zInitPage = mprintf("ckout?exbase=%T", zFrom);
3303
+ }
32943304
}
32953305
zNotFound = find_option("notfound", 0, 1);
32963306
allowRepoList = find_option("repolist",0,0)!=0;
32973307
if( find_option("nocompress",0,0)!=0 ) g.fNoHttpCompress = 1;
32983308
zAltBase = find_option("baseurl", 0, 1);
32993309
--- src/main.c
+++ src/main.c
@@ -3178,10 +3178,11 @@
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")
3185 ** --https Indicates that the input is coming through a reverse
3186 ** proxy that has already translated HTTPS into HTTP.
3187 ** --jsmode MODE Determine how JavaScript is delivered with pages.
@@ -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,21 @@
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
@@ -3178,10 +3178,11 @@
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 ** --from PATH Use PATH as the diff baseline for the /ckout page
3184 ** --localauth Enable automatic login for requests from localhost
3185 ** --localhost Listen on 127.0.0.1 only (always true for "ui")
3186 ** --https Indicates that the input is coming through a reverse
3187 ** proxy that has already translated HTTPS into HTTP.
3188 ** --jsmode MODE Determine how JavaScript is delivered with pages.
@@ -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 *zFrom; /* Value for --from */
3257
3258
3259 #if USE_SEE
3260 db_setup_for_saved_encryption_key();
3261 #endif
@@ -3286,13 +3288,21 @@
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 zFrom = find_option("from", 0, 1);
3294 if( zFrom && zFrom==file_tail(zFrom) ){
3295 fossil_fatal("the argument to --from must be a pathname for"
3296 " the \"ui\" command");
3297 }
3298 zInitPage = find_option("page", "p", 1);
3299 if( zInitPage && zInitPage[0]=='/' ) zInitPage++;
3300 zFossilCmd = find_option("fossilcmd", 0, 1);
3301 if( zFrom && zInitPage==0 ){
3302 zInitPage = mprintf("ckout?exbase=%T", zFrom);
3303 }
3304 }
3305 zNotFound = find_option("notfound", 0, 1);
3306 allowRepoList = find_option("repolist",0,0)!=0;
3307 if( find_option("nocompress",0,0)!=0 ) g.fNoHttpCompress = 1;
3308 zAltBase = find_option("baseurl", 0, 1);
3309
+12 -8
--- src/merge.c
+++ src/merge.c
@@ -687,10 +687,11 @@
687687
** -K|--keep-merge-files On merge conflict, retain the temporary files
688688
** used for merging, named *-baseline, *-original,
689689
** and *-merge.
690690
** -n|--dry-run Do not actually change files on disk
691691
** --nosync Do not auto-sync prior to merging
692
+** --noundo Do not record changes in the undo log
692693
** -v|--verbose Show additional details of the merge
693694
*/
694695
void merge_cmd(void){
695696
int vid; /* Current version "V" */
696697
int mid; /* Version we are merging from "M" */
@@ -712,10 +713,11 @@
712713
int nOverwrite = 0; /* Number of unmanaged files overwritten */
713714
char vAncestor = 'p'; /* If P is an ancestor of V then 'p', else 'n' */
714715
const char *zVersion; /* The VERSION argument */
715716
int bMultiMerge = 0; /* True if there are two or more VERSION arguments */
716717
int nMerge = 0; /* Number of prior merges processed */
718
+ int useUndo = 1; /* True to record changes in the undo log */
717719
Stmt q; /* SQL statment used for merge processing */
718720
719721
720722
/* Notation:
721723
**
@@ -760,10 +762,12 @@
760762
** * The --dry-run option is also useful in combination with --debug.
761763
*/
762764
debugFlag = find_option("debug",0,0)!=0;
763765
if( debugFlag && verboseFlag ) debugFlag = 2;
764766
showVfileFlag = find_option("show-vfile",0,0)!=0;
767
+ useUndo = find_option("noundo",0,0)==0;
768
+ if( dryRunFlag ) useUndo = 0;
765769
766770
verify_all_options();
767771
db_must_be_within_tree();
768772
if( zBinGlob==0 ) zBinGlob = db_get("binary-glob",0);
769773
vid = db_lget_int("checkout", 0);
@@ -926,11 +930,11 @@
926930
integrateFlag ? "integrate:" : "merge-from:");
927931
print_checkin_description(pid, 12, "baseline:");
928932
}
929933
vfile_check_signature(vid, CKSIG_ENOTFILE);
930934
if( nMerge==0 ) db_begin_transaction();
931
- if( !dryRunFlag ) undo_begin();
935
+ if( useUndo ) undo_begin();
932936
if( load_vfile_from_rid(mid) && !forceMissingFlag ){
933937
fossil_fatal("missing content, unable to merge");
934938
}
935939
if( load_vfile_from_rid(pid) && !forceMissingFlag ){
936940
fossil_fatal("missing content, unable to merge");
@@ -1183,12 +1187,12 @@
11831187
int ridm = db_column_int(&q, 1);
11841188
const char *zName = db_column_text(&q, 2);
11851189
int islinkm = db_column_int(&q, 3);
11861190
/* Copy content from idm over into idv. Overwrite idv. */
11871191
fossil_print("UPDATE %s\n", zName);
1192
+ if( useUndo ) undo_save(zName);
11881193
if( !dryRunFlag ){
1189
- undo_save(zName);
11901194
db_multi_exec(
11911195
"UPDATE vfile SET mtime=0, mrid=%d, chnged=%d, islink=%d,"
11921196
" mhash=CASE WHEN rid<>%d"
11931197
" THEN (SELECT uuid FROM blob WHERE blob.rid=%d) END"
11941198
" WHERE id=%d", ridm, integrateFlag?4:2, islinkm, ridm, ridm, idv
@@ -1265,11 +1269,11 @@
12651269
}else{
12661270
i64 sz;
12671271
const char *zErrMsg = 0;
12681272
int nc = 0;
12691273
1270
- if( !dryRunFlag ) undo_save(zName);
1274
+ if( useUndo ) undo_save(zName);
12711275
zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
12721276
sz = file_size(zFullPath, ExtFILE);
12731277
content_get(ridp, &p);
12741278
content_get(ridm, &m);
12751279
if( isBinary ){
@@ -1352,11 +1356,11 @@
13521356
zErrMsg = "local edits lost";
13531357
zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
13541358
sz = file_size(zFullPath, ExtFILE);
13551359
fossil_free(zFullPath);
13561360
}
1357
- if( !dryRunFlag ) undo_save(zName);
1361
+ if( useUndo ) undo_save(zName);
13581362
db_multi_exec(
13591363
"UPDATE vfile SET deleted=1 WHERE id=%d", idv
13601364
);
13611365
if( !dryRunFlag ){
13621366
char *zFullPath = mprintf("%s%s", g.zLocalRoot, zName);
@@ -1399,12 +1403,12 @@
13991403
int idv = db_column_int(&q, 0);
14001404
const char *zOldName = db_column_text(&q, 1);
14011405
const char *zNewName = db_column_text(&q, 2);
14021406
int isExe = db_column_int(&q, 3);
14031407
fossil_print("RENAME %s -> %s\n", zOldName, zNewName);
1404
- if( !dryRunFlag ) undo_save(zOldName);
1405
- if( !dryRunFlag ) undo_save(zNewName);
1408
+ if( useUndo ) undo_save(zOldName);
1409
+ if( useUndo ) undo_save(zNewName);
14061410
db_multi_exec(
14071411
"UPDATE mergestat SET fnr=fnm WHERE fnp=%Q",
14081412
zOldName
14091413
);
14101414
db_multi_exec(
@@ -1498,12 +1502,12 @@
14981502
"VALUES('ADDED',%Q,%d,%Q)",
14991503
/* fnm */ zName,
15001504
/* ridm */ db_column_int(&q,2),
15011505
/* fnr */ zName
15021506
);
1507
+ if( useUndo ) undo_save(zName);
15031508
if( !dryRunFlag ){
1504
- undo_save(zName);
15051509
vfile_to_disk(0, idm, 0, 0);
15061510
}
15071511
}
15081512
db_finalize(&q);
15091513
@@ -1558,9 +1562,9 @@
15581562
}
15591563
if( bMultiMerge && nConflict==0 ){
15601564
nMerge++;
15611565
goto merge_next_child;
15621566
}
1563
- if( !dryRunFlag ) undo_finish();
1567
+ if( useUndo ) undo_finish();
15641568
15651569
db_end_transaction(dryRunFlag);
15661570
}
15671571
--- src/merge.c
+++ src/merge.c
@@ -687,10 +687,11 @@
687 ** -K|--keep-merge-files On merge conflict, retain the temporary files
688 ** used for merging, named *-baseline, *-original,
689 ** and *-merge.
690 ** -n|--dry-run Do not actually change files on disk
691 ** --nosync Do not auto-sync prior to merging
 
692 ** -v|--verbose Show additional details of the merge
693 */
694 void merge_cmd(void){
695 int vid; /* Current version "V" */
696 int mid; /* Version we are merging from "M" */
@@ -712,10 +713,11 @@
712 int nOverwrite = 0; /* Number of unmanaged files overwritten */
713 char vAncestor = 'p'; /* If P is an ancestor of V then 'p', else 'n' */
714 const char *zVersion; /* The VERSION argument */
715 int bMultiMerge = 0; /* True if there are two or more VERSION arguments */
716 int nMerge = 0; /* Number of prior merges processed */
 
717 Stmt q; /* SQL statment used for merge processing */
718
719
720 /* Notation:
721 **
@@ -760,10 +762,12 @@
760 ** * The --dry-run option is also useful in combination with --debug.
761 */
762 debugFlag = find_option("debug",0,0)!=0;
763 if( debugFlag && verboseFlag ) debugFlag = 2;
764 showVfileFlag = find_option("show-vfile",0,0)!=0;
 
 
765
766 verify_all_options();
767 db_must_be_within_tree();
768 if( zBinGlob==0 ) zBinGlob = db_get("binary-glob",0);
769 vid = db_lget_int("checkout", 0);
@@ -926,11 +930,11 @@
926 integrateFlag ? "integrate:" : "merge-from:");
927 print_checkin_description(pid, 12, "baseline:");
928 }
929 vfile_check_signature(vid, CKSIG_ENOTFILE);
930 if( nMerge==0 ) db_begin_transaction();
931 if( !dryRunFlag ) undo_begin();
932 if( load_vfile_from_rid(mid) && !forceMissingFlag ){
933 fossil_fatal("missing content, unable to merge");
934 }
935 if( load_vfile_from_rid(pid) && !forceMissingFlag ){
936 fossil_fatal("missing content, unable to merge");
@@ -1183,12 +1187,12 @@
1183 int ridm = db_column_int(&q, 1);
1184 const char *zName = db_column_text(&q, 2);
1185 int islinkm = db_column_int(&q, 3);
1186 /* Copy content from idm over into idv. Overwrite idv. */
1187 fossil_print("UPDATE %s\n", zName);
 
1188 if( !dryRunFlag ){
1189 undo_save(zName);
1190 db_multi_exec(
1191 "UPDATE vfile SET mtime=0, mrid=%d, chnged=%d, islink=%d,"
1192 " mhash=CASE WHEN rid<>%d"
1193 " THEN (SELECT uuid FROM blob WHERE blob.rid=%d) END"
1194 " WHERE id=%d", ridm, integrateFlag?4:2, islinkm, ridm, ridm, idv
@@ -1265,11 +1269,11 @@
1265 }else{
1266 i64 sz;
1267 const char *zErrMsg = 0;
1268 int nc = 0;
1269
1270 if( !dryRunFlag ) undo_save(zName);
1271 zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
1272 sz = file_size(zFullPath, ExtFILE);
1273 content_get(ridp, &p);
1274 content_get(ridm, &m);
1275 if( isBinary ){
@@ -1352,11 +1356,11 @@
1352 zErrMsg = "local edits lost";
1353 zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
1354 sz = file_size(zFullPath, ExtFILE);
1355 fossil_free(zFullPath);
1356 }
1357 if( !dryRunFlag ) undo_save(zName);
1358 db_multi_exec(
1359 "UPDATE vfile SET deleted=1 WHERE id=%d", idv
1360 );
1361 if( !dryRunFlag ){
1362 char *zFullPath = mprintf("%s%s", g.zLocalRoot, zName);
@@ -1399,12 +1403,12 @@
1399 int idv = db_column_int(&q, 0);
1400 const char *zOldName = db_column_text(&q, 1);
1401 const char *zNewName = db_column_text(&q, 2);
1402 int isExe = db_column_int(&q, 3);
1403 fossil_print("RENAME %s -> %s\n", zOldName, zNewName);
1404 if( !dryRunFlag ) undo_save(zOldName);
1405 if( !dryRunFlag ) undo_save(zNewName);
1406 db_multi_exec(
1407 "UPDATE mergestat SET fnr=fnm WHERE fnp=%Q",
1408 zOldName
1409 );
1410 db_multi_exec(
@@ -1498,12 +1502,12 @@
1498 "VALUES('ADDED',%Q,%d,%Q)",
1499 /* fnm */ zName,
1500 /* ridm */ db_column_int(&q,2),
1501 /* fnr */ zName
1502 );
 
1503 if( !dryRunFlag ){
1504 undo_save(zName);
1505 vfile_to_disk(0, idm, 0, 0);
1506 }
1507 }
1508 db_finalize(&q);
1509
@@ -1558,9 +1562,9 @@
1558 }
1559 if( bMultiMerge && nConflict==0 ){
1560 nMerge++;
1561 goto merge_next_child;
1562 }
1563 if( !dryRunFlag ) undo_finish();
1564
1565 db_end_transaction(dryRunFlag);
1566 }
1567
--- src/merge.c
+++ src/merge.c
@@ -687,10 +687,11 @@
687 ** -K|--keep-merge-files On merge conflict, retain the temporary files
688 ** used for merging, named *-baseline, *-original,
689 ** and *-merge.
690 ** -n|--dry-run Do not actually change files on disk
691 ** --nosync Do not auto-sync prior to merging
692 ** --noundo Do not record changes in the undo log
693 ** -v|--verbose Show additional details of the merge
694 */
695 void merge_cmd(void){
696 int vid; /* Current version "V" */
697 int mid; /* Version we are merging from "M" */
@@ -712,10 +713,11 @@
713 int nOverwrite = 0; /* Number of unmanaged files overwritten */
714 char vAncestor = 'p'; /* If P is an ancestor of V then 'p', else 'n' */
715 const char *zVersion; /* The VERSION argument */
716 int bMultiMerge = 0; /* True if there are two or more VERSION arguments */
717 int nMerge = 0; /* Number of prior merges processed */
718 int useUndo = 1; /* True to record changes in the undo log */
719 Stmt q; /* SQL statment used for merge processing */
720
721
722 /* Notation:
723 **
@@ -760,10 +762,12 @@
762 ** * The --dry-run option is also useful in combination with --debug.
763 */
764 debugFlag = find_option("debug",0,0)!=0;
765 if( debugFlag && verboseFlag ) debugFlag = 2;
766 showVfileFlag = find_option("show-vfile",0,0)!=0;
767 useUndo = find_option("noundo",0,0)==0;
768 if( dryRunFlag ) useUndo = 0;
769
770 verify_all_options();
771 db_must_be_within_tree();
772 if( zBinGlob==0 ) zBinGlob = db_get("binary-glob",0);
773 vid = db_lget_int("checkout", 0);
@@ -926,11 +930,11 @@
930 integrateFlag ? "integrate:" : "merge-from:");
931 print_checkin_description(pid, 12, "baseline:");
932 }
933 vfile_check_signature(vid, CKSIG_ENOTFILE);
934 if( nMerge==0 ) db_begin_transaction();
935 if( useUndo ) undo_begin();
936 if( load_vfile_from_rid(mid) && !forceMissingFlag ){
937 fossil_fatal("missing content, unable to merge");
938 }
939 if( load_vfile_from_rid(pid) && !forceMissingFlag ){
940 fossil_fatal("missing content, unable to merge");
@@ -1183,12 +1187,12 @@
1187 int ridm = db_column_int(&q, 1);
1188 const char *zName = db_column_text(&q, 2);
1189 int islinkm = db_column_int(&q, 3);
1190 /* Copy content from idm over into idv. Overwrite idv. */
1191 fossil_print("UPDATE %s\n", zName);
1192 if( useUndo ) undo_save(zName);
1193 if( !dryRunFlag ){
 
1194 db_multi_exec(
1195 "UPDATE vfile SET mtime=0, mrid=%d, chnged=%d, islink=%d,"
1196 " mhash=CASE WHEN rid<>%d"
1197 " THEN (SELECT uuid FROM blob WHERE blob.rid=%d) END"
1198 " WHERE id=%d", ridm, integrateFlag?4:2, islinkm, ridm, ridm, idv
@@ -1265,11 +1269,11 @@
1269 }else{
1270 i64 sz;
1271 const char *zErrMsg = 0;
1272 int nc = 0;
1273
1274 if( useUndo ) undo_save(zName);
1275 zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
1276 sz = file_size(zFullPath, ExtFILE);
1277 content_get(ridp, &p);
1278 content_get(ridm, &m);
1279 if( isBinary ){
@@ -1352,11 +1356,11 @@
1356 zErrMsg = "local edits lost";
1357 zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
1358 sz = file_size(zFullPath, ExtFILE);
1359 fossil_free(zFullPath);
1360 }
1361 if( useUndo ) undo_save(zName);
1362 db_multi_exec(
1363 "UPDATE vfile SET deleted=1 WHERE id=%d", idv
1364 );
1365 if( !dryRunFlag ){
1366 char *zFullPath = mprintf("%s%s", g.zLocalRoot, zName);
@@ -1399,12 +1403,12 @@
1403 int idv = db_column_int(&q, 0);
1404 const char *zOldName = db_column_text(&q, 1);
1405 const char *zNewName = db_column_text(&q, 2);
1406 int isExe = db_column_int(&q, 3);
1407 fossil_print("RENAME %s -> %s\n", zOldName, zNewName);
1408 if( useUndo ) undo_save(zOldName);
1409 if( useUndo ) undo_save(zNewName);
1410 db_multi_exec(
1411 "UPDATE mergestat SET fnr=fnm WHERE fnp=%Q",
1412 zOldName
1413 );
1414 db_multi_exec(
@@ -1498,12 +1502,12 @@
1502 "VALUES('ADDED',%Q,%d,%Q)",
1503 /* fnm */ zName,
1504 /* ridm */ db_column_int(&q,2),
1505 /* fnr */ zName
1506 );
1507 if( useUndo ) undo_save(zName);
1508 if( !dryRunFlag ){
 
1509 vfile_to_disk(0, idm, 0, 0);
1510 }
1511 }
1512 db_finalize(&q);
1513
@@ -1558,9 +1562,9 @@
1562 }
1563 if( bMultiMerge && nConflict==0 ){
1564 nMerge++;
1565 goto merge_next_child;
1566 }
1567 if( useUndo ) undo_finish();
1568
1569 db_end_transaction(dryRunFlag);
1570 }
1571
+42 -3
--- src/patch.c
+++ src/patch.c
@@ -387,11 +387,11 @@
387387
if( unsaved_changes(0) ){
388388
if( (mFlags & PATCH_FORCE)==0 ){
389389
fossil_fatal("Cannot apply patch: there are unsaved changes "
390390
"in the current check-out");
391391
}else{
392
- blob_appendf(&cmd, "%$ revert", g.nameOfExe);
392
+ blob_appendf(&cmd, "%$ revert --noundo", g.nameOfExe);
393393
if( mFlags & PATCH_DRYRUN ){
394394
fossil_print("%s\n", blob_str(&cmd));
395395
}else{
396396
int rc = fossil_system(blob_str(&cmd));
397397
if( rc ){
@@ -429,23 +429,27 @@
429429
}
430430
}
431431
}
432432
blob_reset(&cmd);
433433
if( db_table_exists("patch","patchmerge") ){
434
+ int nMerge = 0;
434435
db_prepare(&q,
435436
"SELECT type, mhash, upper(type) FROM patch.patchmerge"
436437
" WHERE type IN ('merge','cherrypick','backout','integrate')"
437438
" AND mhash NOT GLOB '*[^a-fA-F0-9]*';"
438439
);
439440
while( db_step(&q)==SQLITE_ROW ){
440441
const char *zType = db_column_text(&q,0);
441442
blob_append_escaped_arg(&cmd, g.nameOfExe, 1);
442443
if( strcmp(zType,"merge")==0 ){
443
- blob_appendf(&cmd, " merge %s\n", db_column_text(&q,1));
444
+ blob_appendf(&cmd, " merge --noundo --nosync %s\n",
445
+ db_column_text(&q,1));
444446
}else{
445
- blob_appendf(&cmd, " merge --%s %s\n", zType, db_column_text(&q,1));
447
+ blob_appendf(&cmd, " merge --%s --noundo --nosync %s\n",
448
+ zType, db_column_text(&q,1));
446449
}
450
+ nMerge++;
447451
if( mFlags & PATCH_VERBOSE ){
448452
fossil_print("%-10s %s\n", db_column_text(&q,2),
449453
db_column_text(&q,0));
450454
}
451455
}
@@ -458,10 +462,45 @@
458462
fossil_fatal("unable to do merges:\n%s",
459463
blob_str(&cmd));
460464
}
461465
}
462466
blob_reset(&cmd);
467
+
468
+ /* 2024-12-16 https://fossil-scm.org/home/forumpost/51a37054
469
+ ** If one or more merge operations occurred in the patch and there are
470
+ ** files that are marked as "chnged' in the local VFILE but which
471
+ ** are not mentioned as having been modified in the patch, then
472
+ ** revert those files.
473
+ */
474
+ if( nMerge ){
475
+ int vid = db_lget_int("checkout", 0);
476
+ int nRevert = 0;
477
+ blob_append_escaped_arg(&cmd, g.nameOfExe, 1);
478
+ blob_appendf(&cmd, " revert --noundo ");
479
+ db_prepare(&q,
480
+ "SELECT pathname FROM vfile WHERE vid=%d AND chnged "
481
+ "EXCEPT SELECT pathname FROM chng",
482
+ vid
483
+ );
484
+ while( db_step(&q)==SQLITE_ROW ){
485
+ blob_append_escaped_arg(&cmd, db_column_text(&q,0), 1);
486
+ nRevert++;
487
+ }
488
+ db_finalize(&q);
489
+ if( nRevert ){
490
+ if( mFlags & PATCH_DRYRUN ){
491
+ fossil_print("%s", blob_str(&cmd));
492
+ }else{
493
+ int rc = fossil_unsafe_system(blob_str(&cmd));
494
+ if( rc ){
495
+ fossil_fatal("unable to do reverts:\n%s",
496
+ blob_str(&cmd));
497
+ }
498
+ }
499
+ }
500
+ blob_reset(&cmd);
501
+ }
463502
}
464503
465504
/* Deletions */
466505
db_prepare(&q, "SELECT pathname FROM patch.chng"
467506
" WHERE origname IS NULL AND delta IS NULL");
468507
--- src/patch.c
+++ src/patch.c
@@ -387,11 +387,11 @@
387 if( unsaved_changes(0) ){
388 if( (mFlags & PATCH_FORCE)==0 ){
389 fossil_fatal("Cannot apply patch: there are unsaved changes "
390 "in the current check-out");
391 }else{
392 blob_appendf(&cmd, "%$ revert", g.nameOfExe);
393 if( mFlags & PATCH_DRYRUN ){
394 fossil_print("%s\n", blob_str(&cmd));
395 }else{
396 int rc = fossil_system(blob_str(&cmd));
397 if( rc ){
@@ -429,23 +429,27 @@
429 }
430 }
431 }
432 blob_reset(&cmd);
433 if( db_table_exists("patch","patchmerge") ){
 
434 db_prepare(&q,
435 "SELECT type, mhash, upper(type) FROM patch.patchmerge"
436 " WHERE type IN ('merge','cherrypick','backout','integrate')"
437 " AND mhash NOT GLOB '*[^a-fA-F0-9]*';"
438 );
439 while( db_step(&q)==SQLITE_ROW ){
440 const char *zType = db_column_text(&q,0);
441 blob_append_escaped_arg(&cmd, g.nameOfExe, 1);
442 if( strcmp(zType,"merge")==0 ){
443 blob_appendf(&cmd, " merge %s\n", db_column_text(&q,1));
 
444 }else{
445 blob_appendf(&cmd, " merge --%s %s\n", zType, db_column_text(&q,1));
 
446 }
 
447 if( mFlags & PATCH_VERBOSE ){
448 fossil_print("%-10s %s\n", db_column_text(&q,2),
449 db_column_text(&q,0));
450 }
451 }
@@ -458,10 +462,45 @@
458 fossil_fatal("unable to do merges:\n%s",
459 blob_str(&cmd));
460 }
461 }
462 blob_reset(&cmd);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
463 }
464
465 /* Deletions */
466 db_prepare(&q, "SELECT pathname FROM patch.chng"
467 " WHERE origname IS NULL AND delta IS NULL");
468
--- src/patch.c
+++ src/patch.c
@@ -387,11 +387,11 @@
387 if( unsaved_changes(0) ){
388 if( (mFlags & PATCH_FORCE)==0 ){
389 fossil_fatal("Cannot apply patch: there are unsaved changes "
390 "in the current check-out");
391 }else{
392 blob_appendf(&cmd, "%$ revert --noundo", g.nameOfExe);
393 if( mFlags & PATCH_DRYRUN ){
394 fossil_print("%s\n", blob_str(&cmd));
395 }else{
396 int rc = fossil_system(blob_str(&cmd));
397 if( rc ){
@@ -429,23 +429,27 @@
429 }
430 }
431 }
432 blob_reset(&cmd);
433 if( db_table_exists("patch","patchmerge") ){
434 int nMerge = 0;
435 db_prepare(&q,
436 "SELECT type, mhash, upper(type) FROM patch.patchmerge"
437 " WHERE type IN ('merge','cherrypick','backout','integrate')"
438 " AND mhash NOT GLOB '*[^a-fA-F0-9]*';"
439 );
440 while( db_step(&q)==SQLITE_ROW ){
441 const char *zType = db_column_text(&q,0);
442 blob_append_escaped_arg(&cmd, g.nameOfExe, 1);
443 if( strcmp(zType,"merge")==0 ){
444 blob_appendf(&cmd, " merge --noundo --nosync %s\n",
445 db_column_text(&q,1));
446 }else{
447 blob_appendf(&cmd, " merge --%s --noundo --nosync %s\n",
448 zType, db_column_text(&q,1));
449 }
450 nMerge++;
451 if( mFlags & PATCH_VERBOSE ){
452 fossil_print("%-10s %s\n", db_column_text(&q,2),
453 db_column_text(&q,0));
454 }
455 }
@@ -458,10 +462,45 @@
462 fossil_fatal("unable to do merges:\n%s",
463 blob_str(&cmd));
464 }
465 }
466 blob_reset(&cmd);
467
468 /* 2024-12-16 https://fossil-scm.org/home/forumpost/51a37054
469 ** If one or more merge operations occurred in the patch and there are
470 ** files that are marked as "chnged' in the local VFILE but which
471 ** are not mentioned as having been modified in the patch, then
472 ** revert those files.
473 */
474 if( nMerge ){
475 int vid = db_lget_int("checkout", 0);
476 int nRevert = 0;
477 blob_append_escaped_arg(&cmd, g.nameOfExe, 1);
478 blob_appendf(&cmd, " revert --noundo ");
479 db_prepare(&q,
480 "SELECT pathname FROM vfile WHERE vid=%d AND chnged "
481 "EXCEPT SELECT pathname FROM chng",
482 vid
483 );
484 while( db_step(&q)==SQLITE_ROW ){
485 blob_append_escaped_arg(&cmd, db_column_text(&q,0), 1);
486 nRevert++;
487 }
488 db_finalize(&q);
489 if( nRevert ){
490 if( mFlags & PATCH_DRYRUN ){
491 fossil_print("%s", blob_str(&cmd));
492 }else{
493 int rc = fossil_unsafe_system(blob_str(&cmd));
494 if( rc ){
495 fossil_fatal("unable to do reverts:\n%s",
496 blob_str(&cmd));
497 }
498 }
499 }
500 blob_reset(&cmd);
501 }
502 }
503
504 /* Deletions */
505 db_prepare(&q, "SELECT pathname FROM patch.chng"
506 " WHERE origname IS NULL AND delta IS NULL");
507
+11 -4
--- src/update.c
+++ src/update.c
@@ -854,10 +854,11 @@
854854
**
855855
** If a file is reverted accidentally, it can be restored using
856856
** the "fossil undo" command.
857857
**
858858
** Options:
859
+** --noundo Do not record changes in the undo/redo log.
859860
** -r|--revision VERSION Revert given FILE(s) back to given
860861
** VERSION
861862
**
862863
** See also: [[redo]], [[undo]], [[checkout]], [[update]]
863864
*/
@@ -867,17 +868,19 @@
867868
ManifestFile *pCoFile; /* File within current check-out manifest */
868869
ManifestFile *pRvFile; /* File within revert version manifest */
869870
const char *zFile; /* Filename relative to check-out root */
870871
const char *zRevision; /* Selected revert version, NULL if current */
871872
Blob record = BLOB_INITIALIZER; /* Contents of each reverted file */
873
+ int useUndo = 1; /* True to record changes in UNDO */
872874
int i;
873875
Stmt q;
874876
int revertAll = 0;
875877
int revisionOptNotSupported = 0;
876878
877879
undo_capture_command_line();
878880
zRevision = find_option("revision", "r", 1);
881
+ useUndo = find_option("noundo", 0, 0)==0;
879882
verify_all_options();
880883
881884
if( g.argc<2 ){
882885
usage("?OPTIONS? [FILE] ...");
883886
}
@@ -890,11 +893,15 @@
890893
/* Get manifests of revert version and (if different) current check-out. */
891894
pRvManifest = historical_manifest(zRevision);
892895
pCoManifest = zRevision ? historical_manifest(0) : 0;
893896
894897
db_begin_transaction();
895
- undo_begin();
898
+ if( useUndo ){
899
+ undo_begin();
900
+ }else{
901
+ undo_reset();
902
+ }
896903
db_multi_exec("CREATE TEMP TABLE torevert(name UNIQUE);");
897904
898905
if( g.argc>2 ){
899906
for(i=2; i<g.argc; i++){
900907
Blob fname;
@@ -987,11 +994,11 @@
987994
if( !pRvFile ){
988995
if( db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q OR origname=%Q",
989996
zFile, zFile)==0 ){
990997
fossil_print("UNMANAGE %s\n", zFile);
991998
}else{
992
- undo_save(zFile);
999
+ if( useUndo ) undo_save(zFile);
9931000
file_delete(zFull);
9941001
fossil_print("DELETE %s\n", zFile);
9951002
}
9961003
db_multi_exec(
9971004
"UPDATE OR REPLACE vfile"
@@ -1014,11 +1021,11 @@
10141021
}
10151022
10161023
/* Get contents of reverted-to file. */
10171024
content_get(fast_uuid_to_rid(pRvFile->zUuid), &record);
10181025
1019
- undo_save(zFile);
1026
+ if( useUndo ) undo_save(zFile);
10201027
if( file_size(zFull, RepoFILE)>=0
10211028
&& (rvPerm==PERM_LNK || file_islink(0))
10221029
){
10231030
file_delete(zFull);
10241031
}
@@ -1040,12 +1047,12 @@
10401047
}
10411048
blob_reset(&record);
10421049
free(zFull);
10431050
}
10441051
db_finalize(&q);
1045
- undo_finish();
1052
+ if( useUndo) undo_finish();
10461053
db_end_transaction(0);
10471054
10481055
/* Deallocate parsed manifest structures. */
10491056
manifest_destroy(pRvManifest);
10501057
manifest_destroy(pCoManifest);
10511058
}
10521059
--- src/update.c
+++ src/update.c
@@ -854,10 +854,11 @@
854 **
855 ** If a file is reverted accidentally, it can be restored using
856 ** the "fossil undo" command.
857 **
858 ** Options:
 
859 ** -r|--revision VERSION Revert given FILE(s) back to given
860 ** VERSION
861 **
862 ** See also: [[redo]], [[undo]], [[checkout]], [[update]]
863 */
@@ -867,17 +868,19 @@
867 ManifestFile *pCoFile; /* File within current check-out manifest */
868 ManifestFile *pRvFile; /* File within revert version manifest */
869 const char *zFile; /* Filename relative to check-out root */
870 const char *zRevision; /* Selected revert version, NULL if current */
871 Blob record = BLOB_INITIALIZER; /* Contents of each reverted file */
 
872 int i;
873 Stmt q;
874 int revertAll = 0;
875 int revisionOptNotSupported = 0;
876
877 undo_capture_command_line();
878 zRevision = find_option("revision", "r", 1);
 
879 verify_all_options();
880
881 if( g.argc<2 ){
882 usage("?OPTIONS? [FILE] ...");
883 }
@@ -890,11 +893,15 @@
890 /* Get manifests of revert version and (if different) current check-out. */
891 pRvManifest = historical_manifest(zRevision);
892 pCoManifest = zRevision ? historical_manifest(0) : 0;
893
894 db_begin_transaction();
895 undo_begin();
 
 
 
 
896 db_multi_exec("CREATE TEMP TABLE torevert(name UNIQUE);");
897
898 if( g.argc>2 ){
899 for(i=2; i<g.argc; i++){
900 Blob fname;
@@ -987,11 +994,11 @@
987 if( !pRvFile ){
988 if( db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q OR origname=%Q",
989 zFile, zFile)==0 ){
990 fossil_print("UNMANAGE %s\n", zFile);
991 }else{
992 undo_save(zFile);
993 file_delete(zFull);
994 fossil_print("DELETE %s\n", zFile);
995 }
996 db_multi_exec(
997 "UPDATE OR REPLACE vfile"
@@ -1014,11 +1021,11 @@
1014 }
1015
1016 /* Get contents of reverted-to file. */
1017 content_get(fast_uuid_to_rid(pRvFile->zUuid), &record);
1018
1019 undo_save(zFile);
1020 if( file_size(zFull, RepoFILE)>=0
1021 && (rvPerm==PERM_LNK || file_islink(0))
1022 ){
1023 file_delete(zFull);
1024 }
@@ -1040,12 +1047,12 @@
1040 }
1041 blob_reset(&record);
1042 free(zFull);
1043 }
1044 db_finalize(&q);
1045 undo_finish();
1046 db_end_transaction(0);
1047
1048 /* Deallocate parsed manifest structures. */
1049 manifest_destroy(pRvManifest);
1050 manifest_destroy(pCoManifest);
1051 }
1052
--- src/update.c
+++ src/update.c
@@ -854,10 +854,11 @@
854 **
855 ** If a file is reverted accidentally, it can be restored using
856 ** the "fossil undo" command.
857 **
858 ** Options:
859 ** --noundo Do not record changes in the undo/redo log.
860 ** -r|--revision VERSION Revert given FILE(s) back to given
861 ** VERSION
862 **
863 ** See also: [[redo]], [[undo]], [[checkout]], [[update]]
864 */
@@ -867,17 +868,19 @@
868 ManifestFile *pCoFile; /* File within current check-out manifest */
869 ManifestFile *pRvFile; /* File within revert version manifest */
870 const char *zFile; /* Filename relative to check-out root */
871 const char *zRevision; /* Selected revert version, NULL if current */
872 Blob record = BLOB_INITIALIZER; /* Contents of each reverted file */
873 int useUndo = 1; /* True to record changes in UNDO */
874 int i;
875 Stmt q;
876 int revertAll = 0;
877 int revisionOptNotSupported = 0;
878
879 undo_capture_command_line();
880 zRevision = find_option("revision", "r", 1);
881 useUndo = find_option("noundo", 0, 0)==0;
882 verify_all_options();
883
884 if( g.argc<2 ){
885 usage("?OPTIONS? [FILE] ...");
886 }
@@ -890,11 +893,15 @@
893 /* Get manifests of revert version and (if different) current check-out. */
894 pRvManifest = historical_manifest(zRevision);
895 pCoManifest = zRevision ? historical_manifest(0) : 0;
896
897 db_begin_transaction();
898 if( useUndo ){
899 undo_begin();
900 }else{
901 undo_reset();
902 }
903 db_multi_exec("CREATE TEMP TABLE torevert(name UNIQUE);");
904
905 if( g.argc>2 ){
906 for(i=2; i<g.argc; i++){
907 Blob fname;
@@ -987,11 +994,11 @@
994 if( !pRvFile ){
995 if( db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q OR origname=%Q",
996 zFile, zFile)==0 ){
997 fossil_print("UNMANAGE %s\n", zFile);
998 }else{
999 if( useUndo ) undo_save(zFile);
1000 file_delete(zFull);
1001 fossil_print("DELETE %s\n", zFile);
1002 }
1003 db_multi_exec(
1004 "UPDATE OR REPLACE vfile"
@@ -1014,11 +1021,11 @@
1021 }
1022
1023 /* Get contents of reverted-to file. */
1024 content_get(fast_uuid_to_rid(pRvFile->zUuid), &record);
1025
1026 if( useUndo ) undo_save(zFile);
1027 if( file_size(zFull, RepoFILE)>=0
1028 && (rvPerm==PERM_LNK || file_islink(0))
1029 ){
1030 file_delete(zFull);
1031 }
@@ -1040,12 +1047,12 @@
1047 }
1048 blob_reset(&record);
1049 free(zFull);
1050 }
1051 db_finalize(&q);
1052 if( useUndo) undo_finish();
1053 db_end_transaction(0);
1054
1055 /* Deallocate parsed manifest structures. */
1056 manifest_destroy(pRvManifest);
1057 manifest_destroy(pCoManifest);
1058 }
1059
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,13 +1,18 @@
11
<title>Change Log</title>
22
33
<h2 id='v2_26'>Changes for version 2.26 (pending)</h2>
44
5
+ * Enhanced the --from option on "[/help?cmd=diff|fossil diff]" so that
6
+ it optionally accepts a directory name as its argument, and uses files
7
+ under that directory as the baseline for the diff.
58
* Added the [/help?cmd=/ckout|/ckout web page] to provide information
69
about pending changes in a working check-out
7
- * The [/help?cmd=ui|fossil ui] command defaults to using the /ckout
8
- page as its start page.
10
+ * The [/help?cmd=ui|fossil ui] command defaults to using the
11
+ [/help?cmd=/ckout|/ckout page] as its start page. Or, if the
12
+ "--from PATH" option is present, the default start page becomes
13
+ "/ckout?exbase=PATH".
914
* Added the [/help?cmd=merge-info|fossil merge-info] command and especially
1015
the --tk option to that command, to provide analysis of the most recent
1116
merge or update operation.
1217
* Issue a warning if a user tries to commit on a check-in where the
1318
branch has been changed.
1419
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,13 +1,18 @@
1 <title>Change Log</title>
2
3 <h2 id='v2_26'>Changes for version 2.26 (pending)</h2>
4
 
 
 
5 * Added the [/help?cmd=/ckout|/ckout web page] to provide information
6 about pending changes in a working check-out
7 * The [/help?cmd=ui|fossil ui] command defaults to using the /ckout
8 page as its start page.
 
 
9 * Added the [/help?cmd=merge-info|fossil merge-info] command and especially
10 the --tk option to that command, to provide analysis of the most recent
11 merge or update operation.
12 * Issue a warning if a user tries to commit on a check-in where the
13 branch has been changed.
14
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,13 +1,18 @@
1 <title>Change Log</title>
2
3 <h2 id='v2_26'>Changes for version 2.26 (pending)</h2>
4
5 * Enhanced the --from option on "[/help?cmd=diff|fossil diff]" so that
6 it optionally accepts a directory name as its argument, and uses files
7 under that directory as the baseline for the diff.
8 * Added the [/help?cmd=/ckout|/ckout web page] to provide information
9 about pending changes in a working check-out
10 * The [/help?cmd=ui|fossil ui] command defaults to using the
11 [/help?cmd=/ckout|/ckout page] as its start page. Or, if the
12 "--from PATH" option is present, the default start page becomes
13 "/ckout?exbase=PATH".
14 * Added the [/help?cmd=merge-info|fossil merge-info] command and especially
15 the --tk option to that command, to provide analysis of the most recent
16 merge or update operation.
17 * Issue a warning if a user tries to commit on a check-in where the
18 branch has been changed.
19

Keyboard Shortcuts

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