|
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
|
"︙</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\">︙</td>" |
|
1639
|
"<td></td><td></td>" |
|
1640
|
"<td class=\"diffln difflnr difflne\">︙</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], ">\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], "<\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 |