Fossil SCM
Sync with trunk.
Commit
361fc76769c0e976629fba0e519e95cef803aa2cc9450faa56bb17a74aab0be5
Parent
aa7ddd8094688f0…
12 files changed
+2
-2
+5
-1
+92
-16
+24
-13
+24
-13
+210
-62
+210
-62
+10
+12
-8
+42
-3
+11
-4
+7
-2
+2
-2
| --- src/checkin.c | ||
| +++ src/checkin.c | ||
| @@ -1563,17 +1563,17 @@ | ||
| 1563 | 1563 | break; |
| 1564 | 1564 | } |
| 1565 | 1565 | diffFiles[i].nName = strlen(diffFiles[i].zName); |
| 1566 | 1566 | diffFiles[i].nUsed = 0; |
| 1567 | 1567 | } |
| 1568 | - diff_against_disk(0, &DCfg, diffFiles, &prompt); | |
| 1568 | + diff_version_to_checkout(0, &DCfg, diffFiles, &prompt); | |
| 1569 | 1569 | for( i=0; diffFiles[i].zName; ++i ){ |
| 1570 | 1570 | fossil_free(diffFiles[i].zName); |
| 1571 | 1571 | } |
| 1572 | 1572 | fossil_free(diffFiles); |
| 1573 | 1573 | }else{ |
| 1574 | - diff_against_disk(0, &DCfg, 0, &prompt); | |
| 1574 | + diff_version_to_checkout(0, &DCfg, 0, &prompt); | |
| 1575 | 1575 | } |
| 1576 | 1576 | } |
| 1577 | 1577 | prompt_for_user_comment(pComment, &prompt); |
| 1578 | 1578 | blob_reset(&prompt); |
| 1579 | 1579 | } |
| 1580 | 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_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 @@ | ||
| 110 | 110 | |
| 111 | 111 | set fromIndex [lsearch -glob $fossilcmd *-from] |
| 112 | 112 | set toIndex [lsearch -glob $fossilcmd *-to] |
| 113 | 113 | set branchIndex [lsearch -glob $fossilcmd *-branch] |
| 114 | 114 | 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 | + } | |
| 116 | 120 | set fB {current check-out} |
| 117 | 121 | if {$fromIndex > -1} {set fA [lindex $fossilcmd $fromIndex+1]} |
| 118 | 122 | if {$toIndex > -1} {set fB [lindex $fossilcmd $toIndex+1]} |
| 119 | 123 | if {$branchIndex > -1} {set fA "branch point"; set fB "leaf of branch '[lindex $fossilcmd $branchIndex+1]'"} |
| 120 | 124 | if {$checkinIndex > -1} {set fA "primary parent"; set fB [lindex $fossilcmd $checkinIndex+1]} |
| 121 | 125 |
| --- 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 @@ | ||
| 784 | 784 | blob_reset(&file); |
| 785 | 785 | return rc; |
| 786 | 786 | } |
| 787 | 787 | |
| 788 | 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. | |
| 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. | |
| 792 | 793 | ** |
| 793 | 794 | ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the |
| 794 | 795 | ** command zDiffCmd to do the diffing. |
| 795 | 796 | ** |
| 796 | 797 | ** When using an external diff program, zBinGlob contains the GLOB patterns |
| 797 | 798 | ** for file names to treat as binary. If fIncludeBinary is zero, these files |
| 798 | 799 | ** will be skipped in addition to files that may contain binary content. |
| 799 | 800 | */ |
| 800 | -void diff_against_disk( | |
| 801 | +void diff_version_to_checkout( | |
| 801 | 802 | const char *zFrom, /* Version to difference from */ |
| 802 | 803 | DiffConfig *pCfg, /* Flags controlling diff output */ |
| 803 | 804 | 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 */ | |
| 805 | 806 | ){ |
| 806 | 807 | int vid; |
| 807 | 808 | Blob sql; |
| 808 | 809 | Stmt q; |
| 809 | 810 | int asNewFile; /* Treat non-existant files as empty files */ |
| @@ -928,20 +929,20 @@ | ||
| 928 | 929 | db_finalize(&q); |
| 929 | 930 | db_end_transaction(1); /* ROLLBACK */ |
| 930 | 931 | } |
| 931 | 932 | |
| 932 | 933 | /* |
| 933 | -** Run a diff between the undo buffer and files on disk. | |
| 934 | +** Run a diff from the undo buffer to files on disk. | |
| 934 | 935 | ** |
| 935 | 936 | ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the |
| 936 | 937 | ** command zDiffCmd to do the diffing. |
| 937 | 938 | ** |
| 938 | 939 | ** When using an external diff program, zBinGlob contains the GLOB patterns |
| 939 | 940 | ** for file names to treat as binary. If fIncludeBinary is zero, these files |
| 940 | 941 | ** will be skipped in addition to files that may contain binary content. |
| 941 | 942 | */ |
| 942 | -static void diff_against_undo( | |
| 943 | +static void diff_undo_to_checkout( | |
| 943 | 944 | DiffConfig *pCfg, /* Flags controlling diff output */ |
| 944 | 945 | FileDirList *pFileDir /* List of files and directories to diff */ |
| 945 | 946 | ){ |
| 946 | 947 | Stmt q; |
| 947 | 948 | Blob content; |
| @@ -1088,10 +1089,67 @@ | ||
| 1088 | 1089 | } |
| 1089 | 1090 | } |
| 1090 | 1091 | manifest_destroy(pFrom); |
| 1091 | 1092 | manifest_destroy(pTo); |
| 1092 | 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 | + | |
| 1093 | 1151 | |
| 1094 | 1152 | /* |
| 1095 | 1153 | ** Return the name of the external diff command, or return NULL if |
| 1096 | 1154 | ** no external diff command is defined. |
| 1097 | 1155 | */ |
| @@ -1224,10 +1282,14 @@ | ||
| 1224 | 1282 | ** option specifies the check-in from which the second version of the file |
| 1225 | 1283 | ** or files is taken. If there is no "--to" option then the (possibly edited) |
| 1226 | 1284 | ** files in the current check-out are used. The "--checkin VERSION" option |
| 1227 | 1285 | ** shows the changes made by check-in VERSION relative to its primary parent. |
| 1228 | 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. | |
| 1229 | 1291 | ** |
| 1230 | 1292 | ** The "-i" command-line option forces the use of Fossil's own internal |
| 1231 | 1293 | ** diff logic rather than any external diff program that might be configured |
| 1232 | 1294 | ** using the "setting" command. If no external diff program is configured, |
| 1233 | 1295 | ** then the "-i" option is a no-op. The "-i" option converts "gdiff" into |
| @@ -1256,11 +1318,13 @@ | ||
| 1256 | 1318 | ** with negative N meaning show all content |
| 1257 | 1319 | ** --dark Use dark mode for the Tcl/Tk-based GUI and HTML |
| 1258 | 1320 | ** --diff-binary BOOL Include binary files with external commands |
| 1259 | 1321 | ** --exec-abs-paths Force absolute path names on external commands |
| 1260 | 1322 | ** --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. | |
| 1262 | 1326 | ** -w|--ignore-all-space Ignore white space when comparing lines |
| 1263 | 1327 | ** -i|--internal Use internal diff logic |
| 1264 | 1328 | ** --invert Invert the diff |
| 1265 | 1329 | ** --json Output formatted as JSON |
| 1266 | 1330 | ** -n|--linenum Show line numbers |
| @@ -1270,11 +1334,11 @@ | ||
| 1270 | 1334 | ** --strip-trailing-cr Strip trailing CR |
| 1271 | 1335 | ** --tcl Tcl-formatted output used internally by --tk |
| 1272 | 1336 | ** --tclsh PATH Tcl/Tk shell used for --tk (default: "tclsh") |
| 1273 | 1337 | ** --tk Launch a Tcl/Tk GUI for display |
| 1274 | 1338 | ** --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 | |
| 1276 | 1340 | ** --unified Unified diff |
| 1277 | 1341 | ** -v|--verbose Output complete text of added or deleted files |
| 1278 | 1342 | ** -h|--versions Show compared versions in the diff header |
| 1279 | 1343 | ** --webpage Format output as a stand-alone HTML webpage |
| 1280 | 1344 | ** -W|--width N Width of lines in side-by-side diff |
| @@ -1287,10 +1351,11 @@ | ||
| 1287 | 1351 | const char *zCheckin; /* Check-in version number */ |
| 1288 | 1352 | const char *zBranch; /* Branch to diff */ |
| 1289 | 1353 | int againstUndo = 0; /* Diff against files in the undo buffer */ |
| 1290 | 1354 | FileDirList *pFileDir = 0; /* Restrict the diff to these files */ |
| 1291 | 1355 | DiffConfig DCfg; /* Diff configuration object */ |
| 1356 | + int bFromIsDir = 0; /* True if zFrom is a directory name */ | |
| 1292 | 1357 | |
| 1293 | 1358 | if( find_option("tk",0,0)!=0 || has_option("tclsh") ){ |
| 1294 | 1359 | diff_tk("diff", 2); |
| 1295 | 1360 | return; |
| 1296 | 1361 | } |
| @@ -1298,11 +1363,11 @@ | ||
| 1298 | 1363 | zFrom = find_option("from", "r", 1); |
| 1299 | 1364 | zTo = find_option("to", 0, 1); |
| 1300 | 1365 | zCheckin = find_option("checkin", "ci", 1); |
| 1301 | 1366 | zBranch = find_option("branch", 0, 1); |
| 1302 | 1367 | 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) ){ | |
| 1304 | 1369 | fossil_fatal("cannot use --undo together with --from, --to, --checkin," |
| 1305 | 1370 | " or --branch"); |
| 1306 | 1371 | } |
| 1307 | 1372 | if( zBranch ){ |
| 1308 | 1373 | if( zTo || zFrom || zCheckin ){ |
| @@ -1309,15 +1374,13 @@ | ||
| 1309 | 1374 | fossil_fatal("cannot use --from, --to, or --checkin with --branch"); |
| 1310 | 1375 | } |
| 1311 | 1376 | zTo = zBranch; |
| 1312 | 1377 | zFrom = mprintf("root:%s", zBranch); |
| 1313 | 1378 | } |
| 1314 | - if( zCheckin!=0 && ( zFrom!=0 || zTo!=0 ) ){ | |
| 1379 | + if( zCheckin!=0 && (zFrom!=0 || zTo!=0) ){ | |
| 1315 | 1380 | fossil_fatal("cannot use --checkin together with --from or --to"); |
| 1316 | 1381 | } |
| 1317 | - diff_options(&DCfg, isGDiff, 0); | |
| 1318 | - determine_exec_relative_option(1); | |
| 1319 | 1382 | if( 0==zCheckin ){ |
| 1320 | 1383 | if( zTo==0 || againstUndo ){ |
| 1321 | 1384 | db_must_be_within_tree(); |
| 1322 | 1385 | }else if( zFrom==0 ){ |
| 1323 | 1386 | fossil_fatal("must use --from if --to is present"); |
| @@ -1325,10 +1388,21 @@ | ||
| 1325 | 1388 | db_find_and_open_repository(0, 0); |
| 1326 | 1389 | } |
| 1327 | 1390 | }else{ |
| 1328 | 1391 | db_find_and_open_repository(0, 0); |
| 1329 | 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); | |
| 1330 | 1404 | verify_all_options(); |
| 1331 | 1405 | g.diffCnt[0] = g.diffCnt[1] = g.diffCnt[2] = 0; |
| 1332 | 1406 | if( g.argc>=3 ){ |
| 1333 | 1407 | int i; |
| 1334 | 1408 | Blob fname; |
| @@ -1357,18 +1431,20 @@ | ||
| 1357 | 1431 | if( zFrom==0 ){ |
| 1358 | 1432 | fossil_fatal("check-in %s has no parent", zTo); |
| 1359 | 1433 | } |
| 1360 | 1434 | } |
| 1361 | 1435 | diff_begin(&DCfg); |
| 1362 | - if( againstUndo ){ | |
| 1436 | + if( bFromIsDir ){ | |
| 1437 | + diff_externbase_to_checkout(zFrom, &DCfg, pFileDir); | |
| 1438 | + }else if( againstUndo ){ | |
| 1363 | 1439 | if( db_lget_int("undo_available",0)==0 ){ |
| 1364 | 1440 | fossil_print("No undo or redo is available\n"); |
| 1365 | 1441 | return; |
| 1366 | 1442 | } |
| 1367 | - diff_against_undo(&DCfg, pFileDir); | |
| 1443 | + diff_undo_to_checkout(&DCfg, pFileDir); | |
| 1368 | 1444 | }else if( zTo==0 ){ |
| 1369 | - diff_against_disk(zFrom, &DCfg, pFileDir, 0); | |
| 1445 | + diff_version_to_checkout(zFrom, &DCfg, pFileDir, 0); | |
| 1370 | 1446 | }else{ |
| 1371 | 1447 | diff_two_versions(zFrom, zTo, &DCfg, pFileDir); |
| 1372 | 1448 | } |
| 1373 | 1449 | if( pFileDir ){ |
| 1374 | 1450 | int i; |
| 1375 | 1451 |
| --- 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 @@ | ||
| 28 | 28 | window.fossil.onPageLoad(function(){ |
| 29 | 29 | /** |
| 30 | 30 | Adds toggle checkboxes to each file entry in the diff views for |
| 31 | 31 | /info and similar pages. |
| 32 | 32 | */ |
| 33 | + if( !window.fossil.page.diffControlContainer ){ | |
| 34 | + return; | |
| 35 | + } | |
| 33 | 36 | 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. */ | |
| 35 | 44 | const addToggle = function(diffElem){ |
| 36 | 45 | const sib = diffElem.previousElementSibling, |
| 37 | 46 | ckbox = sib ? D.addClass(D.checkbox(true), 'diff-toggle') : 0; |
| 38 | 47 | if(!sib) return; |
| 39 | 48 | const lblToggle = D.label(); |
| 40 | 49 | D.append(lblToggle, ckbox, D.text(" show/hide ")); |
| 41 | 50 | const wrapper = D.append(D.span(), lblToggle); |
| 42 | 51 | allToggles.push(ckbox); |
| 52 | + ++checkedCount; | |
| 43 | 53 | D.append(sib, D.append(wrapper, lblToggle)); |
| 44 | 54 | ckbox.addEventListener('change', function(){ |
| 45 | 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 | + } | |
| 46 | 61 | }, false); |
| 47 | 62 | }; |
| 48 | 63 | if( !document.querySelector('body.fdiff') ){ |
| 49 | 64 | /* Don't show the diff toggle button for /fdiff because it only |
| 50 | 65 | has a single file to show (and also a different DOM layout). */ |
| 51 | 66 | document.querySelectorAll('table.diff').forEach(addToggle); |
| 52 | 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 | + */ | |
| 53 | 73 | const icm = allToggles.length>1 ? window.fossil.page.diffControlContainer : 0; |
| 54 | 74 | if(icm) { |
| 55 | - const btnAll = D.addClass(D.a("#", "Show/Hide"), "button"); | |
| 75 | + btnAll = D.addClass(D.a("#", "Hide diffs"), "button"); | |
| 56 | 76 | D.append( icm, btnAll ); |
| 57 | 77 | btnAll.addEventListener('click', function(ev){ |
| 58 | 78 | ev.preventDefault(); |
| 59 | 79 | 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 ){ | |
| 71 | 82 | /* Toggle all entries to match this new state. We use click() |
| 72 | 83 | instead of ckbox.checked=... so that the on-change event handler |
| 73 | 84 | fires. */ |
| 74 | 85 | if(ckbox.checked!==show) ckbox.click(); |
| 75 | 86 | } |
| 76 | 87 |
| --- 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 @@ | ||
| 28 | 28 | window.fossil.onPageLoad(function(){ |
| 29 | 29 | /** |
| 30 | 30 | Adds toggle checkboxes to each file entry in the diff views for |
| 31 | 31 | /info and similar pages. |
| 32 | 32 | */ |
| 33 | + if( !window.fossil.page.diffControlContainer ){ | |
| 34 | + return; | |
| 35 | + } | |
| 33 | 36 | 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. */ | |
| 35 | 44 | const addToggle = function(diffElem){ |
| 36 | 45 | const sib = diffElem.previousElementSibling, |
| 37 | 46 | ckbox = sib ? D.addClass(D.checkbox(true), 'diff-toggle') : 0; |
| 38 | 47 | if(!sib) return; |
| 39 | 48 | const lblToggle = D.label(); |
| 40 | 49 | D.append(lblToggle, ckbox, D.text(" show/hide ")); |
| 41 | 50 | const wrapper = D.append(D.span(), lblToggle); |
| 42 | 51 | allToggles.push(ckbox); |
| 52 | + ++checkedCount; | |
| 43 | 53 | D.append(sib, D.append(wrapper, lblToggle)); |
| 44 | 54 | ckbox.addEventListener('change', function(){ |
| 45 | 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 | + } | |
| 46 | 61 | }, false); |
| 47 | 62 | }; |
| 48 | 63 | if( !document.querySelector('body.fdiff') ){ |
| 49 | 64 | /* Don't show the diff toggle button for /fdiff because it only |
| 50 | 65 | has a single file to show (and also a different DOM layout). */ |
| 51 | 66 | document.querySelectorAll('table.diff').forEach(addToggle); |
| 52 | 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 | + */ | |
| 53 | 73 | const icm = allToggles.length>1 ? window.fossil.page.diffControlContainer : 0; |
| 54 | 74 | if(icm) { |
| 55 | - const btnAll = D.addClass(D.a("#", "Show/Hide"), "button"); | |
| 75 | + btnAll = D.addClass(D.a("#", "Hide diffs"), "button"); | |
| 56 | 76 | D.append( icm, btnAll ); |
| 57 | 77 | btnAll.addEventListener('click', function(ev){ |
| 58 | 78 | ev.preventDefault(); |
| 59 | 79 | 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 ){ | |
| 71 | 82 | /* Toggle all entries to match this new state. We use click() |
| 72 | 83 | instead of ckbox.checked=... so that the on-change event handler |
| 73 | 84 | fires. */ |
| 74 | 85 | if(ckbox.checked!==show) ckbox.click(); |
| 75 | 86 | } |
| 76 | 87 |
| --- 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 @@ | ||
| 609 | 609 | db_finalize(&q); |
| 610 | 610 | style_finish_page(); |
| 611 | 611 | } |
| 612 | 612 | |
| 613 | 613 | /* |
| 614 | -** WEBPAGE: ckout | |
| 615 | -** | |
| 616 | -** Show information about the current checkout. This page only functions | |
| 617 | -** if the web server is run on a loopback interface (in other words, was | |
| 618 | -** started using "fossil ui" or similar) from with on open check-out. | |
| 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 | |
| 622 | 615 | */ |
| 623 | -void ckout_page(void){ | |
| 624 | - int vid; | |
| 625 | - char *zHostname; | |
| 626 | - char *zCwd; | |
| 616 | +static void ckout_normal_diff(int vid){ | |
| 627 | 617 | int diffType; /* 0: no diff, 1: unified, 2: side-by-side */ |
| 628 | 618 | DiffConfig DCfg,*pCfg; /* Diff details */ |
| 629 | - const char *zHome; /* Home directory */ | |
| 630 | 619 | const char *zW; /* The "w" query parameter */ |
| 631 | 620 | int nChng; /* Number of changes */ |
| 632 | 621 | Stmt q; |
| 633 | 622 | |
| 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 | 623 | diffType = preferred_diff_type(); |
| 640 | 624 | 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 | 625 | nChng = db_int(0, "SELECT count(*) FROM vfile" |
| 662 | 626 | " WHERE vid=%d AND (deleted OR chnged OR rid==0)", vid); |
| 663 | 627 | if( nChng==0 ){ |
| 664 | 628 | @ <p>No uncommitted changes</p> |
| 665 | - style_finish_page(); | |
| 666 | 629 | return; |
| 667 | 630 | } |
| 668 | 631 | db_prepare(&q, |
| 669 | 632 | /* 0 1 2 3 4 5 6 */ |
| 670 | 633 | "SELECT pathname, deleted, chnged , rid==0, rid, islink, uuid" |
| @@ -677,11 +640,10 @@ | ||
| 677 | 640 | if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){ |
| 678 | 641 | DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG; |
| 679 | 642 | }else{ |
| 680 | 643 | DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG; |
| 681 | 644 | } |
| 682 | - @ <hr> | |
| 683 | 645 | @ <div class="sectionmenu info-changes-menu"> |
| 684 | 646 | zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":""; |
| 685 | 647 | if( diffType!=0 ){ |
| 686 | 648 | /* Class "smb-hide-diff" required by the fossil.diff.js script. */ |
| 687 | 649 | const char *zBtnClass = "button smb-hide-diff"; |
| @@ -762,12 +724,170 @@ | ||
| 762 | 724 | blob_reset(&old); |
| 763 | 725 | blob_reset(&new); |
| 764 | 726 | } |
| 765 | 727 | } |
| 766 | 728 | 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 Diff</a> | |
| 757 | + } | |
| 758 | + if( diffType!=2 ){ | |
| 759 | + @ %z(chref("button","%R?diff=2&exbase=%h%s",zExBase,zW))\ | |
| 760 | + @ Side-by-Side Diff</a> | |
| 761 | + } | |
| 762 | + if( diffType!=0 ){ | |
| 763 | + if( *zW ){ | |
| 764 | + @ %z(chref("button","%R?diff=%d&exbase=%h",diffType,zExBase))\ | |
| 765 | + @ Show Whitespace Changes</a> | |
| 766 | + }else{ | |
| 767 | + @ %z(chref("button","%R?diff=%d&exbase=%h&w",diffType,zExBase))\ | |
| 768 | + @ Ignore 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); | |
| 768 | 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 | + } | |
| 769 | 889 | style_finish_page(); |
| 770 | 890 | } |
| 771 | 891 | |
| 772 | 892 | /* |
| 773 | 893 | ** WEBPAGE: vinfo |
| @@ -2123,10 +2243,16 @@ | ||
| 2123 | 2243 | ** WEBPAGE: jchunk hidden |
| 2124 | 2244 | ** URL: /jchunk/HASH?from=N&to=M |
| 2125 | 2245 | ** |
| 2126 | 2246 | ** Return lines of text from a file as a JSON array - one entry in the |
| 2127 | 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. | |
| 2128 | 2254 | ** |
| 2129 | 2255 | ** **Warning:** This is an internal-use-only interface that is subject to |
| 2130 | 2256 | ** change at any moment. External application should not use this interface |
| 2131 | 2257 | ** since the application will break when this interface changes, and this |
| 2132 | 2258 | ** interface will undoubtedly change. |
| @@ -2138,10 +2264,11 @@ | ||
| 2138 | 2264 | ** ajax_route_error(). |
| 2139 | 2265 | */ |
| 2140 | 2266 | void jchunk_page(void){ |
| 2141 | 2267 | int rid = 0; |
| 2142 | 2268 | const char *zName = PD("name", ""); |
| 2269 | + int nName = (int)(strlen(zName)&0x7fffffff); | |
| 2143 | 2270 | int iFrom = atoi(PD("from","0")); |
| 2144 | 2271 | int iTo = atoi(PD("to","0")); |
| 2145 | 2272 | int ln; |
| 2146 | 2273 | int go = 1; |
| 2147 | 2274 | const char *zSep; |
| @@ -2158,36 +2285,57 @@ | ||
| 2158 | 2285 | cgi_check_for_malice(); |
| 2159 | 2286 | if( !g.perm.Read ){ |
| 2160 | 2287 | ajax_route_error(403, "Access requires Read permissions."); |
| 2161 | 2288 | return; |
| 2162 | 2289 | } |
| 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 | 2290 | if( iFrom<1 || iTo<iFrom ){ |
| 2184 | 2291 | ajax_route_error(500, "Invalid line range from=%d, to=%d.", |
| 2185 | 2292 | iFrom, iTo); |
| 2186 | 2293 | return; |
| 2187 | 2294 | } |
| 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 | + } | |
| 2189 | 2337 | g.isConst = 1; |
| 2190 | 2338 | cgi_set_content_type("application/json"); |
| 2191 | 2339 | ln = 0; |
| 2192 | 2340 | while( go && ln<iFrom ){ |
| 2193 | 2341 | go = blob_line(&content, &line); |
| 2194 | 2342 |
| --- 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 Diff</a> |
| 757 | } |
| 758 | if( diffType!=2 ){ |
| 759 | @ %z(chref("button","%R?diff=2&exbase=%h%s",zExBase,zW))\ |
| 760 | @ Side-by-Side Diff</a> |
| 761 | } |
| 762 | if( diffType!=0 ){ |
| 763 | if( *zW ){ |
| 764 | @ %z(chref("button","%R?diff=%d&exbase=%h",diffType,zExBase))\ |
| 765 | @ Show Whitespace Changes</a> |
| 766 | }else{ |
| 767 | @ %z(chref("button","%R?diff=%d&exbase=%h&w",diffType,zExBase))\ |
| 768 | @ Ignore 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 @@ | ||
| 609 | 609 | db_finalize(&q); |
| 610 | 610 | style_finish_page(); |
| 611 | 611 | } |
| 612 | 612 | |
| 613 | 613 | /* |
| 614 | -** WEBPAGE: ckout | |
| 615 | -** | |
| 616 | -** Show information about the current checkout. This page only functions | |
| 617 | -** if the web server is run on a loopback interface (in other words, was | |
| 618 | -** started using "fossil ui" or similar) from with on open check-out. | |
| 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 | |
| 622 | 615 | */ |
| 623 | -void ckout_page(void){ | |
| 624 | - int vid; | |
| 625 | - char *zHostname; | |
| 626 | - char *zCwd; | |
| 616 | +static void ckout_normal_diff(int vid){ | |
| 627 | 617 | int diffType; /* 0: no diff, 1: unified, 2: side-by-side */ |
| 628 | 618 | DiffConfig DCfg,*pCfg; /* Diff details */ |
| 629 | - const char *zHome; /* Home directory */ | |
| 630 | 619 | const char *zW; /* The "w" query parameter */ |
| 631 | 620 | int nChng; /* Number of changes */ |
| 632 | 621 | Stmt q; |
| 633 | 622 | |
| 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 | 623 | diffType = preferred_diff_type(); |
| 640 | 624 | 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 | 625 | nChng = db_int(0, "SELECT count(*) FROM vfile" |
| 662 | 626 | " WHERE vid=%d AND (deleted OR chnged OR rid==0)", vid); |
| 663 | 627 | if( nChng==0 ){ |
| 664 | 628 | @ <p>No uncommitted changes</p> |
| 665 | - style_finish_page(); | |
| 666 | 629 | return; |
| 667 | 630 | } |
| 668 | 631 | db_prepare(&q, |
| 669 | 632 | /* 0 1 2 3 4 5 6 */ |
| 670 | 633 | "SELECT pathname, deleted, chnged , rid==0, rid, islink, uuid" |
| @@ -677,11 +640,10 @@ | ||
| 677 | 640 | if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){ |
| 678 | 641 | DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG; |
| 679 | 642 | }else{ |
| 680 | 643 | DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG; |
| 681 | 644 | } |
| 682 | - @ <hr> | |
| 683 | 645 | @ <div class="sectionmenu info-changes-menu"> |
| 684 | 646 | zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":""; |
| 685 | 647 | if( diffType!=0 ){ |
| 686 | 648 | /* Class "smb-hide-diff" required by the fossil.diff.js script. */ |
| 687 | 649 | const char *zBtnClass = "button smb-hide-diff"; |
| @@ -762,12 +724,170 @@ | ||
| 762 | 724 | blob_reset(&old); |
| 763 | 725 | blob_reset(&new); |
| 764 | 726 | } |
| 765 | 727 | } |
| 766 | 728 | 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 Diff</a> | |
| 757 | + } | |
| 758 | + if( diffType!=2 ){ | |
| 759 | + @ %z(chref("button","%R?diff=2&exbase=%h%s",zExBase,zW))\ | |
| 760 | + @ Side-by-Side Diff</a> | |
| 761 | + } | |
| 762 | + if( diffType!=0 ){ | |
| 763 | + if( *zW ){ | |
| 764 | + @ %z(chref("button","%R?diff=%d&exbase=%h",diffType,zExBase))\ | |
| 765 | + @ Show Whitespace Changes</a> | |
| 766 | + }else{ | |
| 767 | + @ %z(chref("button","%R?diff=%d&exbase=%h&w",diffType,zExBase))\ | |
| 768 | + @ Ignore 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); | |
| 768 | 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 | + } | |
| 769 | 889 | style_finish_page(); |
| 770 | 890 | } |
| 771 | 891 | |
| 772 | 892 | /* |
| 773 | 893 | ** WEBPAGE: vinfo |
| @@ -2123,10 +2243,16 @@ | ||
| 2123 | 2243 | ** WEBPAGE: jchunk hidden |
| 2124 | 2244 | ** URL: /jchunk/HASH?from=N&to=M |
| 2125 | 2245 | ** |
| 2126 | 2246 | ** Return lines of text from a file as a JSON array - one entry in the |
| 2127 | 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. | |
| 2128 | 2254 | ** |
| 2129 | 2255 | ** **Warning:** This is an internal-use-only interface that is subject to |
| 2130 | 2256 | ** change at any moment. External application should not use this interface |
| 2131 | 2257 | ** since the application will break when this interface changes, and this |
| 2132 | 2258 | ** interface will undoubtedly change. |
| @@ -2138,10 +2264,11 @@ | ||
| 2138 | 2264 | ** ajax_route_error(). |
| 2139 | 2265 | */ |
| 2140 | 2266 | void jchunk_page(void){ |
| 2141 | 2267 | int rid = 0; |
| 2142 | 2268 | const char *zName = PD("name", ""); |
| 2269 | + int nName = (int)(strlen(zName)&0x7fffffff); | |
| 2143 | 2270 | int iFrom = atoi(PD("from","0")); |
| 2144 | 2271 | int iTo = atoi(PD("to","0")); |
| 2145 | 2272 | int ln; |
| 2146 | 2273 | int go = 1; |
| 2147 | 2274 | const char *zSep; |
| @@ -2158,36 +2285,57 @@ | ||
| 2158 | 2285 | cgi_check_for_malice(); |
| 2159 | 2286 | if( !g.perm.Read ){ |
| 2160 | 2287 | ajax_route_error(403, "Access requires Read permissions."); |
| 2161 | 2288 | return; |
| 2162 | 2289 | } |
| 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 | 2290 | if( iFrom<1 || iTo<iFrom ){ |
| 2184 | 2291 | ajax_route_error(500, "Invalid line range from=%d, to=%d.", |
| 2185 | 2292 | iFrom, iTo); |
| 2186 | 2293 | return; |
| 2187 | 2294 | } |
| 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 | + } | |
| 2189 | 2337 | g.isConst = 1; |
| 2190 | 2338 | cgi_set_content_type("application/json"); |
| 2191 | 2339 | ln = 0; |
| 2192 | 2340 | while( go && ln<iFrom ){ |
| 2193 | 2341 | go = blob_line(&content, &line); |
| 2194 | 2342 |
| --- 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 Diff</a> |
| 757 | } |
| 758 | if( diffType!=2 ){ |
| 759 | @ %z(chref("button","%R?diff=2&exbase=%h%s",zExBase,zW))\ |
| 760 | @ Side-by-Side Diff</a> |
| 761 | } |
| 762 | if( diffType!=0 ){ |
| 763 | if( *zW ){ |
| 764 | @ %z(chref("button","%R?diff=%d&exbase=%h",diffType,zExBase))\ |
| 765 | @ Show Whitespace Changes</a> |
| 766 | }else{ |
| 767 | @ %z(chref("button","%R?diff=%d&exbase=%h&w",diffType,zExBase))\ |
| 768 | @ Ignore 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 @@ | ||
| 3178 | 3178 | ** --errorlog FILE Append HTTP error messages to FILE |
| 3179 | 3179 | ** --extroot DIR Document root for the /ext extension mechanism |
| 3180 | 3180 | ** --files GLOBLIST Comma-separated list of glob patterns for static files |
| 3181 | 3181 | ** --fossilcmd PATH The pathname of the "fossil" executable on the remote |
| 3182 | 3182 | ** system when REPOSITORY is remote. |
| 3183 | +** --from PATH Use PATH as the diff baseline for the /ckout page | |
| 3183 | 3184 | ** --localauth Enable automatic login for requests from localhost |
| 3184 | 3185 | ** --localhost Listen on 127.0.0.1 only (always true for "ui") |
| 3185 | 3186 | ** --https Indicates that the input is coming through a reverse |
| 3186 | 3187 | ** proxy that has already translated HTTPS into HTTP. |
| 3187 | 3188 | ** --jsmode MODE Determine how JavaScript is delivered with pages. |
| @@ -3250,10 +3251,11 @@ | ||
| 3250 | 3251 | const char *zInitPage = 0; /* Start on this page. --page option */ |
| 3251 | 3252 | int findServerArg = 2; /* argv index for find_server_repository() */ |
| 3252 | 3253 | char *zRemote = 0; /* Remote host on which to run "fossil ui" */ |
| 3253 | 3254 | const char *zJsMode; /* The --jsmode parameter */ |
| 3254 | 3255 | const char *zFossilCmd =0; /* Name of "fossil" binary on remote system */ |
| 3256 | + const char *zFrom; /* Value for --from */ | |
| 3255 | 3257 | |
| 3256 | 3258 | |
| 3257 | 3259 | #if USE_SEE |
| 3258 | 3260 | db_setup_for_saved_encryption_key(); |
| 3259 | 3261 | #endif |
| @@ -3286,13 +3288,21 @@ | ||
| 3286 | 3288 | g.useLocalauth = find_option("localauth", 0, 0)!=0; |
| 3287 | 3289 | Th_InitTraceLog(); |
| 3288 | 3290 | zPort = find_option("port", "P", 1); |
| 3289 | 3291 | isUiCmd = g.argv[1][0]=='u'; |
| 3290 | 3292 | if( isUiCmd ){ |
| 3293 | + 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 | + } | |
| 3291 | 3298 | zInitPage = find_option("page", "p", 1); |
| 3292 | 3299 | if( zInitPage && zInitPage[0]=='/' ) zInitPage++; |
| 3293 | 3300 | zFossilCmd = find_option("fossilcmd", 0, 1); |
| 3301 | + if( zFrom && zInitPage==0 ){ | |
| 3302 | + zInitPage = mprintf("ckout?exbase=%T", zFrom); | |
| 3303 | + } | |
| 3294 | 3304 | } |
| 3295 | 3305 | zNotFound = find_option("notfound", 0, 1); |
| 3296 | 3306 | allowRepoList = find_option("repolist",0,0)!=0; |
| 3297 | 3307 | if( find_option("nocompress",0,0)!=0 ) g.fNoHttpCompress = 1; |
| 3298 | 3308 | zAltBase = find_option("baseurl", 0, 1); |
| 3299 | 3309 |
| --- 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 @@ | ||
| 687 | 687 | ** -K|--keep-merge-files On merge conflict, retain the temporary files |
| 688 | 688 | ** used for merging, named *-baseline, *-original, |
| 689 | 689 | ** and *-merge. |
| 690 | 690 | ** -n|--dry-run Do not actually change files on disk |
| 691 | 691 | ** --nosync Do not auto-sync prior to merging |
| 692 | +** --noundo Do not record changes in the undo log | |
| 692 | 693 | ** -v|--verbose Show additional details of the merge |
| 693 | 694 | */ |
| 694 | 695 | void merge_cmd(void){ |
| 695 | 696 | int vid; /* Current version "V" */ |
| 696 | 697 | int mid; /* Version we are merging from "M" */ |
| @@ -712,10 +713,11 @@ | ||
| 712 | 713 | int nOverwrite = 0; /* Number of unmanaged files overwritten */ |
| 713 | 714 | char vAncestor = 'p'; /* If P is an ancestor of V then 'p', else 'n' */ |
| 714 | 715 | const char *zVersion; /* The VERSION argument */ |
| 715 | 716 | int bMultiMerge = 0; /* True if there are two or more VERSION arguments */ |
| 716 | 717 | int nMerge = 0; /* Number of prior merges processed */ |
| 718 | + int useUndo = 1; /* True to record changes in the undo log */ | |
| 717 | 719 | Stmt q; /* SQL statment used for merge processing */ |
| 718 | 720 | |
| 719 | 721 | |
| 720 | 722 | /* Notation: |
| 721 | 723 | ** |
| @@ -760,10 +762,12 @@ | ||
| 760 | 762 | ** * The --dry-run option is also useful in combination with --debug. |
| 761 | 763 | */ |
| 762 | 764 | debugFlag = find_option("debug",0,0)!=0; |
| 763 | 765 | if( debugFlag && verboseFlag ) debugFlag = 2; |
| 764 | 766 | showVfileFlag = find_option("show-vfile",0,0)!=0; |
| 767 | + useUndo = find_option("noundo",0,0)==0; | |
| 768 | + if( dryRunFlag ) useUndo = 0; | |
| 765 | 769 | |
| 766 | 770 | verify_all_options(); |
| 767 | 771 | db_must_be_within_tree(); |
| 768 | 772 | if( zBinGlob==0 ) zBinGlob = db_get("binary-glob",0); |
| 769 | 773 | vid = db_lget_int("checkout", 0); |
| @@ -926,11 +930,11 @@ | ||
| 926 | 930 | integrateFlag ? "integrate:" : "merge-from:"); |
| 927 | 931 | print_checkin_description(pid, 12, "baseline:"); |
| 928 | 932 | } |
| 929 | 933 | vfile_check_signature(vid, CKSIG_ENOTFILE); |
| 930 | 934 | if( nMerge==0 ) db_begin_transaction(); |
| 931 | - if( !dryRunFlag ) undo_begin(); | |
| 935 | + if( useUndo ) undo_begin(); | |
| 932 | 936 | if( load_vfile_from_rid(mid) && !forceMissingFlag ){ |
| 933 | 937 | fossil_fatal("missing content, unable to merge"); |
| 934 | 938 | } |
| 935 | 939 | if( load_vfile_from_rid(pid) && !forceMissingFlag ){ |
| 936 | 940 | fossil_fatal("missing content, unable to merge"); |
| @@ -1183,12 +1187,12 @@ | ||
| 1183 | 1187 | int ridm = db_column_int(&q, 1); |
| 1184 | 1188 | const char *zName = db_column_text(&q, 2); |
| 1185 | 1189 | int islinkm = db_column_int(&q, 3); |
| 1186 | 1190 | /* Copy content from idm over into idv. Overwrite idv. */ |
| 1187 | 1191 | fossil_print("UPDATE %s\n", zName); |
| 1192 | + if( useUndo ) undo_save(zName); | |
| 1188 | 1193 | if( !dryRunFlag ){ |
| 1189 | - undo_save(zName); | |
| 1190 | 1194 | db_multi_exec( |
| 1191 | 1195 | "UPDATE vfile SET mtime=0, mrid=%d, chnged=%d, islink=%d," |
| 1192 | 1196 | " mhash=CASE WHEN rid<>%d" |
| 1193 | 1197 | " THEN (SELECT uuid FROM blob WHERE blob.rid=%d) END" |
| 1194 | 1198 | " WHERE id=%d", ridm, integrateFlag?4:2, islinkm, ridm, ridm, idv |
| @@ -1265,11 +1269,11 @@ | ||
| 1265 | 1269 | }else{ |
| 1266 | 1270 | i64 sz; |
| 1267 | 1271 | const char *zErrMsg = 0; |
| 1268 | 1272 | int nc = 0; |
| 1269 | 1273 | |
| 1270 | - if( !dryRunFlag ) undo_save(zName); | |
| 1274 | + if( useUndo ) undo_save(zName); | |
| 1271 | 1275 | zFullPath = mprintf("%s/%s", g.zLocalRoot, zName); |
| 1272 | 1276 | sz = file_size(zFullPath, ExtFILE); |
| 1273 | 1277 | content_get(ridp, &p); |
| 1274 | 1278 | content_get(ridm, &m); |
| 1275 | 1279 | if( isBinary ){ |
| @@ -1352,11 +1356,11 @@ | ||
| 1352 | 1356 | zErrMsg = "local edits lost"; |
| 1353 | 1357 | zFullPath = mprintf("%s/%s", g.zLocalRoot, zName); |
| 1354 | 1358 | sz = file_size(zFullPath, ExtFILE); |
| 1355 | 1359 | fossil_free(zFullPath); |
| 1356 | 1360 | } |
| 1357 | - if( !dryRunFlag ) undo_save(zName); | |
| 1361 | + if( useUndo ) undo_save(zName); | |
| 1358 | 1362 | db_multi_exec( |
| 1359 | 1363 | "UPDATE vfile SET deleted=1 WHERE id=%d", idv |
| 1360 | 1364 | ); |
| 1361 | 1365 | if( !dryRunFlag ){ |
| 1362 | 1366 | char *zFullPath = mprintf("%s%s", g.zLocalRoot, zName); |
| @@ -1399,12 +1403,12 @@ | ||
| 1399 | 1403 | int idv = db_column_int(&q, 0); |
| 1400 | 1404 | const char *zOldName = db_column_text(&q, 1); |
| 1401 | 1405 | const char *zNewName = db_column_text(&q, 2); |
| 1402 | 1406 | int isExe = db_column_int(&q, 3); |
| 1403 | 1407 | 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); | |
| 1406 | 1410 | db_multi_exec( |
| 1407 | 1411 | "UPDATE mergestat SET fnr=fnm WHERE fnp=%Q", |
| 1408 | 1412 | zOldName |
| 1409 | 1413 | ); |
| 1410 | 1414 | db_multi_exec( |
| @@ -1498,12 +1502,12 @@ | ||
| 1498 | 1502 | "VALUES('ADDED',%Q,%d,%Q)", |
| 1499 | 1503 | /* fnm */ zName, |
| 1500 | 1504 | /* ridm */ db_column_int(&q,2), |
| 1501 | 1505 | /* fnr */ zName |
| 1502 | 1506 | ); |
| 1507 | + if( useUndo ) undo_save(zName); | |
| 1503 | 1508 | if( !dryRunFlag ){ |
| 1504 | - undo_save(zName); | |
| 1505 | 1509 | vfile_to_disk(0, idm, 0, 0); |
| 1506 | 1510 | } |
| 1507 | 1511 | } |
| 1508 | 1512 | db_finalize(&q); |
| 1509 | 1513 | |
| @@ -1558,9 +1562,9 @@ | ||
| 1558 | 1562 | } |
| 1559 | 1563 | if( bMultiMerge && nConflict==0 ){ |
| 1560 | 1564 | nMerge++; |
| 1561 | 1565 | goto merge_next_child; |
| 1562 | 1566 | } |
| 1563 | - if( !dryRunFlag ) undo_finish(); | |
| 1567 | + if( useUndo ) undo_finish(); | |
| 1564 | 1568 | |
| 1565 | 1569 | db_end_transaction(dryRunFlag); |
| 1566 | 1570 | } |
| 1567 | 1571 |
| --- 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 @@ | ||
| 387 | 387 | if( unsaved_changes(0) ){ |
| 388 | 388 | if( (mFlags & PATCH_FORCE)==0 ){ |
| 389 | 389 | fossil_fatal("Cannot apply patch: there are unsaved changes " |
| 390 | 390 | "in the current check-out"); |
| 391 | 391 | }else{ |
| 392 | - blob_appendf(&cmd, "%$ revert", g.nameOfExe); | |
| 392 | + blob_appendf(&cmd, "%$ revert --noundo", g.nameOfExe); | |
| 393 | 393 | if( mFlags & PATCH_DRYRUN ){ |
| 394 | 394 | fossil_print("%s\n", blob_str(&cmd)); |
| 395 | 395 | }else{ |
| 396 | 396 | int rc = fossil_system(blob_str(&cmd)); |
| 397 | 397 | if( rc ){ |
| @@ -429,23 +429,27 @@ | ||
| 429 | 429 | } |
| 430 | 430 | } |
| 431 | 431 | } |
| 432 | 432 | blob_reset(&cmd); |
| 433 | 433 | if( db_table_exists("patch","patchmerge") ){ |
| 434 | + int nMerge = 0; | |
| 434 | 435 | db_prepare(&q, |
| 435 | 436 | "SELECT type, mhash, upper(type) FROM patch.patchmerge" |
| 436 | 437 | " WHERE type IN ('merge','cherrypick','backout','integrate')" |
| 437 | 438 | " AND mhash NOT GLOB '*[^a-fA-F0-9]*';" |
| 438 | 439 | ); |
| 439 | 440 | while( db_step(&q)==SQLITE_ROW ){ |
| 440 | 441 | const char *zType = db_column_text(&q,0); |
| 441 | 442 | blob_append_escaped_arg(&cmd, g.nameOfExe, 1); |
| 442 | 443 | 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)); | |
| 444 | 446 | }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)); | |
| 446 | 449 | } |
| 450 | + nMerge++; | |
| 447 | 451 | if( mFlags & PATCH_VERBOSE ){ |
| 448 | 452 | fossil_print("%-10s %s\n", db_column_text(&q,2), |
| 449 | 453 | db_column_text(&q,0)); |
| 450 | 454 | } |
| 451 | 455 | } |
| @@ -458,10 +462,45 @@ | ||
| 458 | 462 | fossil_fatal("unable to do merges:\n%s", |
| 459 | 463 | blob_str(&cmd)); |
| 460 | 464 | } |
| 461 | 465 | } |
| 462 | 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 | + } | |
| 463 | 502 | } |
| 464 | 503 | |
| 465 | 504 | /* Deletions */ |
| 466 | 505 | db_prepare(&q, "SELECT pathname FROM patch.chng" |
| 467 | 506 | " WHERE origname IS NULL AND delta IS NULL"); |
| 468 | 507 |
| --- 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 @@ | ||
| 854 | 854 | ** |
| 855 | 855 | ** If a file is reverted accidentally, it can be restored using |
| 856 | 856 | ** the "fossil undo" command. |
| 857 | 857 | ** |
| 858 | 858 | ** Options: |
| 859 | +** --noundo Do not record changes in the undo/redo log. | |
| 859 | 860 | ** -r|--revision VERSION Revert given FILE(s) back to given |
| 860 | 861 | ** VERSION |
| 861 | 862 | ** |
| 862 | 863 | ** See also: [[redo]], [[undo]], [[checkout]], [[update]] |
| 863 | 864 | */ |
| @@ -867,17 +868,19 @@ | ||
| 867 | 868 | ManifestFile *pCoFile; /* File within current check-out manifest */ |
| 868 | 869 | ManifestFile *pRvFile; /* File within revert version manifest */ |
| 869 | 870 | const char *zFile; /* Filename relative to check-out root */ |
| 870 | 871 | const char *zRevision; /* Selected revert version, NULL if current */ |
| 871 | 872 | Blob record = BLOB_INITIALIZER; /* Contents of each reverted file */ |
| 873 | + int useUndo = 1; /* True to record changes in UNDO */ | |
| 872 | 874 | int i; |
| 873 | 875 | Stmt q; |
| 874 | 876 | int revertAll = 0; |
| 875 | 877 | int revisionOptNotSupported = 0; |
| 876 | 878 | |
| 877 | 879 | undo_capture_command_line(); |
| 878 | 880 | zRevision = find_option("revision", "r", 1); |
| 881 | + useUndo = find_option("noundo", 0, 0)==0; | |
| 879 | 882 | verify_all_options(); |
| 880 | 883 | |
| 881 | 884 | if( g.argc<2 ){ |
| 882 | 885 | usage("?OPTIONS? [FILE] ..."); |
| 883 | 886 | } |
| @@ -890,11 +893,15 @@ | ||
| 890 | 893 | /* Get manifests of revert version and (if different) current check-out. */ |
| 891 | 894 | pRvManifest = historical_manifest(zRevision); |
| 892 | 895 | pCoManifest = zRevision ? historical_manifest(0) : 0; |
| 893 | 896 | |
| 894 | 897 | db_begin_transaction(); |
| 895 | - undo_begin(); | |
| 898 | + if( useUndo ){ | |
| 899 | + undo_begin(); | |
| 900 | + }else{ | |
| 901 | + undo_reset(); | |
| 902 | + } | |
| 896 | 903 | db_multi_exec("CREATE TEMP TABLE torevert(name UNIQUE);"); |
| 897 | 904 | |
| 898 | 905 | if( g.argc>2 ){ |
| 899 | 906 | for(i=2; i<g.argc; i++){ |
| 900 | 907 | Blob fname; |
| @@ -987,11 +994,11 @@ | ||
| 987 | 994 | if( !pRvFile ){ |
| 988 | 995 | if( db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q OR origname=%Q", |
| 989 | 996 | zFile, zFile)==0 ){ |
| 990 | 997 | fossil_print("UNMANAGE %s\n", zFile); |
| 991 | 998 | }else{ |
| 992 | - undo_save(zFile); | |
| 999 | + if( useUndo ) undo_save(zFile); | |
| 993 | 1000 | file_delete(zFull); |
| 994 | 1001 | fossil_print("DELETE %s\n", zFile); |
| 995 | 1002 | } |
| 996 | 1003 | db_multi_exec( |
| 997 | 1004 | "UPDATE OR REPLACE vfile" |
| @@ -1014,11 +1021,11 @@ | ||
| 1014 | 1021 | } |
| 1015 | 1022 | |
| 1016 | 1023 | /* Get contents of reverted-to file. */ |
| 1017 | 1024 | content_get(fast_uuid_to_rid(pRvFile->zUuid), &record); |
| 1018 | 1025 | |
| 1019 | - undo_save(zFile); | |
| 1026 | + if( useUndo ) undo_save(zFile); | |
| 1020 | 1027 | if( file_size(zFull, RepoFILE)>=0 |
| 1021 | 1028 | && (rvPerm==PERM_LNK || file_islink(0)) |
| 1022 | 1029 | ){ |
| 1023 | 1030 | file_delete(zFull); |
| 1024 | 1031 | } |
| @@ -1040,12 +1047,12 @@ | ||
| 1040 | 1047 | } |
| 1041 | 1048 | blob_reset(&record); |
| 1042 | 1049 | free(zFull); |
| 1043 | 1050 | } |
| 1044 | 1051 | db_finalize(&q); |
| 1045 | - undo_finish(); | |
| 1052 | + if( useUndo) undo_finish(); | |
| 1046 | 1053 | db_end_transaction(0); |
| 1047 | 1054 | |
| 1048 | 1055 | /* Deallocate parsed manifest structures. */ |
| 1049 | 1056 | manifest_destroy(pRvManifest); |
| 1050 | 1057 | manifest_destroy(pCoManifest); |
| 1051 | 1058 | } |
| 1052 | 1059 |
| --- 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 |
+7
-2
| --- www/changes.wiki | ||
| +++ www/changes.wiki | ||
| @@ -1,13 +1,18 @@ | ||
| 1 | 1 | <title>Change Log</title> |
| 2 | 2 | |
| 3 | 3 | <h2 id='v2_26'>Changes for version 2.26 (pending)</h2> |
| 4 | 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. | |
| 5 | 8 | * Added the [/help?cmd=/ckout|/ckout web page] to provide information |
| 6 | 9 | 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". | |
| 9 | 14 | * Added the [/help?cmd=merge-info|fossil merge-info] command and especially |
| 10 | 15 | the --tk option to that command, to provide analysis of the most recent |
| 11 | 16 | merge or update operation. |
| 12 | 17 | * Issue a warning if a user tries to commit on a check-in where the |
| 13 | 18 | branch has been changed. |
| 14 | 19 |
| --- 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 |