Fossil

Check-in [ea52b7d0]
Login

Check-in [ea52b7d0]

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

Overview
Comment:Add the --webpage option to the various "diff" commands. This option causes the diff output to be in the form of a stand-alone webpage that can be sent to a remote collaborator (for example via a chat attachment).
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: ea52b7d06cf7c0493c7e5c74b8925173e2d8689e5d81b964ab54bdbeb7b64f28
User & Date: drh 2021-08-25 16:10:23
Context
2021-08-25
16:22
Fix a problem in the "fossil patch diff" command introduced by the previous --webpage check-in. ... (check-in: 874e7fa7 user: drh tags: trunk)
16:10
Add the --webpage option to the various "diff" commands. This option causes the diff output to be in the form of a stand-alone webpage that can be sent to a remote collaborator (for example via a chat attachment). ... (check-in: ea52b7d0 user: drh tags: trunk)
13:10
Improvements to the g= query parameter on login redirects. This is an attempt to fix the problem described by forum post forum f81625500d. ... (check-in: 3571c871 user: drh tags: trunk)
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to src/diff.c.

40
41
42
43
44
45
46

47
48
49
50
51
52
53
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54







+







#define DIFF_NUMSTAT      ((u64)0x80000000) /* Show line count of changes */
#define DIFF_NOOPT        (((u64)0x01)<<32) /* Suppress optimizations (debug) */
#define DIFF_INVERT       (((u64)0x02)<<32) /* Invert the diff (debug) */
#define DIFF_CONTEXT_EX   (((u64)0x04)<<32) /* Use context even if zero */
#define DIFF_NOTTOOBIG    (((u64)0x08)<<32) /* Only display if not too big */
#define DIFF_STRIP_EOLCR  (((u64)0x10)<<32) /* Strip trailing CR */
#define DIFF_SLOW_SBS     (((u64)0x20)<<32) /* Better but slower side-by-side */
#define DIFF_WEBPAGE      (((u64)0x40)<<32) /* Complete webpage */

/*
** These error messages are shared in multiple locations.  They are defined
** here for consistency.
*/
#define DIFF_CANNOT_COMPUTE_BINARY \
    "cannot compute difference between binary files\n"
363
364
365
366
367
368
369

370
371
372
373
374
375
376
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378







+







  int showLn;      /* Show line numbers */
  int html;        /* Render as HTML */
  int showDivider = 0;  /* True to show the divider between diff blocks */

  nContext = diff_context_lines(diffFlags);
  showLn = (diffFlags & DIFF_LINENO)!=0;
  html = (diffFlags & DIFF_HTML)!=0;
  if( html ) blob_append(pOut, "<pre class=\"udiff\">\n", -1);
  A = p->aFrom;
  B = p->aTo;
  R = p->aEdit;
  mxr = p->nEdit;
  while( mxr>2 && R[mxr-1]==0 && R[mxr-2]==0 ){ mxr -= 3; }
  for(r=0; r<mxr; r += 3*nr){
    /* Figure out how many triples to show in a single block */
498
499
500
501
502
503
504

505
506
507
508
509
510
511
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514







+







    m = R[r+nr*3];
    if( m>nContext ) m = nContext;
    for(j=0; j<m; j++){
      if( showLn ) appendDiffLineno(pOut, a+j+1, b+j+1, html);
      appendDiffLine(pOut, ' ', &A[a+j], html, 0);
    }
  }
  if( html ) blob_append(pOut, "</pre>\n", -1);
}

/*
** Status of a single output line
*/
typedef struct SbsLine SbsLine;
struct SbsLine {
2033
2034
2035
2036
2037
2038
2039



2040
2041
2042
2043
2044
2045
2046
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052







+
+
+







  }
  if( find_option("html",0,0)!=0 ) diffFlags |= DIFF_HTML;
  if( find_option("linenum","n",0)!=0 ) diffFlags |= DIFF_LINENO;
  if( find_option("noopt",0,0)!=0 ) diffFlags |= DIFF_NOOPT;
  if( find_option("numstat",0,0)!=0 ) diffFlags |= DIFF_NUMSTAT;
  if( find_option("invert",0,0)!=0 ) diffFlags |= DIFF_INVERT;
  if( find_option("brief",0,0)!=0 ) diffFlags |= DIFF_BRIEF;
  if( find_option("webpage",0,0)!=0 ){
    diffFlags |= DIFF_HTML|DIFF_WEBPAGE|DIFF_LINENO;
  }
  return diffFlags;
}

/*
** COMMAND: test-rawdiff
**
** Usage: %fossil test-rawdiff FILE1 FILE2
2092
2093
2094
2095
2096
2097
2098

2099
2100
2101
2102
2103

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







+





+







  if( zRe ){
    const char *zErr = re_compile(&pRe, zRe, 0);
    if( zErr ) fossil_fatal("regex error: %s", zErr);
  }
  diffFlag = diff_options();
  verify_all_options();
  if( g.argc!=4 ) usage("FILE1 FILE2");
  diff_header(diffFlag);
  diff_print_filenames(g.argv[2], g.argv[3], diffFlag, 0);
  blob_read_from_file(&a, g.argv[2], ExtFILE);
  blob_read_from_file(&b, g.argv[3], ExtFILE);
  blob_zero(&out);
  text_diff(&a, &b, &out, pRe, diffFlag);
  diff_footer(diffFlag);
  blob_write_to_file(&out, "-");
  re_free(pRe);
}

/**************************************************************************
** The basic difference engine is above.  What follows is the annotation
** engine.  Both are in the same file since they share many components.

Changes to src/diffcmd.c.

105
106
107
108
109
110
111
112

113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130






131
132
133
134
135
136
137
105
106
107
108
109
110
111

112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143







-
+


















+
+
+
+
+
+







  return 0;
}

/*
** Print the "Index:" message that patches wants to see at the top of a diff.
*/
void diff_print_index(const char *zFile, u64 diffFlags, Blob *diffBlob){
  if( (diffFlags & (DIFF_SIDEBYSIDE|DIFF_BRIEF|DIFF_NUMSTAT))==0 ){
  if( (diffFlags & (DIFF_SIDEBYSIDE|DIFF_BRIEF|DIFF_NUMSTAT|DIFF_WEBPAGE))==0 ){
    char *z = mprintf("Index: %s\n%.66c\n", zFile, '=');
    if( !diffBlob ){
      fossil_print("%s", z);
    }else{
      blob_appendf(diffBlob, "%s", z);
    }
    fossil_free(z);
  }
}

/*
** Print the +++/--- filename lines for a diff operation.
*/
void diff_print_filenames(const char *zLeft, const char *zRight,
 u64 diffFlags, Blob *diffBlob){
  char *z = 0;
  if( diffFlags & DIFF_BRIEF ){
    /* no-op */
  }else if( diffFlags & DIFF_WEBPAGE ){
    if( fossil_strcmp(zLeft,zRight)==0 ){
      z = mprintf("<h1>%h</h1>\n", zLeft);
    }else{
      z = mprintf("<h1>%h &lrarr; %h</h1>\n", zLeft, zRight);
    }
  }else if( diffFlags & DIFF_SIDEBYSIDE ){
    int w = diff_width(diffFlags);
    int n1 = strlen(zLeft);
    int n2 = strlen(zRight);
    int x;
    if( n1==n2 && fossil_strcmp(zLeft,zRight)==0 ){
      if( n1>w*2 ) n1 = w*2;
152
153
154
155
156
157
158













































































159
160
161
162
163
164
165
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







  if( !diffBlob ){
    fossil_print("%s", z);
  }else{
    blob_appendf(diffBlob, "%s", z);
  }
  fossil_free(z);
}

/*
** Default header text for diff with --webpage
*/
static const char zWebpageHdr[] = 
@ <!DOCTYPE html>
@ <html>
@ <head>
@ <style>
@ table.sbsdiffcols {
@   width: 90%;
@   border-spacing: 0;
@   font-size: xx-small;
@ }
@ table.sbsdiffcols td {
@   padding: 0;
@   vertical-align: top;
@ }
@ table.sbsdiffcols pre {
@   margin: 0;
@   padding: 0;
@   border: 0;
@   font-size: inherit;
@   background: inherit;
@   color: inherit;
@ }
@ div.difflncol {
@   padding-right: 1em;
@   text-align: right;
@   color: #a0a0a0;
@ }
@ div.difftxtcol {
@   width: 45em;
@   overflow-x: auto;
@ }
@ div.diffmkrcol {
@   padding: 0 1em;
@ }
@ span.diffchng {
@   background-color: #c0c0ff;
@ }
@ span.diffadd {
@   background-color: #c0ffc0;
@ }
@ span.diffrm {
@   background-color: #ffc8c8;
@ }
@ span.diffhr {
@   display: inline-block;
@   margin: .5em 0 1em;
@   color: #0000ff;
@ }
@ span.diffln {
@   color: #a0a0a0;
@ }
@ </style>
@ </head>
@ <body>
;

/*
** Print a header or footer on the overall diff output.
**
** This is only a factor for --webpage, in which case the header
** is the HTML header CSS definitions and the footer is the HTML
** close tags.
*/
void diff_header(u64 diffFlags){
  if( (diffFlags & DIFF_WEBPAGE)!=0 ){
    fossil_print("%s", zWebpageHdr);
  }
}
void diff_footer(u64 diffFlags){
  if( (diffFlags & DIFF_WEBPAGE)!=0 ){
    fossil_print("</body></html>\n");
  }
}

/*
** Show the difference between two files, one in memory and one on disk.
**
** The difference is the set of edits needed to transform pFile1 into
** zFile2.  The content of pFile1 is in memory.  zFile2 exists on disk.
**
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
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946

947

948
949
950
951
952
953
954







+

+









-
+
-







**   --checkin VERSION           Show diff of all changes in VERSION
**   --command PROG              External diff program. Overrides "diff-command"
**   -c|--context N              Use N lines of context
**   --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
**   -N|--new-file               Alias for --verbose
**   --numstat                   Show only the number of lines delete and added
**   -y|--side-by-side           Side-by-side diff
**   --strip-trailing-cr         Strip trailing CR
**   --tclsh PATH                Tcl/Tk 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
**   -N|--new-file               Alias for --verbose
**   --webpage                   Format output as a stand-alone HTML webpage
**   -w|--ignore-all-space       Ignore white space when comparing lines
**   -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 */
  int isInternDiff;          /* True for internal diff */
  int verboseFlag;           /* True if -v or --verbose flag is used */
951
952
953
954
955
956
957

958
959
960
961
962
963
964
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049







+







      "SELECT uuid FROM blob, plink"
      " 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_header(diffFlags);
  if( againstUndo ){
    if( db_lget_int("undo_available",0)==0 ){
      fossil_print("No undo or redo is available\n");
      return;
    }
    diff_against_undo(zDiffCmd, zBinGlob, fIncludeBinary,
                      diffFlags, pFileDir);
978
979
980
981
982
983
984

985
986
987
988
989
990
991
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077







+







      ){
        fossil_fatal("not found: '%s'", g.argv[i+2]);
      }
      fossil_free(pFileDir[i].zName);
    }
    fossil_free(pFileDir);
  }
  diff_footer(diffFlags);
  if ( diffFlags & DIFF_NUMSTAT ){
    fossil_print("%10d %10d TOTAL over %d changed files\n", 
                 g.diffCnt[1], g.diffCnt[2], g.diffCnt[0]);
  }
}

/*

Changes to src/info.c.

333
334
335
336
337
338
339
340

341
342
343
344
345
346
347
348
349
350
351
352
353
354
355

356
357
358
359

360
361
362
363

364
365
366
367
368
369
370
371
372
373
333
334
335
336
337
338
339

340
341
342
343
344
345
346
347
348
349
350
351
352

353

354

355


356



357
358
359
360

361
362
363
364
365
366
367







-
+












-

-
+
-

-
-
+
-
-
-

+


-







  const char *zFrom,    /* Diff from this artifact */
  const char *zTo,      /*  ... to this artifact */
  u64 diffFlags,        /* Diff formatting flags */
  ReCompiled *pRe       /* Only show change matching this regex */
){
  int fromid;
  int toid;
  Blob from, to, out;
  Blob from, to;
  if( zFrom ){
    fromid = uuid_to_rid(zFrom, 0);
    content_get(fromid, &from);
  }else{
    blob_zero(&from);
  }
  if( zTo ){
    toid = uuid_to_rid(zTo, 0);
    content_get(toid, &to);
  }else{
    blob_zero(&to);
  }
  blob_zero(&out);
  if( diffFlags & DIFF_SIDEBYSIDE ){
    text_diff(&from, &to, &out, pRe, diffFlags | DIFF_HTML | DIFF_NOTTOOBIG);
    diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG;
    @ %s(blob_str(&out))
  }else{
    text_diff(&from, &to, &out, pRe,
           diffFlags | DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG);
    diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG;
    @ <pre class="udiff">
    @ %s(blob_str(&out))
    @ </pre>
  }
  text_diff(&from, &to, cgi_output_blob(), pRe, diffFlags);
  blob_reset(&from);
  blob_reset(&to);
  blob_reset(&out);
}

/*
** Write a line of web-page output that shows changes that have occurred
** to a file between two check-ins.
*/
static void append_file_change_line(

Changes to src/patch.c.

722
723
724
725
726
727
728

729
730
731
732
733
734
735
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736







+







  const char *zDiffCmd,    /* Command used for diffing */
  const char *zBinGlob,    /* GLOB pattern to determine binary files */
  int fIncludeBinary,      /* Do diffs against binary files */
  u64 diffFlags            /* Other diff flags */
){
  int nErr = 0;
  Stmt q;
  int bWebpage = (diffFlags && DIFF_WEBPAGE)!=0;
  Blob empty;
  blob_zero(&empty);

  if( (mFlags & PATCH_FORCE)==0 ){
    /* Check to ensure that the patch is against the repository that
    ** we have opened.
    **
760
761
762
763
764
765
766
767


768
769
770
771
772
773
774
761
762
763
764
765
766
767

768
769
770
771
772
773
774
775
776







-
+
+







                             " WHERE key='baseline'");
      if( zBaseline ){
        fossil_fatal("the baseline of the patch (check-in %S) is not found "
                     "in the %s repository", zBaseline, g.zRepositoryName);
      }
    }
  }
  

  diff_header(diffFlags);  
  db_prepare(&q,
     "SELECT"
       " (SELECT blob.rid FROM blob WHERE blob.uuid=chng.hash),"
       " pathname,"    /* 1: new pathname */
       " origname,"    /* 2: original pathname.  Null if not renamed */
       " delta,"       /* 3: delta.  NULL if deleted.  empty is no change */
       " hash"         /* 4: baseline hash */
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
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







-
+









-
+







                     zUuid, zName);
      }
    }
    zName = db_column_text(&q, 1);
    rid = db_column_int(&q, 0);

    if( db_column_type(&q,3)==SQLITE_NULL ){
      fossil_print("DELETE %s\n", zName);
      if( !bWebpage ) fossil_print("DELETE %s\n", zName);
      diff_print_index(zName, diffFlags, 0);
      isBin2 = 0;
      content_get(rid, &a);
      isBin1 = fIncludeBinary ? 0 : looks_like_binary(&a);
      diff_file_mem(&a, &empty, isBin1, isBin2, zName, zDiffCmd,
                    zBinGlob, fIncludeBinary, diffFlags);
    }else if( rid==0 ){
      db_ephemeral_blob(&q, 3, &a);
      blob_uncompress(&a, &a);
      fossil_print("ADDED %s\n", zName);
      if( !bWebpage ) fossil_print("ADDED %s\n", zName);
      diff_print_index(zName, diffFlags, 0);
      isBin1 = 0;
      isBin2 = fIncludeBinary ? 0 : looks_like_binary(&a);
      diff_file_mem(&empty, &a, isBin1, isBin2, zName, zDiffCmd,
                    zBinGlob, fIncludeBinary, diffFlags);
      blob_reset(&a);
    }else if( db_column_bytes(&q, 3)>0 ){
832
833
834
835
836
837
838

839
840
841
842
843
844
845
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848







+







                    zDiffCmd, zBinGlob, fIncludeBinary, diffFlags);
      blob_reset(&a);
      blob_reset(&b);
      blob_reset(&delta);
    }
  }
  db_finalize(&q);
  diff_footer(diffFlags);
  if( nErr ) fossil_fatal("abort due to prior errors");
}


/*
** COMMAND: patch
**

Changes to src/stash.c.

408
409
410
411
412
413
414

415

416
417
418
419
420
421
422
423
424
425
426
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
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
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







+

+
















-
+






-
+












-
+







  const char *zBinGlob,    /* GLOB pattern to determine binary files */
  int fBaseline,           /* Diff against original baseline check-in if true */
  int fIncludeBinary,      /* Do diffs against binary files */
  u64 diffFlags            /* Other diff flags */
){
  Stmt q;
  Blob empty;
  int bWebpage = (diffFlags & DIFF_WEBPAGE)!=0;
  blob_zero(&empty);
  diff_header(diffFlags);
  db_prepare(&q,
     "SELECT blob.rid, isRemoved, isExec, isLink, origname, newname, delta"
     "  FROM stashfile, blob WHERE stashid=%d AND blob.uuid=stashfile.hash",
     stashid
  );
  while( db_step(&q)==SQLITE_ROW ){
    int rid = db_column_int(&q, 0);
    int isRemoved = db_column_int(&q, 1);
    int isLink = db_column_int(&q, 3);
    int isBin1, isBin2;
    const char *zOrig = db_column_text(&q, 4);
    const char *zNew = db_column_text(&q, 5);
    char *zOPath = mprintf("%s%s", g.zLocalRoot, zOrig);
    Blob a, b;
    if( rid==0 ){
      db_ephemeral_blob(&q, 6, &a);
      fossil_print("ADDED %s\n", zNew);
      if( !bWebpage ) fossil_print("ADDED %s\n", zNew);
      diff_print_index(zNew, diffFlags, 0);
      isBin1 = 0;
      isBin2 = fIncludeBinary ? 0 : looks_like_binary(&a);
      diff_file_mem(&empty, &a, isBin1, isBin2, zNew, zDiffCmd,
                    zBinGlob, fIncludeBinary, diffFlags);
    }else if( isRemoved ){
      fossil_print("DELETE %s\n", zOrig);
      if( !bWebpage) fossil_print("DELETE %s\n", zOrig);
      diff_print_index(zNew, diffFlags, 0);
      isBin2 = 0;
      if( fBaseline ){
        content_get(rid, &a);
        isBin1 = fIncludeBinary ? 0 : looks_like_binary(&a);
        diff_file_mem(&a, &empty, isBin1, isBin2, zOrig, zDiffCmd,
                      zBinGlob, fIncludeBinary, diffFlags);
      }
    }else{
      Blob delta;
      int isOrigLink = file_islink(zOPath);
      db_ephemeral_blob(&q, 6, &delta);
      fossil_print("CHANGED %s\n", zNew);
      if( !bWebpage ) fossil_print("CHANGED %s\n", zNew);
      if( !isOrigLink != !isLink ){
        diff_print_index(zNew, diffFlags, 0);
        diff_print_filenames(zOrig, zNew, diffFlags, 0);
        printf(DIFF_CANNOT_COMPUTE_SYMLINK);
      }else{
        content_get(rid, &a);
        blob_delta_apply(&a, &delta, &b);
471
472
473
474
475
476
477

478
479
480
481
482
483
484
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487







+







        blob_reset(&a);
        blob_reset(&b);
      }
      blob_reset(&delta);
    }
  }
  db_finalize(&q);
  diff_footer(diffFlags);
}

/*
** Drop the indicated stash
*/
static void stash_drop(int stashid){
  db_multi_exec(