Fossil SCM

fossil-scm / src / diff.c
Blame History Raw 3999 lines
1
/*
2
** Copyright (c) 2007 D. Richard Hipp
3
**
4
** This program is free software; you can redistribute it and/or
5
** modify it under the terms of the Simplified BSD License (also
6
** known as the "2-Clause License" or "FreeBSD License".)
7
**
8
** This program is distributed in the hope that it will be useful,
9
** but without any warranty; without even the implied warranty of
10
** merchantability or fitness for a particular purpose.
11
**
12
** Author contact information:
13
** [email protected]
14
** http://www.hwaci.com/drh/
15
**
16
*******************************************************************************
17
**
18
** This file contains code used to compute a "diff" between two
19
** text files.
20
*/
21
#include "config.h"
22
#include "diff.h"
23
#include <assert.h>
24
#include <errno.h>
25
26
27
#if INTERFACE
28
/*
29
** Flag parameters to the text_diff() routine used to control the formatting
30
** of the diff output.
31
*/
32
#define DIFF_IGNORE_EOLWS 0x00000001 /* Ignore end-of-line whitespace */
33
#define DIFF_IGNORE_ALLWS 0x00000003 /* Ignore all whitespace */
34
#define DIFF_SIDEBYSIDE 0x00000004 /* Generate a side-by-side diff */
35
#define DIFF_VERBOSE 0x00000008 /* Missing shown as empty files */
36
#define DIFF_BRIEF 0x00000010 /* Show filenames only */
37
#define DIFF_HTML 0x00000020 /* Render for HTML */
38
#define DIFF_LINENO 0x00000040 /* Show line numbers */
39
#define DIFF_NUMSTAT 0x00000080 /* Show line count of changes */
40
#define DIFF_NOOPT 0x00000100 /* Suppress optimizations (debug) */
41
#define DIFF_INVERT 0x00000200 /* Invert the diff (debug) */
42
#define DIFF_CONTEXT_EX 0x00000400 /* Use context even if zero */
43
#define DIFF_NOTTOOBIG 0x00000800 /* Only display if not too big */
44
#define DIFF_STRIP_EOLCR 0x00001000 /* Strip trailing CR */
45
#define DIFF_SLOW_SBS 0x00002000 /* Better but slower side-by-side */
46
#define DIFF_WEBPAGE 0x00004000 /* Complete webpage */
47
#define DIFF_BROWSER 0x00008000 /* The --browser option */
48
#define DIFF_JSON 0x00010000 /* JSON output */
49
#define DIFF_DEBUG 0x00020000 /* Debugging diff output */
50
#define DIFF_RAW 0x00040000 /* Raw triples - for debugging */
51
#define DIFF_TCL 0x00080000 /* For the --tk option */
52
#define DIFF_INCBINARY 0x00100000 /* The --diff-binary option */
53
#define DIFF_SHOW_VERS 0x00200000 /* Show compared versions */
54
#define DIFF_DARKMODE 0x00400000 /* Use dark mode for HTML */
55
#define DIFF_BY_TOKEN 0x01000000 /* Split on tokens, not lines */
56
57
/*
58
** Per file information that may influence output.
59
*/
60
#define DIFF_FILE_ADDED 0x40000000 /* Added or rename destination */
61
#define DIFF_FILE_DELETED 0x80000000 /* Deleted or rename source */
62
#define DIFF_FILE_MASK 0xc0000000 /* Used for clearing file flags */
63
64
/*
65
** These error messages are shared in multiple locations. They are defined
66
** here for consistency.
67
*/
68
#define DIFF_CANNOT_COMPUTE_BINARY \
69
"cannot compute difference between binary files\n"
70
71
#define DIFF_CANNOT_COMPUTE_SYMLINK \
72
"cannot compute difference between symlink and regular file\n"
73
74
#define DIFF_TOO_MANY_CHANGES \
75
"more than 10,000 changes\n"
76
77
#define DIFF_WHITESPACE_ONLY \
78
"whitespace changes only\n"
79
80
/*
81
** Maximum length of a line in a text file, in bytes. (2**15 = 32768 bytes)
82
*/
83
#define LENGTH_MASK_SZ 15
84
#define LENGTH_MASK ((1<<LENGTH_MASK_SZ)-1)
85
86
/*
87
** An instance of this object describes the formatting and processing
88
** details desired of a "diff" operation.
89
**
90
** Conceptually, this object is as an encoding of the command-line options
91
** for the "fossil diff" command. That is not a precise description, though,
92
** because not all diff operations are started from the command-line. But
93
** the idea is sound.
94
**
95
** Information encoded by this object includes but is not limited to:
96
**
97
** * The desired output format (unified vs. side-by-side,
98
** TCL, JSON, HTML vs. plain-text).
99
**
100
** * Number of lines of context surrounding each difference block
101
**
102
** * Width of output columns for text side-by-side diffop
103
*/
104
struct DiffConfig {
105
u64 diffFlags; /* Diff flags */
106
int nContext; /* Number of lines of context */
107
int wColumn; /* Column width in -y mode */
108
u32 nFile; /* Number of files diffed so far */
109
const char *zDiffCmd; /* External diff command to use instead of builtin */
110
const char *zBinGlob; /* GLOB pattern for binary files */
111
ReCompiled *pRe; /* Show only changes matching this pattern */
112
const char *zLeftHash; /* HASH-id of the left file */
113
const char *azLabel[2]; /* Optional labels for left and right files */
114
};
115
116
#endif /* INTERFACE */
117
118
/*
119
** Initialize memory for a DiffConfig based on just a diffFlags integer.
120
*/
121
DiffConfig *diff_config_init(DiffConfig *pCfg, u64 diffFlags){
122
memset(pCfg, 0, sizeof(*pCfg));
123
pCfg->diffFlags = diffFlags;
124
return pCfg;
125
}
126
127
/*
128
** Information about each line of a file being diffed.
129
**
130
** The lower LENGTH_MASK_SZ bits of the hash (DLine.h) are the length
131
** of the line. If any line is longer than LENGTH_MASK characters,
132
** the file is considered binary.
133
*/
134
typedef struct DLine DLine;
135
struct DLine {
136
const char *z; /* The text of the line */
137
u64 h; /* Hash of the line */
138
unsigned short indent; /* Index of first non-space */
139
unsigned short n; /* number of bytes */
140
unsigned short nw; /* number of bytes without leading/trailing space */
141
unsigned int iNext; /* 1+(Index of next line with same the same hash) */
142
143
/* an array of DLine elements serves two purposes. The fields
144
** above are one per line of input text. But each entry is also
145
** a bucket in a hash table, as follows: */
146
unsigned int iHash; /* 1+(first entry in the hash chain) */
147
};
148
149
/*
150
** Length of a dline
151
*/
152
#define LENGTH(X) ((X)->n)
153
154
/*
155
** Number of diff chunks generated
156
*/
157
static int nChunk = 0;
158
159
/*
160
** A context for running a raw diff.
161
**
162
** The aEdit[] array describes the raw diff. Each triple of integers in
163
** aEdit[] means:
164
**
165
** (1) COPY: Number of lines aFrom and aTo have in common
166
** (2) DELETE: Number of lines found only in aFrom
167
** (3) INSERT: Number of lines found only in aTo
168
**
169
** The triples repeat until all lines of both aFrom and aTo are accounted
170
** for.
171
*/
172
typedef struct DContext DContext;
173
struct DContext {
174
int *aEdit; /* Array of copy/delete/insert triples */
175
int nEdit; /* Number of integers (3x num of triples) in aEdit[] */
176
int nEditAlloc; /* Space allocated for aEdit[] */
177
DLine *aFrom; /* File on left side of the diff */
178
int nFrom; /* Number of lines in aFrom[] */
179
DLine *aTo; /* File on right side of the diff */
180
int nTo; /* Number of lines in aTo[] */
181
int (*xDiffer)(const DLine *,const DLine *); /* comparison function */
182
};
183
184
/* Fast isspace for use by diff */
185
static const char diffIsSpace[] = {
186
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0,
187
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
188
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
189
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
190
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
191
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
192
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
193
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
194
195
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
196
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
197
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
198
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
199
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
200
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
201
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
202
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
203
};
204
#define diff_isspace(X) (diffIsSpace[(unsigned char)(X)])
205
206
/*
207
** Count the number of lines in the input string. Include the last line
208
** in the count even if it lacks the \n terminator. If an empty string
209
** is specified, the number of lines is zero. For the purposes of this
210
** function, a string is considered empty if it contains no characters
211
** -OR- it contains only NUL characters.
212
**
213
** Returns true if the input seems to be plain text input, else false.
214
** If it returns false, pnLine is not modified, else it is set to the
215
** number of lines in z.
216
*/
217
int count_lines(
218
const char *z,
219
int n,
220
int *pnLine
221
){
222
int nLine;
223
const char *zNL, *z2;
224
for(nLine=0, z2=z; (zNL = strchr(z2,'\n'))!=0; z2=zNL+1, nLine++){}
225
if( z2[0]!='\0' ){
226
nLine++;
227
do{ z2++; }while( z2[0]!='\0' );
228
}
229
if( n!=(int)(z2-z) ) return 0;
230
if( pnLine ) *pnLine = nLine;
231
return 1;
232
}
233
234
/*
235
** Return an array of DLine objects containing a pointer to the
236
** start of each line and a hash of that line. The lower
237
** bits of the hash store the length of each line.
238
**
239
** Trailing whitespace is removed from each line. 2010-08-20: Not any
240
** more. If trailing whitespace is ignored, the "patch" command gets
241
** confused by the diff output. Ticket [a9f7b23c2e376af5b0e5b]
242
**
243
** Return 0 if the file is binary or contains a line that is
244
** too long.
245
**
246
** Profiling show that in most cases this routine consumes the bulk of
247
** the CPU time on a diff.
248
*/
249
static DLine *break_into_lines(
250
const char *z,
251
int n,
252
int *pnLine,
253
u64 diffFlags
254
){
255
int nLine, i, k, nn, s, x;
256
u64 h, h2;
257
DLine *a;
258
const char *zNL;
259
260
if( count_lines(z, n, &nLine)==0 ){
261
return 0;
262
}
263
assert( nLine>0 || z[0]=='\0' );
264
a = fossil_malloc( sizeof(a[0])*nLine );
265
memset(a, 0, sizeof(a[0])*nLine);
266
if( nLine==0 ){
267
*pnLine = 0;
268
return a;
269
}
270
i = 0;
271
do{
272
zNL = strchr(z,'\n');
273
if( zNL==0 ) zNL = z+n;
274
nn = (int)(zNL - z);
275
if( nn>LENGTH_MASK ){
276
fossil_free(a);
277
return 0;
278
}
279
a[i].z = z;
280
k = nn;
281
if( diffFlags & DIFF_STRIP_EOLCR ){
282
if( k>0 && z[k-1]=='\r' ){ k--; }
283
}
284
a[i].n = k;
285
if( diffFlags & DIFF_IGNORE_EOLWS ){
286
while( k>0 && diff_isspace(z[k-1]) ){ k--; }
287
}
288
if( (diffFlags & DIFF_IGNORE_ALLWS)==DIFF_IGNORE_ALLWS ){
289
int numws = 0;
290
for(s=0; s<k && z[s]<=' '; s++){}
291
a[i].indent = s;
292
a[i].nw = k - s;
293
for(h=0, x=s; x<k; x++){
294
char c = z[x];
295
if( diff_isspace(c) ){
296
++numws;
297
}else{
298
h = (h^c)*9000000000000000041LL;
299
}
300
}
301
k -= numws;
302
}else{
303
int k2 = k & ~0x7;
304
u64 m;
305
for(h=x=s=0; x<k2; x += 8){
306
memcpy(&m, z+x, 8);
307
h = (h^m)*9000000000000000041LL;
308
}
309
m = 0;
310
memcpy(&m, z+x, k-k2);
311
h ^= m;
312
}
313
a[i].h = h = ((h%281474976710597LL)<<LENGTH_MASK_SZ) | (k-s);
314
h2 = h % nLine;
315
a[i].iNext = a[h2].iHash;
316
a[h2].iHash = i+1;
317
z += nn+1; n -= nn+1;
318
i++;
319
}while( zNL[0]!='\0' && zNL[1]!='\0' );
320
assert( i==nLine );
321
322
/* Return results */
323
*pnLine = nLine;
324
return a;
325
}
326
327
/*
328
** Character classes for the purpose of tokenization.
329
**
330
** 1 - alphanumeric
331
** 2 - whitespace
332
** 3 - punctuation
333
*/
334
static char aTCharClass[256] = {
335
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
336
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
337
2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
338
1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3,
339
340
3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
341
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3,
342
3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
343
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3,
344
345
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
346
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
347
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
348
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
349
350
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
351
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
352
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
353
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
354
};
355
356
/*
357
** Count the number of tokens in the given string.
358
*/
359
static int count_tokens(const unsigned char *p, int n){
360
int nToken = 0;
361
int iPrev = 0;
362
int i;
363
for(i=0; i<n; i++){
364
char x = aTCharClass[p[i]];
365
if( x!=iPrev ){
366
iPrev = x;
367
nToken++;
368
}
369
}
370
return nToken;
371
}
372
373
/*
374
** Return an array of DLine objects containing a pointer to the
375
** start of each token and a hash of that token. The lower
376
** bits of the hash store the length of each token.
377
**
378
** This is like break_into_lines() except that it works with tokens
379
** instead of lines. A token is:
380
**
381
** * A contiguous sequence of alphanumeric characters.
382
** * A contiguous sequence of whitespace
383
** * A contiguous sequence of punctuation characters.
384
**
385
** Return 0 if the file is binary or contains a line that is
386
** too long.
387
*/
388
static DLine *break_into_tokens(
389
const char *z,
390
int n,
391
int *pnToken,
392
u64 diffFlags
393
){
394
int nToken, i, k;
395
u64 h, h2;
396
DLine *a;
397
unsigned char *p = (unsigned char*)z;
398
399
nToken = count_tokens(p, n);
400
a = fossil_malloc( sizeof(a[0])*(nToken+1) );
401
memset(a, 0, sizeof(a[0])*(nToken+1));
402
if( n==0 ){
403
*pnToken = 0;
404
return a;
405
}
406
i = 0;
407
while( n>0 ){
408
char x = aTCharClass[*p];
409
h = 0xcbf29ce484222325LL;
410
for(k=1; k<n && aTCharClass[p[k]]==x; k++){
411
h ^= p[k];
412
h *= 0x100000001b3LL;
413
}
414
a[i].z = (char*)p;
415
a[i].n = k;
416
a[i].h = h = ((h%281474976710597LL)<<LENGTH_MASK_SZ) | k;
417
h2 = h % nToken;
418
a[i].iNext = a[h2].iHash;
419
a[h2].iHash = i+1;
420
p += k; n -= k;
421
i++;
422
};
423
assert( i==nToken );
424
425
/* Return results */
426
*pnToken = nToken;
427
return a;
428
}
429
430
/*
431
** Return zero if two DLine elements are identical.
432
*/
433
static int compare_dline(const DLine *pA, const DLine *pB){
434
if( pA->h!=pB->h ) return 1;
435
return memcmp(pA->z,pB->z, pA->h&LENGTH_MASK);
436
}
437
438
/*
439
** Return zero if two DLine elements are identical, ignoring
440
** all whitespace. The indent field of pA/pB already points
441
** to the first non-space character in the string.
442
*/
443
static int compare_dline_ignore_allws(const DLine *pA, const DLine *pB){
444
if( pA->h==pB->h ){
445
int a, b;
446
if( memcmp(pA->z, pB->z, pA->h&LENGTH_MASK)==0 ) return 0;
447
a = pA->indent;
448
b = pB->indent;
449
while( a<pA->n || b<pB->n ){
450
if( a<pA->n && b<pB->n && pA->z[a++] != pB->z[b++] ) return 1;
451
while( a<pA->n && diff_isspace(pA->z[a])) ++a;
452
while( b<pB->n && diff_isspace(pB->z[b])) ++b;
453
}
454
return pA->n-a != pB->n-b;
455
}
456
return 1;
457
}
458
459
/*
460
** Return true if the regular expression *pRe matches any of the
461
** N dlines
462
*/
463
static int re_dline_match(
464
ReCompiled *pRe, /* The regular expression to be matched */
465
const DLine *aDLine, /* First of N DLines to compare against */
466
int N /* Number of DLines to check */
467
){
468
while( N-- ){
469
if( re_match(pRe, (const unsigned char *)aDLine->z, LENGTH(aDLine)) ){
470
return 1;
471
}
472
aDLine++;
473
}
474
return 0;
475
}
476
477
/*
478
** Append a single line of context-diff output to pOut.
479
*/
480
static void appendDiffLine(
481
Blob *pOut, /* Where to write the line of output */
482
char cPrefix, /* One of " ", "+", or "-" */
483
const DLine *pLine /* The line to be output */
484
){
485
blob_append_char(pOut, cPrefix);
486
blob_append(pOut, pLine->z, pLine->n);
487
blob_append_char(pOut, '\n');
488
}
489
490
/*
491
** Add two line numbers to the beginning of an output line for a context
492
** diff. One or the other of the two numbers might be zero, which means
493
** to leave that number field blank.
494
*/
495
static void appendDiffLineno(Blob *pOut, int lnA, int lnB){
496
if( lnA>0 ){
497
blob_appendf(pOut, "%6d ", lnA);
498
}else{
499
blob_append(pOut, " ", 7);
500
}
501
if( lnB>0 ){
502
blob_appendf(pOut, "%6d ", lnB);
503
}else{
504
blob_append(pOut, " ", 8);
505
}
506
}
507
508
/*
509
** Output a patch-style text diff.
510
*/
511
static void contextDiff(
512
DContext *p, /* The difference */
513
Blob *pOut, /* Output a context diff to here */
514
DiffConfig *pCfg /* Configuration options */
515
){
516
const DLine *A; /* Left side of the diff */
517
const DLine *B; /* Right side of the diff */
518
int a = 0; /* Index of next line in A[] */
519
int b = 0; /* Index of next line in B[] */
520
int *R; /* Array of COPY/DELETE/INSERT triples */
521
int r; /* Index into R[] */
522
int nr; /* Number of COPY/DELETE/INSERT triples to process */
523
int mxr; /* Maximum value for r */
524
int na, nb; /* Number of lines shown from A and B */
525
int i, j; /* Loop counters */
526
int m; /* Number of lines to output */
527
int skip; /* Number of lines to skip */
528
int nContext; /* Number of lines of context */
529
int showLn; /* Show line numbers */
530
int showDivider = 0; /* True to show the divider between diff blocks */
531
532
nContext = diff_context_lines(pCfg);
533
showLn = (pCfg->diffFlags & DIFF_LINENO)!=0;
534
A = p->aFrom;
535
B = p->aTo;
536
R = p->aEdit;
537
mxr = p->nEdit;
538
while( mxr>2 && R[mxr-1]==0 && R[mxr-2]==0 ){ mxr -= 3; }
539
for(r=0; r<mxr; r += 3*nr){
540
/* Figure out how many triples to show in a single block */
541
for(nr=1; 3*nr<mxr && R[r+nr*3]>0 && R[r+nr*3]<(int)nContext*2; nr++){}
542
/* printf("r=%d nr=%d\n", r, nr); */
543
544
/* For the current block comprising nr triples, figure out
545
** how many lines of A and B are to be displayed
546
*/
547
if( R[r]>nContext ){
548
na = nb = nContext;
549
skip = R[r] - nContext;
550
}else{
551
na = nb = R[r];
552
skip = 0;
553
}
554
for(i=0; i<nr; i++){
555
na += R[r+i*3+1];
556
nb += R[r+i*3+2];
557
}
558
if( R[r+nr*3]>nContext ){
559
na += nContext;
560
nb += nContext;
561
}else{
562
na += R[r+nr*3];
563
nb += R[r+nr*3];
564
}
565
for(i=1; i<nr; i++){
566
na += R[r+i*3];
567
nb += R[r+i*3];
568
}
569
570
/* Show the header for this block, or if we are doing a modified
571
** context diff that contains line numbers, show the separator from
572
** the previous block.
573
*/
574
nChunk++;
575
if( showLn ){
576
if( !showDivider ){
577
/* Do not show a top divider */
578
showDivider = 1;
579
}else{
580
blob_appendf(pOut, "%.80c\n", '.');
581
}
582
}else{
583
/*
584
* If the patch changes an empty file or results in an empty file,
585
* the block header must use 0,0 as position indicator and not 1,0.
586
* Otherwise, patch would be confused and may reject the diff.
587
*/
588
blob_appendf(pOut,"@@ -%d,%d +%d,%d @@",
589
na ? a+skip+1 : a+skip, na,
590
nb ? b+skip+1 : b+skip, nb);
591
blob_append(pOut, "\n", 1);
592
}
593
594
/* Show the initial common area */
595
a += skip;
596
b += skip;
597
m = R[r] - skip;
598
for(j=0; j<m; j++){
599
if( showLn ) appendDiffLineno(pOut, a+j+1, b+j+1);
600
appendDiffLine(pOut, ' ', &A[a+j]);
601
}
602
a += m;
603
b += m;
604
605
/* Show the differences */
606
for(i=0; i<nr; i++){
607
m = R[r+i*3+1];
608
for(j=0; j<m; j++){
609
if( showLn ) appendDiffLineno(pOut, a+j+1, 0);
610
appendDiffLine(pOut, '-', &A[a+j]);
611
}
612
a += m;
613
m = R[r+i*3+2];
614
for(j=0; j<m; j++){
615
if( showLn ) appendDiffLineno(pOut, 0, b+j+1);
616
appendDiffLine(pOut, '+', &B[b+j]);
617
}
618
b += m;
619
if( i<nr-1 ){
620
m = R[r+i*3+3];
621
for(j=0; j<m; j++){
622
if( showLn ) appendDiffLineno(pOut, a+j+1, b+j+1);
623
appendDiffLine(pOut, ' ', &A[a+j]);
624
}
625
b += m;
626
a += m;
627
}
628
}
629
630
/* Show the final common area */
631
assert( nr==i );
632
m = R[r+nr*3];
633
if( m>nContext ) m = nContext;
634
for(j=0; j<m; j++){
635
if( showLn ) appendDiffLineno(pOut, a+j+1, b+j+1);
636
appendDiffLine(pOut, ' ', &A[a+j]);
637
}
638
}
639
}
640
641
#define MX_CSN 8 /* Maximum number of change spans across a change region */
642
643
/*
644
** A description of zero or more (up to MX_CSN) areas of difference
645
** between two lines of text.
646
*/
647
typedef struct LineChange LineChange;
648
struct LineChange {
649
int n; /* Number of change spans */
650
struct Span {
651
int iStart1; /* Byte offset to start of a change on the left */
652
int iLen1; /* Length of the left change in bytes */
653
int iStart2; /* Byte offset to start of a change on the right */
654
int iLen2; /* Length of the change on the right in bytes */
655
int isMin; /* True if this change is known to have no useful subdivs */
656
} a[MX_CSN]; /* Array of change spans, sorted order */
657
};
658
659
/*
660
** The two text segments zLeft and zRight are known to be different on
661
** both ends, but they might have a common segment in the middle. If
662
** they do not have a common segment, return 0. If they do have a large
663
** common segment, return 1 and before doing so set:
664
**
665
** aLCS[0] = start of the common segment in zLeft
666
** aLCS[1] = end of the common segment in zLeft
667
** aLCS[2] = start of the common segment in zLeft
668
** aLCS[3] = end of the common segment in zLeft
669
**
670
** This computation is for display purposes only and does not have to be
671
** optimal or exact.
672
*/
673
static int textLCS(
674
const char *zLeft, int nA, /* String on the left */
675
const char *zRight, int nB, /* String on the right */
676
int *aLCS /* Identify bounds of LCS here */
677
){
678
const unsigned char *zA = (const unsigned char*)zLeft; /* left string */
679
const unsigned char *zB = (const unsigned char*)zRight; /* right string */
680
int i, j, k; /* Loop counters */
681
int lenBest = 0; /* Match length to beat */
682
683
for(i=0; i<nA-lenBest; i++){
684
unsigned char cA = zA[i];
685
if( (cA&0xc0)==0x80 ) continue;
686
for(j=0; j<nB-lenBest; j++ ){
687
if( zB[j]==cA ){
688
for(k=1; j+k<nB && i+k<nA && zB[j+k]==zA[i+k]; k++){}
689
while( (zB[j+k]&0xc0)==0x80 ){ k--; }
690
if( k>lenBest ){
691
lenBest = k;
692
aLCS[0] = i;
693
aLCS[1] = i+k;
694
aLCS[2] = j;
695
aLCS[3] = j+k;
696
}
697
}
698
}
699
}
700
return lenBest>0;
701
}
702
703
/*
704
** Find the smallest spans that are different between two text strings that
705
** are known to be different on both ends.
706
*/
707
static int textLineChanges(
708
const char *zLeft, int nA, /* String on the left */
709
const char *zRight, int nB, /* String on the right */
710
LineChange *p /* Write results here */
711
){
712
p->n = 1;
713
p->a[0].iStart1 = 0;
714
p->a[0].iLen1 = nA;
715
p->a[0].iStart2 = 0;
716
p->a[0].iLen2 = nB;
717
p->a[0].isMin = 0;
718
while( p->n<MX_CSN-1 ){
719
int mxi = -1;
720
int mxLen = -1;
721
int x, i;
722
int aLCS[4];
723
struct Span *a, *b;
724
memset(aLCS, 0, sizeof(aLCS));
725
for(i=0; i<p->n; i++){
726
if( p->a[i].isMin ) continue;
727
x = p->a[i].iLen1;
728
if( p->a[i].iLen2<x ) x = p->a[i].iLen2;
729
if( x>mxLen ){
730
mxLen = x;
731
mxi = i;
732
}
733
}
734
if( mxLen<6 ) break;
735
x = textLCS(zLeft + p->a[mxi].iStart1, p->a[mxi].iLen1,
736
zRight + p->a[mxi].iStart2, p->a[mxi].iLen2, aLCS);
737
if( x==0 ){
738
p->a[mxi].isMin = 1;
739
continue;
740
}
741
a = p->a+mxi;
742
b = a+1;
743
if( mxi<p->n-1 ){
744
memmove(b+1, b, sizeof(*b)*(p->n-mxi-1));
745
}
746
p->n++;
747
b->iStart1 = a->iStart1 + aLCS[1];
748
b->iLen1 = a->iLen1 - aLCS[1];
749
a->iLen1 = aLCS[0];
750
b->iStart2 = a->iStart2 + aLCS[3];
751
b->iLen2 = a->iLen2 - aLCS[3];
752
a->iLen2 = aLCS[2];
753
b->isMin = 0;
754
}
755
return p->n;
756
}
757
758
/*
759
** Return true if the string starts with n spaces
760
*/
761
static int allSpaces(const char *z, int n){
762
int i;
763
for(i=0; i<n && diff_isspace(z[i]); i++){}
764
return i==n;
765
}
766
767
/*
768
** Try to improve the human-readability of the LineChange p.
769
**
770
** (1) If the first change span shows a change of indentation, try to
771
** move that indentation change to the left margin.
772
**
773
** (2) Try to shift changes so that they begin or end with a space.
774
*/
775
static void improveReadability(
776
const char *zA, /* Left line of the change */
777
const char *zB, /* Right line of the change */
778
LineChange *p /* The LineChange to be adjusted */
779
){
780
int j, n, len;
781
if( p->n<1 ) return;
782
783
/* (1) Attempt to move indentation changes to the left margin */
784
if( p->a[0].iLen1==0
785
&& (len = p->a[0].iLen2)>0
786
&& (j = p->a[0].iStart2)>0
787
&& zB[0]==zB[j]
788
&& allSpaces(zB, j)
789
){
790
for(n=1; n<len && n<j && zB[j]==zB[j+n]; n++){}
791
if( n<len ){
792
memmove(&p->a[1], &p->a[0], sizeof(p->a[0])*p->n);
793
p->n++;
794
p->a[0] = p->a[1];
795
p->a[1].iStart2 += n;
796
p->a[1].iLen2 -= n;
797
p->a[0].iLen2 = n;
798
}
799
p->a[0].iStart1 = 0;
800
p->a[0].iStart2 = 0;
801
}else
802
if( p->a[0].iLen2==0
803
&& (len = p->a[0].iLen1)>0
804
&& (j = p->a[0].iStart1)>0
805
&& zA[0]==zA[j]
806
&& allSpaces(zA, j)
807
){
808
for(n=1; n<len && n<j && zA[j]==zA[j+n]; n++){}
809
if( n<len ){
810
memmove(&p->a[1], &p->a[0], sizeof(p->a[0])*p->n);
811
p->n++;
812
p->a[0] = p->a[1];
813
p->a[1].iStart1 += n;
814
p->a[1].iLen1 -= n;
815
p->a[0].iLen1 = n;
816
}
817
p->a[0].iStart1 = 0;
818
p->a[0].iStart2 = 0;
819
}
820
821
/* (2) Try to shift changes so that they begin or end with a
822
** space. (TBD) */
823
}
824
825
826
/*
827
** Given two lines of text, pFrom and pTo, compute a set of changes
828
** between those two lines, for enhanced display purposes.
829
**
830
** The result is written into the LineChange object given by the
831
** third parameter.
832
*/
833
static void oneLineChange(
834
const DLine *pLeft, /* Left line of the change */
835
const DLine *pRight, /* Right line of the change */
836
LineChange *p /* OUTPUT: Write the results here */
837
){
838
int nLeft; /* Length of left line in bytes */
839
int nRight; /* Length of right line in bytes */
840
int nShort; /* Shortest of left and right */
841
int nPrefix; /* Length of common prefix */
842
int nSuffix; /* Length of common suffix */
843
int nCommon; /* Total byte length of suffix and prefix */
844
const char *zLeft; /* Text of the left line */
845
const char *zRight; /* Text of the right line */
846
int nLeftDiff; /* nLeft - nPrefix - nSuffix */
847
int nRightDiff; /* nRight - nPrefix - nSuffix */
848
849
nLeft = pLeft->n;
850
zLeft = pLeft->z;
851
nRight = pRight->n;
852
zRight = pRight->z;
853
nShort = nLeft<nRight ? nLeft : nRight;
854
855
nPrefix = 0;
856
while( nPrefix<nShort && zLeft[nPrefix]==zRight[nPrefix] ){
857
nPrefix++;
858
}
859
if( nPrefix<nShort ){
860
while( nPrefix>0 && (zLeft[nPrefix]&0xc0)==0x80 ) nPrefix--;
861
}
862
nSuffix = 0;
863
if( nPrefix<nShort ){
864
while( nSuffix<nShort
865
&& zLeft[nLeft-nSuffix-1]==zRight[nRight-nSuffix-1] ){
866
nSuffix++;
867
}
868
if( nSuffix<nShort ){
869
while( nSuffix>0 && (zLeft[nLeft-nSuffix]&0xc0)==0x80 ) nSuffix--;
870
}
871
if( nSuffix==nLeft || nSuffix==nRight ) nPrefix = 0;
872
}
873
nCommon = nPrefix + nSuffix;
874
875
/* If the prefix and suffix overlap, that means that we are dealing with
876
** a pure insertion or deletion of text that can have multiple alignments.
877
** Try to find an alignment to begins and ends on whitespace, or on
878
** punctuation, rather than in the middle of a name or number.
879
*/
880
if( nCommon > nShort ){
881
int iBest = -1;
882
int iBestVal = -1;
883
int i;
884
int nLong = nLeft<nRight ? nRight : nLeft;
885
int nGap = nLong - nShort;
886
for(i=nShort-nSuffix; i<=nPrefix; i++){
887
int iVal = 0;
888
char c = zLeft[i];
889
if( diff_isspace(c) ){
890
iVal += 5;
891
}else if( !fossil_isalnum(c) ){
892
iVal += 2;
893
}
894
c = zLeft[i+nGap-1];
895
if( diff_isspace(c) ){
896
iVal += 5;
897
}else if( !fossil_isalnum(c) ){
898
iVal += 2;
899
}
900
if( iVal>iBestVal ){
901
iBestVal = iVal;
902
iBest = i;
903
}
904
}
905
nPrefix = iBest;
906
nSuffix = nShort - nPrefix;
907
nCommon = nPrefix + nSuffix;
908
}
909
910
/* A single chunk of text inserted */
911
if( nCommon==nLeft ){
912
p->n = 1;
913
p->a[0].iStart1 = nPrefix;
914
p->a[0].iLen1 = 0;
915
p->a[0].iStart2 = nPrefix;
916
p->a[0].iLen2 = nRight - nCommon;
917
improveReadability(zLeft, zRight, p);
918
return;
919
}
920
921
/* A single chunk of text deleted */
922
if( nCommon==nRight ){
923
p->n = 1;
924
p->a[0].iStart1 = nPrefix;
925
p->a[0].iLen1 = nLeft - nCommon;
926
p->a[0].iStart2 = nPrefix;
927
p->a[0].iLen2 = 0;
928
improveReadability(zLeft, zRight, p);
929
return;
930
}
931
932
/* At this point we know that there is a chunk of text that has
933
** changed between the left and the right. Check to see if there
934
** is a large unchanged section in the middle of that changed block.
935
*/
936
nLeftDiff = nLeft - nCommon;
937
nRightDiff = nRight - nCommon;
938
if( nLeftDiff >= 4
939
&& nRightDiff >= 4
940
&& textLineChanges(&zLeft[nPrefix], nLeftDiff,
941
&zRight[nPrefix], nRightDiff, p)>1
942
){
943
int i;
944
for(i=0; i<p->n; i++){
945
p->a[i].iStart1 += nPrefix;
946
p->a[i].iStart2 += nPrefix;
947
}
948
improveReadability(zLeft, zRight, p);
949
return;
950
}
951
952
/* If all else fails, show a single big change between left and right */
953
p->n = 1;
954
p->a[0].iStart1 = nPrefix;
955
p->a[0].iLen1 = nLeft - nCommon;
956
p->a[0].iStart2 = nPrefix;
957
p->a[0].iLen2 = nRight - nCommon;
958
improveReadability(zLeft, zRight, p);
959
}
960
961
/*
962
** COMMAND: test-line-diff
963
** Usage: %fossil% test-line-diff STRING1 STRING2
964
**
965
** Show the differences between the two strings. Used for testing
966
** the oneLineChange() routine in the diff logic.
967
*/
968
void test_line_diff(void){
969
DLine a, b;
970
LineChange chng;
971
int i, j, x;
972
if( g.argc!=4 ) usage("STRING1 STRING2");
973
a.z = g.argv[2];
974
a.n = (int)strlen(a.z);
975
b.z = g.argv[3];
976
b.n = (int)strlen(b.z);
977
oneLineChange(&a, &b, &chng);
978
fossil_print("left: [%s]\n", a.z);
979
for(i=x=0; i<chng.n; i++){
980
int ofst = chng.a[i].iStart1;
981
int len = chng.a[i].iLen1;
982
if( len ){
983
if( x==0 ){ fossil_print("%*s", 8, ""); }
984
while( ofst > x ){
985
if( (a.z[x]&0xc0)!=0x80 ) fossil_print(" ");
986
x++;
987
}
988
for(j=0; j<len; j++, x++){
989
if( (a.z[x]&0xc0)!=0x80 ) fossil_print("%d",i);
990
}
991
}
992
}
993
if( x ) fossil_print("\n");
994
fossil_print("right: [%s]\n", b.z);
995
for(i=x=0; i<chng.n; i++){
996
int ofst = chng.a[i].iStart2;
997
int len = chng.a[i].iLen2;
998
if( len ){
999
if( x==0 ){ fossil_print("%*s", 8, ""); }
1000
while( ofst > x ){
1001
if( (b.z[x]&0xc0)!=0x80 ) fossil_print(" ");
1002
x++;
1003
}
1004
for(j=0; j<len; j++, x++){
1005
if( (b.z[x]&0xc0)!=0x80 ) fossil_print("%d",i);
1006
}
1007
}
1008
}
1009
if( x ) fossil_print("\n");
1010
}
1011
1012
/*
1013
** Minimum of two values
1014
*/
1015
static int minInt(int a, int b){ return a<b ? a : b; }
1016
1017
1018
1019
/*
1020
** This is an abstract superclass for an object that accepts difference
1021
** lines and formats them for display. Subclasses of this object format
1022
** the diff output in different ways.
1023
**
1024
** To subclass, create an instance of the DiffBuilder object and fill
1025
** in appropriate method implementations.
1026
*/
1027
typedef struct DiffBuilder DiffBuilder;
1028
struct DiffBuilder {
1029
void (*xSkip)(DiffBuilder*, unsigned int, int);
1030
void (*xCommon)(DiffBuilder*,const DLine*);
1031
void (*xInsert)(DiffBuilder*,const DLine*);
1032
void (*xDelete)(DiffBuilder*,const DLine*);
1033
void (*xReplace)(DiffBuilder*,const DLine*,const DLine*);
1034
void (*xEdit)(DiffBuilder*,const DLine*,const DLine*);
1035
void (*xEnd)(DiffBuilder*);
1036
unsigned int lnLeft; /* Lines seen on the left (delete) side */
1037
unsigned int lnRight; /* Lines seen on the right (insert) side */
1038
unsigned int nPending; /* Number of pending lines */
1039
int eState; /* State of the output */
1040
int width; /* Display width */
1041
Blob *pOut; /* Output blob */
1042
Blob aCol[5]; /* Holding blobs */
1043
DiffConfig *pCfg; /* Configuration information */
1044
};
1045
1046
/************************* DiffBuilderDebug ********************************/
1047
/* This version of DiffBuilder is used for debugging the diff and diff
1048
** diff formatter logic. It is accessed using the (undocumented) --debug
1049
** option to the diff command. The output is human-readable text that
1050
** describes the various method calls that are invoked against the DiffBuilder
1051
** object.
1052
*/
1053
static void dfdebugSkip(DiffBuilder *p, unsigned int n, int isFinal){
1054
blob_appendf(p->pOut, "SKIP %d (%d..%d left and %d..%d right)%s\n",
1055
n, p->lnLeft+1, p->lnLeft+n, p->lnRight+1, p->lnRight+n,
1056
isFinal ? " FINAL" : "");
1057
p->lnLeft += n;
1058
p->lnRight += n;
1059
}
1060
static void dfdebugCommon(DiffBuilder *p, const DLine *pLine){
1061
p->lnLeft++;
1062
p->lnRight++;
1063
blob_appendf(p->pOut, "COMMON %8u %8u %.*s\n",
1064
p->lnLeft, p->lnRight, (int)pLine->n, pLine->z);
1065
}
1066
static void dfdebugInsert(DiffBuilder *p, const DLine *pLine){
1067
p->lnRight++;
1068
blob_appendf(p->pOut, "INSERT %8d %.*s\n",
1069
p->lnRight, (int)pLine->n, pLine->z);
1070
}
1071
static void dfdebugDelete(DiffBuilder *p, const DLine *pLine){
1072
p->lnLeft++;
1073
blob_appendf(p->pOut, "DELETE %8u %.*s\n",
1074
p->lnLeft, (int)pLine->n, pLine->z);
1075
}
1076
static void dfdebugReplace(DiffBuilder *p, const DLine *pX, const DLine *pY){
1077
p->lnLeft++;
1078
p->lnRight++;
1079
blob_appendf(p->pOut, "REPLACE %8u %.*s\n",
1080
p->lnLeft, (int)pX->n, pX->z);
1081
blob_appendf(p->pOut, " %8u %.*s\n",
1082
p->lnRight, (int)pY->n, pY->z);
1083
}
1084
static void dfdebugEdit(DiffBuilder *p, const DLine *pX, const DLine *pY){
1085
int i, j;
1086
int x;
1087
LineChange chng;
1088
p->lnLeft++;
1089
p->lnRight++;
1090
blob_appendf(p->pOut, "EDIT %8u %.*s\n",
1091
p->lnLeft, (int)pX->n, pX->z);
1092
oneLineChange(pX, pY, &chng);
1093
for(i=x=0; i<chng.n; i++){
1094
int ofst = chng.a[i].iStart1;
1095
int len = chng.a[i].iLen1;
1096
if( len ){
1097
char c = '0' + i;
1098
if( x==0 ){ blob_appendf(p->pOut, "%*s", 26, ""); }
1099
while( ofst > x ){
1100
if( (pX->z[x]&0xc0)!=0x80 ) blob_append_char(p->pOut, ' ');
1101
x++;
1102
}
1103
for(j=0; j<len; j++, x++){
1104
if( (pX->z[x]&0xc0)!=0x80 ) blob_append_char(p->pOut, c);
1105
}
1106
}
1107
}
1108
if( x ) blob_append_char(p->pOut, '\n');
1109
blob_appendf(p->pOut, " %8u %.*s\n",
1110
p->lnRight, (int)pY->n, pY->z);
1111
for(i=x=0; i<chng.n; i++){
1112
int ofst = chng.a[i].iStart2;
1113
int len = chng.a[i].iLen2;
1114
if( len ){
1115
char c = '0' + i;
1116
if( x==0 ){ blob_appendf(p->pOut, "%*s", 26, ""); }
1117
while( ofst > x ){
1118
if( (pY->z[x]&0xc0)!=0x80 ) blob_append_char(p->pOut, ' ');
1119
x++;
1120
}
1121
for(j=0; j<len; j++, x++){
1122
if( (pY->z[x]&0xc0)!=0x80 ) blob_append_char(p->pOut, c);
1123
}
1124
}
1125
}
1126
if( x ) blob_append_char(p->pOut, '\n');
1127
}
1128
static void dfdebugEnd(DiffBuilder *p){
1129
blob_appendf(p->pOut, "END with %u lines left and %u lines right\n",
1130
p->lnLeft, p->lnRight);
1131
fossil_free(p);
1132
}
1133
static DiffBuilder *dfdebugNew(Blob *pOut){
1134
DiffBuilder *p = fossil_malloc(sizeof(*p));
1135
p->xSkip = dfdebugSkip;
1136
p->xCommon = dfdebugCommon;
1137
p->xInsert = dfdebugInsert;
1138
p->xDelete = dfdebugDelete;
1139
p->xReplace = dfdebugReplace;
1140
p->xEdit = dfdebugEdit;
1141
p->xEnd = dfdebugEnd;
1142
p->lnLeft = p->lnRight = 0;
1143
p->pOut = pOut;
1144
return p;
1145
}
1146
1147
/************************* DiffBuilderTcl ********************************/
1148
/*
1149
** This formatter outputs a description of the diff formatted as TCL, for
1150
** use by the --tk option to "diff". See also the "diff.tcl" file. The
1151
** output can be viewed directly using the --tcl option.
1152
**
1153
** There is one line per method call:
1154
**
1155
** SKIP n -- Skip "n" lines of input
1156
** COM string -- "string" is an unchanged context line
1157
** INS string -- "string" is in the right file only
1158
** DEL string -- "string" is in the left file only
1159
** EDIT string .... -- Complex edit between left and right
1160
**
1161
** The EDIT verb will be followed by 3*N or 3*N+1 strings. The triples
1162
** each show:
1163
**
1164
** 1. Common text
1165
** 2. Text from the left side
1166
** 3. Text on the right that replaces (2) from the left
1167
**
1168
** For inserted text (2) will be an empty string. For deleted text, (3)
1169
** will be an empty string. (1) might be empty for the first triple if
1170
** the line begins with an edit. After all triples, there might be one
1171
** additional string which is a common suffix.
1172
*/
1173
static void dftclSkip(DiffBuilder *p, unsigned int n, int isFinal){
1174
blob_appendf(p->pOut, "SKIP %u\n", n);
1175
}
1176
static void dftclCommon(DiffBuilder *p, const DLine *pLine){
1177
blob_appendf(p->pOut, "COM ");
1178
blob_append_tcl_literal(p->pOut, pLine->z, pLine->n);
1179
blob_append_char(p->pOut, '\n');
1180
}
1181
static void dftclInsert(DiffBuilder *p, const DLine *pLine){
1182
blob_append(p->pOut, "INS ", -1);
1183
blob_append_tcl_literal(p->pOut, pLine->z, pLine->n);
1184
blob_append_char(p->pOut, '\n');
1185
}
1186
static void dftclDelete(DiffBuilder *p, const DLine *pLine){
1187
blob_append(p->pOut, "DEL ", -1);
1188
blob_append_tcl_literal(p->pOut, pLine->z, pLine->n);
1189
blob_append_char(p->pOut, '\n');
1190
}
1191
static void dftclReplace(DiffBuilder *p, const DLine *pX, const DLine *pY){
1192
blob_append(p->pOut, "EDIT \"\" ", -1);
1193
blob_append_tcl_literal(p->pOut, pX->z, pX->n);
1194
blob_append_char(p->pOut, ' ');
1195
blob_append_tcl_literal(p->pOut, pY->z, pY->n);
1196
blob_append_char(p->pOut, '\n');
1197
}
1198
static void dftclEdit(DiffBuilder *p, const DLine *pX, const DLine *pY){
1199
int i, x;
1200
LineChange chng;
1201
blob_append(p->pOut, "EDIT", 4);
1202
oneLineChange(pX, pY, &chng);
1203
for(i=x=0; i<chng.n; i++){
1204
blob_append_char(p->pOut, ' ');
1205
blob_append_tcl_literal(p->pOut, pX->z + x, chng.a[i].iStart1 - x);
1206
x = chng.a[i].iStart1;
1207
blob_append_char(p->pOut, ' ');
1208
blob_append_tcl_literal(p->pOut, pX->z + x, chng.a[i].iLen1);
1209
x += chng.a[i].iLen1;
1210
blob_append_char(p->pOut, ' ');
1211
blob_append_tcl_literal(p->pOut,
1212
pY->z + chng.a[i].iStart2, chng.a[i].iLen2);
1213
}
1214
if( x<pX->n ){
1215
blob_append_char(p->pOut, ' ');
1216
blob_append_tcl_literal(p->pOut, pX->z + x, pX->n - x);
1217
}
1218
blob_append_char(p->pOut, '\n');
1219
}
1220
static void dftclEnd(DiffBuilder *p){
1221
fossil_free(p);
1222
}
1223
static DiffBuilder *dftclNew(Blob *pOut){
1224
DiffBuilder *p = fossil_malloc(sizeof(*p));
1225
p->xSkip = dftclSkip;
1226
p->xCommon = dftclCommon;
1227
p->xInsert = dftclInsert;
1228
p->xDelete = dftclDelete;
1229
p->xReplace = dftclReplace;
1230
p->xEdit = dftclEdit;
1231
p->xEnd = dftclEnd;
1232
p->pOut = pOut;
1233
return p;
1234
}
1235
1236
/************************* DiffBuilderJson ********************************/
1237
/*
1238
** This formatter generates a JSON array that describes the difference.
1239
**
1240
** The Json array consists of integer opcodes with each opcode followed
1241
** by zero or more arguments:
1242
**
1243
** Syntax Mnemonic Description
1244
** ----------- -------- --------------------------
1245
** 0 END This is the end of the diff
1246
** 1 INTEGER SKIP Skip N lines from both files
1247
** 2 STRING COMMON The line shown by STRING is in both files
1248
** 3 STRING INSERT The line STRING is in only the right file
1249
** 4 STRING DELETE The STRING line is in only the left file
1250
** 5 SUBARRAY EDIT One line is different on left and right.
1251
**
1252
** The SUBARRAY is an array of 3*N+1 strings with N>=0. The triples
1253
** represent common-text, left-text, and right-text. The last string
1254
** in SUBARRAY is the common-suffix. Any string can be empty if it does
1255
** not apply.
1256
*/
1257
static void dfjsonSkip(DiffBuilder *p, unsigned int n, int isFinal){
1258
blob_appendf(p->pOut, "1,%u,\n", n);
1259
}
1260
static void dfjsonCommon(DiffBuilder *p, const DLine *pLine){
1261
blob_append(p->pOut, "2,",2);
1262
blob_append_json_literal(p->pOut, pLine->z, (int)pLine->n);
1263
blob_append(p->pOut, ",\n",2);
1264
}
1265
static void dfjsonInsert(DiffBuilder *p, const DLine *pLine){
1266
blob_append(p->pOut, "3,",2);
1267
blob_append_json_literal(p->pOut, pLine->z, (int)pLine->n);
1268
blob_append(p->pOut, ",\n",2);
1269
}
1270
static void dfjsonDelete(DiffBuilder *p, const DLine *pLine){
1271
blob_append(p->pOut, "4,",2);
1272
blob_append_json_literal(p->pOut, pLine->z, (int)pLine->n);
1273
blob_append(p->pOut, ",\n",2);
1274
}
1275
static void dfjsonReplace(DiffBuilder *p, const DLine *pX, const DLine *pY){
1276
blob_append(p->pOut, "5,[\"\",",-1);
1277
blob_append_json_literal(p->pOut, pX->z, (int)pX->n);
1278
blob_append(p->pOut, ",",1);
1279
blob_append_json_literal(p->pOut, pY->z, (int)pY->n);
1280
blob_append(p->pOut, ",\"\"],\n",-1);
1281
}
1282
static void dfjsonEdit(DiffBuilder *p, const DLine *pX, const DLine *pY){
1283
int i, x;
1284
LineChange chng;
1285
blob_append(p->pOut, "5,[", 3);
1286
oneLineChange(pX, pY, &chng);
1287
for(i=x=0; i<chng.n; i++){
1288
if(i>0){
1289
blob_append_char(p->pOut, ',');
1290
}
1291
blob_append_json_literal(p->pOut, pX->z + x, chng.a[i].iStart1 - x);
1292
x = chng.a[i].iStart1;
1293
blob_append_char(p->pOut, ',');
1294
blob_append_json_literal(p->pOut, pX->z + x, chng.a[i].iLen1);
1295
x += chng.a[i].iLen1;
1296
blob_append_char(p->pOut, ',');
1297
blob_append_json_literal(p->pOut,
1298
pY->z + chng.a[i].iStart2, chng.a[i].iLen2);
1299
}
1300
blob_append_char(p->pOut, ',');
1301
blob_append_json_literal(p->pOut, pX->z + x, pX->n - x);
1302
blob_append(p->pOut, "],\n",3);
1303
}
1304
static void dfjsonEnd(DiffBuilder *p){
1305
blob_append(p->pOut, "0]}", 3);
1306
fossil_free(p);
1307
}
1308
static DiffBuilder *dfjsonNew(Blob *pOut){
1309
DiffBuilder *p = fossil_malloc(sizeof(*p));
1310
p->xSkip = dfjsonSkip;
1311
p->xCommon = dfjsonCommon;
1312
p->xInsert = dfjsonInsert;
1313
p->xDelete = dfjsonDelete;
1314
p->xReplace = dfjsonReplace;
1315
p->xEdit = dfjsonEdit;
1316
p->xEnd = dfjsonEnd;
1317
p->lnLeft = p->lnRight = 0;
1318
p->pOut = pOut;
1319
blob_append_char(pOut, '[');
1320
return p;
1321
}
1322
1323
/************************* DiffBuilderUnified********************************/
1324
/* This formatter generates a unified diff for HTML.
1325
**
1326
** The result is a <table> with four columns. The four columns hold:
1327
**
1328
** 1. The line numbers for the first file.
1329
** 2. The line numbers for the second file.
1330
** 3. The "diff mark": "+" or "-" or just a space
1331
** 4. Text of the line
1332
**
1333
** Inserted lines are marked with <ins> and deleted lines are marked
1334
** with <del>. The whole line is marked this way, not just the part that
1335
** changed. The part that change has an additional nested <ins> or <del>.
1336
** The CSS needs to be set up such that a single <ins> or <del> gives a
1337
** light background and a nested <ins> or <del> gives a darker background.
1338
** Additional attributes (like bold font) might also be added to nested
1339
** <ins> and <del> since those are the characters that have actually
1340
** changed.
1341
**
1342
** Accumulator strategy:
1343
**
1344
** * Delete line numbers are output directly to p->pOut
1345
** * Insert line numbers accumulate in p->aCol[0].
1346
** * Separator marks accumulate in p->aCol[1].
1347
** * Change text accumulates in p->aCol[2].
1348
** * Pending insert line numbers go into p->aCol[3].
1349
** * Pending insert text goes into p->aCol[4].
1350
**
1351
** eState is 1 if text has an open <del>
1352
*/
1353
static void dfunifiedFinishDelete(DiffBuilder *p){
1354
if( p->eState==0 ) return;
1355
blob_append(p->pOut, "</del>", 6);
1356
blob_append(&p->aCol[2], "</del>", 6);
1357
p->eState = 0;
1358
}
1359
static void dfunifiedFinishInsert(DiffBuilder *p){
1360
unsigned int i;
1361
if( p->nPending==0 ) return;
1362
dfunifiedFinishDelete(p);
1363
1364
/* Blank lines for delete line numbers for each inserted line */
1365
for(i=0; i<p->nPending; i++) blob_append_char(p->pOut, '\n');
1366
1367
/* Insert line numbers */
1368
blob_append(&p->aCol[0], "<ins>", 5);
1369
blob_append_xfer(&p->aCol[0], &p->aCol[3]);
1370
blob_append(&p->aCol[0], "</ins>", 6);
1371
1372
/* "+" marks for the separator on inserted lines */
1373
for(i=0; i<p->nPending; i++) blob_append(&p->aCol[1], "+\n", 2);
1374
1375
/* Text of the inserted lines */
1376
blob_append(&p->aCol[2], "<ins>", 5);
1377
blob_append_xfer(&p->aCol[2], &p->aCol[4]);
1378
blob_append(&p->aCol[2], "</ins>", 6);
1379
1380
p->nPending = 0;
1381
}
1382
static void dfunifiedFinishRow(DiffBuilder *p){
1383
dfunifiedFinishDelete(p);
1384
dfunifiedFinishInsert(p);
1385
if( blob_size(&p->aCol[0])==0 ) return;
1386
blob_append(p->pOut, "</pre></td><td class=\"diffln difflnr\"><pre>\n", -1);
1387
blob_append_xfer(p->pOut, &p->aCol[0]);
1388
blob_append(p->pOut, "</pre></td><td class=\"diffsep\"><pre>\n", -1);
1389
blob_append_xfer(p->pOut, &p->aCol[1]);
1390
blob_append(p->pOut, "</pre></td><td class=\"difftxt difftxtu\"><pre>\n",-1);
1391
blob_append_xfer(p->pOut, &p->aCol[2]);
1392
blob_append(p->pOut, "</pre></td></tr>\n", -1);
1393
}
1394
static void dfunifiedStartRow(DiffBuilder *p){
1395
if( blob_size(&p->aCol[0])>0 ) return;
1396
blob_appendf(p->pOut,"<tr id=\"chunk%d\" class=\"diffchunk\">"
1397
"<td class=\"diffln difflnl\"><pre>\n", ++nChunk);
1398
}
1399
static void dfunifiedSkip(DiffBuilder *p, unsigned int n, int isFinal){
1400
dfunifiedFinishRow(p);
1401
if( p->pCfg && p->pCfg->zLeftHash ){
1402
blob_appendf(p->pOut,
1403
"<tr class=\"diffskip\" data-startln=\"%d\" data-endln=\"%d\""
1404
" id=\"skip%xh%xi%x\">\n",
1405
p->lnLeft+1, p->lnLeft+n,
1406
nChunk, p->lnLeft, n);
1407
}else{
1408
blob_append(p->pOut, "<tr>", 4);
1409
}
1410
blob_append(p->pOut, "<td class=\"diffln difflne\">"
1411
"&#xfe19;</td><td></td><td></td></tr>\n", -1);
1412
p->lnLeft += n;
1413
p->lnRight += n;
1414
}
1415
static void dfunifiedCommon(DiffBuilder *p, const DLine *pLine){
1416
dfunifiedStartRow(p);
1417
dfunifiedFinishDelete(p);
1418
dfunifiedFinishInsert(p);
1419
p->lnLeft++;
1420
p->lnRight++;
1421
blob_appendf(p->pOut,"%d\n", p->lnLeft);
1422
blob_appendf(&p->aCol[0],"%d\n",p->lnRight);
1423
blob_append_char(&p->aCol[1], '\n');
1424
htmlize_to_blob(&p->aCol[2], pLine->z, (int)pLine->n);
1425
blob_append_char(&p->aCol[2], '\n');
1426
}
1427
static void dfunifiedInsert(DiffBuilder *p, const DLine *pLine){
1428
dfunifiedStartRow(p);
1429
p->lnRight++;
1430
blob_appendf(&p->aCol[3],"%d\n", p->lnRight);
1431
blob_append(&p->aCol[4], "<ins>", 5);
1432
htmlize_to_blob(&p->aCol[4], pLine->z, (int)pLine->n);
1433
blob_append(&p->aCol[4], "</ins>\n", 7);
1434
p->nPending++;
1435
}
1436
static void dfunifiedDelete(DiffBuilder *p, const DLine *pLine){
1437
dfunifiedStartRow(p);
1438
dfunifiedFinishInsert(p);
1439
if( p->eState==0 ){
1440
dfunifiedFinishInsert(p);
1441
blob_append(p->pOut, "<del>", 5);
1442
blob_append(&p->aCol[2], "<del>", 5);
1443
p->eState = 1;
1444
}
1445
p->lnLeft++;
1446
blob_appendf(p->pOut,"%d\n", p->lnLeft);
1447
blob_append_char(&p->aCol[0],'\n');
1448
blob_append(&p->aCol[1],"-\n",2);
1449
blob_append(&p->aCol[2], "<del>", 5);
1450
htmlize_to_blob(&p->aCol[2], pLine->z, (int)pLine->n);
1451
blob_append(&p->aCol[2], "</del>\n", 7);
1452
}
1453
static void dfunifiedReplace(DiffBuilder *p, const DLine *pX, const DLine *pY){
1454
dfunifiedStartRow(p);
1455
if( p->eState==0 ){
1456
dfunifiedFinishInsert(p);
1457
blob_append(p->pOut, "<del>", 5);
1458
blob_append(&p->aCol[2], "<del>", 5);
1459
p->eState = 1;
1460
}
1461
p->lnLeft++;
1462
p->lnRight++;
1463
blob_appendf(p->pOut,"%d\n", p->lnLeft);
1464
blob_append_char(&p->aCol[0], '\n');
1465
blob_append(&p->aCol[1], "-\n", 2);
1466
1467
htmlize_to_blob(&p->aCol[2], pX->z, pX->n);
1468
blob_append_char(&p->aCol[2], '\n');
1469
1470
blob_appendf(&p->aCol[3],"%d\n", p->lnRight);
1471
1472
htmlize_to_blob(&p->aCol[4], pY->z, pY->n);
1473
blob_append_char(&p->aCol[4], '\n');
1474
p->nPending++;
1475
}
1476
static void dfunifiedEdit(DiffBuilder *p, const DLine *pX, const DLine *pY){
1477
int i;
1478
int x;
1479
LineChange chng;
1480
oneLineChange(pX, pY, &chng);
1481
dfunifiedStartRow(p);
1482
if( p->eState==0 ){
1483
dfunifiedFinishInsert(p);
1484
blob_append(p->pOut, "<del>", 5);
1485
blob_append(&p->aCol[2], "<del>", 5);
1486
p->eState = 1;
1487
}
1488
p->lnLeft++;
1489
p->lnRight++;
1490
blob_appendf(p->pOut,"%d\n", p->lnLeft);
1491
blob_append_char(&p->aCol[0], '\n');
1492
blob_append(&p->aCol[1], "-\n", 2);
1493
1494
for(i=x=0; i<chng.n; i++){
1495
int ofst = chng.a[i].iStart1;
1496
int len = chng.a[i].iLen1;
1497
if( len ){
1498
htmlize_to_blob(&p->aCol[2], pX->z+x, ofst - x);
1499
x = ofst;
1500
blob_append(&p->aCol[2], "<del>", 5);
1501
htmlize_to_blob(&p->aCol[2], pX->z+x, len);
1502
x += len;
1503
blob_append(&p->aCol[2], "</del>", 6);
1504
}
1505
}
1506
htmlize_to_blob(&p->aCol[2], pX->z+x, pX->n - x);
1507
blob_append_char(&p->aCol[2], '\n');
1508
1509
blob_appendf(&p->aCol[3],"%d\n", p->lnRight);
1510
for(i=x=0; i<chng.n; i++){
1511
int ofst = chng.a[i].iStart2;
1512
int len = chng.a[i].iLen2;
1513
if( len ){
1514
htmlize_to_blob(&p->aCol[4], pY->z+x, ofst - x);
1515
x = ofst;
1516
blob_append(&p->aCol[4], "<ins>", 5);
1517
htmlize_to_blob(&p->aCol[4], pY->z+x, len);
1518
x += len;
1519
blob_append(&p->aCol[4], "</ins>", 6);
1520
}
1521
}
1522
htmlize_to_blob(&p->aCol[4], pY->z+x, pY->n - x);
1523
blob_append_char(&p->aCol[4], '\n');
1524
p->nPending++;
1525
}
1526
static void dfunifiedEnd(DiffBuilder *p){
1527
dfunifiedFinishRow(p);
1528
blob_append(p->pOut, "</table>\n",-1);
1529
fossil_free(p);
1530
}
1531
static DiffBuilder *dfunifiedNew(Blob *pOut, DiffConfig *pCfg){
1532
DiffBuilder *p = fossil_malloc(sizeof(*p));
1533
p->xSkip = dfunifiedSkip;
1534
p->xCommon = dfunifiedCommon;
1535
p->xInsert = dfunifiedInsert;
1536
p->xDelete = dfunifiedDelete;
1537
p->xReplace = dfunifiedReplace;
1538
p->xEdit = dfunifiedEdit;
1539
p->xEnd = dfunifiedEnd;
1540
p->lnLeft = p->lnRight = 0;
1541
p->eState = 0;
1542
p->nPending = 0;
1543
p->pOut = pOut;
1544
if( pCfg->zLeftHash ){
1545
blob_appendf(pOut, "<table class=\"diff udiff\" data-lefthash=\"%s\">\n",
1546
pCfg->zLeftHash);
1547
}else{
1548
blob_append(pOut, "<table class=\"diff udiff\">\n", -1);
1549
}
1550
blob_init(&p->aCol[0], 0, 0);
1551
blob_init(&p->aCol[1], 0, 0);
1552
blob_init(&p->aCol[2], 0, 0);
1553
blob_init(&p->aCol[3], 0, 0);
1554
blob_init(&p->aCol[4], 0, 0);
1555
p->pCfg = pCfg;
1556
return p;
1557
}
1558
1559
/************************* DiffBuilderSplit ******************************/
1560
/* This formatter creates a side-by-side diff in HTML. The output is a
1561
** <table> with 5 columns:
1562
**
1563
** 1. Line numbers for the first file.
1564
** 2. Text for the first file.
1565
** 3. The difference mark. "<", ">", "|" or blank
1566
** 4. Line numbers for the second file.
1567
** 5. Text for the second file.
1568
**
1569
** The <ins> and <del> strategy is the same as for unified diff above.
1570
** In fact, the same CSS can be used for both.
1571
**
1572
** Accumulator strategy:
1573
**
1574
** * Left line numbers are output directly to p->pOut
1575
** * Left text accumulates in p->aCol[0].
1576
** * Edit marks accumulates in p->aCol[1].
1577
** * Right line numbers accumulate in p->aCol[2].
1578
** * Right text accumulates in p->aCol[3].
1579
**
1580
** eState:
1581
** 0 In common block
1582
** 1 Have <del> on the left
1583
** 2 Have <ins> on the right
1584
** 3 Have <del> on left and <ins> on the right
1585
*/
1586
static void dfsplitChangeState(DiffBuilder *p, int newState){
1587
if( p->eState == newState ) return;
1588
if( (p->eState&1)==0 && (newState & 1)!=0 ){
1589
blob_append(p->pOut, "<del>", 5);
1590
blob_append(&p->aCol[0], "<del>", 5);
1591
p->eState |= 1;
1592
}else if( (p->eState&1)!=0 && (newState & 1)==0 ){
1593
blob_append(p->pOut, "</del>", 6);
1594
blob_append(&p->aCol[0], "</del>", 6);
1595
p->eState &= ~1;
1596
}
1597
if( (p->eState&2)==0 && (newState & 2)!=0 ){
1598
blob_append(&p->aCol[2], "<ins>", 5);
1599
blob_append(&p->aCol[3], "<ins>", 5);
1600
p->eState |= 2;
1601
}else if( (p->eState&2)!=0 && (newState & 2)==0 ){
1602
blob_append(&p->aCol[2], "</ins>", 6);
1603
blob_append(&p->aCol[3], "</ins>", 6);
1604
p->eState &= ~2;
1605
}
1606
}
1607
static void dfsplitFinishRow(DiffBuilder *p){
1608
if( blob_size(&p->aCol[0])==0 ) return;
1609
dfsplitChangeState(p, 0);
1610
blob_append(p->pOut, "</pre></td><td class=\"difftxt difftxtl\"><pre>\n",-1);
1611
blob_append_xfer(p->pOut, &p->aCol[0]);
1612
blob_append(p->pOut, "</pre></td><td class=\"diffsep\"><pre>\n", -1);
1613
blob_append_xfer(p->pOut, &p->aCol[1]);
1614
blob_append(p->pOut, "</pre></td><td class=\"diffln difflnr\"><pre>\n",-1);
1615
blob_append_xfer(p->pOut, &p->aCol[2]);
1616
blob_append(p->pOut, "</pre></td><td class=\"difftxt difftxtr\"><pre>\n",-1);
1617
blob_append_xfer(p->pOut, &p->aCol[3]);
1618
blob_append(p->pOut, "</pre></td></tr>\n", -1);
1619
}
1620
static void dfsplitStartRow(DiffBuilder *p){
1621
if( blob_size(&p->aCol[0])>0 ) return;
1622
blob_appendf(p->pOut,"<tr id=\"chunk%d\" class=\"diffchunk\">"
1623
"<td class=\"diffln difflnl\"><pre>\n", ++nChunk);
1624
p->eState = 0;
1625
}
1626
static void dfsplitSkip(DiffBuilder *p, unsigned int n, int isFinal){
1627
dfsplitFinishRow(p);
1628
if( p->pCfg && p->pCfg->zLeftHash ){
1629
blob_appendf(p->pOut,
1630
"<tr class=\"diffskip\" data-startln=\"%d\" data-endln=\"%d\""
1631
" id=\"skip%xh%xi%x\">\n",
1632
p->lnLeft+1, p->lnLeft+n,
1633
nChunk,p->lnLeft,n);
1634
}else{
1635
blob_append(p->pOut, "<tr>", 4);
1636
}
1637
blob_append(p->pOut,
1638
"<td class=\"diffln difflnl difflne\">&#xfe19;</td>"
1639
"<td></td><td></td>"
1640
"<td class=\"diffln difflnr difflne\">&#xfe19;</td>"
1641
"<td/td></tr>\n", -1);
1642
p->lnLeft += n;
1643
p->lnRight += n;
1644
}
1645
static void dfsplitCommon(DiffBuilder *p, const DLine *pLine){
1646
dfsplitStartRow(p);
1647
dfsplitChangeState(p, 0);
1648
p->lnLeft++;
1649
p->lnRight++;
1650
blob_appendf(p->pOut,"%d\n", p->lnLeft);
1651
htmlize_to_blob(&p->aCol[0], pLine->z, (int)pLine->n);
1652
blob_append_char(&p->aCol[0], '\n');
1653
blob_append_char(&p->aCol[1], '\n');
1654
blob_appendf(&p->aCol[2],"%d\n",p->lnRight);
1655
htmlize_to_blob(&p->aCol[3], pLine->z, (int)pLine->n);
1656
blob_append_char(&p->aCol[3], '\n');
1657
}
1658
static void dfsplitInsert(DiffBuilder *p, const DLine *pLine){
1659
dfsplitStartRow(p);
1660
dfsplitChangeState(p, 2);
1661
p->lnRight++;
1662
blob_append_char(p->pOut, '\n');
1663
blob_append_char(&p->aCol[0], '\n');
1664
blob_append(&p->aCol[1], "&gt;\n", -1);
1665
blob_appendf(&p->aCol[2],"%d\n", p->lnRight);
1666
blob_append(&p->aCol[3], "<ins>", 5);
1667
htmlize_to_blob(&p->aCol[3], pLine->z, (int)pLine->n);
1668
blob_append(&p->aCol[3], "</ins>\n", 7);
1669
}
1670
static void dfsplitDelete(DiffBuilder *p, const DLine *pLine){
1671
dfsplitStartRow(p);
1672
dfsplitChangeState(p, 1);
1673
p->lnLeft++;
1674
blob_appendf(p->pOut,"%d\n", p->lnLeft);
1675
blob_append(&p->aCol[0], "<del>", 5);
1676
htmlize_to_blob(&p->aCol[0], pLine->z, (int)pLine->n);
1677
blob_append(&p->aCol[0], "</del>\n", 7);
1678
blob_append(&p->aCol[1], "&lt;\n", -1);
1679
blob_append_char(&p->aCol[2],'\n');
1680
blob_append_char(&p->aCol[3],'\n');
1681
}
1682
static void dfsplitReplace(DiffBuilder *p, const DLine *pX, const DLine *pY){
1683
dfsplitStartRow(p);
1684
dfsplitChangeState(p, 3);
1685
p->lnLeft++;
1686
p->lnRight++;
1687
blob_appendf(p->pOut,"%d\n", p->lnLeft);
1688
htmlize_to_blob(&p->aCol[0], pX->z, pX->n);
1689
blob_append_char(&p->aCol[0], '\n');
1690
1691
blob_append(&p->aCol[1], "|\n", 2);
1692
1693
blob_appendf(&p->aCol[2],"%d\n", p->lnRight);
1694
1695
htmlize_to_blob(&p->aCol[3], pY->z, pY->n);
1696
blob_append_char(&p->aCol[3], '\n');
1697
}
1698
static void dfsplitEdit(DiffBuilder *p, const DLine *pX, const DLine *pY){
1699
int i;
1700
int x;
1701
LineChange chng;
1702
oneLineChange(pX, pY, &chng);
1703
dfsplitStartRow(p);
1704
dfsplitChangeState(p, 3);
1705
p->lnLeft++;
1706
p->lnRight++;
1707
blob_appendf(p->pOut,"%d\n", p->lnLeft);
1708
for(i=x=0; i<chng.n; i++){
1709
int ofst = chng.a[i].iStart1;
1710
int len = chng.a[i].iLen1;
1711
if( len ){
1712
htmlize_to_blob(&p->aCol[0], pX->z+x, ofst - x);
1713
x = ofst;
1714
if( chng.a[i].iLen2 ){
1715
blob_append(&p->aCol[0], "<del class='edit'>", -1);
1716
}else{
1717
blob_append(&p->aCol[0], "<del>", 5);
1718
}
1719
htmlize_to_blob(&p->aCol[0], pX->z+x, len);
1720
x += len;
1721
blob_append(&p->aCol[0], "</del>", 6);
1722
}
1723
}
1724
htmlize_to_blob(&p->aCol[0], pX->z+x, pX->n - x);
1725
blob_append_char(&p->aCol[0], '\n');
1726
1727
blob_append(&p->aCol[1], "|\n", 2);
1728
1729
blob_appendf(&p->aCol[2],"%d\n", p->lnRight);
1730
for(i=x=0; i<chng.n; i++){
1731
int ofst = chng.a[i].iStart2;
1732
int len = chng.a[i].iLen2;
1733
if( len ){
1734
htmlize_to_blob(&p->aCol[3], pY->z+x, ofst - x);
1735
x = ofst;
1736
if( chng.a[i].iLen1 ){
1737
blob_append(&p->aCol[3], "<ins class='edit'>", -1);
1738
}else{
1739
blob_append(&p->aCol[3], "<ins>", 5);
1740
}
1741
htmlize_to_blob(&p->aCol[3], pY->z+x, len);
1742
x += len;
1743
blob_append(&p->aCol[3], "</ins>", 6);
1744
}
1745
}
1746
htmlize_to_blob(&p->aCol[3], pY->z+x, pY->n - x);
1747
blob_append_char(&p->aCol[3], '\n');
1748
}
1749
static void dfsplitEnd(DiffBuilder *p){
1750
dfsplitFinishRow(p);
1751
blob_append(p->pOut, "</table>\n",-1);
1752
fossil_free(p);
1753
}
1754
static DiffBuilder *dfsplitNew(Blob *pOut, DiffConfig *pCfg){
1755
DiffBuilder *p = fossil_malloc(sizeof(*p));
1756
p->xSkip = dfsplitSkip;
1757
p->xCommon = dfsplitCommon;
1758
p->xInsert = dfsplitInsert;
1759
p->xDelete = dfsplitDelete;
1760
p->xReplace = dfsplitReplace;
1761
p->xEdit = dfsplitEdit;
1762
p->xEnd = dfsplitEnd;
1763
p->lnLeft = p->lnRight = 0;
1764
p->eState = 0;
1765
p->pOut = pOut;
1766
if( pCfg->zLeftHash ){
1767
blob_appendf(pOut,
1768
"<table class=\"diff splitdiff\" data-lefthash=\"%s\">\n",
1769
pCfg->zLeftHash);
1770
}else{
1771
blob_append(pOut, "<table class=\"diff splitdiff\">\n", -1);
1772
}
1773
blob_init(&p->aCol[0], 0, 0);
1774
blob_init(&p->aCol[1], 0, 0);
1775
blob_init(&p->aCol[2], 0, 0);
1776
blob_init(&p->aCol[3], 0, 0);
1777
blob_init(&p->aCol[4], 0, 0);
1778
p->pCfg = pCfg;
1779
return p;
1780
}
1781
1782
/************************* DiffBuilderSbs ******************************/
1783
/* This formatter creates a side-by-side diff in text.
1784
*/
1785
static void dfsbsSkip(DiffBuilder *p, unsigned int n, int isFinal){
1786
if( (p->lnLeft || p->lnRight) && !isFinal ){
1787
blob_appendf(p->pOut, "%.*c\n", p->width*2 + 16, '.');
1788
}
1789
p->lnLeft += n;
1790
p->lnRight += n;
1791
}
1792
1793
/*
1794
** Append at least iMin characters (not bytes) and at most iMax characters
1795
** from pX onto the into of p.
1796
**
1797
** This comment contains multibyte unicode characters (ü, Æ, ð) in order
1798
** to test the ability of the diff code to handle such characters.
1799
*/
1800
static void sbs_append_chars(Blob *p, int iMin, int iMax, const DLine *pX){
1801
int i;
1802
const char *z = pX->z;
1803
for(i=0; i<iMax && i<pX->n; i++){
1804
char c = z[i];
1805
blob_append_char(p, c);
1806
if( (c&0xc0)==0x80 ){ iMin++; iMax++; }
1807
}
1808
while( i<iMin ){
1809
blob_append_char(p, ' ');
1810
i++;
1811
}
1812
}
1813
1814
static void dfsbsCommon(DiffBuilder *p, const DLine *pLine){
1815
p->lnLeft++;
1816
p->lnRight++;
1817
blob_appendf(p->pOut,"%6u ", p->lnLeft);
1818
sbs_append_chars(p->pOut, p->width, p->width, pLine);
1819
blob_appendf(p->pOut," %6u ", p->lnRight);
1820
sbs_append_chars(p->pOut, 0, p->width, pLine);
1821
blob_append_char(p->pOut, '\n');
1822
}
1823
static void dfsbsInsert(DiffBuilder *p, const DLine *pLine){
1824
p->lnRight++;
1825
blob_appendf(p->pOut,"%6s %*s > %6u ",
1826
"", p->width, "", p->lnRight);
1827
sbs_append_chars(p->pOut, 0, p->width, pLine);
1828
blob_append_char(p->pOut, '\n');
1829
}
1830
static void dfsbsDelete(DiffBuilder *p, const DLine *pLine){
1831
p->lnLeft++;
1832
blob_appendf(p->pOut,"%6u ", p->lnLeft);
1833
sbs_append_chars(p->pOut, p->width, p->width, pLine);
1834
blob_append(p->pOut," <\n", 3);
1835
}
1836
static void dfsbsEdit(DiffBuilder *p, const DLine *pX, const DLine *pY){
1837
p->lnLeft++;
1838
p->lnRight++;
1839
blob_appendf(p->pOut,"%6u ", p->lnLeft);
1840
sbs_append_chars(p->pOut, p->width, p->width, pX);
1841
blob_appendf(p->pOut, " | %6u ", p->lnRight);
1842
sbs_append_chars(p->pOut, 0, p->width, pY);
1843
blob_append_char(p->pOut, '\n');
1844
}
1845
static void dfsbsEnd(DiffBuilder *p){
1846
fossil_free(p);
1847
}
1848
static DiffBuilder *dfsbsNew(Blob *pOut, DiffConfig *pCfg){
1849
DiffBuilder *p = fossil_malloc(sizeof(*p));
1850
p->xSkip = dfsbsSkip;
1851
p->xCommon = dfsbsCommon;
1852
p->xInsert = dfsbsInsert;
1853
p->xDelete = dfsbsDelete;
1854
p->xReplace = dfsbsEdit;
1855
p->xEdit = dfsbsEdit;
1856
p->xEnd = dfsbsEnd;
1857
p->lnLeft = p->lnRight = 0;
1858
p->width = diff_width(pCfg);
1859
p->pOut = pOut;
1860
return p;
1861
}
1862
/****************************************************************************/
1863
/*
1864
** Return the number between 0 and 100 that is smaller the closer pA and
1865
** pB match. Return 0 for a perfect match. Return 100 if pA and pB are
1866
** completely different.
1867
**
1868
** The current algorithm is as follows:
1869
**
1870
** (1) Remove leading and trailing whitespace.
1871
** (2) Truncate both strings to at most 250 characters
1872
** (3) If the two strings have a common prefix, measure that prefix
1873
** (4) Find the length of the longest common subsequence that is
1874
** at least 150% longer than the common prefix.
1875
** (5) Longer common subsequences yield lower scores.
1876
*/
1877
static int match_dline(DLine *pA, DLine *pB){
1878
const char *zA; /* Left string */
1879
const char *zB; /* right string */
1880
int nA; /* Bytes in zA[] */
1881
int nB; /* Bytes in zB[] */
1882
int nMin;
1883
int nPrefix;
1884
int avg; /* Average length of A and B */
1885
int i, j, k; /* Loop counters */
1886
int best = 0; /* Longest match found so far */
1887
int score; /* Final score. 0..100 */
1888
unsigned char c; /* Character being examined */
1889
unsigned char aFirst[256]; /* aFirst[X] = index in zB[] of first char X */
1890
unsigned char aNext[252]; /* aNext[i] = index in zB[] of next zB[i] char */
1891
1892
zA = pA->z;
1893
if( pA->nw==0 && pA->n ){
1894
for(i=0; i<pA->n && diff_isspace(zA[i]); i++){}
1895
pA->indent = i;
1896
for(j=pA->n-1; j>i && diff_isspace(zA[j]); j--){}
1897
pA->nw = j - i + 1;
1898
}
1899
zA += pA->indent;
1900
nA = pA->nw;
1901
1902
zB = pB->z;
1903
if( pB->nw==0 && pB->n ){
1904
for(i=0; i<pB->n && diff_isspace(zB[i]); i++){}
1905
pB->indent = i;
1906
for(j=pB->n-1; j>i && diff_isspace(zB[j]); j--){}
1907
pB->nw = j - i + 1;
1908
}
1909
zB += pB->indent;
1910
nB = pB->nw;
1911
1912
if( nA>250 ) nA = 250;
1913
if( nB>250 ) nB = 250;
1914
avg = (nA+nB)/2;
1915
if( avg==0 ) return 0;
1916
nMin = nA;
1917
if( nB<nMin ) nMin = nB;
1918
if( nMin==0 ) return 68;
1919
for(nPrefix=0; nPrefix<nMin && zA[nPrefix]==zB[nPrefix]; nPrefix++){}
1920
best = 0;
1921
if( nPrefix>5 && nPrefix>nMin/2 ){
1922
best = nPrefix*3/2;
1923
if( best>=avg - 2 ) best = avg - 2;
1924
}
1925
if( nA==nB && memcmp(zA, zB, nA)==0 ) return 0;
1926
memset(aFirst, 0xff, sizeof(aFirst));
1927
zA--; zB--; /* Make both zA[] and zB[] 1-indexed */
1928
for(i=nB; i>0; i--){
1929
c = (unsigned char)zB[i];
1930
aNext[i] = aFirst[c];
1931
aFirst[c] = i;
1932
}
1933
for(i=1; i<=nA-best; i++){
1934
c = (unsigned char)zA[i];
1935
for(j=aFirst[c]; j<nB-best && memcmp(&zA[i],&zB[j],best)==0; j = aNext[j]){
1936
int limit = minInt(nA-i, nB-j);
1937
for(k=best; k<=limit && zA[k+i]==zB[k+j]; k++){}
1938
if( k>best ) best = k;
1939
}
1940
}
1941
score = 5 + ((best>=avg) ? 0 : (avg - best)*95/avg);
1942
1943
#if 0
1944
fprintf(stderr, "A: [%.*s]\nB: [%.*s]\nbest=%d avg=%d score=%d\n",
1945
nA, zA+1, nB, zB+1, best, avg, score);
1946
#endif
1947
1948
/* Return the result */
1949
return score;
1950
}
1951
1952
/*
1953
** COMMAND: test-line-match
1954
** Usage: %fossil test-line-match STRING1 STRING2
1955
**
1956
** Return a score from 0 to 100 that is how similar STRING1 is to
1957
** STRING2. Smaller numbers mean more similar. 0 is an exact match.
1958
**
1959
** This command is used to test to match_dline() function in the
1960
** internal Fossil diff logic.
1961
*/
1962
void test_dline_match(void){
1963
DLine a, b;
1964
int x;
1965
if( g.argc!=4 ) usage("STRING1 STRING2");
1966
a.z = g.argv[2];
1967
a.n = (int)strlen(a.z);
1968
b.z = g.argv[3];
1969
b.n = (int)strlen(b.z);
1970
x = match_dline(&a, &b);
1971
fossil_print("%d\n", x);
1972
}
1973
1974
/* Forward declarations for recursion */
1975
static unsigned char *diffBlockAlignment(
1976
DLine *aLeft, int nLeft, /* Text on the left */
1977
DLine *aRight, int nRight, /* Text on the right */
1978
DiffConfig *pCfg, /* Configuration options */
1979
int *pNResult /* OUTPUT: Bytes of result */
1980
);
1981
static void longestCommonSequence(
1982
DContext *p, /* Two files being compared */
1983
int iS1, int iE1, /* Range of lines in p->aFrom[] */
1984
int iS2, int iE2, /* Range of lines in p->aTo[] */
1985
int *piSX, int *piEX, /* Write p->aFrom[] common segment here */
1986
int *piSY, int *piEY /* Write p->aTo[] common segment here */
1987
);
1988
1989
/*
1990
** Make a copy of a list of nLine DLine objects from one array to
1991
** another. Hash the new array to ignore whitespace.
1992
*/
1993
static void diffDLineXfer(
1994
DLine *aTo,
1995
const DLine *aFrom,
1996
int nLine
1997
){
1998
int i, j, k;
1999
u64 h, h2;
2000
for(i=0; i<nLine; i++) aTo[i].iHash = 0;
2001
for(i=0; i<nLine; i++){
2002
const char *z = aFrom[i].z;
2003
int n = aFrom[i].n;
2004
for(j=0; j<n && diff_isspace(z[j]); j++){}
2005
aTo[i].z = &z[j];
2006
for(k=aFrom[i].n; k>j && diff_isspace(z[k-1]); k--){}
2007
aTo[i].n = n = k-j;
2008
aTo[i].indent = 0;
2009
aTo[i].nw = 0;
2010
for(h=0; j<k; j++){
2011
char c = z[j];
2012
if( !diff_isspace(c) ){
2013
h = (h^c)*9000000000000000041LL;
2014
}
2015
}
2016
aTo[i].h = h = ((h%281474976710597LL)<<LENGTH_MASK_SZ) | n;
2017
h2 = h % nLine;
2018
aTo[i].iNext = aTo[h2].iHash;
2019
aTo[h2].iHash = i+1;
2020
}
2021
}
2022
2023
2024
/*
2025
** For a difficult diff-block alignment that was originally for
2026
** the default consider-all-whitespace algorithm, try to find the
2027
** longest common subsequence between the two blocks that involves
2028
** only whitespace changes.
2029
*/
2030
static unsigned char *diffBlockAlignmentIgnoreSpace(
2031
DLine *aLeft, int nLeft, /* Text on the left */
2032
DLine *aRight, int nRight, /* Text on the right */
2033
DiffConfig *pCfg, /* Configuration options */
2034
int *pNResult /* OUTPUT: Bytes of result */
2035
){
2036
DContext dc;
2037
int iSX, iEX; /* Start and end of LCS on the left */
2038
int iSY, iEY; /* Start and end of the LCS on the right */
2039
unsigned char *a1, *a2;
2040
int n1, n2, nLCS;
2041
2042
dc.aEdit = 0;
2043
dc.nEdit = 0;
2044
dc.nEditAlloc = 0;
2045
dc.nFrom = nLeft;
2046
dc.nTo = nRight;
2047
dc.xDiffer = compare_dline_ignore_allws;
2048
dc.aFrom = fossil_malloc( sizeof(DLine)*(nLeft+nRight) );
2049
dc.aTo = &dc.aFrom[dc.nFrom];
2050
diffDLineXfer(dc.aFrom, aLeft, nLeft);
2051
diffDLineXfer(dc.aTo, aRight, nRight);
2052
longestCommonSequence(&dc,0,nLeft,0,nRight,&iSX,&iEX,&iSY,&iEY);
2053
fossil_free(dc.aFrom);
2054
nLCS = iEX - iSX;
2055
if( nLCS<5 ) return 0; /* No good LCS was found */
2056
2057
if( pCfg->diffFlags & DIFF_DEBUG ){
2058
fossil_print(" LCS size=%d\n"
2059
" [%.*s]\n"
2060
" [%.*s]\n",
2061
nLCS, aLeft[iSX].n, aLeft[iSX].z,
2062
aLeft[iEX-1].n, aLeft[iEX-1].z);
2063
}
2064
2065
a1 = diffBlockAlignment(aLeft,iSX,aRight,iSY,pCfg,&n1);
2066
a2 = diffBlockAlignment(aLeft+iEX, nLeft-iEX,
2067
aRight+iEY, nRight-iEY,
2068
pCfg, &n2);
2069
a1 = fossil_realloc(a1, n1+nLCS+n2);
2070
memcpy(a1+n1+nLCS,a2,n2);
2071
memset(a1+n1,3,nLCS);
2072
fossil_free(a2);
2073
*pNResult = n1+n2+nLCS;
2074
return a1;
2075
}
2076
2077
2078
/*
2079
** This is a helper route for diffBlockAlignment(). In this case,
2080
** a very large block is encountered that might be too expensive to
2081
** use the O(N*N) Wagner edit distance algorithm. So instead, this
2082
** block implements a less-precise but faster O(N*logN) divide-and-conquer
2083
** approach.
2084
*/
2085
static unsigned char *diffBlockAlignmentDivideAndConquer(
2086
DLine *aLeft, int nLeft, /* Text on the left */
2087
DLine *aRight, int nRight, /* Text on the right */
2088
DiffConfig *pCfg, /* Configuration options */
2089
int *pNResult /* OUTPUT: Bytes of result */
2090
){
2091
DLine *aSmall; /* The smaller of aLeft and aRight */
2092
DLine *aBig; /* The larger of aLeft and aRight */
2093
int nSmall, nBig; /* Size of aSmall and aBig. nSmall<=nBig */
2094
int iDivSmall, iDivBig; /* Divider point for aSmall and aBig */
2095
int iDivLeft, iDivRight; /* Divider point for aLeft and aRight */
2096
unsigned char *a1, *a2; /* Results of the alignments on two halves */
2097
int n1, n2; /* Number of entries in a1 and a2 */
2098
int score, bestScore; /* Score and best score seen so far */
2099
int i; /* Loop counter */
2100
2101
if( nLeft>nRight ){
2102
aSmall = aRight;
2103
nSmall = nRight;
2104
aBig = aLeft;
2105
nBig = nLeft;
2106
}else{
2107
aSmall = aLeft;
2108
nSmall = nLeft;
2109
aBig = aRight;
2110
nBig = nRight;
2111
}
2112
iDivBig = nBig/2;
2113
iDivSmall = nSmall/2;
2114
2115
if( pCfg->diffFlags & DIFF_DEBUG ){
2116
fossil_print(" Divide at [%.*s]\n",
2117
aBig[iDivBig].n, aBig[iDivBig].z);
2118
}
2119
2120
bestScore = 10000;
2121
for(i=0; i<nSmall; i++){
2122
score = match_dline(aBig+iDivBig, aSmall+i) + abs(i-nSmall/2)*2;
2123
if( score<bestScore ){
2124
bestScore = score;
2125
iDivSmall = i;
2126
}
2127
}
2128
if( aSmall==aRight ){
2129
iDivRight = iDivSmall;
2130
iDivLeft = iDivBig;
2131
}else{
2132
iDivRight = iDivBig;
2133
iDivLeft = iDivSmall;
2134
}
2135
a1 = diffBlockAlignment(aLeft,iDivLeft,aRight,iDivRight,pCfg,&n1);
2136
a2 = diffBlockAlignment(aLeft+iDivLeft, nLeft-iDivLeft,
2137
aRight+iDivRight, nRight-iDivRight,
2138
pCfg, &n2);
2139
a1 = fossil_realloc(a1, n1+n2 );
2140
memcpy(a1+n1,a2,n2);
2141
fossil_free(a2);
2142
*pNResult = n1+n2;
2143
return a1;
2144
}
2145
2146
2147
/*
2148
** The threshold at which diffBlockAlignment transitions from the
2149
** O(N*N) Wagner minimum-edit-distance algorithm to a less process
2150
** O(NlogN) divide-and-conquer approach.
2151
*/
2152
#define DIFF_ALIGN_MX 1225
2153
2154
/*
2155
** There is a change block in which nLeft lines of text on the left are
2156
** converted into nRight lines of text on the right. This routine computes
2157
** how the lines on the left line up with the lines on the right.
2158
**
2159
** The return value is a buffer of unsigned characters, obtained from
2160
** fossil_malloc(). (The caller needs to free the return value using
2161
** fossil_free().) Entries in the returned array have values as follows:
2162
**
2163
** 1. Delete the next line of pLeft.
2164
** 2. Insert the next line of pRight.
2165
** 3. The next line of pLeft changes into the next line of pRight.
2166
** 4. Delete one line from pLeft and add one line to pRight.
2167
**
2168
** The length of the returned array will be at most nLeft+nRight bytes.
2169
** If the first bytes is 4, that means we could not compute reasonable
2170
** alignment between the two blocks.
2171
**
2172
** Algorithm: Wagner's minimum edit-distance algorithm, modified by
2173
** adding a cost to each match based on how well the two rows match
2174
** each other. Insertion and deletion costs are 50. Match costs
2175
** are between 0 and 100 where 0 is a perfect match 100 is a complete
2176
** mismatch.
2177
*/
2178
static unsigned char *diffBlockAlignment(
2179
DLine *aLeft, int nLeft, /* Text on the left */
2180
DLine *aRight, int nRight, /* Text on the right */
2181
DiffConfig *pCfg, /* Configuration options */
2182
int *pNResult /* OUTPUT: Bytes of result */
2183
){
2184
int i, j, k; /* Loop counters */
2185
int *a; /* One row of the Wagner matrix */
2186
int *pToFree; /* Space that needs to be freed */
2187
unsigned char *aM; /* Wagner result matrix */
2188
int aBuf[100]; /* Stack space for a[] if nRight not to big */
2189
2190
if( nLeft==0 ){
2191
aM = fossil_malloc( nRight + 2 );
2192
memset(aM, 2, nRight);
2193
*pNResult = nRight;
2194
return aM;
2195
}
2196
if( nRight==0 ){
2197
aM = fossil_malloc( nLeft + 2 );
2198
memset(aM, 1, nLeft);
2199
*pNResult = nLeft;
2200
return aM;
2201
}
2202
2203
if( pCfg->diffFlags & DIFF_DEBUG ){
2204
fossil_print("BlockAlignment:\n [%.*s] + %d\n [%.*s] + %d\n",
2205
aLeft[0].n, aLeft[0].z, nLeft,
2206
aRight[0].n, aRight[0].z, nRight);
2207
}
2208
2209
/* For large alignments, try to use alternative algorithms that are
2210
** faster than the O(N*N) Wagner edit distance.
2211
*/
2212
if( (i64)nLeft*(i64)nRight>DIFF_ALIGN_MX
2213
&& (pCfg->diffFlags & DIFF_SLOW_SBS)==0
2214
){
2215
if( (pCfg->diffFlags & DIFF_IGNORE_ALLWS)==0 ){
2216
unsigned char *aRes;
2217
aRes = diffBlockAlignmentIgnoreSpace(
2218
aLeft, nLeft,aRight, nRight,pCfg,pNResult);
2219
if( aRes ) return aRes;
2220
}
2221
return diffBlockAlignmentDivideAndConquer(
2222
aLeft, nLeft,aRight, nRight,pCfg,pNResult);
2223
}
2224
2225
/* If we reach this point, we will be doing an O(N*N) Wagner minimum
2226
** edit distance to compute the alignment.
2227
*/
2228
if( nRight < count(aBuf)-1 ){
2229
pToFree = 0;
2230
a = aBuf;
2231
}else{
2232
a = pToFree = fossil_malloc( sizeof(a[0])*(nRight+1) );
2233
}
2234
aM = fossil_malloc( (nLeft+1)*(nRight+1) );
2235
2236
2237
/* Compute the best alignment */
2238
for(i=0; i<=nRight; i++){
2239
aM[i] = 2;
2240
a[i] = i*50;
2241
}
2242
aM[0] = 0;
2243
for(j=1; j<=nLeft; j++){
2244
int p = a[0];
2245
a[0] = p+50;
2246
aM[j*(nRight+1)] = 1;
2247
for(i=1; i<=nRight; i++){
2248
int m = a[i-1]+50;
2249
int d = 2;
2250
if( m>a[i]+50 ){
2251
m = a[i]+50;
2252
d = 1;
2253
}
2254
if( m>p ){
2255
int score = match_dline(&aLeft[j-1], &aRight[i-1]);
2256
if( (score<=90 || (i<j+1 && i>j-1)) && m>p+score ){
2257
m = p+score;
2258
d = 3 | score*4;
2259
}
2260
}
2261
p = a[i];
2262
a[i] = m;
2263
aM[j*(nRight+1)+i] = d;
2264
}
2265
}
2266
2267
/* Compute the lowest-cost path back through the matrix */
2268
i = nRight;
2269
j = nLeft;
2270
k = (nRight+1)*(nLeft+1)-1;
2271
while( i+j>0 ){
2272
unsigned char c = aM[k];
2273
if( c>=3 ){
2274
assert( i>0 && j>0 );
2275
i--;
2276
j--;
2277
aM[k] = 3;
2278
}else if( c==2 ){
2279
assert( i>0 );
2280
i--;
2281
}else{
2282
assert( j>0 );
2283
j--;
2284
}
2285
k--;
2286
aM[k] = aM[j*(nRight+1)+i];
2287
}
2288
k++;
2289
i = (nRight+1)*(nLeft+1) - k;
2290
memmove(aM, &aM[k], i);
2291
*pNResult = i;
2292
2293
/* Return the result */
2294
fossil_free(pToFree);
2295
return aM;
2296
}
2297
2298
/*
2299
** R[] is an array of six integer, two COPY/DELETE/INSERT triples for a
2300
** pair of adjacent differences. Return true if the gap between these
2301
** two differences is so small that they should be rendered as a single
2302
** edit.
2303
*/
2304
static int smallGap(const int *R, int ma, int mb){
2305
int m = R[3];
2306
ma += R[4] + m;
2307
mb += R[5] + m;
2308
if( ma*mb>DIFF_ALIGN_MX ) return 0;
2309
return m<=2 || m<=(R[1]+R[2]+R[4]+R[5])/8;
2310
}
2311
2312
/*
2313
** Format a diff using a DiffBuilder object
2314
*/
2315
static void formatDiff(
2316
DContext *p, /* The computed diff */
2317
DiffConfig *pCfg, /* Configuration options */
2318
DiffBuilder *pBuilder /* The formatter object */
2319
){
2320
DLine *A; /* Left side of the diff */
2321
DLine *B; /* Right side of the diff */
2322
unsigned int a = 0; /* Index of next line in A[] */
2323
unsigned int b = 0; /* Index of next line in B[] */
2324
const int *R; /* Array of COPY/DELETE/INSERT triples */
2325
unsigned int r; /* Index into R[] */
2326
unsigned int nr; /* Number of COPY/DELETE/INSERT triples to process */
2327
unsigned int mxr; /* Maximum value for r */
2328
unsigned int i, j; /* Loop counters */
2329
unsigned int m, ma, mb;/* Number of lines to output */
2330
signed int skip = 0; /* Number of lines to skip */
2331
unsigned int nContext; /* Lines of context above and below each change */
2332
2333
nContext = diff_context_lines(pCfg);
2334
A = p->aFrom;
2335
B = p->aTo;
2336
R = p->aEdit;
2337
mxr = p->nEdit;
2338
while( mxr>2 && R[mxr-1]==0 && R[mxr-2]==0 ){ mxr -= 3; }
2339
2340
for(r=0; r<mxr; r += 3*nr){
2341
/* Figure out how many triples to show in a single block */
2342
for(nr=1; 3*nr<mxr && R[r+nr*3]>0 && R[r+nr*3]<(int)nContext*2; nr++){}
2343
2344
/* If there is a regex, skip this block (generate no diff output)
2345
** if the regex matches or does not match both insert and delete.
2346
** Only display the block if one side matches but the other side does
2347
** not.
2348
*/
2349
if( pCfg->pRe ){
2350
int hideBlock = 1;
2351
int xa = a, xb = b;
2352
for(i=0; hideBlock && i<nr; i++){
2353
int c1, c2;
2354
xa += R[r+i*3];
2355
xb += R[r+i*3];
2356
c1 = re_dline_match(pCfg->pRe, &A[xa], R[r+i*3+1]);
2357
c2 = re_dline_match(pCfg->pRe, &B[xb], R[r+i*3+2]);
2358
hideBlock = c1==c2;
2359
xa += R[r+i*3+1];
2360
xb += R[r+i*3+2];
2361
}
2362
if( hideBlock ){
2363
a = xa;
2364
b = xb;
2365
continue;
2366
}
2367
}
2368
2369
/* Figure out how many lines of A and B are to be displayed
2370
** for this change block.
2371
*/
2372
if( R[r]>(int)nContext ){
2373
skip = R[r] - nContext;
2374
}else{
2375
skip = 0;
2376
}
2377
/* Show the initial common area */
2378
a += skip;
2379
b += skip;
2380
m = R[r] - skip;
2381
if( r ) skip -= nContext;
2382
if( skip>0 ){
2383
if( skip<(int)nContext ){
2384
/* If the amount to skip is less that the context band, then
2385
** go ahead and show the skip band as it is not worth eliding */
2386
for(j=0; (int)j<skip; j++){
2387
pBuilder->xCommon(pBuilder, &A[a+j-skip]);
2388
}
2389
}else{
2390
pBuilder->xSkip(pBuilder, skip, 0);
2391
}
2392
}
2393
for(j=0; j<m; j++){
2394
pBuilder->xCommon(pBuilder, &A[a+j]);
2395
}
2396
a += m;
2397
b += m;
2398
2399
/* Show the differences */
2400
for(i=0; i<nr; i++){
2401
int nAlign;
2402
unsigned char *alignment;
2403
ma = R[r+i*3+1]; /* Lines on left but not on right */
2404
mb = R[r+i*3+2]; /* Lines on right but not on left */
2405
2406
/* Try merging the current block with subsequent blocks, if the
2407
** subsequent blocks are nearby and there result isn't too big.
2408
*/
2409
while( i<nr-1 && smallGap(&R[r+i*3],ma,mb) ){
2410
i++;
2411
m = R[r+i*3];
2412
ma += R[r+i*3+1] + m;
2413
mb += R[r+i*3+2] + m;
2414
}
2415
2416
/* Try to find an alignment for the lines within this one block */
2417
alignment = diffBlockAlignment(&A[a], ma, &B[b], mb, pCfg, &nAlign);
2418
2419
for(j=0; ma+mb>0; j++){
2420
assert( (int)j<nAlign );
2421
switch( alignment[j] ){
2422
case 1: {
2423
/* Delete one line from the left */
2424
pBuilder->xDelete(pBuilder, &A[a]);
2425
ma--;
2426
a++;
2427
break;
2428
}
2429
case 2: {
2430
/* Insert one line on the right */
2431
pBuilder->xInsert(pBuilder, &B[b]);
2432
assert( mb>0 );
2433
mb--;
2434
b++;
2435
break;
2436
}
2437
case 3: {
2438
/* The left line is changed into the right line */
2439
if( p->xDiffer(&A[a], &B[b])==0 ){
2440
pBuilder->xCommon(pBuilder, &A[a]);
2441
}else{
2442
pBuilder->xEdit(pBuilder, &A[a], &B[b]);
2443
}
2444
assert( ma>0 && mb>0 );
2445
ma--;
2446
mb--;
2447
a++;
2448
b++;
2449
break;
2450
}
2451
case 4: {
2452
/* Delete from left then separately insert on the right */
2453
pBuilder->xReplace(pBuilder, &A[a], &B[b]);
2454
ma--;
2455
a++;
2456
mb--;
2457
b++;
2458
break;
2459
}
2460
}
2461
}
2462
assert( nAlign==(int)j );
2463
fossil_free(alignment);
2464
if( i<nr-1 ){
2465
m = R[r+i*3+3];
2466
for(j=0; j<m; j++){
2467
pBuilder->xCommon(pBuilder, &A[a+j]);
2468
}
2469
b += m;
2470
a += m;
2471
}
2472
}
2473
2474
/* Show the final common area */
2475
assert( nr==i );
2476
m = R[r+nr*3];
2477
if( m>nContext ) m = nContext;
2478
for(j=0; j<m && j<nContext; j++){
2479
pBuilder->xCommon(pBuilder, &A[a+j]);
2480
}
2481
}
2482
if( R[r]>(int)nContext ){
2483
pBuilder->xSkip(pBuilder, R[r] - nContext, 1);
2484
}
2485
pBuilder->xEnd(pBuilder);
2486
}
2487
2488
2489
/*
2490
** Compute the optimal longest common subsequence (LCS) using an
2491
** exhaustive search. This version of the LCS is only used for
2492
** shorter input strings since runtime is O(N*N) where N is the
2493
** input string length.
2494
*/
2495
static void optimalLCS(
2496
DContext *p, /* Two files being compared */
2497
int iS1, int iE1, /* Range of lines in p->aFrom[] */
2498
int iS2, int iE2, /* Range of lines in p->aTo[] */
2499
int *piSX, int *piEX, /* Write p->aFrom[] common segment here */
2500
int *piSY, int *piEY /* Write p->aTo[] common segment here */
2501
){
2502
int mxLength = 0; /* Length of longest common subsequence */
2503
int i, j; /* Loop counters */
2504
int k; /* Length of a candidate subsequence */
2505
int iSXb = iS1; /* Best match so far */
2506
int iSYb = iS2; /* Best match so far */
2507
2508
for(i=iS1; i<iE1-mxLength; i++){
2509
for(j=iS2; j<iE2-mxLength; j++){
2510
if( p->xDiffer(&p->aFrom[i], &p->aTo[j]) ) continue;
2511
if( mxLength && p->xDiffer(&p->aFrom[i+mxLength], &p->aTo[j+mxLength]) ){
2512
continue;
2513
}
2514
k = 1;
2515
while( i+k<iE1 && j+k<iE2 && p->xDiffer(&p->aFrom[i+k],&p->aTo[j+k])==0 ){
2516
k++;
2517
}
2518
if( k>mxLength ){
2519
iSXb = i;
2520
iSYb = j;
2521
mxLength = k;
2522
}
2523
}
2524
}
2525
*piSX = iSXb;
2526
*piEX = iSXb + mxLength;
2527
*piSY = iSYb;
2528
*piEY = iSYb + mxLength;
2529
}
2530
2531
/*
2532
** Compare two blocks of text on lines iS1 through iE1-1 of the aFrom[]
2533
** file and lines iS2 through iE2-1 of the aTo[] file. Locate a sequence
2534
** of lines in these two blocks that are exactly the same. Return
2535
** the bounds of the matching sequence.
2536
**
2537
** If there are two or more possible answers of the same length, the
2538
** returned sequence should be the one closest to the center of the
2539
** input range.
2540
**
2541
** Ideally, the common sequence should be the longest possible common
2542
** sequence. However, an exact computation of LCS is O(N*N) which is
2543
** way too slow for larger files. So this routine uses an O(N)
2544
** heuristic approximation based on hashing that usually works about
2545
** as well. But if the O(N) algorithm doesn't get a good solution
2546
** and N is not too large, we fall back to an exact solution by
2547
** calling optimalLCS().
2548
*/
2549
static void longestCommonSequence(
2550
DContext *p, /* Two files being compared */
2551
int iS1, int iE1, /* Range of lines in p->aFrom[] */
2552
int iS2, int iE2, /* Range of lines in p->aTo[] */
2553
int *piSX, int *piEX, /* Write p->aFrom[] common segment here */
2554
int *piSY, int *piEY /* Write p->aTo[] common segment here */
2555
){
2556
int i, j, k; /* Loop counters */
2557
int n; /* Loop limit */
2558
DLine *pA, *pB; /* Pointers to lines */
2559
int iSX, iSY, iEX, iEY; /* Current match */
2560
int skew = 0; /* How lopsided is the match */
2561
int dist = 0; /* Distance of match from center */
2562
int mid; /* Center of the chng */
2563
int iSXb, iSYb, iEXb, iEYb; /* Best match so far */
2564
int iSXp, iSYp, iEXp, iEYp; /* Previous match */
2565
sqlite3_int64 bestScore; /* Best score so far */
2566
sqlite3_int64 score; /* Score for current candidate LCS */
2567
int span; /* combined width of the input sequences */
2568
int cutoff = 4; /* Max hash chain entries to follow */
2569
int nextCutoff = -1; /* Value of cutoff for next iteration */
2570
2571
span = (iE1 - iS1) + (iE2 - iS2);
2572
bestScore = -9223300000*(sqlite3_int64)1000000000;
2573
score = 0;
2574
iSXb = iSXp = iS1;
2575
iEXb = iEXp = iS1;
2576
iSYb = iSYp = iS2;
2577
iEYb = iEYp = iS2;
2578
mid = (iE1 + iS1)/2;
2579
do{
2580
nextCutoff = 0;
2581
for(i=iS1; i<iE1; i++){
2582
int limit = 0;
2583
j = p->aTo[p->aFrom[i].h % p->nTo].iHash;
2584
while( j>0
2585
&& (j-1<iS2 || j>=iE2 || p->xDiffer(&p->aFrom[i], &p->aTo[j-1]))
2586
){
2587
if( limit++ > cutoff ){
2588
j = 0;
2589
nextCutoff = cutoff*4;
2590
break;
2591
}
2592
j = p->aTo[j-1].iNext;
2593
}
2594
if( j==0 ) continue;
2595
assert( i>=iSXb && i>=iSXp );
2596
if( i<iEXb && j>=iSYb && j<iEYb ) continue;
2597
if( i<iEXp && j>=iSYp && j<iEYp ) continue;
2598
iSX = i;
2599
iSY = j-1;
2600
pA = &p->aFrom[iSX-1];
2601
pB = &p->aTo[iSY-1];
2602
n = minInt(iSX-iS1, iSY-iS2);
2603
for(k=0; k<n && p->xDiffer(pA,pB)==0; k++, pA--, pB--){}
2604
iSX -= k;
2605
iSY -= k;
2606
iEX = i+1;
2607
iEY = j;
2608
pA = &p->aFrom[iEX];
2609
pB = &p->aTo[iEY];
2610
n = minInt(iE1-iEX, iE2-iEY);
2611
for(k=0; k<n && p->xDiffer(pA,pB)==0; k++, pA++, pB++){}
2612
iEX += k;
2613
iEY += k;
2614
skew = (iSX-iS1) - (iSY-iS2);
2615
if( skew<0 ) skew = -skew;
2616
dist = (iSX+iEX)/2 - mid;
2617
if( dist<0 ) dist = -dist;
2618
score = (iEX - iSX)*(sqlite3_int64)span - (skew + dist);
2619
if( score>bestScore ){
2620
bestScore = score;
2621
iSXb = iSX;
2622
iSYb = iSY;
2623
iEXb = iEX;
2624
iEYb = iEY;
2625
}else if( iEX>iEXp ){
2626
iSXp = iSX;
2627
iSYp = iSY;
2628
iEXp = iEX;
2629
iEYp = iEY;
2630
}
2631
}
2632
}while( iSXb==iEXb && nextCutoff && (cutoff=nextCutoff)<=64 );
2633
if( iSXb==iEXb && (sqlite3_int64)(iE1-iS1)*(iE2-iS2)<2500 ){
2634
/* If no common sequence is found using the hashing heuristic and
2635
** the input is not too big, use the expensive exact solution */
2636
optimalLCS(p, iS1, iE1, iS2, iE2, piSX, piEX, piSY, piEY);
2637
}else{
2638
*piSX = iSXb;
2639
*piSY = iSYb;
2640
*piEX = iEXb;
2641
*piEY = iEYb;
2642
}
2643
}
2644
2645
/*
2646
** Expand the size of aEdit[] array to hold at least nEdit elements.
2647
*/
2648
static void expandEdit(DContext *p, int nEdit){
2649
p->aEdit = fossil_realloc(p->aEdit, nEdit*sizeof(int));
2650
p->nEditAlloc = nEdit;
2651
}
2652
2653
/*
2654
** Append a new COPY/DELETE/INSERT triple.
2655
*/
2656
static void appendTriple(DContext *p, int nCopy, int nDel, int nIns){
2657
/* printf("APPEND %d/%d/%d\n", nCopy, nDel, nIns); */
2658
if( p->nEdit>=3 ){
2659
if( p->aEdit[p->nEdit-1]==0 ){
2660
if( p->aEdit[p->nEdit-2]==0 ){
2661
p->aEdit[p->nEdit-3] += nCopy;
2662
p->aEdit[p->nEdit-2] += nDel;
2663
p->aEdit[p->nEdit-1] += nIns;
2664
return;
2665
}
2666
if( nCopy==0 ){
2667
p->aEdit[p->nEdit-2] += nDel;
2668
p->aEdit[p->nEdit-1] += nIns;
2669
return;
2670
}
2671
}
2672
if( nCopy==0 && nDel==0 ){
2673
p->aEdit[p->nEdit-1] += nIns;
2674
return;
2675
}
2676
}
2677
if( p->nEdit+3>p->nEditAlloc ){
2678
expandEdit(p, p->nEdit*2 + 15);
2679
if( p->aEdit==0 ) return;
2680
}
2681
p->aEdit[p->nEdit++] = nCopy;
2682
p->aEdit[p->nEdit++] = nDel;
2683
p->aEdit[p->nEdit++] = nIns;
2684
}
2685
2686
/*
2687
** A common subsequence between p->aFrom and p->aTo has been found.
2688
** This routine tries to judge if the subsequence really is a valid
2689
** match or rather is just an artifact of an indentation change.
2690
**
2691
** Return non-zero if the subsequence is valid. Return zero if the
2692
** subsequence seems likely to be an editing artifact and should be
2693
** ignored.
2694
**
2695
** This routine is a heuristic optimization intended to give more
2696
** intuitive diff results following an indentation change it code that
2697
** is formatted similarly to C/C++, Javascript, Go, TCL, and similar
2698
** languages that use {...} for nesting. A correct diff is computed
2699
** even if this routine always returns true (non-zero). But sometimes
2700
** a more intuitive diff can result if this routine returns false.
2701
**
2702
** The subsequences consists of the rows iSX through iEX-1 (inclusive)
2703
** in p->aFrom[]. The total sequences is iS1 through iE1-1 (inclusive)
2704
** of p->aFrom[].
2705
**
2706
** Example where this heuristic is useful, see the diff at
2707
** https://www.sqlite.org/src/fdiff?v1=0e79dd15cbdb4f48&v2=33955a6fd874dd97
2708
**
2709
** See also discussion at https://fossil-scm.org/forum/forumpost/9ba3284295
2710
**
2711
** ALGORITHM (subject to change and refinement):
2712
**
2713
** 1. If the subsequence is larger than 1/7th of the original span,
2714
** then consider it valid. --> return 1
2715
**
2716
** 2. If no lines of the subsequence contains more than one
2717
** non-whitespace character, --> return 0
2718
**
2719
** 3. If any line of the subsequence contains more than one non-whitespace
2720
** character and is unique across the entire sequence after ignoring
2721
** leading and trailing whitespace --> return 1
2722
**
2723
** 4. Otherwise, it is potentially an artifact of an indentation
2724
** change. --> return 0
2725
*/
2726
static int likelyNotIndentChngArtifact(
2727
DContext *p, /* The complete diff context */
2728
int iS1, /* Start of the main segment */
2729
int iSX, /* Start of the subsequence */
2730
int iEX, /* First row past the end of the subsequence */
2731
int iE1 /* First row past the end of the main segment */
2732
){
2733
int i, j, n;
2734
2735
/* Rule (1) */
2736
if( (iEX-iSX)*7 >= (iE1-iS1) ) return 1;
2737
2738
/* Compute DLine.indent and DLine.nw for all lines of the subsequence.
2739
** If no lines contain more than one non-whitespace character return
2740
** 0 because the subsequence could be due to an indentation change.
2741
** Rule (2).
2742
*/
2743
n = 0;
2744
for(i=iSX; i<iEX; i++){
2745
DLine *pA = &p->aFrom[i];
2746
if( pA->nw==0 && pA->n ){
2747
const char *zA = pA->z;
2748
const int nn = pA->n;
2749
int ii, jj;
2750
for(ii=0; ii<nn && diff_isspace(zA[ii]); ii++){}
2751
pA->indent = ii;
2752
for(jj=nn-1; jj>ii && diff_isspace(zA[jj]); jj--){}
2753
pA->nw = jj - ii + 1;
2754
}
2755
if( pA->nw>1 ) n++;
2756
}
2757
if( n==0 ) return 0;
2758
2759
/* Compute DLine.indent and DLine.nw for the entire sequence */
2760
for(i=iS1; i<iE1; i++){
2761
DLine *pA;
2762
if( i==iSX ){
2763
i = iEX;
2764
if( i>=iE1 ) break;
2765
}
2766
pA = &p->aFrom[i];
2767
if( pA->nw==0 && pA->n ){
2768
const char *zA = pA->z;
2769
const int nn = pA->n;
2770
int ii, jj;
2771
for(ii=0; ii<nn && diff_isspace(zA[ii]); ii++){}
2772
pA->indent = ii;
2773
for(jj=nn-1; jj>ii && diff_isspace(zA[jj]); jj--){}
2774
pA->nw = jj - ii + 1;
2775
}
2776
}
2777
2778
/* Check to see if any subsequence line that has more than one
2779
** non-whitespace character is unique across the entire sequence.
2780
** Rule (3)
2781
*/
2782
for(i=iSX; i<iEX; i++){
2783
const char *z = p->aFrom[i].z + p->aFrom[i].indent;
2784
const int nw = p->aFrom[i].nw;
2785
if( nw<=1 ) continue;
2786
for(j=iS1; j<iSX; j++){
2787
if( p->aFrom[j].nw!=nw ) continue;
2788
if( memcmp(p->aFrom[j].z+p->aFrom[j].indent,z,nw)==0 ) break;
2789
}
2790
if( j<iSX ) continue;
2791
for(j=iEX; j<iE1; j++){
2792
if( p->aFrom[j].nw!=nw ) continue;
2793
if( memcmp(p->aFrom[j].z+p->aFrom[j].indent,z,nw)==0 ) break;
2794
}
2795
if( j>=iE1 ) break;
2796
}
2797
return i<iEX;
2798
}
2799
2800
2801
/*
2802
** Do a single step in the difference. Compute a sequence of
2803
** copy/delete/insert steps that will convert lines iS1 through iE1-1 of
2804
** the input into lines iS2 through iE2-1 of the output and write
2805
** that sequence into the difference context.
2806
**
2807
** The algorithm is to find a block of common text near the middle
2808
** of the two segments being diffed. Then recursively compute
2809
** differences on the blocks before and after that common segment.
2810
** Special cases apply if either input segment is empty or if the
2811
** two segments have no text in common.
2812
*/
2813
static void diff_step(DContext *p, int iS1, int iE1, int iS2, int iE2){
2814
int iSX, iEX, iSY, iEY;
2815
2816
if( iE1<=iS1 ){
2817
/* The first segment is empty */
2818
if( iE2>iS2 ){
2819
appendTriple(p, 0, 0, iE2-iS2);
2820
}
2821
return;
2822
}
2823
if( iE2<=iS2 ){
2824
/* The second segment is empty */
2825
appendTriple(p, 0, iE1-iS1, 0);
2826
return;
2827
}
2828
2829
/* Find the longest matching segment between the two sequences */
2830
longestCommonSequence(p, iS1, iE1, iS2, iE2, &iSX, &iEX, &iSY, &iEY);
2831
2832
if( iEX>iSX+5
2833
|| (iEX>iSX && likelyNotIndentChngArtifact(p,iS1,iSX,iEX,iE1) )
2834
){
2835
/* A common segment has been found.
2836
** Recursively diff either side of the matching segment */
2837
diff_step(p, iS1, iSX, iS2, iSY);
2838
if( iEX>iSX ){
2839
appendTriple(p, iEX - iSX, 0, 0);
2840
}
2841
diff_step(p, iEX, iE1, iEY, iE2);
2842
}else{
2843
/* The two segments have nothing in common. Delete the first then
2844
** insert the second. */
2845
appendTriple(p, 0, iE1-iS1, iE2-iS2);
2846
}
2847
}
2848
2849
/*
2850
** Compute the differences between two files already loaded into
2851
** the DContext structure.
2852
**
2853
** A divide and conquer technique is used. We look for a large
2854
** block of common text that is in the middle of both files. Then
2855
** compute the difference on those parts of the file before and
2856
** after the common block. This technique is fast, but it does
2857
** not necessarily generate the minimum difference set. On the
2858
** other hand, we do not need a minimum difference set, only one
2859
** that makes sense to human readers, which this algorithm does.
2860
**
2861
** Any common text at the beginning and end of the two files is
2862
** removed before starting the divide-and-conquer algorithm.
2863
*/
2864
static void diff_all(DContext *p){
2865
int mnE, iS, iE1, iE2;
2866
2867
/* Carve off the common header and footer */
2868
iE1 = p->nFrom;
2869
iE2 = p->nTo;
2870
while( iE1>0 && iE2>0 && p->xDiffer(&p->aFrom[iE1-1], &p->aTo[iE2-1])==0 ){
2871
iE1--;
2872
iE2--;
2873
}
2874
mnE = iE1<iE2 ? iE1 : iE2;
2875
for(iS=0; iS<mnE && p->xDiffer(&p->aFrom[iS],&p->aTo[iS])==0; iS++){}
2876
2877
/* do the difference */
2878
if( iS>0 ){
2879
appendTriple(p, iS, 0, 0);
2880
}
2881
diff_step(p, iS, iE1, iS, iE2);
2882
if( iE1<p->nFrom ){
2883
appendTriple(p, p->nFrom - iE1, 0, 0);
2884
}
2885
2886
/* Terminate the COPY/DELETE/INSERT triples with three zeros */
2887
expandEdit(p, p->nEdit+3);
2888
if( p->aEdit ){
2889
p->aEdit[p->nEdit++] = 0;
2890
p->aEdit[p->nEdit++] = 0;
2891
p->aEdit[p->nEdit++] = 0;
2892
}
2893
}
2894
2895
/*
2896
** Attempt to shift insertion or deletion blocks so that they begin and
2897
** end on lines that are pure whitespace. In other words, try to transform
2898
** this:
2899
**
2900
** int func1(int x){
2901
** return x*10;
2902
** +}
2903
** +
2904
** +int func2(int x){
2905
** + return x*20;
2906
** }
2907
**
2908
** int func3(int x){
2909
** return x/5;
2910
** }
2911
**
2912
** Into one of these:
2913
**
2914
** int func1(int x){ int func1(int x){
2915
** return x*10; return x*10;
2916
** } }
2917
** +
2918
** +int func2(int x){ +int func2(int x){
2919
** + return x*20; + return x*20;
2920
** +} +}
2921
** +
2922
** int func3(int x){ int func3(int x){
2923
** return x/5; return x/5;
2924
** } }
2925
*/
2926
static void diff_optimize(DContext *p){
2927
int r; /* Index of current triple */
2928
int lnFrom; /* Line number in p->aFrom */
2929
int lnTo; /* Line number in p->aTo */
2930
int cpy, del, ins;
2931
2932
lnFrom = lnTo = 0;
2933
for(r=0; r<p->nEdit; r += 3){
2934
cpy = p->aEdit[r];
2935
del = p->aEdit[r+1];
2936
ins = p->aEdit[r+2];
2937
lnFrom += cpy;
2938
lnTo += cpy;
2939
2940
/* Shift insertions toward the beginning of the file */
2941
while( cpy>0 && del==0 && ins>0 ){
2942
DLine *pTop = &p->aFrom[lnFrom-1]; /* Line before start of insert */
2943
DLine *pBtm = &p->aTo[lnTo+ins-1]; /* Last line inserted */
2944
if( p->xDiffer(pTop, pBtm) ) break;
2945
if( LENGTH(pTop+1)+LENGTH(pBtm)<=LENGTH(pTop)+LENGTH(pBtm-1) ) break;
2946
lnFrom--;
2947
lnTo--;
2948
p->aEdit[r]--;
2949
p->aEdit[r+3]++;
2950
cpy--;
2951
}
2952
2953
/* Shift insertions toward the end of the file */
2954
while( r+3<p->nEdit && p->aEdit[r+3]>0 && del==0 && ins>0 ){
2955
DLine *pTop = &p->aTo[lnTo]; /* First line inserted */
2956
DLine *pBtm = &p->aTo[lnTo+ins]; /* First line past end of insert */
2957
if( p->xDiffer(pTop, pBtm) ) break;
2958
if( LENGTH(pTop)+LENGTH(pBtm-1)<=LENGTH(pTop+1)+LENGTH(pBtm) ) break;
2959
lnFrom++;
2960
lnTo++;
2961
p->aEdit[r]++;
2962
p->aEdit[r+3]--;
2963
cpy++;
2964
}
2965
2966
/* Shift deletions toward the beginning of the file */
2967
while( cpy>0 && del>0 && ins==0 ){
2968
DLine *pTop = &p->aFrom[lnFrom-1]; /* Line before start of delete */
2969
DLine *pBtm = &p->aFrom[lnFrom+del-1]; /* Last line deleted */
2970
if( p->xDiffer(pTop, pBtm) ) break;
2971
if( LENGTH(pTop+1)+LENGTH(pBtm)<=LENGTH(pTop)+LENGTH(pBtm-1) ) break;
2972
lnFrom--;
2973
lnTo--;
2974
p->aEdit[r]--;
2975
p->aEdit[r+3]++;
2976
cpy--;
2977
}
2978
2979
/* Shift deletions toward the end of the file */
2980
while( r+3<p->nEdit && p->aEdit[r+3]>0 && del>0 && ins==0 ){
2981
DLine *pTop = &p->aFrom[lnFrom]; /* First line deleted */
2982
DLine *pBtm = &p->aFrom[lnFrom+del]; /* First line past end of delete */
2983
if( p->xDiffer(pTop, pBtm) ) break;
2984
if( LENGTH(pTop)+LENGTH(pBtm-1)<=LENGTH(pTop)+LENGTH(pBtm) ) break;
2985
lnFrom++;
2986
lnTo++;
2987
p->aEdit[r]++;
2988
p->aEdit[r+3]--;
2989
cpy++;
2990
}
2991
2992
lnFrom += del;
2993
lnTo += ins;
2994
}
2995
}
2996
2997
/*
2998
** Extract the number of lines of context from diffFlags. Supply an
2999
** appropriate default if no context width is specified. If pCfg is
3000
** NULL then the compile-time default is used (which gets propagated
3001
** to JS-side state by certain pages).
3002
*/
3003
int diff_context_lines(DiffConfig *pCfg){
3004
const int dflt = 5;
3005
if(pCfg!=0){
3006
int n = pCfg->nContext;
3007
if( n==0 && (pCfg->diffFlags & DIFF_CONTEXT_EX)==0 ) n = dflt;
3008
return n<0 ? 0x7ffffff : n;
3009
}else{
3010
return dflt;
3011
}
3012
}
3013
3014
/*
3015
** Extract the width of columns for side-by-side diff. Supply an
3016
** appropriate default if no width is given.
3017
**
3018
** Calculate the default automatically, based on terminal's current width:
3019
** term-width = 2*diff-col + diff-marker + 1
3020
** diff-col = lineno + lmargin + text-width + rmargin
3021
**
3022
** text-width = (term-width - diff-marker - 1)/2 - lineno - lmargin - rmargin
3023
*/
3024
int diff_width(DiffConfig *pCfg){
3025
int w = pCfg->wColumn;
3026
if( w==0 ){
3027
static struct {
3028
unsigned int lineno, lmargin, text, rmargin, marker;
3029
} sbsW = { 5, 2, 0, 0, 3 };
3030
const unsigned int wMin = 24, wMax = 132;
3031
unsigned int tw = terminal_get_width(80);
3032
unsigned int twMin =
3033
(wMin + sbsW.lineno + sbsW.lmargin + sbsW.rmargin)*2 + sbsW.marker + 1;
3034
unsigned int twMax =
3035
(wMax + sbsW.lineno + sbsW.lmargin + sbsW.rmargin)*2 + sbsW.marker + 1;
3036
3037
if( tw<twMin ){
3038
tw = twMin;
3039
}else if( tw>twMax ){
3040
tw = twMax;
3041
}
3042
sbsW.text =
3043
(tw - sbsW.marker - 1)/2 - sbsW.lineno - sbsW.lmargin - sbsW.rmargin;
3044
w = sbsW.text;
3045
}
3046
return w;
3047
}
3048
3049
/*
3050
** Append the error message to pOut.
3051
*/
3052
void diff_errmsg(Blob *pOut, const char *msg, int diffFlags){
3053
if( diffFlags & DIFF_HTML ){
3054
blob_appendf(pOut, "<p class=\"generalError\">%s</p>", msg);
3055
}else{
3056
blob_append(pOut, msg, -1);
3057
}
3058
}
3059
3060
/*
3061
** Generate a report of the differences between files pA_Blob and pB_Blob.
3062
**
3063
** If pOut!=NULL then append text to pOut that will be the difference,
3064
** formatted according to flags in diffFlags. The pOut Blob must have
3065
** already been initialized.
3066
**
3067
** If pOut==NULL then no formatting occurs. Instead, this routine
3068
** returns a pointer to an array of integers. The integers come in
3069
** triples. The elements of each triple are:
3070
**
3071
** 1. The number of lines to copy
3072
** 2. The number of lines to delete
3073
** 3. The number of lines to insert
3074
**
3075
** The return vector is terminated by a triple of all zeros. The caller
3076
** should free the returned vector using fossil_free().
3077
**
3078
** This diff utility does not work on binary files. If a binary
3079
** file is encountered, 0 is returned and pOut is written with
3080
** text "cannot compute difference between binary files".
3081
*/
3082
int *text_diff(
3083
Blob *pA_Blob, /* FROM file */
3084
Blob *pB_Blob, /* TO file */
3085
Blob *pOut, /* Write diff here if not NULL */
3086
DiffConfig *pCfg /* Configuration options */
3087
){
3088
int ignoreWs; /* Ignore whitespace */
3089
DContext c;
3090
int nDel = 0, nIns = 0;
3091
3092
if( pCfg->diffFlags & DIFF_INVERT ){
3093
Blob *pTemp = pA_Blob;
3094
pA_Blob = pB_Blob;
3095
pB_Blob = pTemp;
3096
}
3097
ignoreWs = (pCfg->diffFlags & DIFF_IGNORE_ALLWS)!=0;
3098
blob_to_utf8_no_bom(pA_Blob, 0);
3099
blob_to_utf8_no_bom(pB_Blob, 0);
3100
3101
/* Prepare the input files */
3102
memset(&c, 0, sizeof(c));
3103
if( (pCfg->diffFlags & DIFF_IGNORE_ALLWS)==DIFF_IGNORE_ALLWS ){
3104
c.xDiffer = compare_dline_ignore_allws;
3105
}else{
3106
c.xDiffer = compare_dline;
3107
}
3108
if( pCfg->diffFlags & DIFF_BY_TOKEN ){
3109
c.aFrom = break_into_tokens(blob_str(pA_Blob), blob_size(pA_Blob),
3110
&c.nFrom, pCfg->diffFlags);
3111
c.aTo = break_into_tokens(blob_str(pB_Blob), blob_size(pB_Blob),
3112
&c.nTo, pCfg->diffFlags);
3113
}else{
3114
c.aFrom = break_into_lines(blob_str(pA_Blob), blob_size(pA_Blob),
3115
&c.nFrom, pCfg->diffFlags);
3116
c.aTo = break_into_lines(blob_str(pB_Blob), blob_size(pB_Blob),
3117
&c.nTo, pCfg->diffFlags);
3118
}
3119
if( c.aFrom==0 || c.aTo==0 ){
3120
fossil_free(c.aFrom);
3121
fossil_free(c.aTo);
3122
if( pOut ){
3123
diff_errmsg(pOut, DIFF_CANNOT_COMPUTE_BINARY, pCfg->diffFlags);
3124
}
3125
return 0;
3126
}
3127
3128
/* Compute the difference */
3129
diff_all(&c);
3130
if( ignoreWs && c.nEdit==6 && c.aEdit[1]==0 && c.aEdit[2]==0 ){
3131
fossil_free(c.aFrom);
3132
fossil_free(c.aTo);
3133
fossil_free(c.aEdit);
3134
if( pOut ) diff_errmsg(pOut, DIFF_WHITESPACE_ONLY, pCfg->diffFlags);
3135
return 0;
3136
}
3137
if( (pCfg->diffFlags & DIFF_NOTTOOBIG)!=0 ){
3138
int i, m, n;
3139
int *a = c.aEdit;
3140
int mx = c.nEdit;
3141
for(i=m=n=0; i<mx; i+=3){ m += a[i]; n += a[i+1]+a[i+2]; }
3142
if( n>10000 ){
3143
fossil_free(c.aFrom);
3144
fossil_free(c.aTo);
3145
fossil_free(c.aEdit);
3146
if( pOut ) diff_errmsg(pOut, DIFF_TOO_MANY_CHANGES, pCfg->diffFlags);
3147
return 0;
3148
}
3149
}
3150
if( (pCfg->diffFlags & DIFF_NOOPT)==0 ){
3151
diff_optimize(&c);
3152
}
3153
if( (pCfg->diffFlags & DIFF_BY_TOKEN)!=0 ){
3154
/* Convert token counts into byte counts. */
3155
int i;
3156
int iA = 0;
3157
int iB = 0;
3158
for(i=0; c.aEdit[i] || c.aEdit[i+1] || c.aEdit[i+2]; i+=3){
3159
int k, sum;
3160
for(k=0, sum=0; k<c.aEdit[i]; k++) sum += c.aFrom[iA++].n;
3161
iB += c.aEdit[i];
3162
c.aEdit[i] = sum;
3163
for(k=0, sum=0; k<c.aEdit[i+1]; k++) sum += c.aFrom[iA++].n;
3164
c.aEdit[i+1] = sum;
3165
for(k=0, sum=0; k<c.aEdit[i+2]; k++) sum += c.aTo[iB++].n;
3166
c.aEdit[i+2] = sum;
3167
}
3168
}
3169
3170
if( pCfg->diffFlags & DIFF_NUMSTAT ){
3171
int i;
3172
for(i=0; c.aEdit[i] || c.aEdit[i+1] || c.aEdit[i+2]; i+=3){
3173
nDel += c.aEdit[i+1];
3174
nIns += c.aEdit[i+2];
3175
}
3176
g.diffCnt[1] += nIns;
3177
g.diffCnt[2] += nDel;
3178
if( nIns+nDel ){
3179
g.diffCnt[0]++;
3180
}
3181
}
3182
3183
if( pOut ){
3184
if( pCfg->diffFlags & DIFF_NUMSTAT && !(pCfg->diffFlags & DIFF_HTML)){
3185
if( nIns+nDel ){
3186
if( !(pCfg->diffFlags & DIFF_BRIEF) ){
3187
blob_appendf(pOut, "%10d %10d", nIns, nDel);
3188
}
3189
}
3190
}else if( pCfg->diffFlags & (DIFF_RAW|DIFF_BY_TOKEN) ){
3191
const int *R = c.aEdit;
3192
unsigned int r;
3193
for(r=0; R[r] || R[r+1] || R[r+2]; r += 3){
3194
blob_appendf(pOut, " copy %6d delete %6d insert %6d\n",
3195
R[r], R[r+1], R[r+2]);
3196
}
3197
}else if( pCfg->diffFlags & DIFF_JSON ){
3198
DiffBuilder *pBuilder = dfjsonNew(pOut);
3199
formatDiff(&c, pCfg, pBuilder);
3200
blob_append_char(pOut, '\n');
3201
}else if( pCfg->diffFlags & DIFF_TCL ){
3202
DiffBuilder *pBuilder = dftclNew(pOut);
3203
formatDiff(&c, pCfg, pBuilder);
3204
}else if( pCfg->diffFlags & DIFF_SIDEBYSIDE ){
3205
DiffBuilder *pBuilder;
3206
if( pCfg->diffFlags & DIFF_HTML ){
3207
pBuilder = dfsplitNew(pOut, pCfg);
3208
}else{
3209
pBuilder = dfsbsNew(pOut, pCfg);
3210
}
3211
formatDiff(&c, pCfg, pBuilder);
3212
}else if( pCfg->diffFlags & DIFF_DEBUG ){
3213
DiffBuilder *pBuilder = dfdebugNew(pOut);
3214
formatDiff(&c, pCfg, pBuilder);
3215
}else if( pCfg->diffFlags & DIFF_HTML ){
3216
DiffBuilder *pBuilder = dfunifiedNew(pOut, pCfg);
3217
formatDiff(&c, pCfg, pBuilder);
3218
}else{
3219
contextDiff(&c, pOut, pCfg);
3220
}
3221
fossil_free(c.aFrom);
3222
fossil_free(c.aTo);
3223
fossil_free(c.aEdit);
3224
return 0;
3225
}else{
3226
/* If a context diff is not requested, then return the
3227
** array of COPY/DELETE/INSERT triples.
3228
*/
3229
free(c.aFrom);
3230
free(c.aTo);
3231
return c.aEdit;
3232
}
3233
}
3234
3235
/*
3236
** Initialize the DiffConfig object using command-line options.
3237
**
3238
** Process diff-related command-line options and return an appropriate
3239
** "diffFlags" integer.
3240
**
3241
** -b|--browser Show the diff output in a web-browser
3242
** --brief Show filenames only DIFF_BRIEF
3243
** --by Shorthand for "--browser -y"
3244
** -c|--context N N lines of context. nContext
3245
** --dark Use dark mode for Tcl/Tk and HTML output
3246
** --html Format for HTML DIFF_HTML
3247
** -i|--internal Use built-in diff, not an external tool
3248
** --invert Invert the diff DIFF_INVERT
3249
** --json Output formatted as JSON
3250
** --label NAME Column label. Can be repeated once.
3251
** -n|--linenum Show line numbers DIFF_LINENO
3252
** -N|--new-file Alias for --verbose
3253
** --noopt Disable optimization DIFF_NOOPT
3254
** -s|--numstat Show change counts DIFF_NUMSTAT
3255
** --strip-trailing-cr Strip trailing CR DIFF_STRIP_EOLCR
3256
** --tcl Tcl-formatted output used internally by --tk
3257
** --unified Unified diff. ~DIFF_SIDEBYSIDE
3258
** -v|--verbose Show complete text of added or deleted files
3259
** -w|--ignore-all-space Ignore all whitespaces DIFF_IGNORE_ALLWS
3260
** --webpage Format output as a stand-alone HTML webpage
3261
** -W|--width N N character lines. wColumn
3262
** -y|--side-by-side Side-by-side diff. DIFF_SIDEBYSIDE
3263
** -Z|--ignore-trailing-space Ignore eol-whitespaces DIFF_IGNORE_EOLWS
3264
*/
3265
void diff_options(DiffConfig *pCfg, int isGDiff, int bUnifiedTextOnly){
3266
u64 diffFlags = 0;
3267
const char *z;
3268
int f;
3269
3270
memset(pCfg, 0, sizeof(*pCfg));
3271
if( find_option("ignore-trailing-space","Z",0)!=0 ){
3272
diffFlags = DIFF_IGNORE_EOLWS;
3273
}
3274
if( find_option("ignore-all-space","w",0)!=0 ){
3275
diffFlags = DIFF_IGNORE_ALLWS; /* stronger than DIFF_IGNORE_EOLWS */
3276
}
3277
if( find_option("strip-trailing-cr",0,0)!=0 ){
3278
diffFlags |= DIFF_STRIP_EOLCR;
3279
}
3280
if( !bUnifiedTextOnly ){
3281
if( find_option("side-by-side","y",0)!=0 ) diffFlags |= DIFF_SIDEBYSIDE;
3282
if( find_option("yy",0,0)!=0 ){
3283
diffFlags |= DIFF_SIDEBYSIDE | DIFF_SLOW_SBS;
3284
}
3285
if( find_option("html",0,0)!=0 ) diffFlags |= DIFF_HTML;
3286
if( find_option("unified",0,0)!=0 ) diffFlags &= ~DIFF_SIDEBYSIDE;
3287
if( find_option("webpage",0,0)!=0 ){
3288
diffFlags |= DIFF_HTML|DIFF_WEBPAGE|DIFF_LINENO;
3289
}
3290
if( find_option("browser","b",0)!=0 ){
3291
diffFlags |= DIFF_HTML|DIFF_WEBPAGE|DIFF_LINENO|DIFF_BROWSER;
3292
}
3293
if( find_option("by",0,0)!=0 ){
3294
diffFlags |= DIFF_HTML|DIFF_WEBPAGE|DIFF_LINENO|DIFF_BROWSER
3295
|DIFF_SIDEBYSIDE;
3296
}
3297
if( find_option("json",0,0)!=0 ){
3298
diffFlags |= DIFF_JSON;
3299
}
3300
if( find_option("tcl",0,0)!=0 ){
3301
diffFlags |= DIFF_TCL;
3302
}
3303
3304
/* Undocumented and unsupported flags used for development
3305
** debugging and analysis: */
3306
if( find_option("debug",0,0)!=0 ) diffFlags |= DIFF_DEBUG;
3307
if( find_option("raw",0,0)!=0 ) diffFlags |= DIFF_RAW;
3308
if( find_option("bytoken",0,0)!=0 ){
3309
diffFlags = DIFF_RAW|DIFF_BY_TOKEN;
3310
}
3311
}
3312
if( (z = find_option("context","c",1))!=0 ){
3313
char *zEnd;
3314
f = (int)strtol(z, &zEnd, 10);
3315
if( zEnd[0]==0 && errno!=ERANGE ){
3316
pCfg->nContext = f;
3317
diffFlags |= DIFF_CONTEXT_EX;
3318
}
3319
}
3320
if( (z = find_option("width","W",1))!=0 && (f = atoi(z))>0 ){
3321
pCfg->wColumn = f;
3322
}
3323
pCfg->azLabel[0] = find_option("label",0,1);
3324
if( pCfg->azLabel[0] ){
3325
pCfg->azLabel[1] = find_option("label",0,1);
3326
}
3327
if( find_option("linenum","n",0)!=0 ) diffFlags |= DIFF_LINENO;
3328
if( find_option("noopt",0,0)!=0 ) diffFlags |= DIFF_NOOPT;
3329
if( find_option("numstat","s",0)!=0 ) diffFlags |= DIFF_NUMSTAT;
3330
if( find_option("versions","h",0)!=0 ) diffFlags |= DIFF_SHOW_VERS;
3331
if( find_option("dark",0,0)!=0 ) diffFlags |= DIFF_DARKMODE;
3332
if( find_option("invert",0,0)!=0 ) diffFlags |= DIFF_INVERT;
3333
if( find_option("brief",0,0)!=0 ) diffFlags |= DIFF_BRIEF;
3334
if( find_option("internal","i",0)==0
3335
&& (diffFlags & (DIFF_HTML|DIFF_TCL|DIFF_DEBUG|DIFF_JSON))==0
3336
){
3337
pCfg->zDiffCmd = find_option("command", 0, 1);
3338
if( pCfg->zDiffCmd==0 ) pCfg->zDiffCmd = diff_command_external(isGDiff);
3339
if( pCfg->zDiffCmd ){
3340
const char *zDiffBinary;
3341
pCfg->zBinGlob = diff_get_binary_glob();
3342
zDiffBinary = find_option("diff-binary", 0, 1);
3343
if( zDiffBinary ){
3344
if( is_truth(zDiffBinary) ) diffFlags |= DIFF_INCBINARY;
3345
}else if( db_get_boolean("diff-binary", 1) ){
3346
diffFlags |= DIFF_INCBINARY;
3347
}
3348
}else if( isGDiff) {
3349
/* No external gdiff command found, using --by */
3350
diffFlags |= DIFF_HTML|DIFF_WEBPAGE|DIFF_LINENO|DIFF_BROWSER
3351
|DIFF_SIDEBYSIDE;
3352
}
3353
}
3354
if( find_option("verbose","v",0)!=0 ) diffFlags |= DIFF_VERBOSE;
3355
/* Deprecated, but retained for script compatibility. */
3356
else if( find_option("new-file","N",0)!=0 ) diffFlags |= DIFF_VERBOSE;
3357
3358
pCfg->diffFlags = diffFlags;
3359
}
3360
3361
/*
3362
** COMMAND: test-diff
3363
** COMMAND: xdiff
3364
**
3365
** Usage: %fossil xdiff [options] FILE1 FILE2
3366
**
3367
** Compute an "external diff" between two files. By "external diff" we mean
3368
** a diff between two disk files that are not necessarily under management.
3369
** In other words, this command provides a mechanism to use Fossil's file
3370
** difference engine on arbitrary disk files. See the "diff" command for
3371
** computing differences between files that are under management.
3372
**
3373
** This command prints the differences between the two files FILE1 and FILE2.
3374
** all of the usual diff formatting options (--tk, --by, -c N, etc.) apply.
3375
** See the "diff" command for a full list of command-line options.
3376
*/
3377
void xdiff_cmd(void){
3378
Blob a, b, out;
3379
const char *zRe; /* Regex filter for diff output */
3380
DiffConfig DCfg;
3381
3382
if( find_option("tk",0,0)!=0 ){
3383
diff_tk("xdiff", 2);
3384
return;
3385
}
3386
find_option("i",0,0);
3387
find_option("v",0,0);
3388
diff_options(&DCfg, 0, 0);
3389
zRe = find_option("regexp","e",1);
3390
if( zRe ){
3391
const char *zErr = fossil_re_compile(&DCfg.pRe, zRe, 0);
3392
if( zErr ) fossil_fatal("regex error: %s", zErr);
3393
}
3394
verify_all_options();
3395
if( g.argc!=4 ) usage("FILE1 FILE2");
3396
blob_zero(&out);
3397
diff_begin(&DCfg);
3398
diff_print_filenames(g.argv[2], g.argv[3], &DCfg, &out);
3399
blob_read_from_file(&a, g.argv[2], ExtFILE);
3400
blob_read_from_file(&b, g.argv[3], ExtFILE);
3401
text_diff(&a, &b, &out, &DCfg);
3402
blob_write_to_file(&out, "-");
3403
diff_end(&DCfg, 0);
3404
re_free(DCfg.pRe);
3405
}
3406
3407
/*
3408
** COMMAND: fdiff
3409
**
3410
** Usage: %fossil fdiff [options] HASH1 HASH2
3411
**
3412
** Compute a diff between two artifacts in a repository (either a repository
3413
** identified by the "-R FILENAME" option, or the repository that contains
3414
** the working directory).
3415
**
3416
** All of the usual diff formatting options (--tk, --by, -c N, etc.) apply.
3417
** See the "diff" command for a full list of command-line options.
3418
*/
3419
void fdiff_cmd(void){
3420
Blob a, b, out;
3421
const char *zRe; /* Regex filter for diff output */
3422
int rid;
3423
DiffConfig DCfg;
3424
3425
if( find_option("tk",0,0)!=0 ){
3426
diff_tk("fdiff", 2);
3427
return;
3428
}
3429
find_option("i",0,0);
3430
find_option("v",0,0);
3431
diff_options(&DCfg, 0, 0);
3432
zRe = find_option("regexp","e",1);
3433
if( zRe ){
3434
const char *zErr = fossil_re_compile(&DCfg.pRe, zRe, 0);
3435
if( zErr ) fossil_fatal("regex error: %s", zErr);
3436
}
3437
db_find_and_open_repository(0, 0);
3438
verify_all_options();
3439
if( g.argc!=4 ) usage("HASH1 HASH2");
3440
blob_zero(&out);
3441
diff_begin(&DCfg);
3442
diff_print_filenames(g.argv[2], g.argv[3], &DCfg, &out);
3443
rid = name_to_typed_rid(g.argv[2], 0);
3444
content_get(rid, &a);
3445
rid = name_to_typed_rid(g.argv[3], 0);
3446
content_get(rid, &b);
3447
text_diff(&a, &b, &out, &DCfg);
3448
blob_write_to_file(&out, "-");
3449
diff_end(&DCfg, 0);
3450
re_free(DCfg.pRe);
3451
}
3452
3453
/**************************************************************************
3454
** The basic difference engine is above. What follows is the annotation
3455
** engine. Both are in the same file since they share many components.
3456
*/
3457
3458
/*
3459
** The status of an annotation operation is recorded by an instance
3460
** of the following structure.
3461
*/
3462
typedef struct Annotator Annotator;
3463
struct Annotator {
3464
DContext c; /* The diff-engine context */
3465
struct AnnLine { /* Lines of the original files... */
3466
const char *z; /* The text of the line */
3467
short int n; /* Number of bytes (omitting trailing \n) */
3468
short int iVers; /* Level at which tag was set */
3469
} *aOrig;
3470
int nOrig; /* Number of elements in aOrig[] */
3471
int nVers; /* Number of versions analyzed */
3472
int bMoreToDo; /* True if the limit was reached */
3473
int origId; /* RID for the zOrigin version */
3474
int showId; /* RID for the version being analyzed */
3475
struct AnnVers {
3476
const char *zFUuid; /* File being analyzed */
3477
const char *zMUuid; /* Check-in containing the file */
3478
const char *zDate; /* Date of the check-in */
3479
const char *zBgColor; /* Suggested background color */
3480
const char *zUser; /* Name of user who did the check-in */
3481
unsigned cnt; /* Number of lines contributed by this check-in */
3482
} *aVers; /* For each check-in analyzed */
3483
char **azVers; /* Names of versions analyzed */
3484
};
3485
3486
/*
3487
** Initialize the annotation process by specifying the file that is
3488
** to be annotated. The annotator takes control of the input Blob and
3489
** will release it when it is finished with it.
3490
*/
3491
static int annotation_start(Annotator *p, Blob *pInput, u64 diffFlags){
3492
int i;
3493
3494
memset(p, 0, sizeof(*p));
3495
if( (diffFlags & DIFF_IGNORE_ALLWS)==DIFF_IGNORE_ALLWS ){
3496
p->c.xDiffer = compare_dline_ignore_allws;
3497
}else{
3498
p->c.xDiffer = compare_dline;
3499
}
3500
p->c.aTo = break_into_lines(blob_str(pInput), blob_size(pInput),&p->c.nTo,
3501
diffFlags);
3502
if( p->c.aTo==0 ){
3503
return 1;
3504
}
3505
p->aOrig = fossil_malloc( sizeof(p->aOrig[0])*p->c.nTo );
3506
for(i=0; i<p->c.nTo; i++){
3507
p->aOrig[i].z = p->c.aTo[i].z;
3508
p->aOrig[i].n = p->c.aTo[i].n;
3509
p->aOrig[i].iVers = -1;
3510
}
3511
p->nOrig = p->c.nTo;
3512
return 0;
3513
}
3514
3515
/*
3516
** The input pParent is the next most recent ancestor of the file
3517
** being annotated. Do another step of the annotation. Return true
3518
** if additional annotation is required.
3519
*/
3520
static int annotation_step(
3521
Annotator *p,
3522
Blob *pParent,
3523
int iVers,
3524
u64 diffFlags
3525
){
3526
int i, j;
3527
int lnTo;
3528
3529
/* Prepare the parent file to be diffed */
3530
p->c.aFrom = break_into_lines(blob_str(pParent), blob_size(pParent),
3531
&p->c.nFrom, diffFlags);
3532
if( p->c.aFrom==0 ){
3533
return 1;
3534
}
3535
3536
/* Compute the differences going from pParent to the file being
3537
** annotated. */
3538
diff_all(&p->c);
3539
3540
/* Where new lines are inserted on this difference, record the
3541
** iVers as the source of the new line.
3542
*/
3543
for(i=lnTo=0; i<p->c.nEdit; i+=3){
3544
int nCopy = p->c.aEdit[i];
3545
int nIns = p->c.aEdit[i+2];
3546
lnTo += nCopy;
3547
for(j=0; j<nIns; j++, lnTo++){
3548
if( p->aOrig[lnTo].iVers<0 ){
3549
p->aOrig[lnTo].iVers = iVers;
3550
}
3551
}
3552
}
3553
3554
/* Clear out the diff results */
3555
fossil_free(p->c.aEdit);
3556
p->c.aEdit = 0;
3557
p->c.nEdit = 0;
3558
p->c.nEditAlloc = 0;
3559
3560
/* Clear out the from file */
3561
free(p->c.aFrom);
3562
3563
/* Return no errors */
3564
return 0;
3565
}
3566
3567
/* Return the current time as milliseconds since the Julian epoch */
3568
static sqlite3_int64 current_time_in_milliseconds(void){
3569
static sqlite3_vfs *clockVfs = 0;
3570
sqlite3_int64 t;
3571
if( clockVfs==0 ) clockVfs = sqlite3_vfs_find(0);
3572
if( clockVfs->iVersion>=2 && clockVfs->xCurrentTimeInt64!=0 ){
3573
clockVfs->xCurrentTimeInt64(clockVfs, &t);
3574
}else{
3575
double r;
3576
clockVfs->xCurrentTime(clockVfs, &r);
3577
t = (sqlite3_int64)(r*86400000.0);
3578
}
3579
return t;
3580
}
3581
3582
/*
3583
** Compute a complete annotation on a file. The file is identified by its
3584
** filename and check-in name (NULL for current check-in).
3585
*/
3586
static void annotate_file(
3587
Annotator *p, /* The annotator */
3588
const char *zFilename, /* The name of the file to be annotated */
3589
const char *zRevision, /* Use the version of the file in this check-in */
3590
const char *zLimit, /* Limit the number of versions analyzed */
3591
const char *zOrigin, /* The origin check-in, or NULL for root-of-tree */
3592
u64 annFlags /* Flags to alter the annotation */
3593
){
3594
Blob toAnnotate; /* Text of the final (mid) version of the file */
3595
Blob step; /* Text of previous revision */
3596
int cid; /* Selected check-in ID */
3597
int origid = 0; /* The origin ID or zero */
3598
int rid; /* Artifact ID of the file being annotated */
3599
int fnid; /* Filename ID */
3600
Stmt q; /* Query returning all ancestor versions */
3601
int cnt = 0; /* Number of versions analyzed */
3602
int iLimit; /* Maximum number of versions to analyze */
3603
sqlite3_int64 mxTime; /* Halt at this time if not already complete */
3604
3605
memset(p, 0, sizeof(*p));
3606
3607
if( zLimit ){
3608
if( strcmp(zLimit,"none")==0 ){
3609
iLimit = 0;
3610
mxTime = 0;
3611
}else if( sqlite3_strglob("*[0-9]s", zLimit)==0 ){
3612
iLimit = 0;
3613
mxTime =
3614
(sqlite3_int64)(curre 1000.0*atof(zLimit));
3615
}else{
3616
iLimit = atoi(zLimit);
3617
if( iLimit<=0 ) iLimit = 30;
3618
mxTime = 0;
3619
}
3620
}else{
3621
/* Default limit is as much as we can do in 1.000 seconds */
3622
iLimit = 0;
3623
mxTime = current_time_in_milliseconds()+1000;
3624
}
3625
db_begin_transaction();
3626
3627
/* Get the artifact ID for the check-in being analyzed */
3628
if( zRevision ){
3629
cid = name_to_typed_rid(zRevision, "ci");
3630
}else{
3631
db_must_be_within_tree();
3632
cid = db_lget_int("checkout", 0);
3633
}
3634
origid = zOrigin ? name_to_typed_rid(zOrigin, "ci") : 0;
3635
3636
/* Compute all direct ancestors of the check-in being analyzed into
3637
** the "ancestor" table. */
3638
if( origid ){
3639
path_shortest_stored_in_ancestor_table(origid, cid);
3640
}else{
3641
compute_direct_ancestors(cid);
3642
}
3643
3644
/* Get filename ID */
3645
fnid = db_int(0, "SELECT fnid FROM filename WHERE name=%Q", zFilename);
3646
if( fnid==0 ){
3647
fossil_fatal("no such file: %Q", zFilename);
3648
}
3649
3650
db_prepare(&q,
3651
"SELECT DISTINCT"
3652
" (SELECT uuid FROM blob WHERE rid=mlink.fid),"
3653
" (SELECT uuid FROM blob WHERE rid=mlink.mid),"
3654
" date(event.mtime),"
3655
" coalesce(event.euser,event.user),"
3656
" mlink.fid"
3657
" FROM mlink, event, ancestor"
3658
" WHERE mlink.fnid=%d"
3659
" AND ancestor.rid=mlink.mid"
3660
" AND event.objid=mlink.mid"
3661
" AND mlink.mid!=mlink.pid"
3662
" ORDER BY ancestor.generation;",
3663
fnid
3664
);
3665
3666
while( db_step(&q)==SQLITE_ROW ){
3667
if( cnt>=3 ){ /* Process at least 3 rows before imposing limits */
3668
if( (iLimit>0 && cnt>=iLimit)
3669
|| (cnt>0 && mxTime>0 && current_time_in_milliseconds()>mxTime)
3670
){
3671
p->bMoreToDo = 1;
3672
break;
3673
}
3674
}
3675
rid = db_column_int(&q, 4);
3676
if( cnt==0 ){
3677
if( !content_get(rid, &toAnnotate) ){
3678
fossil_fatal("unable to retrieve content of artifact #%d", rid);
3679
}
3680
blob_to_utf8_no_bom(&toAnnotate, 0);
3681
annotation_start(p, &toAnnotate, annFlags);
3682
p->bMoreToDo = origid!=0;
3683
p->origId = origid;
3684
p->showId = cid;
3685
}
3686
p->aVers = fossil_realloc(p->aVers, (p->nVers+1)*sizeof(p->aVers[0]));
3687
p->aVers[p->nVers].zFUuid = fossil_strdup(db_column_text(&q, 0));
3688
p->aVers[p->nVers].zMUuid = fossil_strdup(db_column_text(&q, 1));
3689
p->aVers[p->nVers].zDate = fossil_strdup(db_column_text(&q, 2));
3690
p->aVers[p->nVers].zUser = fossil_strdup(db_column_text(&q, 3));
3691
if( cnt>0 ){
3692
content_get(rid, &step);
3693
blob_to_utf8_no_bom(&step, 0);
3694
annotation_step(p, &step, p->nVers-1, annFlags);
3695
blob_reset(&step);
3696
}
3697
p->nVers++;
3698
cnt++;
3699
}
3700
3701
if( p->nVers==0 ){
3702
if( zRevision ){
3703
fossil_fatal("file %s does not exist in check-in %s",
3704
zFilename, zRevision);
3705
}else{
3706
fossil_fatal("no history for file: %s", zFilename);
3707
}
3708
}
3709
3710
db_finalize(&q);
3711
db_end_transaction(0);
3712
}
3713
3714
/*
3715
** Return a color from a gradient.
3716
*/
3717
unsigned gradient_color(unsigned c1, unsigned c2, int n, int i){
3718
unsigned c; /* Result color */
3719
unsigned x1, x2;
3720
if( i==0 || n==0 ) return c1;
3721
else if(i>=n) return c2;
3722
x1 = (c1>>16)&0xff;
3723
x2 = (c2>>16)&0xff;
3724
c = (x1*(n-i) + x2*i)/n<<16 & 0xff0000;
3725
x1 = (c1>>8)&0xff;
3726
x2 = (c2>>8)&0xff;
3727
c |= (x1*(n-i) + x2*i)/n<<8 & 0xff00;
3728
x1 = c1&0xff;
3729
x2 = c2&0xff;
3730
c |= (x1*(n-i) + x2*i)/n & 0xff;
3731
return c;
3732
}
3733
3734
/*
3735
** WEBPAGE: annotate
3736
** WEBPAGE: blame
3737
** WEBPAGE: praise
3738
**
3739
** URL: /annotate?checkin=ID&filename=FILENAME
3740
** URL: /blame?checkin=ID&filename=FILENAME
3741
** URL: /praise?checkin=ID&filename=FILENAME
3742
**
3743
** Show the most recent change to each line of a text file. /annotate shows
3744
** the date of the changes and the check-in hash (with a link to the
3745
** check-in). /blame and /praise also show the user who made the check-in.
3746
**
3747
** Reverse Annotations: Normally, these web pages look at versions of
3748
** FILENAME moving backwards in time back toward the root check-in. However,
3749
** if the origin= query parameter is used to specify some future check-in
3750
** (example: "origin=trunk") then these pages show changes moving towards
3751
** that alternative origin. Thus using "origin=trunk" on an historical
3752
** version of the file shows the first time each line in the file was changed
3753
** or removed by any subsequent check-in.
3754
**
3755
** Query parameters:
3756
**
3757
** checkin=ID The check-in at which to start the annotation
3758
** filename=FILENAME The filename.
3759
** filevers=BOOLEAN Show file versions rather than check-in versions
3760
** limit=LIMIT Limit the amount of analysis. LIMIT can be one of:
3761
** none No limit
3762
** Xs As much as can be computed in X seconds
3763
** N N versions
3764
** log=BOOLEAN Show a log of versions analyzed
3765
** origin=ID The origin check-in. If unspecified, the root
3766
** check-in over the entire repository is used.
3767
** Specify "origin=trunk" or similar for a reverse
3768
** annotation
3769
** w=BOOLEAN Ignore whitespace
3770
*/
3771
void annotation_page(void){
3772
int i;
3773
const char *zLimit; /* Depth limit */
3774
u64 annFlags = DIFF_STRIP_EOLCR;
3775
int showLog; /* True to display the log */
3776
int fileVers; /* Show file version instead of check-in versions */
3777
int ignoreWs; /* Ignore whitespace */
3778
const char *zFilename; /* Name of file to annotate */
3779
const char *zRevision; /* Name of check-in from which to start annotation */
3780
const char *zCI; /* The check-in containing zFilename */
3781
const char *zOrigin; /* The origin of the analysis */
3782
int szHash; /* Number of characters in %S display */
3783
char *zLink;
3784
Annotator ann;
3785
HQuery url;
3786
struct AnnVers *p;
3787
unsigned clr1, clr2, clr;
3788
int bBlame = g.zPath[0]!='a';/* True for BLAME output. False for ANNOTATE. */
3789
3790
/* Gather query parameters */
3791
login_check_credentials();
3792
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
3793
if( robot_restrict("annotate") ) return;
3794
fossil_nice_default();
3795
zFilename = P("filename");
3796
zRevision = PD("checkin",0);
3797
zOrigin = P("origin");
3798
zLimit = P("limit");
3799
showLog = PB("log");
3800
fileVers = PB("filevers");
3801
ignoreWs = PB("w");
3802
if( ignoreWs ) annFlags |= DIFF_IGNORE_ALLWS;
3803
cgi_check_for_malice();
3804
3805
/* compute the annotation */
3806
annotate_file(&ann, zFilename, zRevision, zLimit, zOrigin, annFlags);
3807
zCI = ann.aVers[0].zMUuid;
3808
3809
/* generate the web page */
3810
style_set_current_feature("annotate");
3811
style_header("Annotation For %h", zFilename);
3812
if( bBlame ){
3813
url_initialize(&url, "blame");
3814
}else{
3815
url_initialize(&url, "annotate");
3816
}
3817
url_add_parameter(&url, "checkin", P("checkin"));
3818
url_add_parameter(&url, "filename", zFilename);
3819
if( zLimit ){
3820
url_add_parameter(&url, "limit", zLimit);
3821
}
3822
url_add_parameter(&url, "w", ignoreWs ? "1" : "0");
3823
url_add_parameter(&url, "log", showLog ? "1" : "0");
3824
url_add_parameter(&url, "filevers", fileVers ? "1" : "0");
3825
style_submenu_checkbox("w", "Ignore Whitespace", 0, 0);
3826
style_submenu_checkbox("log", "Log", 0, "toggle_annotation_log");
3827
style_submenu_checkbox("filevers", "Link to Files", 0, 0);
3828
if( ann.bMoreToDo ){
3829
style_submenu_element("All Ancestors", "%s",
3830
url_render(&url, "limit", "none", 0, 0));
3831
}
3832
if( skin_detail_boolean("white-foreground") ){
3833
clr1 = 0xa04040;
3834
clr2 = 0x4059a0;
3835
}else{
3836
clr1 = 0xffb5b5; /* Recent changes: red (hot) */
3837
clr2 = 0xb5e0ff; /* Older changes: blue (cold) */
3838
}
3839
for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){
3840
clr = gradient_color(clr1, clr2, ann.nVers-1, i);
3841
ann.aVers[i].zBgColor = mprintf("#%06x", clr);
3842
}
3843
3844
@ <div id="annotation_log" style='display:%s(showLog?"block":"none");'>
3845
if( zOrigin ){
3846
zLink = href("%R/finfo?name=%t&from=%!S&to=%!S",zFilename,zCI,zOrigin);
3847
}else{
3848
zLink = href("%R/finfo?name=%t&from=%!S",zFilename,zCI);
3849
}
3850
@ <h2>Versions of %z(zLink)%h(zFilename)</a> analyzed:</h2>
3851
@ <ol>
3852
for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){
3853
@ <li><span style='background-color:%s(p->zBgColor);'>%s(p->zDate)
3854
@ check-in %z(href("%R/info/%!S",p->zMUuid))%S(p->zMUuid)</a>
3855
@ artifact %z(href("%R/artifact/%!S",p->zFUuid))%S(p->zFUuid)</a>
3856
@ </span>
3857
}
3858
@ </ol>
3859
@ <hr>
3860
@ </div>
3861
3862
if( !ann.bMoreToDo ){
3863
assert( ann.origId==0 ); /* bMoreToDo always set for a point-to-point */
3864
@ <h2>Origin for each line in
3865
@ %z(href("%R/finfo?name=%h&from=%!S", zFilename, zCI))%h(zFilename)</a>
3866
@ from check-in %z(href("%R/info/%!S",zCI))%S(zCI)</a>:</h2>
3867
}else if( ann.origId>0 ){
3868
@ <h2>Lines of
3869
@ %z(href("%R/finfo?name=%h&from=%!S", zFilename, zCI))%h(zFilename)</a>
3870
@ from check-in %z(href("%R/info/%!S",zCI))%S(zCI)</a>
3871
@ that are changed by the sequence of edits moving toward
3872
@ check-in %z(href("%R/info/%!S",zOrigin))%S(zOrigin)</a>:</h2>
3873
}else{
3874
@ <h2>Lines added by the %d(ann.nVers) most recent ancestors of
3875
@ %z(href("%R/finfo?name=%h&from=%!S", zFilename, zCI))%h(zFilename)</a>
3876
@ from check-in %z(href("%R/info/%!S",zCI))%S(zCI)</a>:</h2>
3877
}
3878
@ <pre>
3879
szHash = 10;
3880
for(i=0; i<ann.nOrig; i++){
3881
int iVers = ann.aOrig[i].iVers;
3882
char *z = (char*)ann.aOrig[i].z;
3883
int n = ann.aOrig[i].n;
3884
char zPrefix[300];
3885
z[n] = 0;
3886
if( iVers<0 && !ann.bMoreToDo ) iVers = ann.nVers-1;
3887
3888
if( bBlame ){
3889
if( iVers>=0 ){
3890
struct AnnVers *p = ann.aVers+iVers;
3891
const char *zUuid = fileVers ? p->zFUuid : p->zMUuid;
3892
char *zLink = xhref("target='infowindow'", "%R/info/%!S", zUuid);
3893
sqlite3_snprintf(sizeof(zPrefix), zPrefix,
3894
"<span style='background-color:%s'>"
3895
"%s%.10s</a> %s</span> %13.13s:",
3896
p->zBgColor, zLink, zUuid, p->zDate, p->zUser);
3897
fossil_free(zLink);
3898
}else{
3899
sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%*s", szHash+26, "");
3900
}
3901
}else{
3902
if( iVers>=0 ){
3903
struct AnnVers *p = ann.aVers+iVers;
3904
const char *zUuid = fileVers ? p->zFUuid : p->zMUuid;
3905
char *zLink = xhref("target='infowindow'", "%R/info/%!S", zUuid);
3906
sqlite3_snprintf(sizeof(zPrefix), zPrefix,
3907
"<span style='background-color:%s'>"
3908
"%s%.10s</a> %s</span> %4d:",
3909
p->zBgColor, zLink, zUuid, p->zDate, i+1);
3910
fossil_free(zLink);
3911
}else{
3912
sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%*s%4d:",szHash+12,"",i+1);
3913
}
3914
}
3915
@ %s(zPrefix) %h(z)
3916
3917
}
3918
@ </pre>
3919
style_finish_page();
3920
}
3921
3922
/*
3923
** COMMAND: annotate
3924
** COMMAND: blame
3925
** COMMAND: praise*
3926
**
3927
** Usage: %fossil annotate|blame|praise ?OPTIONS? FILENAME
3928
**
3929
** Output the text of a file with markings to show when each line of the file
3930
** was last modified. The version currently checked out is shown by default.
3931
** Other versions may be specified using the -r option. The "annotate" command
3932
** shows line numbers and omits the username. The "blame" and "praise" commands
3933
** show the user who made each check-in.
3934
**
3935
** Reverse Annotations: Normally, these commands look at versions of
3936
** FILENAME moving backwards in time back toward the root check-in, and
3937
** thus the output shows the most recent change to each line. However,
3938
** if the -o|--origin option is used to specify some future check-in
3939
** (example: "-o trunk") then these commands show changes moving towards
3940
** that alternative origin. Thus using "-o trunk" on an historical version
3941
** of the file shows the first time each line in the file was changed or
3942
** removed by any subsequent check-in.
3943
**
3944
** With -t or -T, the "blame" and "praise" commands show for each file the
3945
** latest (relative to the revision given by -r) check-in that modified it and
3946
** the check-in's author. If not given, the revision defaults to "current" for
3947
** a check-out. Option -T additionally shows a comment snippet for the check-in.
3948
**
3949
** Options:
3950
** --filevers Show file version numbers rather than
3951
** check-in versions
3952
** -r|--revision VERSION The specific check-in containing the file
3953
** -l|--log List all versions analyzed
3954
** -n|--limit LIMIT LIMIT can be one of:
3955
** N Up to N versions
3956
** Xs As much as possible in X seconds
3957
** none No limit
3958
** -o|--origin VERSION The origin check-in. By default this is the
3959
** root of the repository. Set to the name of
3960
** the main branch (usually "trunk") or
3961
** similar for a reverse annotation.
3962
** -t Show latest check-in and its author for each
3963
** tracked file in the tree as of VERSION
3964
** -T Like -t, plus comment snippet
3965
** -w|--ignore-all-space Ignore white space when comparing lines
3966
** -Z|--ignore-trailing-space Ignore whitespace at line end
3967
**
3968
** See also: [[info]], [[finfo]], [[timeline]]
3969
*/
3970
void annotate_cmd(void){
3971
const char *zRevision; /* Revision name, or NULL for current check-in */
3972
Annotator ann; /* The annotation of the file */
3973
int i; /* Loop counter */
3974
const char *zLimit; /* The value to the -n|--limit option */
3975
const char *zOrig; /* The value for -o|--origin */
3976
int showLog; /* True to show the log */
3977
int fileVers; /* Show file version instead of check-in versions */
3978
u64 annFlags = 0; /* Flags to control annotation properties */
3979
int bBlame = 0; /* True for BLAME output. False for ANNOTATE. */
3980
int bTreeInfo = 0; /* Show for the entire tree: 1=checkin, 2=with comment */
3981
int szHash; /* Display size of a version hash */
3982
Blob treename; /* Name of file to be annotated */
3983
char *zFilename; /* Name of file to be annotated */
3984
3985
bBlame = g.argv[1][0]!='a';
3986
if( find_option("t","t",0)!=0 ) bTreeInfo = 1;
3987
if( find_option("T","T",0)!=0 ) bTreeInfo = 2;
3988
zRevision = find_option("revision","r",1);
3989
if( bBlame && bTreeInfo ){
3990
if( find_repository_option()!=0 && zRevision==0 ){
3991
fossil_fatal("the -r is required in addition to -R");
3992
}
3993
db_find_and_open_repository(0, 0);
3994
if( zRevision==0 ) zRevision = "current";
3995
ls_cmd_rev(zRevision,1,1,0,1,bTreeInfo,0,0);
3996
return;
3997
}
3998
zLimit = find_option("limit","n",1);
3999
zOrig = fin

Keyboard Shortcuts

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