Fossil

Check-in [5fbb14f73a]
Login

Check-in [5fbb14f73a]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Sync with trunk.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | diff-word-wrap
Files: files | file ages | folders
SHA3-256: 5fbb14f73aaf0c7e02cf12f1a1d1cdfb30e44b13b76910c7e4796d2ef5d92122
User & Date: florian 2024-12-17 06:56:00
Context
2024-12-17
06:56
Sync with trunk. ... (Leaf check-in: 5fbb14f73a user: florian tags: diff-word-wrap)
06:12
On the new /ckout UI page, output the page footer only once. ... (check-in: 92372ce946 user: florian tags: trunk)
2024-12-13
17:11
Sync with trunk. ... (check-in: 4d7277762f user: florian tags: diff-word-wrap)
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/checkin.c.

1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
          diffFiles[0].zName[0] = '.';
          diffFiles[0].zName[1] = 0;
          break;
        }
        diffFiles[i].nName = strlen(diffFiles[i].zName);
        diffFiles[i].nUsed = 0;
      }
      diff_against_disk(0, &DCfg, diffFiles, &prompt);
      for( i=0; diffFiles[i].zName; ++i ){
        fossil_free(diffFiles[i].zName);
      }
      fossil_free(diffFiles);
    }else{
      diff_against_disk(0, &DCfg, 0, &prompt);
    }
  }
  prompt_for_user_comment(pComment, &prompt);
  blob_reset(&prompt);
}

/*







|





|







1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
          diffFiles[0].zName[0] = '.';
          diffFiles[0].zName[1] = 0;
          break;
        }
        diffFiles[i].nName = strlen(diffFiles[i].zName);
        diffFiles[i].nUsed = 0;
      }
      diff_version_to_checkout(0, &DCfg, diffFiles, &prompt);
      for( i=0; diffFiles[i].zName; ++i ){
        fossil_free(diffFiles[i].zName);
      }
      fossil_free(diffFiles);
    }else{
      diff_version_to_checkout(0, &DCfg, 0, &prompt);
    }
  }
  prompt_for_user_comment(pComment, &prompt);
  blob_reset(&prompt);
}

/*

Changes to src/diff.tcl.

108
109
110
111
112
113
114



115

116
117
118
119
120
121
122
  array set widths {txt 3 ln 3 mkr 1}
  
  
  set fromIndex [lsearch -glob $fossilcmd *-from]
  set toIndex [lsearch -glob $fossilcmd *-to]
  set branchIndex [lsearch -glob $fossilcmd *-branch]
  set checkinIndex [lsearch -glob $fossilcmd *-checkin]



  set fA {base check-in}

  set fB {current check-out}
  if {$fromIndex > -1} {set fA [lindex $fossilcmd $fromIndex+1]}
  if {$toIndex > -1} {set fB [lindex $fossilcmd $toIndex+1]}
  if {$branchIndex > -1} {set fA "branch point"; set fB "leaf of branch '[lindex $fossilcmd $branchIndex+1]'"}
  if {$checkinIndex > -1} {set fA "primary parent"; set fB [lindex $fossilcmd $checkinIndex+1]}
  
  







>
>
>
|
>







108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
  array set widths {txt 3 ln 3 mkr 1}
  
  
  set fromIndex [lsearch -glob $fossilcmd *-from]
  set toIndex [lsearch -glob $fossilcmd *-to]
  set branchIndex [lsearch -glob $fossilcmd *-branch]
  set checkinIndex [lsearch -glob $fossilcmd *-checkin]
  if {[string match *?--external-baseline* $fossilcmd]} {
    set fA {external baseline}
  } else {
    set fA {base check-in}
  }
  set fB {current check-out}
  if {$fromIndex > -1} {set fA [lindex $fossilcmd $fromIndex+1]}
  if {$toIndex > -1} {set fB [lindex $fossilcmd $toIndex+1]}
  if {$branchIndex > -1} {set fA "branch point"; set fB "leaf of branch '[lindex $fossilcmd $branchIndex+1]'"}
  if {$checkinIndex > -1} {set fA "primary parent"; set fB [lindex $fossilcmd $checkinIndex+1]}
  
  

Changes to src/diffcmd.c.

762
763
764
765
766
767
768
769
770
771

772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
    rc = memcmp(blob_buffer(&file), blob_buffer(blob), blob_size(&file))==0;
  }
  blob_reset(&file);
  return rc;
}

/*
** Run a diff between the version zFrom and files on disk.  zFrom might
** be NULL which means to simply show the difference between the edited
** files on disk and the check-out on which they are based.

**
** Use the internal diff logic if zDiffCmd is NULL.  Otherwise call the
** command zDiffCmd to do the diffing.
**
** When using an external diff program, zBinGlob contains the GLOB patterns
** for file names to treat as binary.  If fIncludeBinary is zero, these files
** will be skipped in addition to files that may contain binary content.
*/
void diff_against_disk(
  const char *zFrom,        /* Version to difference from */
  DiffConfig *pCfg,         /* Flags controlling diff output */
  FileDirList *pFileDir,    /* Which files to diff */
  Blob *pOut            /* Blob to output diff instead of stdout */
){
  int vid;
  Blob sql;
  Stmt q;
  int asNewFile;            /* Treat non-existant files as empty files */
  int isNumStat;            /* True for --numstat */








|
|
|
>








|



|







762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
    rc = memcmp(blob_buffer(&file), blob_buffer(blob), blob_size(&file))==0;
  }
  blob_reset(&file);
  return rc;
}

/*
** Run a diff between the version zFrom and files on disk in the current
** working checkout.  zFrom might be NULL which means to simply show the
** difference between the edited files on disk and the check-out on which
** they are based.
**
** Use the internal diff logic if zDiffCmd is NULL.  Otherwise call the
** command zDiffCmd to do the diffing.
**
** When using an external diff program, zBinGlob contains the GLOB patterns
** for file names to treat as binary.  If fIncludeBinary is zero, these files
** will be skipped in addition to files that may contain binary content.
*/
void diff_version_to_checkout(
  const char *zFrom,        /* Version to difference from */
  DiffConfig *pCfg,         /* Flags controlling diff output */
  FileDirList *pFileDir,    /* Which files to diff */
  Blob *pOut                /* Blob to output diff instead of stdout */
){
  int vid;
  Blob sql;
  Stmt q;
  int asNewFile;            /* Treat non-existant files as empty files */
  int isNumStat;            /* True for --numstat */

906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
    blob_reset(&fname);
  }
  db_finalize(&q);
  db_end_transaction(1);  /* ROLLBACK */
}

/*
** Run a diff between the undo buffer and files on disk.
**
** Use the internal diff logic if zDiffCmd is NULL.  Otherwise call the
** command zDiffCmd to do the diffing.
**
** When using an external diff program, zBinGlob contains the GLOB patterns
** for file names to treat as binary.  If fIncludeBinary is zero, these files
** will be skipped in addition to files that may contain binary content.
*/
static void diff_against_undo(
  DiffConfig *pCfg,         /* Flags controlling diff output */
  FileDirList *pFileDir     /* List of files and directories to diff */
){
  Stmt q;
  Blob content;
  db_prepare(&q, "SELECT pathname, content FROM undo");
  blob_init(&content, 0, 0);







|








|







907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
    blob_reset(&fname);
  }
  db_finalize(&q);
  db_end_transaction(1);  /* ROLLBACK */
}

/*
** Run a diff from the undo buffer to files on disk.
**
** Use the internal diff logic if zDiffCmd is NULL.  Otherwise call the
** command zDiffCmd to do the diffing.
**
** When using an external diff program, zBinGlob contains the GLOB patterns
** for file names to treat as binary.  If fIncludeBinary is zero, these files
** will be skipped in addition to files that may contain binary content.
*/
static void diff_undo_to_checkout(
  DiffConfig *pCfg,         /* Flags controlling diff output */
  FileDirList *pFileDir     /* List of files and directories to diff */
){
  Stmt q;
  Blob content;
  db_prepare(&q, "SELECT pathname, content FROM undo");
  blob_init(&content, 0, 0);
1066
1067
1068
1069
1070
1071
1072

























































1073
1074
1075
1076
1077
1078
1079
      pFromFile = manifest_file_next(pFrom,0);
      pToFile = manifest_file_next(pTo,0);
    }
  }
  manifest_destroy(pFrom);
  manifest_destroy(pTo);
}


























































/*
** Return the name of the external diff command, or return NULL if
** no external diff command is defined.
*/
const char *diff_command_external(int guiDiff){
  const char *zDefault;







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
      pFromFile = manifest_file_next(pFrom,0);
      pToFile = manifest_file_next(pTo,0);
    }
  }
  manifest_destroy(pFrom);
  manifest_destroy(pTo);
}

/*
** Compute the difference from an external tree of files to the current
** working checkout with its edits.
**
** To put it another way:  Every managed file in the current working
** checkout is compared to the file with same name under zExternBase.  The
** zExternBase files are on the left and the files in the current working
** directory are on the right.
*/
void diff_externbase_to_checkout(
  const char *zExternBase,   /* Remote tree to use as the baseline */
  DiffConfig *pCfg,          /* Diff settings */
  FileDirList *pFileDir      /* Only look at these files */
){
  int vid;
  Stmt q;

  vid = db_lget_int("checkout",0);
  if( file_isdir(zExternBase, ExtFILE)!=1 ){
    fossil_fatal("\"%s\" is not a directory", zExternBase);
  }
  db_prepare(&q,
    "SELECT pathname FROM vfile WHERE vid=%d ORDER BY pathname",
    vid
  );
  while( db_step(&q)==SQLITE_ROW ){
    const char *zFile;  /* Name of file in the repository */
    char *zLhs;         /* Full name of left-hand side file */
    char *zRhs;         /* Full name of right-hand side file */
    Blob rhs;           /* Full text of RHS */
    Blob lhs;           /* Full text of LHS */

    zFile = db_column_text(&q,0);
    if( !file_dir_match(pFileDir, zFile) ) continue;
    zLhs = mprintf("%s/%s", zExternBase, zFile);
    zRhs = mprintf("%s%s", g.zLocalRoot, zFile);
    if( file_size(zLhs, ExtFILE)<0 ){
      blob_zero(&lhs);
    }else{
      blob_read_from_file(&lhs, zLhs, ExtFILE);
    }
    blob_read_from_file(&rhs, zRhs, ExtFILE);
    if( blob_size(&lhs)!=blob_size(&rhs)
     || memcmp(blob_buffer(&lhs), blob_buffer(&rhs), blob_size(&lhs))!=0
    ){
      diff_print_index(zFile, pCfg, 0);
      diff_file_mem(&lhs, &rhs, zFile, pCfg);
    }
    blob_reset(&lhs);
    blob_reset(&rhs);
    fossil_free(zLhs);
    fossil_free(zRhs);
  }
  db_finalize(&q);
}


/*
** Return the name of the external diff command, or return NULL if
** no external diff command is defined.
*/
const char *diff_command_external(int guiDiff){
  const char *zDefault;
1202
1203
1204
1205
1206
1207
1208




1209
1210
1211
1212
1213
1214
1215
** for the diff operation.  If not specified, the source check-in is the
** base check-in for the current check-out. Similarly, the "--to VERSION"
** option specifies the check-in from which the second version of the file
** or files is taken.  If there is no "--to" option then the (possibly edited)
** files in the current check-out are used.  The "--checkin VERSION" option
** shows the changes made by check-in VERSION relative to its primary parent.
** The "--branch BRANCHNAME" shows all the changes on the branch BRANCHNAME.




**
** The "-i" command-line option forces the use of Fossil's own internal
** diff logic rather than any external diff program that might be configured
** using the "setting" command.  If no external diff program is configured,
** then the "-i" option is a no-op.  The "-i" option converts "gdiff" into
** "diff".
**







>
>
>
>







1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
** for the diff operation.  If not specified, the source check-in is the
** base check-in for the current check-out. Similarly, the "--to VERSION"
** option specifies the check-in from which the second version of the file
** or files is taken.  If there is no "--to" option then the (possibly edited)
** files in the current check-out are used.  The "--checkin VERSION" option
** shows the changes made by check-in VERSION relative to its primary parent.
** The "--branch BRANCHNAME" shows all the changes on the branch BRANCHNAME.
**
** With the "--from VERSION" option, if VERSION is actually a directory name
** (not a tag or check-in hash) then the files under that directory are used
** as the baseline for the diff.
**
** The "-i" command-line option forces the use of Fossil's own internal
** diff logic rather than any external diff program that might be configured
** using the "setting" command.  If no external diff program is configured,
** then the "-i" option is a no-op.  The "-i" option converts "gdiff" into
** "diff".
**
1234
1235
1236
1237
1238
1239
1240
1241


1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271

1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309











1310
1311
1312
1313
1314
1315
1316
**   --command PROG              External diff program. Overrides "diff-command"
**   -c|--context N              Show N lines of context around each change,
**                               with negative N meaning show all content
**   --dark                      Use dark mode for the Tcl/Tk-based GUI and HTML
**   --diff-binary BOOL          Include binary files with external commands
**   --exec-abs-paths            Force absolute path names on external commands
**   --exec-rel-paths            Force relative path names on external commands
**   -r|--from VERSION           Select VERSION as source for the diff


**   -w|--ignore-all-space       Ignore white space when comparing lines
**   -i|--internal               Use internal diff logic
**   --invert                    Invert the diff
**   --json                      Output formatted as JSON
**   -n|--linenum                Show line numbers
**   -N|--new-file               Alias for --verbose
**   --numstat                   Show only the number of added and deleted lines
**   -y|--side-by-side           Side-by-side diff
**   --strip-trailing-cr         Strip trailing CR
**   --tcl                       Tcl-formatted output used internally by --tk
**   --tclsh PATH                Tcl/Tk shell used for --tk (default: "tclsh")
**   --tk                        Launch a Tcl/Tk GUI for display
**   --to VERSION                Select VERSION as target for the diff
**   --undo                      Diff against the "undo" buffer
**   --unified                   Unified diff
**   -v|--verbose                Output complete text of added or deleted files
**   -h|--versions               Show compared versions in the diff header
**   --webpage                   Format output as a stand-alone HTML webpage
**   -W|--width N                Width of lines in side-by-side diff
**   -Z|--ignore-trailing-space  Ignore changes to end-of-line whitespace
*/
void diff_cmd(void){
  int isGDiff;               /* True for gdiff.  False for normal diff */
  const char *zFrom;         /* Source version number */
  const char *zTo;           /* Target version number */
  const char *zCheckin;      /* Check-in version number */
  const char *zBranch;       /* Branch to diff */
  int againstUndo = 0;       /* Diff against files in the undo buffer */
  FileDirList *pFileDir = 0; /* Restrict the diff to these files */
  DiffConfig DCfg;           /* Diff configuration object */


  if( find_option("tk",0,0)!=0 || has_option("tclsh") ){
    diff_tk("diff", 2);
    return;
  }
  isGDiff = g.argv[1][0]=='g';
  zFrom = find_option("from", "r", 1);
  zTo = find_option("to", 0, 1);
  zCheckin = find_option("checkin", "ci", 1);
  zBranch = find_option("branch", 0, 1);
  againstUndo = find_option("undo",0,0)!=0;
  if( againstUndo && ( zFrom!=0 || zTo!=0 || zCheckin!=0 || zBranch!=0) ){
    fossil_fatal("cannot use --undo together with --from, --to, --checkin,"
                 " or --branch");
  }
  if( zBranch ){
    if( zTo || zFrom || zCheckin ){
      fossil_fatal("cannot use --from, --to, or --checkin with --branch");
    }
    zTo = zBranch;
    zFrom = mprintf("root:%s", zBranch);
  }
  if( zCheckin!=0 && ( zFrom!=0 || zTo!=0 ) ){
    fossil_fatal("cannot use --checkin together with --from or --to");
  }
  diff_options(&DCfg, isGDiff, 0);
  determine_exec_relative_option(1);
  if( 0==zCheckin ){
    if( zTo==0 || againstUndo ){
      db_must_be_within_tree();
    }else if( zFrom==0 ){
      fossil_fatal("must use --from if --to is present");
    }else{
      db_find_and_open_repository(0, 0);
    }
  }else{
    db_find_and_open_repository(0, 0);
  }











  verify_all_options();
  g.diffCnt[0] = g.diffCnt[1] = g.diffCnt[2] = 0;
  if( g.argc>=3 ){
    int i;
    Blob fname;
    pFileDir = fossil_malloc( sizeof(*pFileDir) * (g.argc-1) );
    memset(pFileDir, 0, sizeof(*pFileDir) * (g.argc-1));







|
>
>













|
















>











|










|


<
<











>
>
>
>
>
>
>
>
>
>
>







1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361


1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
**   --command PROG              External diff program. Overrides "diff-command"
**   -c|--context N              Show N lines of context around each change,
**                               with negative N meaning show all content
**   --dark                      Use dark mode for the Tcl/Tk-based GUI and HTML
**   --diff-binary BOOL          Include binary files with external commands
**   --exec-abs-paths            Force absolute path names on external commands
**   --exec-rel-paths            Force relative path names on external commands
**   -r|--from VERSION           Use VERSION as the baseline for the diff, or
**                               if VERSION is a directory name, use files in
**                               that directory as the baseline.
**   -w|--ignore-all-space       Ignore white space when comparing lines
**   -i|--internal               Use internal diff logic
**   --invert                    Invert the diff
**   --json                      Output formatted as JSON
**   -n|--linenum                Show line numbers
**   -N|--new-file               Alias for --verbose
**   --numstat                   Show only the number of added and deleted lines
**   -y|--side-by-side           Side-by-side diff
**   --strip-trailing-cr         Strip trailing CR
**   --tcl                       Tcl-formatted output used internally by --tk
**   --tclsh PATH                Tcl/Tk shell used for --tk (default: "tclsh")
**   --tk                        Launch a Tcl/Tk GUI for display
**   --to VERSION                Select VERSION as target for the diff
**   --undo                      Use the undo buffer as the baseline
**   --unified                   Unified diff
**   -v|--verbose                Output complete text of added or deleted files
**   -h|--versions               Show compared versions in the diff header
**   --webpage                   Format output as a stand-alone HTML webpage
**   -W|--width N                Width of lines in side-by-side diff
**   -Z|--ignore-trailing-space  Ignore changes to end-of-line whitespace
*/
void diff_cmd(void){
  int isGDiff;               /* True for gdiff.  False for normal diff */
  const char *zFrom;         /* Source version number */
  const char *zTo;           /* Target version number */
  const char *zCheckin;      /* Check-in version number */
  const char *zBranch;       /* Branch to diff */
  int againstUndo = 0;       /* Diff against files in the undo buffer */
  FileDirList *pFileDir = 0; /* Restrict the diff to these files */
  DiffConfig DCfg;           /* Diff configuration object */
  int bFromIsDir = 0;        /* True if zFrom is a directory name */

  if( find_option("tk",0,0)!=0 || has_option("tclsh") ){
    diff_tk("diff", 2);
    return;
  }
  isGDiff = g.argv[1][0]=='g';
  zFrom = find_option("from", "r", 1);
  zTo = find_option("to", 0, 1);
  zCheckin = find_option("checkin", "ci", 1);
  zBranch = find_option("branch", 0, 1);
  againstUndo = find_option("undo",0,0)!=0;
  if( againstUndo && (zFrom!=0 || zTo!=0 || zCheckin!=0 || zBranch!=0) ){
    fossil_fatal("cannot use --undo together with --from, --to, --checkin,"
                 " or --branch");
  }
  if( zBranch ){
    if( zTo || zFrom || zCheckin ){
      fossil_fatal("cannot use --from, --to, or --checkin with --branch");
    }
    zTo = zBranch;
    zFrom = mprintf("root:%s", zBranch);
  }
  if( zCheckin!=0 && (zFrom!=0 || zTo!=0) ){
    fossil_fatal("cannot use --checkin together with --from or --to");
  }


  if( 0==zCheckin ){
    if( zTo==0 || againstUndo ){
      db_must_be_within_tree();
    }else if( zFrom==0 ){
      fossil_fatal("must use --from if --to is present");
    }else{
      db_find_and_open_repository(0, 0);
    }
  }else{
    db_find_and_open_repository(0, 0);
  }
  determine_exec_relative_option(1);
  if( zFrom!=file_tail(zFrom)
   && file_isdir(zFrom, ExtFILE)==1
   && !db_exists("SELECT 1 FROM tag WHERE tagname='sym-%q'", zFrom)
  ){
    bFromIsDir = 1;
    if( zTo ){
      fossil_fatal("cannot use --to together with \"--from PATH\"");
    }
  }
  diff_options(&DCfg, isGDiff, 0);
  verify_all_options();
  g.diffCnt[0] = g.diffCnt[1] = g.diffCnt[2] = 0;
  if( g.argc>=3 ){
    int i;
    Blob fname;
    pFileDir = fossil_malloc( sizeof(*pFileDir) * (g.argc-1) );
    memset(pFileDir, 0, sizeof(*pFileDir) * (g.argc-1));
1335
1336
1337
1338
1339
1340
1341


1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
      " WHERE plink.cid=%d AND plink.isprim AND plink.pid=blob.rid",
      ridTo);
    if( zFrom==0 ){
      fossil_fatal("check-in %s has no parent", zTo);
    }
  }
  diff_begin(&DCfg);


  if( againstUndo ){
    if( db_lget_int("undo_available",0)==0 ){
      fossil_print("No undo or redo is available\n");
      return;
    }
    diff_against_undo(&DCfg, pFileDir);
  }else if( zTo==0 ){
    diff_against_disk(zFrom, &DCfg, pFileDir, 0);
  }else{
    diff_two_versions(zFrom, zTo, &DCfg, pFileDir);
  }
  if( pFileDir ){
    int i;
    for(i=0; pFileDir[i].zName; i++){
      if( pFileDir[i].nUsed==0







>
>
|




|

|







1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
      " WHERE plink.cid=%d AND plink.isprim AND plink.pid=blob.rid",
      ridTo);
    if( zFrom==0 ){
      fossil_fatal("check-in %s has no parent", zTo);
    }
  }
  diff_begin(&DCfg);
  if( bFromIsDir ){
    diff_externbase_to_checkout(zFrom, &DCfg, pFileDir);
  }else if( againstUndo ){
    if( db_lget_int("undo_available",0)==0 ){
      fossil_print("No undo or redo is available\n");
      return;
    }
    diff_undo_to_checkout(&DCfg, pFileDir);
  }else if( zTo==0 ){
    diff_version_to_checkout(zFrom, &DCfg, pFileDir, 0);
  }else{
    diff_two_versions(zFrom, zTo, &DCfg, pFileDir);
  }
  if( pFileDir ){
    int i;
    for(i=0; pFileDir[i].zName; i++){
      if( pFileDir[i].nUsed==0

Changes to src/fossil.diff.js.

26
27
28
29
30
31
32



33
34






35
36
37
38
39
40
41
42

43
44
45





46
47
48
49
50
51
52





53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
});

window.fossil.onPageLoad(function(){
  /**
     Adds toggle checkboxes to each file entry in the diff views for
     /info and similar pages.
  */



  const D = window.fossil.dom;
  const allToggles = [/*collection of all diff-toggle checkboxes */];






  const addToggle = function(diffElem){
    const sib = diffElem.previousElementSibling,
          ckbox = sib ? D.addClass(D.checkbox(true), 'diff-toggle') : 0;
    if(!sib) return;
    const lblToggle = D.label();
    D.append(lblToggle, ckbox, D.text(" show/hide "));
    const wrapper = D.append(D.span(), lblToggle);
    allToggles.push(ckbox);

    D.append(sib, D.append(wrapper, lblToggle));
    ckbox.addEventListener('change', function(){
      diffElem.classList[this.checked ? 'remove' : 'add']('hidden');





    }, false);
  };
  if( !document.querySelector('body.fdiff') ){
    /* Don't show the diff toggle button for /fdiff because it only
       has a single file to show (and also a different DOM layout). */
    document.querySelectorAll('table.diff').forEach(addToggle);
  }





  const icm = allToggles.length>1 ? window.fossil.page.diffControlContainer : 0;
  if(icm) {
    const btnAll = D.addClass(D.a("#", "Show/Hide"), "button");
    D.append( icm, btnAll );
    btnAll.addEventListener('click', function(ev){
      ev.preventDefault();
      ev.stopPropagation();
      /* Figure out whether we want to show all or hide all: if any diffs are
         toggled off, show all, else hide all. */
      let show = false;
      let ckbox;
      for( ckbox of allToggles ){
        if( !ckbox.checked ){
          show = true;
          break;
        }
      }
      for( ckbox of allToggles ){
        /* Toggle all entries to match this new state. We use click()
           instead of ckbox.checked=... so that the on-change event handler
           fires. */
        if(ckbox.checked!==show) ckbox.click();
      }
    }, false);
  }







>
>
>

|
>
>
>
>
>
>








>



>
>
>
>
>







>
>
>
>
>


|




<
<
|
<
|
<
<
<
<
<
<







26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79


80

81






82
83
84
85
86
87
88
});

window.fossil.onPageLoad(function(){
  /**
     Adds toggle checkboxes to each file entry in the diff views for
     /info and similar pages.
  */
  if( !window.fossil.page.diffControlContainer ){
    return;
  }
  const D = window.fossil.dom;
  const allToggles = [/*collection of all diff-toggle checkboxes*/];
  let checkedCount =
      0 /* When showing more than one diff, keep track of how many
           "show/hide" checkboxes are are checked so we can update the
           "show/hide all" label dynamically. */;
  let btnAll /* UI control to show/hide all diffs */;
  /* Install a diff-toggle button for the given diff table element. */
  const addToggle = function(diffElem){
    const sib = diffElem.previousElementSibling,
          ckbox = sib ? D.addClass(D.checkbox(true), 'diff-toggle') : 0;
    if(!sib) return;
    const lblToggle = D.label();
    D.append(lblToggle, ckbox, D.text(" show/hide "));
    const wrapper = D.append(D.span(), lblToggle);
    allToggles.push(ckbox);
    ++checkedCount;
    D.append(sib, D.append(wrapper, lblToggle));
    ckbox.addEventListener('change', function(){
      diffElem.classList[this.checked ? 'remove' : 'add']('hidden');
      if(btnAll){
        checkedCount += (this.checked ? 1 : -1);
        btnAll.innerText = (checkedCount < allToggles.length)
          ? "Show diffs" : "Hide diffs";
      }
    }, false);
  };
  if( !document.querySelector('body.fdiff') ){
    /* Don't show the diff toggle button for /fdiff because it only
       has a single file to show (and also a different DOM layout). */
    document.querySelectorAll('table.diff').forEach(addToggle);
  }
  /**
     Set up a "toggle all diffs" button which toggles all of the
     above-installed checkboxes, but only if more than one diff is
     rendered.
  */
  const icm = allToggles.length>1 ? window.fossil.page.diffControlContainer : 0;
  if(icm) {
    btnAll = D.addClass(D.a("#", "Hide diffs"), "button");
    D.append( icm, btnAll );
    btnAll.addEventListener('click', function(ev){
      ev.preventDefault();
      ev.stopPropagation();


      const show = checkedCount < allToggles.length;

      for( const ckbox of allToggles ){






        /* Toggle all entries to match this new state. We use click()
           instead of ckbox.checked=... so that the on-change event handler
           fires. */
        if(ckbox.checked!==show) ckbox.click();
      }
    }, false);
  }

Changes to src/info.c.

607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
  www_print_timeline(&q, TIMELINE_DISJOINT|TIMELINE_GRAPH|TIMELINE_NOSCROLL,
                     0, 0, 0, rid, 0, 0);
  db_finalize(&q);
  style_finish_page();
}

/*
** WEBPAGE: ckout
**
** Show information about the current checkout.  This page only functions
** if the web server is run on a loopback interface (in other words, was
** started using "fossil ui" or similar) from with on open check-out.
*/
void ckout_page(void){
  int vid;
  char *zHostname;
  char *zCwd;
  int diffType;               /* 0: no diff,  1: unified,  2: side-by-side */
  DiffConfig DCfg,*pCfg;      /* Diff details */
  const char *zHome;          /* Home directory */
  const char *zW;             /* The "w" query parameter */
  int nChng;                  /* Number of changes */
  Stmt q;

  if( !db_open_local(0) || !cgi_is_loopback(g.zIpAddr) ){
    cgi_redirectf("%R/home");
    return;
  }
  file_chdir(g.zLocalRoot, 0);
  diffType = preferred_diff_type();
  pCfg = construct_diff_flags(diffType, &DCfg);
  vid = db_lget_int("checkout", 0);
  db_unprotect(PROTECT_ALL);
  vfile_check_signature(vid, CKSIG_ENOTFILE);
  db_protect_pop();
  style_set_current_feature("vinfo");
  zHostname = fossil_hostname();
  zCwd = file_getcwd(0,0);
  zHome = fossil_getenv("HOME");
  if( zHome ){
    int nHome = (int)strlen(zHome);
    if( strncmp(zCwd, zHome, nHome)==0 && zCwd[nHome]=='/' ){
      zCwd = mprintf("~%s", zCwd+nHome);
    }
  }
  if( zHostname ){
    style_header("Checkout Status: %h on %h", zCwd, zHostname);
  }else{
    style_header("Checkout Status: %h", zCwd);
  }
  render_checkin_context(vid, 0, 0, 0);
  nChng = db_int(0, "SELECT count(*) FROM vfile"
                    " WHERE vid=%d AND (deleted OR chnged OR rid==0)", vid);
  if( nChng==0 ){
    @ <p>No uncommitted changes</p>
    style_finish_page();
    return;
  }
  db_prepare(&q,
       /*   0         1        2        3       4    5       6 */
    "SELECT pathname, deleted, chnged , rid==0, rid, islink, uuid"
    "  FROM vfile LEFT JOIN blob USING(rid)"
    " WHERE vid=%d"
    "   AND (deleted OR chnged OR rid==0)"
    " ORDER BY pathname /*scan*/",
    vid
  );
  if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){
    DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG;
  }else{
    DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG;
  }
  @ <hr>
  @ <div class="sectionmenu info-changes-menu">
  zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
  if( diffType!=1 ){
    @ %z(chref("button","%R?diff=1%s",zW))Unified&nbsp;Diff</a>
  }
  if( diffType!=2 ){
    @ %z(chref("button","%R?diff=2%s",zW))Side-by-Side&nbsp;Diff</a>







|
<
<
<
<

|
<
<
<


<




<
<
<
<
<


<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




<
















<







607
608
609
610
611
612
613
614




615
616



617
618

619
620
621
622





623
624




















625
626
627
628

629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644

645
646
647
648
649
650
651
  www_print_timeline(&q, TIMELINE_DISJOINT|TIMELINE_GRAPH|TIMELINE_NOSCROLL,
                     0, 0, 0, rid, 0, 0);
  db_finalize(&q);
  style_finish_page();
}

/*
** Render a web-page diff of the changes in the working check-out




*/
static void ckout_normal_diff(int vid){



  int diffType;               /* 0: no diff,  1: unified,  2: side-by-side */
  DiffConfig DCfg,*pCfg;      /* Diff details */

  const char *zW;             /* The "w" query parameter */
  int nChng;                  /* Number of changes */
  Stmt q;






  diffType = preferred_diff_type();
  pCfg = construct_diff_flags(diffType, &DCfg);




















  nChng = db_int(0, "SELECT count(*) FROM vfile"
                    " WHERE vid=%d AND (deleted OR chnged OR rid==0)", vid);
  if( nChng==0 ){
    @ <p>No uncommitted changes</p>

    return;
  }
  db_prepare(&q,
       /*   0         1        2        3       4    5       6 */
    "SELECT pathname, deleted, chnged , rid==0, rid, islink, uuid"
    "  FROM vfile LEFT JOIN blob USING(rid)"
    " WHERE vid=%d"
    "   AND (deleted OR chnged OR rid==0)"
    " ORDER BY pathname /*scan*/",
    vid
  );
  if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){
    DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG;
  }else{
    DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG;
  }

  @ <div class="sectionmenu info-changes-menu">
  zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
  if( diffType!=1 ){
    @ %z(chref("button","%R?diff=1%s",zW))Unified&nbsp;Diff</a>
  }
  if( diffType!=2 ){
    @ %z(chref("button","%R?diff=2%s",zW))Side-by-Side&nbsp;Diff</a>
748
749
750
751
752
753
754










































755















































756


































































757
758
759
760
761
762
763
      blob_read_from_file(&new, zTreename, ExtFILE);
      text_diff(&old, &new, cgi_output_blob(), pCfg);
      blob_reset(&old);
      blob_reset(&new);
    }
  }
  db_finalize(&q);










































  // @ </div> <!-- ap-002 -->















































  append_diff_javascript(diffType);


































































  style_finish_page();
}

/*
** WEBPAGE: vinfo
** WEBPAGE: ci
** URL:  /ci/ARTIFACTID







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
      blob_read_from_file(&new, zTreename, ExtFILE);
      text_diff(&old, &new, cgi_output_blob(), pCfg);
      blob_reset(&old);
      blob_reset(&new);
    }
  }
  db_finalize(&q);
  append_diff_javascript(diffType);
}

/*
** Render a web-page diff of the changes in the working check-out to
** an external reference.
*/
static void ckout_external_base_diff(int vid, const char *zExBase){
  int diffType;               /* 0: no diff,  1: unified,  2: side-by-side */
  DiffConfig DCfg,*pCfg;      /* Diff details */
  const char *zW;             /* The "w" query parameter */
  Stmt q;

  diffType = preferred_diff_type();
  pCfg = construct_diff_flags(diffType, &DCfg);
  db_prepare(&q,
    "SELECT pathname FROM vfile WHERE vid=%d ORDER BY pathname", vid
  );
  if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){
    DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG;
  }else{
    DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG;
  }
  @ <div class="sectionmenu info-changes-menu">
  zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
  if( diffType!=1 ){
    @ %z(chref("button","%R?diff=1&exbase=%h%s",zExBase,zW))\
    @ Unified&nbsp;Diff</a>
  }
  if( diffType!=2 ){
    @ %z(chref("button","%R?diff=2&exbase=%h%s",zExBase,zW))\
    @ Side-by-Side&nbsp;Diff</a>
  }
  if( diffType!=0 ){
    if( *zW ){
      @ %z(chref("button","%R?diff=%d&exbase=%h",diffType,zExBase))\
      @ Show&nbsp;Whitespace&nbsp;Changes</a>
    }else{
      @ %z(chref("button","%R?diff=%d&exbase=%h&w",diffType,zExBase))\
      @ Ignore&nbsp;Whitespace</a>
    }
  }
  @ </div>
  while( db_step(&q)==SQLITE_ROW ){
    const char *zFile;  /* Name of file in the repository */
    char *zLhs;         /* Full name of left-hand side file */
    char *zRhs;         /* Full name of right-hand side file */
    Blob rhs;           /* Full text of RHS */
    Blob lhs;           /* Full text of LHS */

    zFile = db_column_text(&q,0);
    zLhs = mprintf("%s/%s", zExBase, zFile);
    zRhs = mprintf("%s%s", g.zLocalRoot, zFile);
    if( file_size(zLhs, ExtFILE)<0 ){
      @ <div class='file-change-line'><span>
      @ Missing from external baseline: %h(zFile)
      @ </span></div>
    }else{
      blob_read_from_file(&lhs, zLhs, ExtFILE);
      blob_read_from_file(&rhs, zRhs, ExtFILE);
      if( blob_size(&lhs)!=blob_size(&rhs)
       || memcmp(blob_buffer(&lhs), blob_buffer(&rhs), blob_size(&lhs))!=0
      ){
        @ <div class='file-change-line'><span>
        @ Changes to %h(zFile)
        @ </span></div>
        if( pCfg ){
          char *zFullFN;
          char *zHexFN;
          int nFullFN;
          zFullFN = file_canonical_name_dup(zLhs);
          nFullFN = (int)strlen(zFullFN);
          zHexFN = fossil_malloc( nFullFN*2 + 5 );
          zHexFN[0] = 'x';
          encode16((const u8*)zFullFN, (u8*)(zHexFN+1), nFullFN);
          zHexFN[1+nFullFN*2] = 0;
          fossil_free(zFullFN);
          pCfg->zLeftHash = zHexFN;
          text_diff(&lhs, &rhs, cgi_output_blob(), pCfg);
          pCfg->zLeftHash = 0;
          fossil_free(zHexFN);
        }
      }
      blob_reset(&lhs);
      blob_reset(&rhs);
    }
    fossil_free(zLhs);
    fossil_free(zRhs);
  }
  db_finalize(&q);
  append_diff_javascript(diffType);
}

/*
** WEBPAGE: ckout
**
** Show information about the current checkout.  This page only functions
** if the web server is run on a loopback interface (in other words, was
** started using "fossil ui" or similar) from within an open check-out.
**
** If the "exbase=PATH" query parameter is provided, then the diff shown
** uses the files in PATH as the baseline.  This is the same as using
** the "--from PATH" argument to the "fossil diff" command-line.  In fact,
** when using "fossil ui --from PATH", the --from argument becomes the value
** of the exbase query parameter for the start page.
**
** Other query parameters related to diffs are also accepted.
*/
void ckout_page(void){
  int vid;
  const char *zHome;          /* Home directory */
  int nHome;
  const char *zExBase;
  char *zHostname;
  char *zCwd;

  if( !db_open_local(0) || !cgi_is_loopback(g.zIpAddr) ){
    cgi_redirectf("%R/home");
    return;
  }
  file_chdir(g.zLocalRoot, 0);
  vid = db_lget_int("checkout", 0);
  db_unprotect(PROTECT_ALL);
  vfile_check_signature(vid, CKSIG_ENOTFILE);
  db_protect_pop();
  style_set_current_feature("vinfo");
  zHostname = fossil_hostname();
  zCwd = file_getcwd(0,0);
  zHome = fossil_getenv("HOME");
  if( zHome ){
    nHome = (int)strlen(zHome);
    if( strncmp(zCwd, zHome, nHome)==0 && zCwd[nHome]=='/' ){
      zCwd = mprintf("~%s", zCwd+nHome);
    }
  }else{
    nHome = 0;
  }
  if( zHostname ){
    style_header("Checkout Status: %h on %h", zCwd, zHostname);
  }else{
    style_header("Checkout Status: %h", zCwd);
  }
  render_checkin_context(vid, 0, 0, 0);
  @ <hr>
  zExBase = P("exbase");
  if( zExBase && zExBase[0] ){
    char *zCBase = file_canonical_name_dup(zExBase);
    if( nHome && strncmp(zCBase, zHome, nHome)==0 && zCBase[nHome]=='/' ){
      @ <p>Using external baseline: ~%h(zCBase+nHome)</p>
    }else{
      @ <p>Using external baseline: %h(zCBase)</p>
    }
    ckout_external_base_diff(vid, zCBase);
    fossil_free(zCBase);
  }else{
    ckout_normal_diff(vid);
  }
  style_finish_page();
}

/*
** WEBPAGE: vinfo
** WEBPAGE: ci
** URL:  /ci/ARTIFACTID
2075
2076
2077
2078
2079
2080
2081






2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096

2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116

























2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149

/*
** WEBPAGE: jchunk hidden
** URL: /jchunk/HASH?from=N&to=M
**
** Return lines of text from a file as a JSON array - one entry in the
** array for each line of text.






**
** **Warning:**  This is an internal-use-only interface that is subject to
** change at any moment.  External application should not use this interface
** since the application will break when this interface changes, and this
** interface will undoubtedly change.
**
** This page is intended to be used in an XHR from javascript on a
** diff page, to return unseen context to fill in additional context
** when the user clicks on the appropriate button. The response is
** always in JSON form and errors are reported as documented for
** ajax_route_error().
*/
void jchunk_page(void){
  int rid = 0;
  const char *zName = PD("name", "");

  int iFrom = atoi(PD("from","0"));
  int iTo = atoi(PD("to","0"));
  int ln;
  int go = 1;
  const char *zSep;
  Blob content;
  Blob line;
  Blob *pOut;

  if(0){
    ajax_route_error(400, "Just testing client-side error handling.");
    return;
  }

  login_check_credentials();
  cgi_check_for_malice();
  if( !g.perm.Read ){
    ajax_route_error(403, "Access requires Read permissions.");
    return;
  }

























#if 1
  /* Re-enable this block once this code is integrated somewhere into
     the UI. */
  rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zName);
  if( rid==0 ){
    ajax_route_error(404, "Unknown artifact: %h", zName);
    return;
  }
#else
  /* This impl is only to simplify "manual" testing via the JS
     console. */
  rid = symbolic_name_to_rid(zName, "*");
  if( rid==0 ){
    ajax_route_error(404, "Unknown artifact: %h", zName);
    return;
  }else if( rid<0 ){
    ajax_route_error(418, "Ambiguous artifact name: %h", zName);
    return;
  }
#endif
  if( iFrom<1 || iTo<iFrom ){
    ajax_route_error(500, "Invalid line range from=%d, to=%d.",
                     iFrom, iTo);
    return;
  }
  content_get(rid, &content);
  g.isConst = 1;
  cgi_set_content_type("application/json");
  ln = 0;
  while( go && ln<iFrom ){
    go = blob_line(&content, &line);
    ln++;
  }







>
>
>
>
>
>















>




















>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|

<
<
<
|

<







2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288



2289
2290

2291
2292
2293
2294
2295
2296
2297

/*
** WEBPAGE: jchunk hidden
** URL: /jchunk/HASH?from=N&to=M
**
** Return lines of text from a file as a JSON array - one entry in the
** array for each line of text.
**
** The HASH is normally a sha1 or sha3 hash that identifies an artifact
** in the BLOB table of the database.  However, if HASH starts with an "x"
** and is followed by valid hexadecimal, and if we are running in a
** "fossil ui" situation (locally and with privilege), then decode the hex
** into a filename and read the file content from that name.
**
** **Warning:**  This is an internal-use-only interface that is subject to
** change at any moment.  External application should not use this interface
** since the application will break when this interface changes, and this
** interface will undoubtedly change.
**
** This page is intended to be used in an XHR from javascript on a
** diff page, to return unseen context to fill in additional context
** when the user clicks on the appropriate button. The response is
** always in JSON form and errors are reported as documented for
** ajax_route_error().
*/
void jchunk_page(void){
  int rid = 0;
  const char *zName = PD("name", "");
  int nName = (int)(strlen(zName)&0x7fffffff);
  int iFrom = atoi(PD("from","0"));
  int iTo = atoi(PD("to","0"));
  int ln;
  int go = 1;
  const char *zSep;
  Blob content;
  Blob line;
  Blob *pOut;

  if(0){
    ajax_route_error(400, "Just testing client-side error handling.");
    return;
  }

  login_check_credentials();
  cgi_check_for_malice();
  if( !g.perm.Read ){
    ajax_route_error(403, "Access requires Read permissions.");
    return;
  }
  if( iFrom<1 || iTo<iFrom ){
    ajax_route_error(500, "Invalid line range from=%d, to=%d.",
                     iFrom, iTo);
    return;
  }
  if( zName[0]=='x'
   && ((nName-1)&1)==0
   && validate16(&zName[1],nName-1)
   && g.perm.Admin
   && db_open_local(0)
   && cgi_is_loopback(g.zIpAddr)
  ){
    /* Treat the HASH as a hex-encoded filename */
    int n = (nName-1)/2;
    char *zFN = fossil_malloc(n+1);
    decode16((const u8*)&zName[1], (u8*)zFN, nName-1);
    zFN[n] = 0;
    if( file_size(zFN, ExtFILE)<0 ){
      blob_zero(&content);
    }else{
      blob_read_from_file(&content, zFN, ExtFILE);
    }
    fossil_free(zFN);
  }else{
    /* Treat the HASH as an artifact hash matching BLOB.UUID */
#if 1
    /* Re-enable this block once this code is integrated somewhere into
       the UI. */
    rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zName);
    if( rid==0 ){
      ajax_route_error(404, "Unknown artifact: %h", zName);
      return;
    }
#else
    /* This impl is only to simplify "manual" testing via the JS
       console. */
    rid = symbolic_name_to_rid(zName, "*");
    if( rid==0 ){
      ajax_route_error(404, "Unknown artifact: %h", zName);
      return;
    }else if( rid<0 ){
      ajax_route_error(418, "Ambiguous artifact name: %h", zName);
      return;
    }
#endif



    content_get(rid, &content);
  }

  g.isConst = 1;
  cgi_set_content_type("application/json");
  ln = 0;
  while( go && ln<iFrom ){
    go = blob_line(&content, &line);
    ln++;
  }

Changes to src/main.c.

3176
3177
3178
3179
3180
3181
3182

3183
3184
3185
3186
3187
3188
3189
**                       /doc/ckout/...
**   --create            Create a new REPOSITORY if it does not already exist
**   --errorlog FILE     Append HTTP error messages to FILE
**   --extroot DIR       Document root for the /ext extension mechanism
**   --files GLOBLIST    Comma-separated list of glob patterns for static files
**   --fossilcmd PATH    The pathname of the "fossil" executable on the remote
**                       system when REPOSITORY is remote.

**   --localauth         Enable automatic login for requests from localhost
**   --localhost         Listen on 127.0.0.1 only (always true for "ui")
**   --https             Indicates that the input is coming through a reverse
**                       proxy that has already translated HTTPS into HTTP.
**   --jsmode MODE       Determine how JavaScript is delivered with pages.
**                       Mode can be one of:
**                          inline       All JavaScript is inserted inline at







>







3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
**                       /doc/ckout/...
**   --create            Create a new REPOSITORY if it does not already exist
**   --errorlog FILE     Append HTTP error messages to FILE
**   --extroot DIR       Document root for the /ext extension mechanism
**   --files GLOBLIST    Comma-separated list of glob patterns for static files
**   --fossilcmd PATH    The pathname of the "fossil" executable on the remote
**                       system when REPOSITORY is remote.
**   --from PATH         Use PATH as the diff baseline for the /ckout page
**   --localauth         Enable automatic login for requests from localhost
**   --localhost         Listen on 127.0.0.1 only (always true for "ui")
**   --https             Indicates that the input is coming through a reverse
**                       proxy that has already translated HTTPS into HTTP.
**   --jsmode MODE       Determine how JavaScript is delivered with pages.
**                       Mode can be one of:
**                          inline       All JavaScript is inserted inline at
3248
3249
3250
3251
3252
3253
3254

3255
3256
3257
3258
3259
3260
3261
  int fCreate = 0;           /* The --create flag */
  int fNoBrowser = 0;        /* Do not auto-launch web-browser */
  const char *zInitPage = 0; /* Start on this page.  --page option */
  int findServerArg = 2;     /* argv index for find_server_repository() */
  char *zRemote = 0;         /* Remote host on which to run "fossil ui" */
  const char *zJsMode;       /* The --jsmode parameter */
  const char *zFossilCmd =0; /* Name of "fossil" binary on remote system */



#if USE_SEE
  db_setup_for_saved_encryption_key();
#endif

#if defined(_WIN32)







>







3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
  int fCreate = 0;           /* The --create flag */
  int fNoBrowser = 0;        /* Do not auto-launch web-browser */
  const char *zInitPage = 0; /* Start on this page.  --page option */
  int findServerArg = 2;     /* argv index for find_server_repository() */
  char *zRemote = 0;         /* Remote host on which to run "fossil ui" */
  const char *zJsMode;       /* The --jsmode parameter */
  const char *zFossilCmd =0; /* Name of "fossil" binary on remote system */
  const char *zFrom;         /* Value for --from */


#if USE_SEE
  db_setup_for_saved_encryption_key();
#endif

#if defined(_WIN32)
3284
3285
3286
3287
3288
3289
3290





3291
3292
3293



3294
3295
3296
3297
3298
3299
3300
  zTimeout = find_option("max-latency",0,1);
#endif
  g.useLocalauth = find_option("localauth", 0, 0)!=0;
  Th_InitTraceLog();
  zPort = find_option("port", "P", 1);
  isUiCmd = g.argv[1][0]=='u';
  if( isUiCmd ){





    zInitPage = find_option("page", "p", 1);
    if( zInitPage && zInitPage[0]=='/' ) zInitPage++;
    zFossilCmd = find_option("fossilcmd", 0, 1);



  }
  zNotFound = find_option("notfound", 0, 1);
  allowRepoList = find_option("repolist",0,0)!=0;
  if( find_option("nocompress",0,0)!=0 ) g.fNoHttpCompress = 1;
  zAltBase = find_option("baseurl", 0, 1);
  fCreate = find_option("create",0,0)!=0;
  g.zReqType = "HTTP";







>
>
>
>
>



>
>
>







3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
  zTimeout = find_option("max-latency",0,1);
#endif
  g.useLocalauth = find_option("localauth", 0, 0)!=0;
  Th_InitTraceLog();
  zPort = find_option("port", "P", 1);
  isUiCmd = g.argv[1][0]=='u';
  if( isUiCmd ){
    zFrom = find_option("from", 0, 1);
    if( zFrom && zFrom==file_tail(zFrom) ){
      fossil_fatal("the argument to --from must be a pathname for"
                   " the \"ui\" command");
    }
    zInitPage = find_option("page", "p", 1);
    if( zInitPage && zInitPage[0]=='/' ) zInitPage++;
    zFossilCmd = find_option("fossilcmd", 0, 1);
    if( zFrom && zInitPage==0 ){
      zInitPage = mprintf("ckout?exbase=%T", zFrom);
    }
  }
  zNotFound = find_option("notfound", 0, 1);
  allowRepoList = find_option("repolist",0,0)!=0;
  if( find_option("nocompress",0,0)!=0 ) g.fNoHttpCompress = 1;
  zAltBase = find_option("baseurl", 0, 1);
  fCreate = find_option("create",0,0)!=0;
  g.zReqType = "HTTP";

Changes to src/merge.c.

685
686
687
688
689
690
691

692
693
694
695
696
697
698
**   --force-missing         Force the merge even if there is missing content
**   --integrate             Merged branch will be closed when committing
**   -K|--keep-merge-files   On merge conflict, retain the temporary files
**                           used for merging, named *-baseline, *-original,
**                           and *-merge.
**   -n|--dry-run            Do not actually change files on disk
**   --nosync                Do not auto-sync prior to merging

**   -v|--verbose            Show additional details of the merge
*/
void merge_cmd(void){
  int vid;              /* Current version "V" */
  int mid;              /* Version we are merging from "M" */
  int pid = 0;          /* The pivot version - most recent common ancestor P */
  int nid = 0;          /* The name pivot version "N" */







>







685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
**   --force-missing         Force the merge even if there is missing content
**   --integrate             Merged branch will be closed when committing
**   -K|--keep-merge-files   On merge conflict, retain the temporary files
**                           used for merging, named *-baseline, *-original,
**                           and *-merge.
**   -n|--dry-run            Do not actually change files on disk
**   --nosync                Do not auto-sync prior to merging
**   --noundo                Do not record changes in the undo log
**   -v|--verbose            Show additional details of the merge
*/
void merge_cmd(void){
  int vid;              /* Current version "V" */
  int mid;              /* Version we are merging from "M" */
  int pid = 0;          /* The pivot version - most recent common ancestor P */
  int nid = 0;          /* The name pivot version "N" */
710
711
712
713
714
715
716

717
718
719
720
721
722
723
  int keepMergeFlag;    /* True if --keep-merge-files is present */
  int nConflict = 0;    /* Number of conflicts seen */
  int nOverwrite = 0;   /* Number of unmanaged files overwritten */
  char vAncestor = 'p'; /* If P is an ancestor of V then 'p', else 'n' */
  const char *zVersion; /* The VERSION argument */
  int bMultiMerge = 0;  /* True if there are two or more VERSION arguments */
  int nMerge = 0;       /* Number of prior merges processed */

  Stmt q;               /* SQL statment used for merge processing */


  /* Notation:
  **
  **      V     The current check-out
  **      M     The version being merged in







>







711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
  int keepMergeFlag;    /* True if --keep-merge-files is present */
  int nConflict = 0;    /* Number of conflicts seen */
  int nOverwrite = 0;   /* Number of unmanaged files overwritten */
  char vAncestor = 'p'; /* If P is an ancestor of V then 'p', else 'n' */
  const char *zVersion; /* The VERSION argument */
  int bMultiMerge = 0;  /* True if there are two or more VERSION arguments */
  int nMerge = 0;       /* Number of prior merges processed */
  int useUndo = 1;      /* True to record changes in the undo log */
  Stmt q;               /* SQL statment used for merge processing */


  /* Notation:
  **
  **      V     The current check-out
  **      M     The version being merged in
758
759
760
761
762
763
764


765
766
767
768
769
770
771
  ** Hints:
  **   *  Combine --debug and --verbose for still more output.
  **   *  The --dry-run option is also useful in combination with --debug.
  */
  debugFlag = find_option("debug",0,0)!=0;
  if( debugFlag && verboseFlag ) debugFlag = 2;
  showVfileFlag = find_option("show-vfile",0,0)!=0;



  verify_all_options();
  db_must_be_within_tree();
  if( zBinGlob==0 ) zBinGlob = db_get("binary-glob",0);
  vid = db_lget_int("checkout", 0);
  if( vid==0 ){
    fossil_fatal("nothing is checked out");







>
>







760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
  ** Hints:
  **   *  Combine --debug and --verbose for still more output.
  **   *  The --dry-run option is also useful in combination with --debug.
  */
  debugFlag = find_option("debug",0,0)!=0;
  if( debugFlag && verboseFlag ) debugFlag = 2;
  showVfileFlag = find_option("show-vfile",0,0)!=0;
  useUndo = find_option("noundo",0,0)==0;
  if( dryRunFlag ) useUndo = 0;

  verify_all_options();
  db_must_be_within_tree();
  if( zBinGlob==0 ) zBinGlob = db_get("binary-glob",0);
  vid = db_lget_int("checkout", 0);
  if( vid==0 ){
    fossil_fatal("nothing is checked out");
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
  if( verboseFlag ){
    print_checkin_description(mid, 12,
              integrateFlag ? "integrate:" : "merge-from:");
    print_checkin_description(pid, 12, "baseline:");
  }
  vfile_check_signature(vid, CKSIG_ENOTFILE);
  if( nMerge==0 ) db_begin_transaction();
  if( !dryRunFlag ) undo_begin();
  if( load_vfile_from_rid(mid) && !forceMissingFlag ){
    fossil_fatal("missing content, unable to merge");
  }
  if( load_vfile_from_rid(pid) && !forceMissingFlag ){
    fossil_fatal("missing content, unable to merge");
  }
  if( zPivot ){







|







928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
  if( verboseFlag ){
    print_checkin_description(mid, 12,
              integrateFlag ? "integrate:" : "merge-from:");
    print_checkin_description(pid, 12, "baseline:");
  }
  vfile_check_signature(vid, CKSIG_ENOTFILE);
  if( nMerge==0 ) db_begin_transaction();
  if( useUndo ) undo_begin();
  if( load_vfile_from_rid(mid) && !forceMissingFlag ){
    fossil_fatal("missing content, unable to merge");
  }
  if( load_vfile_from_rid(pid) && !forceMissingFlag ){
    fossil_fatal("missing content, unable to merge");
  }
  if( zPivot ){
1181
1182
1183
1184
1185
1186
1187

1188
1189
1190
1191
1192
1193
1194
1195
1196
  while( db_step(&q)==SQLITE_ROW ){
    int idv = db_column_int(&q, 0);
    int ridm = db_column_int(&q, 1);
    const char *zName = db_column_text(&q, 2);
    int islinkm = db_column_int(&q, 3);
    /* Copy content from idm over into idv.  Overwrite idv. */
    fossil_print("UPDATE %s\n", zName);

    if( !dryRunFlag ){
      undo_save(zName);
      db_multi_exec(
        "UPDATE vfile SET mtime=0, mrid=%d, chnged=%d, islink=%d,"
        " mhash=CASE WHEN rid<>%d"
                   " THEN (SELECT uuid FROM blob WHERE blob.rid=%d) END"
        " WHERE id=%d", ridm, integrateFlag?4:2, islinkm, ridm, ridm, idv
      );
      vfile_to_disk(0, idv, 0, 0);







>

<







1185
1186
1187
1188
1189
1190
1191
1192
1193

1194
1195
1196
1197
1198
1199
1200
  while( db_step(&q)==SQLITE_ROW ){
    int idv = db_column_int(&q, 0);
    int ridm = db_column_int(&q, 1);
    const char *zName = db_column_text(&q, 2);
    int islinkm = db_column_int(&q, 3);
    /* Copy content from idm over into idv.  Overwrite idv. */
    fossil_print("UPDATE %s\n", zName);
    if( useUndo ) undo_save(zName);
    if( !dryRunFlag ){

      db_multi_exec(
        "UPDATE vfile SET mtime=0, mrid=%d, chnged=%d, islink=%d,"
        " mhash=CASE WHEN rid<>%d"
                   " THEN (SELECT uuid FROM blob WHERE blob.rid=%d) END"
        " WHERE id=%d", ridm, integrateFlag?4:2, islinkm, ridm, ridm, idv
      );
      vfile_to_disk(0, idv, 0, 0);
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
        /* fnr  */ zName
      ); 
    }else{
      i64 sz;
      const char *zErrMsg = 0;
      int nc = 0;

      if( !dryRunFlag ) undo_save(zName);
      zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
      sz = file_size(zFullPath, ExtFILE);
      content_get(ridp, &p);
      content_get(ridm, &m);
      if( isBinary ){
        rc = -1;
        blob_zero(&r);







|







1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
        /* fnr  */ zName
      ); 
    }else{
      i64 sz;
      const char *zErrMsg = 0;
      int nc = 0;

      if( useUndo ) undo_save(zName);
      zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
      sz = file_size(zFullPath, ExtFILE);
      content_get(ridp, &p);
      content_get(ridm, &m);
      if( isBinary ){
        rc = -1;
        blob_zero(&r);
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
      ridv = 0;
      nc = 1;
      zErrMsg = "local edits lost";
      zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
      sz = file_size(zFullPath, ExtFILE);
      fossil_free(zFullPath);
    }
    if( !dryRunFlag ) undo_save(zName);
    db_multi_exec(
      "UPDATE vfile SET deleted=1 WHERE id=%d", idv
    );
    if( !dryRunFlag ){
      char *zFullPath = mprintf("%s%s", g.zLocalRoot, zName);
      file_delete(zFullPath);
      free(zFullPath);







|







1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
      ridv = 0;
      nc = 1;
      zErrMsg = "local edits lost";
      zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
      sz = file_size(zFullPath, ExtFILE);
      fossil_free(zFullPath);
    }
    if( useUndo ) undo_save(zName);
    db_multi_exec(
      "UPDATE vfile SET deleted=1 WHERE id=%d", idv
    );
    if( !dryRunFlag ){
      char *zFullPath = mprintf("%s%s", g.zLocalRoot, zName);
      file_delete(zFullPath);
      free(zFullPath);
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
  );
  while( db_step(&q)==SQLITE_ROW ){
    int idv = db_column_int(&q, 0);
    const char *zOldName = db_column_text(&q, 1);
    const char *zNewName = db_column_text(&q, 2);
    int isExe = db_column_int(&q, 3);
    fossil_print("RENAME %s -> %s\n", zOldName, zNewName);
    if( !dryRunFlag ) undo_save(zOldName);
    if( !dryRunFlag ) undo_save(zNewName);
    db_multi_exec(
      "UPDATE mergestat SET fnr=fnm WHERE fnp=%Q",
      zOldName
    );
    db_multi_exec(
      "UPDATE vfile SET pathname=NULL, origname=pathname"
      " WHERE vid=%d AND pathname=%Q;"







|
|







1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
  );
  while( db_step(&q)==SQLITE_ROW ){
    int idv = db_column_int(&q, 0);
    const char *zOldName = db_column_text(&q, 1);
    const char *zNewName = db_column_text(&q, 2);
    int isExe = db_column_int(&q, 3);
    fossil_print("RENAME %s -> %s\n", zOldName, zNewName);
    if( useUndo ) undo_save(zOldName);
    if( useUndo ) undo_save(zNewName);
    db_multi_exec(
      "UPDATE mergestat SET fnr=fnm WHERE fnp=%Q",
      zOldName
    );
    db_multi_exec(
      "UPDATE vfile SET pathname=NULL, origname=pathname"
      " WHERE vid=%d AND pathname=%Q;"
1496
1497
1498
1499
1500
1501
1502

1503
1504
1505
1506
1507
1508
1509
1510
1511
    db_multi_exec(
      "INSERT INTO mergestat(op,fnm,ridm,fnr)"
      "VALUES('ADDED',%Q,%d,%Q)",
      /* fnm  */ zName,
      /* ridm */ db_column_int(&q,2),
      /* fnr  */ zName
    );

    if( !dryRunFlag ){
      undo_save(zName);
      vfile_to_disk(0, idm, 0, 0);
    }
  }
  db_finalize(&q);

  /* Report on conflicts
  */







>

<







1500
1501
1502
1503
1504
1505
1506
1507
1508

1509
1510
1511
1512
1513
1514
1515
    db_multi_exec(
      "INSERT INTO mergestat(op,fnm,ridm,fnr)"
      "VALUES('ADDED',%Q,%d,%Q)",
      /* fnm  */ zName,
      /* ridm */ db_column_int(&q,2),
      /* fnr  */ zName
    );
    if( useUndo ) undo_save(zName);
    if( !dryRunFlag ){

      vfile_to_disk(0, idm, 0, 0);
    }
  }
  db_finalize(&q);

  /* Report on conflicts
  */
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
  }else{
    vmerge_insert(0, mid);
  }
  if( bMultiMerge && nConflict==0 ){
    nMerge++;
    goto merge_next_child;
  }
  if( !dryRunFlag ) undo_finish();

  db_end_transaction(dryRunFlag);
}







|



1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
  }else{
    vmerge_insert(0, mid);
  }
  if( bMultiMerge && nConflict==0 ){
    nMerge++;
    goto merge_next_child;
  }
  if( useUndo ) undo_finish();

  db_end_transaction(dryRunFlag);
}

Changes to src/patch.c.

385
386
387
388
389
390
391
392
393
394
395
396
397
398
399

  blob_init(&cmd, 0, 0);
  if( unsaved_changes(0) ){
    if( (mFlags & PATCH_FORCE)==0 ){
      fossil_fatal("Cannot apply patch: there are unsaved changes "
                   "in the current check-out");
    }else{
      blob_appendf(&cmd, "%$ revert", g.nameOfExe);
      if( mFlags & PATCH_DRYRUN ){
        fossil_print("%s\n", blob_str(&cmd));
      }else{
        int rc = fossil_system(blob_str(&cmd));
        if( rc ){
          fossil_fatal("unable to revert preexisting changes: %s",
                       blob_str(&cmd));







|







385
386
387
388
389
390
391
392
393
394
395
396
397
398
399

  blob_init(&cmd, 0, 0);
  if( unsaved_changes(0) ){
    if( (mFlags & PATCH_FORCE)==0 ){
      fossil_fatal("Cannot apply patch: there are unsaved changes "
                   "in the current check-out");
    }else{
      blob_appendf(&cmd, "%$ revert --noundo", g.nameOfExe);
      if( mFlags & PATCH_DRYRUN ){
        fossil_print("%s\n", blob_str(&cmd));
      }else{
        int rc = fossil_system(blob_str(&cmd));
        if( rc ){
          fossil_fatal("unable to revert preexisting changes: %s",
                       blob_str(&cmd));
427
428
429
430
431
432
433

434
435
436
437
438
439
440
441
442
443

444
445

446

447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462



































463
464
465
466
467
468
469
        fossil_fatal("unable to update to the baseline check-out: %s",
                     blob_str(&cmd));
      }
    }
  }
  blob_reset(&cmd);
  if( db_table_exists("patch","patchmerge") ){

    db_prepare(&q,
      "SELECT type, mhash, upper(type) FROM patch.patchmerge"
      " WHERE type IN ('merge','cherrypick','backout','integrate')"
      "   AND mhash NOT GLOB '*[^a-fA-F0-9]*';"
    );
    while( db_step(&q)==SQLITE_ROW ){
      const char *zType = db_column_text(&q,0);
      blob_append_escaped_arg(&cmd, g.nameOfExe, 1);
      if( strcmp(zType,"merge")==0 ){
        blob_appendf(&cmd, " merge %s\n", db_column_text(&q,1));

      }else{
        blob_appendf(&cmd, " merge --%s %s\n", zType, db_column_text(&q,1));

      }

      if( mFlags & PATCH_VERBOSE ){
        fossil_print("%-10s %s\n", db_column_text(&q,2),
                    db_column_text(&q,0));
      }
    }
    db_finalize(&q);
    if( mFlags & PATCH_DRYRUN ){
      fossil_print("%s", blob_str(&cmd));
    }else{
      int rc = fossil_unsafe_system(blob_str(&cmd));
      if( rc ){
        fossil_fatal("unable to do merges:\n%s",
                     blob_str(&cmd));
      }
    }
    blob_reset(&cmd);



































  }

  /* Deletions */
  db_prepare(&q, "SELECT pathname FROM patch.chng"
                 " WHERE origname IS NULL AND delta IS NULL");
  while( db_step(&q)==SQLITE_ROW ){
    if( blob_size(&cmd)==0 ){







>









|
>

|
>

>
















>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
        fossil_fatal("unable to update to the baseline check-out: %s",
                     blob_str(&cmd));
      }
    }
  }
  blob_reset(&cmd);
  if( db_table_exists("patch","patchmerge") ){
    int nMerge = 0;
    db_prepare(&q,
      "SELECT type, mhash, upper(type) FROM patch.patchmerge"
      " WHERE type IN ('merge','cherrypick','backout','integrate')"
      "   AND mhash NOT GLOB '*[^a-fA-F0-9]*';"
    );
    while( db_step(&q)==SQLITE_ROW ){
      const char *zType = db_column_text(&q,0);
      blob_append_escaped_arg(&cmd, g.nameOfExe, 1);
      if( strcmp(zType,"merge")==0 ){
        blob_appendf(&cmd, " merge --noundo --nosync %s\n",
                     db_column_text(&q,1));
      }else{
        blob_appendf(&cmd, " merge --%s --noundo --nosync %s\n",
                     zType, db_column_text(&q,1));
      }
      nMerge++;
      if( mFlags & PATCH_VERBOSE ){
        fossil_print("%-10s %s\n", db_column_text(&q,2),
                    db_column_text(&q,0));
      }
    }
    db_finalize(&q);
    if( mFlags & PATCH_DRYRUN ){
      fossil_print("%s", blob_str(&cmd));
    }else{
      int rc = fossil_unsafe_system(blob_str(&cmd));
      if( rc ){
        fossil_fatal("unable to do merges:\n%s",
                     blob_str(&cmd));
      }
    }
    blob_reset(&cmd);

    /* 2024-12-16 https://fossil-scm.org/home/forumpost/51a37054
    ** If one or more merge operations occurred in the patch and there are
    ** files that are marked as "chnged' in the local VFILE but which
    ** are not mentioned as having been modified in the patch, then
    ** revert those files.
    */
    if( nMerge ){
      int vid = db_lget_int("checkout", 0);
      int nRevert = 0;
      blob_append_escaped_arg(&cmd, g.nameOfExe, 1);
      blob_appendf(&cmd, " revert --noundo ");
      db_prepare(&q,
        "SELECT pathname FROM vfile WHERE vid=%d AND chnged "
        "EXCEPT SELECT pathname FROM chng",
        vid
      );
      while( db_step(&q)==SQLITE_ROW ){
        blob_append_escaped_arg(&cmd, db_column_text(&q,0), 1);
        nRevert++;
      }
      db_finalize(&q);
      if( nRevert ){
        if( mFlags & PATCH_DRYRUN ){
          fossil_print("%s", blob_str(&cmd));
        }else{
          int rc = fossil_unsafe_system(blob_str(&cmd));
          if( rc ){
            fossil_fatal("unable to do reverts:\n%s",
                         blob_str(&cmd));
          }
        }
      }
      blob_reset(&cmd);
    }
  }

  /* Deletions */
  db_prepare(&q, "SELECT pathname FROM patch.chng"
                 " WHERE origname IS NULL AND delta IS NULL");
  while( db_step(&q)==SQLITE_ROW ){
    if( blob_size(&cmd)==0 ){

Changes to src/update.c.

852
853
854
855
856
857
858

859
860
861
862
863
864
865
866
867
868
869
870
871

872
873
874
875
876
877
878

879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894

895



896
897
898
899
900
901
902
**
** Revert all files if no file name is provided.
**
** If a file is reverted accidentally, it can be restored using
** the "fossil undo" command.
**
** Options:

**   -r|--revision VERSION    Revert given FILE(s) back to given
**                            VERSION
**
** See also: [[redo]], [[undo]], [[checkout]], [[update]]
*/
void revert_cmd(void){
  Manifest *pCoManifest;          /* Manifest of current check-out */
  Manifest *pRvManifest;          /* Manifest of selected revert version */
  ManifestFile *pCoFile;          /* File within current check-out manifest */
  ManifestFile *pRvFile;          /* File within revert version manifest */
  const char *zFile;              /* Filename relative to check-out root */
  const char *zRevision;          /* Selected revert version, NULL if current */
  Blob record = BLOB_INITIALIZER; /* Contents of each reverted file */

  int i;
  Stmt q;
  int revertAll = 0;
  int revisionOptNotSupported = 0;

  undo_capture_command_line();
  zRevision = find_option("revision", "r", 1);

  verify_all_options();

  if( g.argc<2 ){
    usage("?OPTIONS? [FILE] ...");
  }
  if( zRevision && g.argc<3 ){
    fossil_fatal("directories or the entire tree can only be reverted"
                 " back to current version");
  }
  db_must_be_within_tree();

  /* Get manifests of revert version and (if different) current check-out. */
  pRvManifest = historical_manifest(zRevision);
  pCoManifest = zRevision ? historical_manifest(0) : 0;

  db_begin_transaction();

  undo_begin();



  db_multi_exec("CREATE TEMP TABLE torevert(name UNIQUE);");

  if( g.argc>2 ){
    for(i=2; i<g.argc; i++){
      Blob fname;
      zFile = mprintf("%/", g.argv[i]);
      blob_zero(&fname);







>













>







>
















>
|
>
>
>







852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
**
** Revert all files if no file name is provided.
**
** If a file is reverted accidentally, it can be restored using
** the "fossil undo" command.
**
** Options:
**   --noundo                 Do not record changes in the undo/redo log.
**   -r|--revision VERSION    Revert given FILE(s) back to given
**                            VERSION
**
** See also: [[redo]], [[undo]], [[checkout]], [[update]]
*/
void revert_cmd(void){
  Manifest *pCoManifest;          /* Manifest of current check-out */
  Manifest *pRvManifest;          /* Manifest of selected revert version */
  ManifestFile *pCoFile;          /* File within current check-out manifest */
  ManifestFile *pRvFile;          /* File within revert version manifest */
  const char *zFile;              /* Filename relative to check-out root */
  const char *zRevision;          /* Selected revert version, NULL if current */
  Blob record = BLOB_INITIALIZER; /* Contents of each reverted file */
  int useUndo = 1;                /* True to record changes in UNDO */
  int i;
  Stmt q;
  int revertAll = 0;
  int revisionOptNotSupported = 0;

  undo_capture_command_line();
  zRevision = find_option("revision", "r", 1);
  useUndo = find_option("noundo", 0, 0)==0;
  verify_all_options();

  if( g.argc<2 ){
    usage("?OPTIONS? [FILE] ...");
  }
  if( zRevision && g.argc<3 ){
    fossil_fatal("directories or the entire tree can only be reverted"
                 " back to current version");
  }
  db_must_be_within_tree();

  /* Get manifests of revert version and (if different) current check-out. */
  pRvManifest = historical_manifest(zRevision);
  pCoManifest = zRevision ? historical_manifest(0) : 0;

  db_begin_transaction();
  if( useUndo ){
    undo_begin();
  }else{
    undo_reset();
  }
  db_multi_exec("CREATE TEMP TABLE torevert(name UNIQUE);");

  if( g.argc>2 ){
    for(i=2; i<g.argc; i++){
      Blob fname;
      zFile = mprintf("%/", g.argv[i]);
      blob_zero(&fname);
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
    zFull = mprintf("%/%/", g.zLocalRoot, zFile);
    pRvFile = pRvManifest? manifest_file_find(pRvManifest, zFile) : 0;
    if( !pRvFile ){
      if( db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q OR origname=%Q",
                 zFile, zFile)==0 ){
        fossil_print("UNMANAGE %s\n", zFile);
      }else{
        undo_save(zFile);
        file_delete(zFull);
        fossil_print("DELETE   %s\n", zFile);
      }
      db_multi_exec(
        "UPDATE OR REPLACE vfile"
        "   SET pathname=origname, origname=NULL"
        " WHERE pathname=%Q AND origname!=pathname;"







|







992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
    zFull = mprintf("%/%/", g.zLocalRoot, zFile);
    pRvFile = pRvManifest? manifest_file_find(pRvManifest, zFile) : 0;
    if( !pRvFile ){
      if( db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q OR origname=%Q",
                 zFile, zFile)==0 ){
        fossil_print("UNMANAGE %s\n", zFile);
      }else{
        if( useUndo ) undo_save(zFile);
        file_delete(zFull);
        fossil_print("DELETE   %s\n", zFile);
      }
      db_multi_exec(
        "UPDATE OR REPLACE vfile"
        "   SET pathname=origname, origname=NULL"
        " WHERE pathname=%Q AND origname!=pathname;"
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
        rvChnged = manifest_file_mperm(pRvFile)!=rvPerm
                || fossil_strcmp(pRvFile->zUuid, pCoFile->zUuid)!=0;
      }

      /* Get contents of reverted-to file. */
      content_get(fast_uuid_to_rid(pRvFile->zUuid), &record);

      undo_save(zFile);
      if( file_size(zFull, RepoFILE)>=0
       && (rvPerm==PERM_LNK || file_islink(0))
      ){
        file_delete(zFull);
      }
      if( rvPerm==PERM_LNK ){
        symlink_create(blob_str(&record), zFull);







|







1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
        rvChnged = manifest_file_mperm(pRvFile)!=rvPerm
                || fossil_strcmp(pRvFile->zUuid, pCoFile->zUuid)!=0;
      }

      /* Get contents of reverted-to file. */
      content_get(fast_uuid_to_rid(pRvFile->zUuid), &record);

      if( useUndo ) undo_save(zFile);
      if( file_size(zFull, RepoFILE)>=0
       && (rvPerm==PERM_LNK || file_islink(0))
      ){
        file_delete(zFull);
      }
      if( rvPerm==PERM_LNK ){
        symlink_create(blob_str(&record), zFull);
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
         mtime, rvChnged, rvPerm==PERM_EXE, rvPerm==PERM_LNK, zFile, zFile
      );
    }
    blob_reset(&record);
    free(zFull);
  }
  db_finalize(&q);
  undo_finish();
  db_end_transaction(0);

  /* Deallocate parsed manifest structures. */
  manifest_destroy(pRvManifest);
  manifest_destroy(pCoManifest);
}







|






1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
         mtime, rvChnged, rvPerm==PERM_EXE, rvPerm==PERM_LNK, zFile, zFile
      );
    }
    blob_reset(&record);
    free(zFull);
  }
  db_finalize(&q);
  if( useUndo) undo_finish();
  db_end_transaction(0);

  /* Deallocate parsed manifest structures. */
  manifest_destroy(pRvManifest);
  manifest_destroy(pCoManifest);
}

Changes to www/changes.wiki.

1
2
3
4



5
6
7
8


9
10
11
12
13
14
15
<title>Change Log</title>

<h2 id='v2_26'>Changes for version 2.26 (pending)</h2>




  *  Added the [/help?cmd=/ckout|/ckout web page] to provide information
     about pending changes in a working check-out
  *  The [/help?cmd=ui|fossil ui] command defaults to using the /ckout
     page as its start page.


  *  Added the [/help?cmd=merge-info|fossil merge-info] command and especially
     the --tk option to that command, to provide analysis of the most recent
     merge or update operation.
  *  Issue a warning if a user tries to commit on a check-in where the
     branch has been changed.
  *  When a merge conflict occurs, a new section is added to the conflict
     text that shows Fossil's suggested resolution to the conflict.




>
>
>


|
|
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<title>Change Log</title>

<h2 id='v2_26'>Changes for version 2.26 (pending)</h2>

  *  Enhanced the --from option on "[/help?cmd=diff|fossil diff]" so that
     it optionally accepts a directory name as its argument, and uses files
     under that directory as the baseline for the diff.
  *  Added the [/help?cmd=/ckout|/ckout web page] to provide information
     about pending changes in a working check-out
  *  The [/help?cmd=ui|fossil ui] command defaults to using the
     [/help?cmd=/ckout|/ckout page] as its start page.  Or, if the
     "--from PATH" option is present, the default start page becomes
     "/ckout?exbase=PATH".
  *  Added the [/help?cmd=merge-info|fossil merge-info] command and especially
     the --tk option to that command, to provide analysis of the most recent
     merge or update operation.
  *  Issue a warning if a user tries to commit on a check-in where the
     branch has been changed.
  *  When a merge conflict occurs, a new section is added to the conflict
     text that shows Fossil's suggested resolution to the conflict.