|
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 implement the "diff" command |
|
19
|
*/ |
|
20
|
#include "config.h" |
|
21
|
#include "diffcmd.h" |
|
22
|
#include <assert.h> |
|
23
|
|
|
24
|
/* includes needed to catch interrupts */ |
|
25
|
#ifdef _WIN32 |
|
26
|
# include <windows.h> |
|
27
|
#else |
|
28
|
# include <signal.h> |
|
29
|
#endif |
|
30
|
|
|
31
|
/* |
|
32
|
** Use the right null device for the platform. |
|
33
|
*/ |
|
34
|
#if defined(_WIN32) |
|
35
|
# define NULL_DEVICE "NUL" |
|
36
|
#else |
|
37
|
# define NULL_DEVICE "/dev/null" |
|
38
|
#endif |
|
39
|
|
|
40
|
/* |
|
41
|
** Used when the name for the diff is unknown. |
|
42
|
*/ |
|
43
|
#define DIFF_NO_NAME "(unknown)" |
|
44
|
|
|
45
|
/* |
|
46
|
** Use the "exec-rel-paths" setting and the --exec-abs-paths and |
|
47
|
** --exec-rel-paths command line options to determine whether |
|
48
|
** certain external commands are executed using relative paths. |
|
49
|
*/ |
|
50
|
static int determine_exec_relative_option(int force){ |
|
51
|
static int relativePaths = -1; |
|
52
|
if( force || relativePaths==-1 ){ |
|
53
|
int relPathOption = find_option("exec-rel-paths", 0, 0)!=0; |
|
54
|
int absPathOption = find_option("exec-abs-paths", 0, 0)!=0; |
|
55
|
#if defined(FOSSIL_ENABLE_EXEC_REL_PATHS) |
|
56
|
relativePaths = db_get_boolean("exec-rel-paths", 1); |
|
57
|
#else |
|
58
|
relativePaths = db_get_boolean("exec-rel-paths", 0); |
|
59
|
#endif |
|
60
|
if( relPathOption ){ relativePaths = 1; } |
|
61
|
if( absPathOption ){ relativePaths = 0; } |
|
62
|
} |
|
63
|
return relativePaths; |
|
64
|
} |
|
65
|
|
|
66
|
#if INTERFACE |
|
67
|
/* |
|
68
|
** An array of FileDirList objects describe the files and directories listed |
|
69
|
** on the command line of a "diff" command. Only those objects listed are |
|
70
|
** actually diffed. |
|
71
|
*/ |
|
72
|
struct FileDirList { |
|
73
|
int nUsed; /* Number of times each entry is used */ |
|
74
|
int nName; /* Length of the entry */ |
|
75
|
char *zName; /* Text of the entry */ |
|
76
|
}; |
|
77
|
#endif |
|
78
|
|
|
79
|
/* |
|
80
|
** Return true if zFile is a file named on the azInclude[] list or is |
|
81
|
** a file in a directory named on the azInclude[] list. |
|
82
|
** |
|
83
|
** if azInclude is NULL, then always include zFile. |
|
84
|
*/ |
|
85
|
static int file_dir_match(FileDirList *p, const char *zFile){ |
|
86
|
if( p==0 || strcmp(p->zName,".")==0 ) return 1; |
|
87
|
if( filenames_are_case_sensitive() ){ |
|
88
|
while( p->zName ){ |
|
89
|
if( strcmp(zFile, p->zName)==0 |
|
90
|
|| (strncmp(zFile, p->zName, p->nName)==0 |
|
91
|
&& zFile[p->nName]=='/') |
|
92
|
){ |
|
93
|
break; |
|
94
|
} |
|
95
|
p++; |
|
96
|
} |
|
97
|
}else{ |
|
98
|
while( p->zName ){ |
|
99
|
if( fossil_stricmp(zFile, p->zName)==0 |
|
100
|
|| (fossil_strnicmp(zFile, p->zName, p->nName)==0 |
|
101
|
&& zFile[p->nName]=='/') |
|
102
|
){ |
|
103
|
break; |
|
104
|
} |
|
105
|
p++; |
|
106
|
} |
|
107
|
} |
|
108
|
if( p->zName ){ |
|
109
|
p->nUsed++; |
|
110
|
return 1; |
|
111
|
} |
|
112
|
return 0; |
|
113
|
} |
|
114
|
|
|
115
|
/* |
|
116
|
** Print details about the compared versions - possibly the working directory |
|
117
|
** or the undo buffer. For check-ins, show hash and commit time. |
|
118
|
** |
|
119
|
** This is intended primarily to go into the "header garbage" that is ignored |
|
120
|
** by patch(1). |
|
121
|
** |
|
122
|
** zFrom and zTo are interpreted as symbolic version names, unless they |
|
123
|
** start with '(', in which case they are printed directly. |
|
124
|
*/ |
|
125
|
void diff_print_versions(const char *zFrom, const char *zTo, DiffConfig *pCfg){ |
|
126
|
if( (pCfg->diffFlags & (DIFF_SIDEBYSIDE|DIFF_BRIEF|DIFF_NUMSTAT| |
|
127
|
DIFF_HTML|DIFF_WEBPAGE|DIFF_BROWSER|DIFF_JSON|DIFF_TCL))==0 ){ |
|
128
|
fossil_print("Fossil-Diff-From: %s\n", |
|
129
|
zFrom[0]=='(' ? zFrom : mprintf("%S %s", |
|
130
|
rid_to_uuid(symbolic_name_to_rid(zFrom, "ci")), |
|
131
|
db_text("","SELECT datetime(%f)||' UTC'", |
|
132
|
symbolic_name_to_mtime(zFrom, 0, 0)))); |
|
133
|
fossil_print("Fossil-Diff-To: %s\n", |
|
134
|
zTo[0]=='(' ? zTo : mprintf("%S %s", |
|
135
|
rid_to_uuid(symbolic_name_to_rid(zTo, "ci")), |
|
136
|
db_text("","SELECT datetime(%f)||' UTC'", |
|
137
|
symbolic_name_to_mtime(zTo, 0, 1)))); |
|
138
|
fossil_print("%.66c\n", '-'); |
|
139
|
} |
|
140
|
} |
|
141
|
|
|
142
|
/* |
|
143
|
** Print the "Index:" message that patches wants to see at the top of a diff. |
|
144
|
*/ |
|
145
|
void diff_print_index(const char *zFile, DiffConfig *pCfg, Blob *pOut){ |
|
146
|
if( (pCfg->diffFlags & |
|
147
|
(DIFF_SIDEBYSIDE|DIFF_BRIEF|DIFF_NUMSTAT|DIFF_JSON| |
|
148
|
DIFF_WEBPAGE|DIFF_TCL))==0 |
|
149
|
){ |
|
150
|
blob_appendf(pOut, "Index: %s\n%.66c\n", zFile, '='); |
|
151
|
} |
|
152
|
} |
|
153
|
|
|
154
|
/* |
|
155
|
** Print the +++/--- filename lines or whatever filename information |
|
156
|
** is appropriate for the output format. |
|
157
|
** |
|
158
|
*/ |
|
159
|
void diff_print_filenames( |
|
160
|
const char *zLeft, /* Name of the left file */ |
|
161
|
const char *zRight, /* Name of the right file */ |
|
162
|
DiffConfig *pCfg, /* Diff configuration */ |
|
163
|
Blob *pOut /* Write to this blob, or stdout of this is NULL */ |
|
164
|
){ |
|
165
|
u64 diffFlags = pCfg->diffFlags; |
|
166
|
/* Standardize on /dev/null, regardless of platform. */ |
|
167
|
if( pCfg->diffFlags & DIFF_FILE_ADDED ) zLeft = "/dev/null"; |
|
168
|
if( pCfg->diffFlags & DIFF_FILE_DELETED ) zRight = "/dev/null"; |
|
169
|
if( pCfg->azLabel[0] ) zLeft = pCfg->azLabel[0]; |
|
170
|
if( pCfg->azLabel[1] ) zRight = pCfg->azLabel[1]; |
|
171
|
if( diffFlags & (DIFF_BRIEF|DIFF_RAW) ){ |
|
172
|
/* no-op */ |
|
173
|
}else if( diffFlags & DIFF_DEBUG ){ |
|
174
|
blob_appendf(pOut, "FILE-LEFT %s\nFILE-RIGHT %s\n", zLeft, zRight); |
|
175
|
}else if( diffFlags & DIFF_WEBPAGE ){ |
|
176
|
if( fossil_strcmp(zLeft,zRight)==0 ){ |
|
177
|
blob_appendf(pOut,"<h1>%h</h1>\n", zLeft); |
|
178
|
}else{ |
|
179
|
blob_appendf(pOut,"<h1>%h ⇆ %h</h1>\n", zLeft, zRight); |
|
180
|
} |
|
181
|
}else if( diffFlags & (DIFF_TCL|DIFF_JSON) ){ |
|
182
|
if( diffFlags & DIFF_TCL ){ |
|
183
|
blob_append(pOut, "FILE ", 5); |
|
184
|
blob_append_tcl_literal(pOut, zLeft, (int)strlen(zLeft)); |
|
185
|
blob_append_char(pOut, ' '); |
|
186
|
blob_append_tcl_literal(pOut, zRight, (int)strlen(zRight)); |
|
187
|
blob_append_char(pOut, '\n'); |
|
188
|
}else{ |
|
189
|
if( pOut ) blob_trim(pOut); |
|
190
|
blob_append(pOut, (pCfg->nFile==0 ? "[{" : ",\n{"), -1); |
|
191
|
pCfg->nFile++; |
|
192
|
blob_append(pOut, "\n \"leftname\":", -1); |
|
193
|
blob_append_json_literal(pOut, zLeft, (int)strlen(zLeft)); |
|
194
|
blob_append(pOut, ",\n \"rightname\":", -1); |
|
195
|
blob_append_json_literal(pOut, zRight, (int)strlen(zRight)); |
|
196
|
blob_append(pOut, ",\n \"diff\":\n", -1); |
|
197
|
} |
|
198
|
}else if( diffFlags & DIFF_SIDEBYSIDE ){ |
|
199
|
int w = diff_width(pCfg); |
|
200
|
int n1 = strlen(zLeft); |
|
201
|
int n2 = strlen(zRight); |
|
202
|
int x; |
|
203
|
if( n1==n2 && fossil_strcmp(zLeft,zRight)==0 ){ |
|
204
|
if( n1>w*2 ) n1 = w*2; |
|
205
|
x = w*2+17 - (n1+2); |
|
206
|
blob_appendf(pOut, "%.*c %.*s %.*c\n", |
|
207
|
x/2, '=', n1, zLeft, (x+1)/2, '='); |
|
208
|
}else{ |
|
209
|
if( w<20 ) w = 20; |
|
210
|
if( n1>w-10 ) n1 = w - 10; |
|
211
|
if( n2>w-10 ) n2 = w - 10; |
|
212
|
blob_appendf(pOut, "%.*c %.*s %.*c versus %.*c %.*s %.*c\n", |
|
213
|
(w-n1+10)/2, '=', n1, zLeft, (w-n1+1)/2, '=', |
|
214
|
(w-n2)/2, '=', n2, zRight, (w-n2+1)/2, '='); |
|
215
|
} |
|
216
|
}else{ |
|
217
|
blob_appendf(pOut, "--- %s\t\n+++ %s\t\n", zLeft, zRight); |
|
218
|
} |
|
219
|
} |
|
220
|
|
|
221
|
|
|
222
|
/* |
|
223
|
** Default header texts for diff with --webpage |
|
224
|
*/ |
|
225
|
static const char zWebpageHdr[] = |
|
226
|
@ <!DOCTYPE html> |
|
227
|
@ <html> |
|
228
|
@ <head> |
|
229
|
@ <meta charset="UTF-8"> |
|
230
|
@ <style> |
|
231
|
@ body { |
|
232
|
@ background-color: white; |
|
233
|
@ } |
|
234
|
@ h1 { |
|
235
|
@ font-size: 150%; |
|
236
|
@ } |
|
237
|
@ |
|
238
|
@ table.diff { |
|
239
|
@ width: 100%; |
|
240
|
@ border-spacing: 0; |
|
241
|
@ border: 1px solid black; |
|
242
|
@ line-height: inherit; |
|
243
|
@ font-size: inherit; |
|
244
|
@ } |
|
245
|
@ table.diff td { |
|
246
|
@ vertical-align: top; |
|
247
|
@ line-height: inherit; |
|
248
|
@ font-size: inherit; |
|
249
|
@ } |
|
250
|
@ table.diff pre { |
|
251
|
@ margin: 0 0 0 0; |
|
252
|
@ line-height: inherit; |
|
253
|
@ font-size: inherit; |
|
254
|
@ } |
|
255
|
@ td.diffln { |
|
256
|
@ width: fit-content; |
|
257
|
@ text-align: right; |
|
258
|
@ padding: 0 1em 0 0; |
|
259
|
@ } |
|
260
|
@ td.difflne { |
|
261
|
@ padding-bottom: 0.4em; |
|
262
|
@ } |
|
263
|
@ td.diffsep { |
|
264
|
@ width: fit-content; |
|
265
|
@ padding: 0 0.3em 0 1em; |
|
266
|
@ line-height: inherit; |
|
267
|
@ font-size: inherit; |
|
268
|
@ } |
|
269
|
@ td.diffsep pre { |
|
270
|
@ line-height: inherit; |
|
271
|
@ font-size: inherit; |
|
272
|
@ } |
|
273
|
@ td.difftxt pre { |
|
274
|
@ overflow-x: auto; |
|
275
|
@ } |
|
276
|
@ td.diffln ins { |
|
277
|
@ background-color: #a0e4b2; |
|
278
|
@ text-decoration: none; |
|
279
|
@ line-height: inherit; |
|
280
|
@ font-size: inherit; |
|
281
|
@ } |
|
282
|
@ td.diffln del { |
|
283
|
@ background-color: #ffc0c0; |
|
284
|
@ text-decoration: none; |
|
285
|
@ line-height: inherit; |
|
286
|
@ font-size: inherit; |
|
287
|
@ } |
|
288
|
@ td.difftxt del { |
|
289
|
@ background-color: #ffe8e8; |
|
290
|
@ text-decoration: none; |
|
291
|
@ line-height: inherit; |
|
292
|
@ font-size: inherit; |
|
293
|
@ } |
|
294
|
@ td.difftxt del > del { |
|
295
|
@ background-color: #ffc0c0; |
|
296
|
@ text-decoration: none; |
|
297
|
@ font-weight: bold; |
|
298
|
@ } |
|
299
|
@ td.difftxt del > del.edit { |
|
300
|
@ background-color: #c0c0ff; |
|
301
|
@ text-decoration: none; |
|
302
|
@ font-weight: bold; |
|
303
|
@ } |
|
304
|
@ td.difftxt ins { |
|
305
|
@ background-color: #dafbe1; |
|
306
|
@ text-decoration: none; |
|
307
|
@ line-height: inherit; |
|
308
|
@ font-size: inherit; |
|
309
|
@ } |
|
310
|
@ td.difftxt ins > ins { |
|
311
|
@ background-color: #a0e4b2; |
|
312
|
@ text-decoration: none; |
|
313
|
@ font-weight: bold; |
|
314
|
@ } |
|
315
|
@ td.difftxt ins > ins.edit { |
|
316
|
@ background-color: #c0c0ff; |
|
317
|
@ text-decoration: none; |
|
318
|
@ font-weight: bold; |
|
319
|
@ } |
|
320
|
@ @media (prefers-color-scheme: dark) { |
|
321
|
@ body { |
|
322
|
@ background-color: #353535; |
|
323
|
@ color: #ffffff; |
|
324
|
@ } |
|
325
|
@ td.diffln ins { |
|
326
|
@ background-color: #559855; |
|
327
|
@ color: #000000; |
|
328
|
@ } |
|
329
|
@ td.diffln del { |
|
330
|
@ background-color: #cc5555; |
|
331
|
@ color: #000000; |
|
332
|
@ } |
|
333
|
@ td.difftxt del { |
|
334
|
@ background-color: #f9cfcf; |
|
335
|
@ color: #000000; |
|
336
|
@ } |
|
337
|
@ td.difftxt del > del { |
|
338
|
@ background-color: #cc5555; |
|
339
|
@ color: #000000; |
|
340
|
@ } |
|
341
|
@ td.difftxt ins { |
|
342
|
@ background-color: #a2dbb2; |
|
343
|
@ color: #000000; |
|
344
|
@ } |
|
345
|
@ td.difftxt ins > ins { |
|
346
|
@ background-color: #559855; |
|
347
|
@ } |
|
348
|
@ } |
|
349
|
@ |
|
350
|
@ </style> |
|
351
|
@ </head> |
|
352
|
@ <body> |
|
353
|
; |
|
354
|
static const char zWebpageHdrDark[] = |
|
355
|
@ <!DOCTYPE html> |
|
356
|
@ <html> |
|
357
|
@ <head> |
|
358
|
@ <meta charset="UTF-8"> |
|
359
|
@ <style> |
|
360
|
@ body { |
|
361
|
@ background-color: #353535; |
|
362
|
@ color: #ffffff; |
|
363
|
@ } |
|
364
|
@ h1 { |
|
365
|
@ font-size: 150%; |
|
366
|
@ } |
|
367
|
@ |
|
368
|
@ table.diff { |
|
369
|
@ width: 100%; |
|
370
|
@ border-spacing: 0; |
|
371
|
@ border: 1px solid black; |
|
372
|
@ line-height: inherit; |
|
373
|
@ font-size: inherit; |
|
374
|
@ } |
|
375
|
@ table.diff td { |
|
376
|
@ vertical-align: top; |
|
377
|
@ line-height: inherit; |
|
378
|
@ font-size: inherit; |
|
379
|
@ } |
|
380
|
@ table.diff pre { |
|
381
|
@ margin: 0 0 0 0; |
|
382
|
@ line-height: inherit; |
|
383
|
@ font-size: inherit; |
|
384
|
@ } |
|
385
|
@ td.diffln { |
|
386
|
@ width: fit-content; |
|
387
|
@ text-align: right; |
|
388
|
@ padding: 0 1em 0 0; |
|
389
|
@ } |
|
390
|
@ td.difflne { |
|
391
|
@ padding-bottom: 0.4em; |
|
392
|
@ } |
|
393
|
@ td.diffsep { |
|
394
|
@ width: fit-content; |
|
395
|
@ padding: 0 0.3em 0 1em; |
|
396
|
@ line-height: inherit; |
|
397
|
@ font-size: inherit; |
|
398
|
@ } |
|
399
|
@ td.diffsep pre { |
|
400
|
@ line-height: inherit; |
|
401
|
@ font-size: inherit; |
|
402
|
@ } |
|
403
|
@ td.difftxt pre { |
|
404
|
@ overflow-x: auto; |
|
405
|
@ } |
|
406
|
@ td.diffln ins { |
|
407
|
@ background-color: #559855; |
|
408
|
@ color: #000000; |
|
409
|
@ text-decoration: none; |
|
410
|
@ line-height: inherit; |
|
411
|
@ font-size: inherit; |
|
412
|
@ } |
|
413
|
@ td.diffln del { |
|
414
|
@ background-color: #cc5555; |
|
415
|
@ color: #000000; |
|
416
|
@ text-decoration: none; |
|
417
|
@ line-height: inherit; |
|
418
|
@ font-size: inherit; |
|
419
|
@ } |
|
420
|
@ td.difftxt del { |
|
421
|
@ background-color: #f9cfcf; |
|
422
|
@ color: #000000; |
|
423
|
@ text-decoration: none; |
|
424
|
@ line-height: inherit; |
|
425
|
@ font-size: inherit; |
|
426
|
@ } |
|
427
|
@ td.difftxt del > del { |
|
428
|
@ background-color: #cc5555; |
|
429
|
@ color: #000000; |
|
430
|
@ text-decoration: none; |
|
431
|
@ font-weight: bold; |
|
432
|
@ } |
|
433
|
@ td.difftxt del > del.edit { |
|
434
|
@ background-color: #c0c0ff; |
|
435
|
@ text-decoration: none; |
|
436
|
@ font-weight: bold; |
|
437
|
@ } |
|
438
|
@ td.difftxt ins { |
|
439
|
@ background-color: #a2dbb2; |
|
440
|
@ color: #000000; |
|
441
|
@ text-decoration: none; |
|
442
|
@ line-height: inherit; |
|
443
|
@ font-size: inherit; |
|
444
|
@ } |
|
445
|
@ td.difftxt ins > ins { |
|
446
|
@ background-color: #559855; |
|
447
|
@ text-decoration: none; |
|
448
|
@ font-weight: bold; |
|
449
|
@ } |
|
450
|
@ td.difftxt ins > ins.edit { |
|
451
|
@ background-color: #c0c0ff; |
|
452
|
@ text-decoration: none; |
|
453
|
@ font-weight: bold; |
|
454
|
@ } |
|
455
|
@ |
|
456
|
@ </style> |
|
457
|
@ </head> |
|
458
|
@ <body> |
|
459
|
; |
|
460
|
const char zWebpageEnd[] = |
|
461
|
@ </body> |
|
462
|
@ </html> |
|
463
|
; |
|
464
|
|
|
465
|
/* |
|
466
|
** State variables used by the --browser option for diff. These must |
|
467
|
** be static variables, not elements of DiffConfig, since they are |
|
468
|
** used by the interrupt handler. |
|
469
|
*/ |
|
470
|
static char *tempDiffFilename; /* File holding the diff HTML */ |
|
471
|
static FILE *diffOut; /* Open to write into tempDiffFilename */ |
|
472
|
|
|
473
|
/* Amount of delay (in milliseconds) between launching the |
|
474
|
** web browser and deleting the temporary file used by --browser |
|
475
|
*/ |
|
476
|
#ifndef FOSSIL_BROWSER_DIFF_DELAY |
|
477
|
# define FOSSIL_BROWSER_DIFF_DELAY 5000 /* 5 seconds by default */ |
|
478
|
#endif |
|
479
|
|
|
480
|
/* |
|
481
|
** If we catch a single while writing the temporary file for the --browser |
|
482
|
** diff output, then delete the temporary file and exit. |
|
483
|
*/ |
|
484
|
static void diff_www_interrupt(int NotUsed){ |
|
485
|
(void)NotUsed; |
|
486
|
if( diffOut ) fclose(diffOut); |
|
487
|
if( tempDiffFilename ) file_delete(tempDiffFilename); |
|
488
|
exit(1); |
|
489
|
} |
|
490
|
#ifdef _WIN32 |
|
491
|
static BOOL WINAPI diff_console_ctrl_handler(DWORD dwCtrlType){ |
|
492
|
if( dwCtrlType==CTRL_C_EVENT ) diff_www_interrupt(0); |
|
493
|
return FALSE; |
|
494
|
} |
|
495
|
#endif |
|
496
|
|
|
497
|
|
|
498
|
/* |
|
499
|
** Do preliminary setup and output before computing a diff. |
|
500
|
** |
|
501
|
** For --browser, redirect stdout to a temporary file that will |
|
502
|
** hold the result. Make arrangements to delete that temporary |
|
503
|
** file if the diff is interrupted. |
|
504
|
** |
|
505
|
** For --browser and --webpage, output the HTML header. |
|
506
|
*/ |
|
507
|
void diff_begin(DiffConfig *pCfg){ |
|
508
|
if( (pCfg->diffFlags & DIFF_BROWSER)!=0 ){ |
|
509
|
tempDiffFilename = fossil_temp_filename(); |
|
510
|
tempDiffFilename = sqlite3_mprintf("%z.html", tempDiffFilename); |
|
511
|
diffOut = fossil_freopen(tempDiffFilename,"wb",stdout); |
|
512
|
if( diffOut==0 ){ |
|
513
|
fossil_fatal("unable to create temporary file \"%s\"", |
|
514
|
tempDiffFilename); |
|
515
|
} |
|
516
|
#ifndef _WIN32 |
|
517
|
signal(SIGINT, diff_www_interrupt); |
|
518
|
#else |
|
519
|
SetConsoleCtrlHandler(diff_console_ctrl_handler, TRUE); |
|
520
|
#endif |
|
521
|
} |
|
522
|
if( (pCfg->diffFlags & DIFF_WEBPAGE)!=0 ){ |
|
523
|
fossil_print("%s",(pCfg->diffFlags & DIFF_DARKMODE)!=0 ? zWebpageHdrDark : |
|
524
|
zWebpageHdr); |
|
525
|
fflush(stdout); |
|
526
|
} |
|
527
|
} |
|
528
|
|
|
529
|
/* Do any final output required by a diff and complete the diff |
|
530
|
** process. |
|
531
|
** |
|
532
|
** For --browser and --webpage, output any javascript required by |
|
533
|
** the diff. (Currently JS is only needed for side-by-side diffs). |
|
534
|
** |
|
535
|
** For --browser, close the connection to the temporary file, then |
|
536
|
** launch a web browser to view the file. After a delay |
|
537
|
** of FOSSIL_BROWSER_DIFF_DELAY milliseconds, delete the temp file. |
|
538
|
*/ |
|
539
|
void diff_end(DiffConfig *pCfg, int nErr){ |
|
540
|
if( (pCfg->diffFlags & DIFF_WEBPAGE)!=0 ){ |
|
541
|
if( pCfg->diffFlags & DIFF_SIDEBYSIDE ){ |
|
542
|
const unsigned char *zJs = builtin_file("diff.js", 0); |
|
543
|
fossil_print("<script>\n%s</script>\n", zJs); |
|
544
|
} |
|
545
|
fossil_print("%s", zWebpageEnd); |
|
546
|
} |
|
547
|
if( (pCfg->diffFlags & DIFF_BROWSER)!=0 && nErr==0 ){ |
|
548
|
char *zCmd = mprintf("%s %$", fossil_web_browser(), tempDiffFilename); |
|
549
|
fclose(diffOut); |
|
550
|
diffOut = fossil_freopen(NULL_DEVICE, "wb", stdout); |
|
551
|
fossil_system(zCmd); |
|
552
|
fossil_free(zCmd); |
|
553
|
diffOut = 0; |
|
554
|
sqlite3_sleep(FOSSIL_BROWSER_DIFF_DELAY); |
|
555
|
file_delete(tempDiffFilename); |
|
556
|
sqlite3_free(tempDiffFilename); |
|
557
|
tempDiffFilename = 0; |
|
558
|
} |
|
559
|
if( (pCfg->diffFlags & DIFF_JSON)!=0 && pCfg->nFile>0 ){ |
|
560
|
fossil_print("]\n"); |
|
561
|
} |
|
562
|
} |
|
563
|
|
|
564
|
/* |
|
565
|
** Show the difference between two files, one in memory and one on disk. |
|
566
|
** |
|
567
|
** The difference is the set of edits needed to transform pFile1 into |
|
568
|
** zFile2. The content of pFile1 is in memory. zFile2 exists on disk. |
|
569
|
*/ |
|
570
|
void diff_file( |
|
571
|
Blob *pFile1, /* In memory content to compare from */ |
|
572
|
const char *zFile2, /* On disk content to compare to */ |
|
573
|
const char *zName, /* Display name of the file */ |
|
574
|
DiffConfig *pCfg, /* Flags to control the diff */ |
|
575
|
Blob *pOut /* Blob to store diff output */ |
|
576
|
){ |
|
577
|
if( pCfg->zDiffCmd==0 ){ |
|
578
|
Blob out; /* Diff output text */ |
|
579
|
Blob file2; /* Content of zFile2 */ |
|
580
|
const char *zName2; /* Name of zFile2 for display */ |
|
581
|
|
|
582
|
/* Read content of zFile2 into memory */ |
|
583
|
blob_zero(&file2); |
|
584
|
if( pCfg->diffFlags & DIFF_FILE_DELETED || file_size(zFile2, ExtFILE)<0 ){ |
|
585
|
zName2 = NULL_DEVICE; |
|
586
|
}else{ |
|
587
|
blob_read_from_file(&file2, zFile2, ExtFILE); |
|
588
|
zName2 = zName; |
|
589
|
} |
|
590
|
|
|
591
|
/* Compute and output the differences */ |
|
592
|
if( (pCfg->diffFlags & DIFF_BRIEF) && !(pCfg->diffFlags & DIFF_NUMSTAT) ){ |
|
593
|
if( blob_compare(pFile1, &file2) ){ |
|
594
|
fossil_print("CHANGED %s\n", zName); |
|
595
|
} |
|
596
|
}else{ |
|
597
|
blob_zero(&out); |
|
598
|
text_diff(pFile1, &file2, &out, pCfg); |
|
599
|
if( blob_size(&out) ){ |
|
600
|
if( pCfg->diffFlags & DIFF_NUMSTAT ){ |
|
601
|
if( !(pCfg->diffFlags & DIFF_BRIEF) ){ |
|
602
|
blob_appendf(pOut, "%s %s\n", blob_str(&out), zName); |
|
603
|
} |
|
604
|
}else{ |
|
605
|
diff_print_filenames(zName, zName2, pCfg, pOut); |
|
606
|
blob_appendf(pOut, "%s\n", blob_str(&out)); |
|
607
|
} |
|
608
|
} |
|
609
|
blob_reset(&out); |
|
610
|
} |
|
611
|
|
|
612
|
/* Release memory resources */ |
|
613
|
blob_reset(&file2); |
|
614
|
}else{ |
|
615
|
Blob nameFile1; /* Name of temporary file to old pFile1 content */ |
|
616
|
Blob cmd; /* Text of command to run */ |
|
617
|
int useTempfile = 1; |
|
618
|
|
|
619
|
if( (pCfg->diffFlags & DIFF_INCBINARY)==0 ){ |
|
620
|
Blob file2; |
|
621
|
if( looks_like_binary(pFile1) ){ |
|
622
|
fossil_print("%s",DIFF_CANNOT_COMPUTE_BINARY); |
|
623
|
return; |
|
624
|
} |
|
625
|
if( pCfg->zBinGlob ){ |
|
626
|
Glob *pBinary = glob_create(pCfg->zBinGlob); |
|
627
|
if( glob_match(pBinary, zName) ){ |
|
628
|
fossil_print("%s",DIFF_CANNOT_COMPUTE_BINARY); |
|
629
|
glob_free(pBinary); |
|
630
|
return; |
|
631
|
} |
|
632
|
glob_free(pBinary); |
|
633
|
} |
|
634
|
blob_zero(&file2); |
|
635
|
if( file_size(zFile2, ExtFILE)>=0 ){ |
|
636
|
blob_read_from_file(&file2, zFile2, ExtFILE); |
|
637
|
} |
|
638
|
if( looks_like_binary(&file2) ){ |
|
639
|
fossil_print("%s",DIFF_CANNOT_COMPUTE_BINARY); |
|
640
|
blob_reset(&file2); |
|
641
|
return; |
|
642
|
} |
|
643
|
blob_reset(&file2); |
|
644
|
} |
|
645
|
|
|
646
|
/* Construct a temporary file to hold pFile1 based on the name of |
|
647
|
** zFile2 */ |
|
648
|
file_tempname(&nameFile1, zFile2, "orig"); |
|
649
|
#if !defined(_WIN32) |
|
650
|
/* On Unix, use /dev/null for added or deleted files. */ |
|
651
|
if( pCfg->diffFlags & DIFF_FILE_ADDED ){ |
|
652
|
blob_init(&nameFile1, NULL_DEVICE, -1); |
|
653
|
useTempfile = 0; |
|
654
|
}else if( pCfg->diffFlags & DIFF_FILE_DELETED ){ |
|
655
|
zFile2 = NULL_DEVICE; |
|
656
|
} |
|
657
|
#endif |
|
658
|
if( useTempfile ) blob_write_to_file(pFile1, blob_str(&nameFile1)); |
|
659
|
|
|
660
|
/* Construct the external diff command */ |
|
661
|
blob_zero(&cmd); |
|
662
|
blob_append(&cmd, pCfg->zDiffCmd, -1); |
|
663
|
if( pCfg->diffFlags & DIFF_INVERT ){ |
|
664
|
blob_append_escaped_arg(&cmd, zFile2, 1); |
|
665
|
blob_append_escaped_arg(&cmd, blob_str(&nameFile1), 1); |
|
666
|
}else{ |
|
667
|
blob_append_escaped_arg(&cmd, blob_str(&nameFile1), 1); |
|
668
|
blob_append_escaped_arg(&cmd, zFile2, 1); |
|
669
|
} |
|
670
|
|
|
671
|
/* Run the external diff command */ |
|
672
|
fossil_system(blob_str(&cmd)); |
|
673
|
|
|
674
|
/* Delete the temporary file and clean up memory used */ |
|
675
|
if( useTempfile ) file_delete(blob_str(&nameFile1)); |
|
676
|
blob_reset(&nameFile1); |
|
677
|
blob_reset(&cmd); |
|
678
|
} |
|
679
|
} |
|
680
|
|
|
681
|
/* |
|
682
|
** Show the difference between two files, both in memory. |
|
683
|
** |
|
684
|
** The difference is the set of edits needed to transform pFile1 into |
|
685
|
** pFile2. |
|
686
|
** |
|
687
|
** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the |
|
688
|
** command zDiffCmd to do the diffing. |
|
689
|
** |
|
690
|
** When using an external diff program, zBinGlob contains the GLOB patterns |
|
691
|
** for file names to treat as binary. If fIncludeBinary is zero, these files |
|
692
|
** will be skipped in addition to files that may contain binary content. |
|
693
|
*/ |
|
694
|
void diff_file_mem( |
|
695
|
Blob *pFile1, /* In memory content to compare from */ |
|
696
|
Blob *pFile2, /* In memory content to compare to */ |
|
697
|
const char *zName, /* Display name of the file */ |
|
698
|
DiffConfig *pCfg /* Diff flags */ |
|
699
|
){ |
|
700
|
if( (pCfg->diffFlags & DIFF_BRIEF) && !(pCfg->diffFlags & DIFF_NUMSTAT) ){ |
|
701
|
return; |
|
702
|
} |
|
703
|
if( pCfg->zDiffCmd==0 ){ |
|
704
|
Blob out; /* Diff output text */ |
|
705
|
|
|
706
|
blob_zero(&out); |
|
707
|
text_diff(pFile1, pFile2, &out, pCfg); |
|
708
|
if( pCfg->diffFlags & DIFF_NUMSTAT ){ |
|
709
|
if( !(pCfg->diffFlags & DIFF_BRIEF) ){ |
|
710
|
fossil_print("%s %s\n", blob_str(&out), zName); |
|
711
|
} |
|
712
|
}else{ |
|
713
|
diff_print_filenames(zName, zName, pCfg, 0); |
|
714
|
fossil_print("%s\n", blob_str(&out)); |
|
715
|
} |
|
716
|
|
|
717
|
/* Release memory resources */ |
|
718
|
blob_reset(&out); |
|
719
|
}else{ |
|
720
|
Blob cmd; |
|
721
|
Blob temp1; |
|
722
|
Blob temp2; |
|
723
|
int useTempfile1 = 1; |
|
724
|
int useTempfile2 = 1; |
|
725
|
|
|
726
|
if( (pCfg->diffFlags & DIFF_INCBINARY)==0 ){ |
|
727
|
if( looks_like_binary(pFile1) || looks_like_binary(pFile2) ){ |
|
728
|
fossil_print("%s",DIFF_CANNOT_COMPUTE_BINARY); |
|
729
|
return; |
|
730
|
} |
|
731
|
if( pCfg->zBinGlob ){ |
|
732
|
Glob *pBinary = glob_create(pCfg->zBinGlob); |
|
733
|
if( glob_match(pBinary, zName) ){ |
|
734
|
fossil_print("%s",DIFF_CANNOT_COMPUTE_BINARY); |
|
735
|
glob_free(pBinary); |
|
736
|
return; |
|
737
|
} |
|
738
|
glob_free(pBinary); |
|
739
|
} |
|
740
|
} |
|
741
|
|
|
742
|
/* Construct temporary file names */ |
|
743
|
file_tempname(&temp1, zName, "before"); |
|
744
|
file_tempname(&temp2, zName, "after"); |
|
745
|
#if !defined(_WIN32) |
|
746
|
/* On Unix, use /dev/null for added or deleted files. */ |
|
747
|
if( pCfg->diffFlags & DIFF_FILE_ADDED ){ |
|
748
|
useTempfile1 = 0; |
|
749
|
blob_init(&temp1, NULL_DEVICE, -1); |
|
750
|
}else if( pCfg->diffFlags & DIFF_FILE_DELETED ){ |
|
751
|
useTempfile2 = 0; |
|
752
|
blob_init(&temp2, NULL_DEVICE, -1); |
|
753
|
} |
|
754
|
#endif |
|
755
|
if( useTempfile1 ) blob_write_to_file(pFile1, blob_str(&temp1)); |
|
756
|
if( useTempfile2 ) blob_write_to_file(pFile2, blob_str(&temp2)); |
|
757
|
|
|
758
|
/* Construct the external diff command */ |
|
759
|
blob_zero(&cmd); |
|
760
|
blob_append(&cmd, pCfg->zDiffCmd, -1); |
|
761
|
blob_append_escaped_arg(&cmd, blob_str(&temp1), 1); |
|
762
|
blob_append_escaped_arg(&cmd, blob_str(&temp2), 1); |
|
763
|
|
|
764
|
/* Run the external diff command */ |
|
765
|
fossil_system(blob_str(&cmd)); |
|
766
|
|
|
767
|
/* Delete the temporary file and clean up memory used */ |
|
768
|
if( useTempfile1 ) file_delete(blob_str(&temp1)); |
|
769
|
if( useTempfile2 ) file_delete(blob_str(&temp2)); |
|
770
|
|
|
771
|
blob_reset(&temp1); |
|
772
|
blob_reset(&temp2); |
|
773
|
blob_reset(&cmd); |
|
774
|
} |
|
775
|
} |
|
776
|
|
|
777
|
/* |
|
778
|
** Return true if the disk file is identical to the Blob. Return zero |
|
779
|
** if the files differ in any way. |
|
780
|
*/ |
|
781
|
int file_same_as_blob(Blob *blob, const char *zDiskFile){ |
|
782
|
Blob file; |
|
783
|
int rc = 0; |
|
784
|
if( blob_size(blob)!=file_size(zDiskFile, ExtFILE) ) return 0; |
|
785
|
blob_zero(&file); |
|
786
|
blob_read_from_file(&file, zDiskFile, ExtFILE); |
|
787
|
if( blob_size(&file)!=blob_size(blob) ){ |
|
788
|
rc = 0; |
|
789
|
}else{ |
|
790
|
rc = memcmp(blob_buffer(&file), blob_buffer(blob), blob_size(&file))==0; |
|
791
|
} |
|
792
|
blob_reset(&file); |
|
793
|
return rc; |
|
794
|
} |
|
795
|
|
|
796
|
/* |
|
797
|
** Run a diff between the version zFrom and files on disk in the current |
|
798
|
** working checkout. zFrom might be NULL which means to simply show the |
|
799
|
** difference between the edited files on disk and the check-out on which |
|
800
|
** they are based. |
|
801
|
** |
|
802
|
** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the |
|
803
|
** command zDiffCmd to do the diffing. |
|
804
|
** |
|
805
|
** When using an external diff program, zBinGlob contains the GLOB patterns |
|
806
|
** for file names to treat as binary. If fIncludeBinary is zero, these files |
|
807
|
** will be skipped in addition to files that may contain binary content. |
|
808
|
*/ |
|
809
|
void diff_version_to_checkout( |
|
810
|
const char *zFrom, /* Version to difference from */ |
|
811
|
DiffConfig *pCfg, /* Flags controlling diff output */ |
|
812
|
FileDirList *pFileDir, /* Which files to diff */ |
|
813
|
Blob *pOut /* Blob to output diff instead of stdout */ |
|
814
|
){ |
|
815
|
int vid; |
|
816
|
Blob sql; |
|
817
|
Stmt q; |
|
818
|
int asNewFile; /* Treat non-existent files as empty files */ |
|
819
|
int isNumStat; /* True for --numstat */ |
|
820
|
|
|
821
|
asNewFile = (pCfg->diffFlags & (DIFF_VERBOSE|DIFF_NUMSTAT|DIFF_HTML))!=0; |
|
822
|
isNumStat = (pCfg->diffFlags & (DIFF_NUMSTAT|DIFF_TCL|DIFF_HTML))!=0; |
|
823
|
vid = db_lget_int("checkout", 0); |
|
824
|
vfile_check_signature(vid, CKSIG_ENOTFILE); |
|
825
|
blob_zero(&sql); |
|
826
|
db_begin_transaction(); |
|
827
|
if( zFrom ){ |
|
828
|
int rid = name_to_typed_rid(zFrom, "ci"); |
|
829
|
if( !is_a_version(rid) ){ |
|
830
|
fossil_fatal("no such check-in: %s", zFrom); |
|
831
|
} |
|
832
|
load_vfile_from_rid(rid); |
|
833
|
blob_append_sql(&sql, |
|
834
|
"SELECT v2.pathname, v2.deleted, v2.chnged, v2.rid==0, v1.rid, v1.islink" |
|
835
|
" FROM vfile v1, vfile v2 " |
|
836
|
" WHERE v1.pathname=v2.pathname AND v1.vid=%d AND v2.vid=%d" |
|
837
|
" AND (v2.deleted OR v2.chnged OR v1.mrid!=v2.rid)" |
|
838
|
"UNION " |
|
839
|
"SELECT pathname, 1, 0, 0, 0, islink" |
|
840
|
" FROM vfile v1" |
|
841
|
" WHERE v1.vid=%d" |
|
842
|
" AND NOT EXISTS(SELECT 1 FROM vfile v2" |
|
843
|
" WHERE v2.vid=%d AND v2.pathname=v1.pathname)" |
|
844
|
"UNION " |
|
845
|
"SELECT pathname, 0, 0, 1, 0, islink" |
|
846
|
" FROM vfile v2" |
|
847
|
" WHERE v2.vid=%d" |
|
848
|
" AND NOT EXISTS(SELECT 1 FROM vfile v1" |
|
849
|
" WHERE v1.vid=%d AND v1.pathname=v2.pathname)" |
|
850
|
" ORDER BY 1 /*scan*/", |
|
851
|
rid, vid, rid, vid, vid, rid |
|
852
|
); |
|
853
|
}else{ |
|
854
|
blob_append_sql(&sql, |
|
855
|
"SELECT pathname, deleted, chnged , rid==0, rid, islink" |
|
856
|
" FROM vfile" |
|
857
|
" WHERE vid=%d" |
|
858
|
" AND (deleted OR chnged OR rid==0)" |
|
859
|
" ORDER BY pathname /*scan*/", |
|
860
|
vid |
|
861
|
); |
|
862
|
} |
|
863
|
if( (pCfg->diffFlags & DIFF_SHOW_VERS)!=0 ){ |
|
864
|
diff_print_versions(zFrom ? zFrom : db_lget("checkout-hash", 0), |
|
865
|
"(workdir)", pCfg); |
|
866
|
} |
|
867
|
db_prepare(&q, "%s", blob_sql_text(&sql)); |
|
868
|
blob_reset(&sql); |
|
869
|
while( db_step(&q)==SQLITE_ROW ){ |
|
870
|
const char *zPathname = db_column_text(&q,0); |
|
871
|
int isDeleted = db_column_int(&q, 1); |
|
872
|
int isChnged = db_column_int(&q,2); |
|
873
|
int isNew = db_column_int(&q,3); |
|
874
|
int srcid = db_column_int(&q, 4); |
|
875
|
int isLink = db_column_int(&q, 5); |
|
876
|
const char *zFullName; |
|
877
|
int showDiff = 1; |
|
878
|
Blob fname; |
|
879
|
|
|
880
|
if( !file_dir_match(pFileDir, zPathname) ) continue; |
|
881
|
if( determine_exec_relative_option(0) ){ |
|
882
|
blob_zero(&fname); |
|
883
|
file_relative_name(zPathname, &fname, 1); |
|
884
|
}else{ |
|
885
|
blob_set(&fname, g.zLocalRoot); |
|
886
|
blob_append(&fname, zPathname, -1); |
|
887
|
} |
|
888
|
zFullName = blob_str(&fname); |
|
889
|
pCfg->diffFlags &= (~DIFF_FILE_MASK); |
|
890
|
if( isDeleted ){ |
|
891
|
if( !isNumStat ){ fossil_print("DELETED %s\n", zPathname); } |
|
892
|
pCfg->diffFlags |= DIFF_FILE_DELETED; |
|
893
|
if( !asNewFile ){ showDiff = 0; zFullName = NULL_DEVICE; } |
|
894
|
}else if( file_access(zFullName, F_OK) ){ |
|
895
|
if( !isNumStat ){ fossil_print("MISSING %s\n", zPathname); } |
|
896
|
if( !asNewFile ){ showDiff = 0; } |
|
897
|
}else if( isNew ){ |
|
898
|
if( !isNumStat ){ fossil_print("ADDED %s\n", zPathname); } |
|
899
|
pCfg->diffFlags |= DIFF_FILE_ADDED; |
|
900
|
srcid = 0; |
|
901
|
if( !asNewFile ){ showDiff = 0; } |
|
902
|
}else if( isChnged==3 ){ |
|
903
|
if( !isNumStat ){ fossil_print("ADDED_BY_MERGE %s\n", zPathname); } |
|
904
|
pCfg->diffFlags |= DIFF_FILE_ADDED; |
|
905
|
srcid = 0; |
|
906
|
if( !asNewFile ){ showDiff = 0; } |
|
907
|
}else if( isChnged==5 ){ |
|
908
|
if( !isNumStat ){ fossil_print("ADDED_BY_INTEGRATE %s\n", zPathname); } |
|
909
|
pCfg->diffFlags |= DIFF_FILE_ADDED; |
|
910
|
srcid = 0; |
|
911
|
if( !asNewFile ){ showDiff = 0; } |
|
912
|
} |
|
913
|
if( showDiff ){ |
|
914
|
Blob content; |
|
915
|
if( !isLink != !file_islink(zFullName) ){ |
|
916
|
diff_print_index(zPathname, pCfg, 0); |
|
917
|
diff_print_filenames(zPathname, zPathname, pCfg, 0); |
|
918
|
fossil_print("%s",DIFF_CANNOT_COMPUTE_SYMLINK); |
|
919
|
continue; |
|
920
|
} |
|
921
|
if( srcid>0 ){ |
|
922
|
content_get(srcid, &content); |
|
923
|
}else{ |
|
924
|
blob_zero(&content); |
|
925
|
} |
|
926
|
if( isChnged==0 |
|
927
|
|| pCfg->diffFlags & DIFF_FILE_DELETED |
|
928
|
|| !file_same_as_blob(&content, zFullName) |
|
929
|
){ |
|
930
|
diff_print_index(zPathname, pCfg, pOut); |
|
931
|
diff_file(&content, zFullName, zPathname, pCfg, pOut); |
|
932
|
} |
|
933
|
blob_reset(&content); |
|
934
|
} |
|
935
|
blob_reset(&fname); |
|
936
|
} |
|
937
|
db_finalize(&q); |
|
938
|
db_end_transaction(1); /* ROLLBACK */ |
|
939
|
} |
|
940
|
|
|
941
|
/* |
|
942
|
** Run a diff from the undo buffer to files on disk. |
|
943
|
** |
|
944
|
** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the |
|
945
|
** command zDiffCmd to do the diffing. |
|
946
|
** |
|
947
|
** When using an external diff program, zBinGlob contains the GLOB patterns |
|
948
|
** for file names to treat as binary. If fIncludeBinary is zero, these files |
|
949
|
** will be skipped in addition to files that may contain binary content. |
|
950
|
*/ |
|
951
|
static void diff_undo_to_checkout( |
|
952
|
DiffConfig *pCfg, /* Flags controlling diff output */ |
|
953
|
FileDirList *pFileDir /* List of files and directories to diff */ |
|
954
|
){ |
|
955
|
Stmt q; |
|
956
|
Blob content; |
|
957
|
db_prepare(&q, "SELECT pathname, content FROM undo"); |
|
958
|
blob_init(&content, 0, 0); |
|
959
|
if( (pCfg->diffFlags & DIFF_SHOW_VERS)!=0 ){ |
|
960
|
diff_print_versions("(undo)", "(workdir)", pCfg); |
|
961
|
} |
|
962
|
while( db_step(&q)==SQLITE_ROW ){ |
|
963
|
char *zFullName; |
|
964
|
const char *zFile = (const char*)db_column_text(&q, 0); |
|
965
|
if( !file_dir_match(pFileDir, zFile) ) continue; |
|
966
|
zFullName = mprintf("%s%s", g.zLocalRoot, zFile); |
|
967
|
db_column_blob(&q, 1, &content); |
|
968
|
diff_file(&content, zFullName, zFile, pCfg, 0); |
|
969
|
fossil_free(zFullName); |
|
970
|
blob_reset(&content); |
|
971
|
} |
|
972
|
db_finalize(&q); |
|
973
|
} |
|
974
|
|
|
975
|
/* |
|
976
|
** Show the difference between two files identified by ManifestFile |
|
977
|
** entries. |
|
978
|
** |
|
979
|
** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the |
|
980
|
** command zDiffCmd to do the diffing. |
|
981
|
** |
|
982
|
** When using an external diff program, zBinGlob contains the GLOB patterns |
|
983
|
** for file names to treat as binary. If fIncludeBinary is zero, these files |
|
984
|
** will be skipped in addition to files that may contain binary content. |
|
985
|
*/ |
|
986
|
static void diff_manifest_entry( |
|
987
|
struct ManifestFile *pFrom, |
|
988
|
struct ManifestFile *pTo, |
|
989
|
DiffConfig *pCfg |
|
990
|
){ |
|
991
|
Blob f1, f2; |
|
992
|
int rid; |
|
993
|
const char *zName; |
|
994
|
if( pFrom ){ |
|
995
|
zName = pFrom->zName; |
|
996
|
}else if( pTo ){ |
|
997
|
zName = pTo->zName; |
|
998
|
}else{ |
|
999
|
zName = DIFF_NO_NAME; |
|
1000
|
} |
|
1001
|
if( (pCfg->diffFlags & DIFF_BRIEF) && !(pCfg->diffFlags & DIFF_NUMSTAT) ){ |
|
1002
|
return; |
|
1003
|
} |
|
1004
|
diff_print_index(zName, pCfg, 0); |
|
1005
|
if( pFrom ){ |
|
1006
|
rid = uuid_to_rid(pFrom->zUuid, 0); |
|
1007
|
content_get(rid, &f1); |
|
1008
|
}else{ |
|
1009
|
blob_zero(&f1); |
|
1010
|
} |
|
1011
|
if( pTo ){ |
|
1012
|
rid = uuid_to_rid(pTo->zUuid, 0); |
|
1013
|
content_get(rid, &f2); |
|
1014
|
}else{ |
|
1015
|
blob_zero(&f2); |
|
1016
|
} |
|
1017
|
diff_file_mem(&f1, &f2, zName, pCfg); |
|
1018
|
blob_reset(&f1); |
|
1019
|
blob_reset(&f2); |
|
1020
|
} |
|
1021
|
|
|
1022
|
/* |
|
1023
|
** Output the differences between two check-ins. |
|
1024
|
** |
|
1025
|
** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the |
|
1026
|
** command zDiffCmd to do the diffing. |
|
1027
|
** |
|
1028
|
** When using an external diff program, zBinGlob contains the GLOB patterns |
|
1029
|
** for file names to treat as binary. If fIncludeBinary is zero, these files |
|
1030
|
** will be skipped in addition to files that may contain binary content. |
|
1031
|
*/ |
|
1032
|
static void diff_two_versions( |
|
1033
|
const char *zFrom, |
|
1034
|
const char *zTo, |
|
1035
|
DiffConfig *pCfg, |
|
1036
|
FileDirList *pFileDir |
|
1037
|
){ |
|
1038
|
Manifest *pFrom, *pTo; |
|
1039
|
ManifestFile *pFromFile, *pToFile; |
|
1040
|
int asNewFlag = (pCfg->diffFlags & (DIFF_VERBOSE|DIFF_NUMSTAT))!=0 ? 1 : 0; |
|
1041
|
|
|
1042
|
pFrom = manifest_get_by_name(zFrom, 0); |
|
1043
|
manifest_file_rewind(pFrom); |
|
1044
|
pFromFile = manifest_file_next(pFrom,0); |
|
1045
|
pTo = manifest_get_by_name(zTo, 0); |
|
1046
|
manifest_file_rewind(pTo); |
|
1047
|
pToFile = manifest_file_next(pTo,0); |
|
1048
|
if( (pCfg->diffFlags & DIFF_SHOW_VERS)!=0 ){ |
|
1049
|
diff_print_versions(zFrom, zTo, pCfg); |
|
1050
|
} |
|
1051
|
while( pFromFile || pToFile ){ |
|
1052
|
int cmp; |
|
1053
|
if( pFromFile==0 ){ |
|
1054
|
cmp = +1; |
|
1055
|
}else if( pToFile==0 ){ |
|
1056
|
cmp = -1; |
|
1057
|
}else{ |
|
1058
|
cmp = fossil_strcmp(pFromFile->zName, pToFile->zName); |
|
1059
|
} |
|
1060
|
pCfg->diffFlags &= (~DIFF_FILE_MASK); |
|
1061
|
if( cmp<0 ){ |
|
1062
|
if( file_dir_match(pFileDir, pFromFile->zName) ){ |
|
1063
|
if( (pCfg->diffFlags & (DIFF_NUMSTAT|DIFF_HTML))==0 ){ |
|
1064
|
fossil_print("DELETED %s\n", pFromFile->zName); |
|
1065
|
} |
|
1066
|
pCfg->diffFlags |= DIFF_FILE_DELETED; |
|
1067
|
if( asNewFlag ){ |
|
1068
|
diff_manifest_entry(pFromFile, 0, pCfg); |
|
1069
|
} |
|
1070
|
} |
|
1071
|
pFromFile = manifest_file_next(pFrom,0); |
|
1072
|
}else if( cmp>0 ){ |
|
1073
|
if( file_dir_match(pFileDir, pToFile->zName) ){ |
|
1074
|
if( (pCfg->diffFlags & |
|
1075
|
(DIFF_NUMSTAT|DIFF_HTML|DIFF_TCL|DIFF_JSON))==0 ){ |
|
1076
|
fossil_print("ADDED %s\n", pToFile->zName); |
|
1077
|
} |
|
1078
|
pCfg->diffFlags |= DIFF_FILE_ADDED; |
|
1079
|
if( asNewFlag ){ |
|
1080
|
diff_manifest_entry(0, pToFile, pCfg); |
|
1081
|
} |
|
1082
|
} |
|
1083
|
pToFile = manifest_file_next(pTo,0); |
|
1084
|
}else if( fossil_strcmp(pFromFile->zUuid, pToFile->zUuid)==0 ){ |
|
1085
|
/* No changes */ |
|
1086
|
(void)file_dir_match(pFileDir, pFromFile->zName); /* Record name usage */ |
|
1087
|
pFromFile = manifest_file_next(pFrom,0); |
|
1088
|
pToFile = manifest_file_next(pTo,0); |
|
1089
|
}else{ |
|
1090
|
if( file_dir_match(pFileDir, pToFile->zName) ){ |
|
1091
|
if((pCfg->diffFlags & DIFF_BRIEF) && !(pCfg->diffFlags & DIFF_NUMSTAT)){ |
|
1092
|
fossil_print("CHANGED %s\n", pFromFile->zName); |
|
1093
|
}else{ |
|
1094
|
diff_manifest_entry(pFromFile, pToFile, pCfg); |
|
1095
|
} |
|
1096
|
} |
|
1097
|
pFromFile = manifest_file_next(pFrom,0); |
|
1098
|
pToFile = manifest_file_next(pTo,0); |
|
1099
|
} |
|
1100
|
} |
|
1101
|
manifest_destroy(pFrom); |
|
1102
|
manifest_destroy(pTo); |
|
1103
|
} |
|
1104
|
|
|
1105
|
/* |
|
1106
|
** Compute the difference from an external tree of files to the current |
|
1107
|
** working checkout with its edits. |
|
1108
|
** |
|
1109
|
** To put it another way: Every managed file in the current working |
|
1110
|
** checkout is compared to the file with same name under zExternBase. The |
|
1111
|
** zExternBase files are on the left and the files in the current working |
|
1112
|
** directory are on the right. |
|
1113
|
*/ |
|
1114
|
void diff_externbase_to_checkout( |
|
1115
|
const char *zExternBase, /* Remote tree to use as the baseline */ |
|
1116
|
DiffConfig *pCfg, /* Diff settings */ |
|
1117
|
FileDirList *pFileDir /* Only look at these files */ |
|
1118
|
){ |
|
1119
|
int vid; |
|
1120
|
Stmt q; |
|
1121
|
|
|
1122
|
vid = db_lget_int("checkout",0); |
|
1123
|
if( file_isdir(zExternBase, ExtFILE)!=1 ){ |
|
1124
|
fossil_fatal("\"%s\" is not a directory", zExternBase); |
|
1125
|
} |
|
1126
|
db_prepare(&q, |
|
1127
|
"SELECT pathname FROM vfile WHERE vid=%d ORDER BY pathname", |
|
1128
|
vid |
|
1129
|
); |
|
1130
|
while( db_step(&q)==SQLITE_ROW ){ |
|
1131
|
const char *zFile; /* Name of file in the repository */ |
|
1132
|
char *zLhs; /* Full name of left-hand side file */ |
|
1133
|
char *zRhs; /* Full name of right-hand side file */ |
|
1134
|
Blob rhs; /* Full text of RHS */ |
|
1135
|
Blob lhs; /* Full text of LHS */ |
|
1136
|
|
|
1137
|
zFile = db_column_text(&q,0); |
|
1138
|
if( !file_dir_match(pFileDir, zFile) ) continue; |
|
1139
|
zLhs = mprintf("%s/%s", zExternBase, zFile); |
|
1140
|
zRhs = mprintf("%s%s", g.zLocalRoot, zFile); |
|
1141
|
if( file_size(zLhs, ExtFILE)<0 ){ |
|
1142
|
blob_zero(&lhs); |
|
1143
|
}else{ |
|
1144
|
blob_read_from_file(&lhs, zLhs, ExtFILE); |
|
1145
|
} |
|
1146
|
blob_read_from_file(&rhs, zRhs, ExtFILE); |
|
1147
|
if( blob_size(&lhs)!=blob_size(&rhs) |
|
1148
|
|| memcmp(blob_buffer(&lhs), blob_buffer(&rhs), blob_size(&lhs))!=0 |
|
1149
|
){ |
|
1150
|
diff_print_index(zFile, pCfg, 0); |
|
1151
|
diff_file_mem(&lhs, &rhs, zFile, pCfg); |
|
1152
|
} |
|
1153
|
blob_reset(&lhs); |
|
1154
|
blob_reset(&rhs); |
|
1155
|
fossil_free(zLhs); |
|
1156
|
fossil_free(zRhs); |
|
1157
|
} |
|
1158
|
db_finalize(&q); |
|
1159
|
} |
|
1160
|
|
|
1161
|
|
|
1162
|
/* |
|
1163
|
** Return the name of the external diff command, or return NULL if |
|
1164
|
** no external diff command is defined. |
|
1165
|
*/ |
|
1166
|
const char *diff_command_external(int guiDiff){ |
|
1167
|
const char *zName; |
|
1168
|
zName = guiDiff ? "gdiff-command" : "diff-command"; |
|
1169
|
return db_get(zName, 0); |
|
1170
|
} |
|
1171
|
|
|
1172
|
/* |
|
1173
|
** Return true if it reasonable to run "diff -tk" for "gdiff". |
|
1174
|
** |
|
1175
|
** Details: Return true if all of the following are true: |
|
1176
|
** |
|
1177
|
** (1) The isGDiff flags is true |
|
1178
|
** (2) The "gdiff-command" setting is undefined |
|
1179
|
** (3) There is a "tclsh" on PATH |
|
1180
|
** (4) There is a "wish" on PATH |
|
1181
|
*/ |
|
1182
|
int gdiff_using_tk(int isGdiff){ |
|
1183
|
if( isGdiff |
|
1184
|
&& db_get("gdiff-command","")[0]==0 |
|
1185
|
&& fossil_app_on_path("tclsh",0) |
|
1186
|
&& fossil_app_on_path("wish",0) |
|
1187
|
){ |
|
1188
|
return 1; |
|
1189
|
} |
|
1190
|
return 0; |
|
1191
|
} |
|
1192
|
|
|
1193
|
/* |
|
1194
|
** A TCL list is being constructed in pBlob. Append a single list |
|
1195
|
** element z[] to that list. Do all necessary quoting and escaping. |
|
1196
|
** The content of z[] might be controlled by an attacker. |
|
1197
|
*/ |
|
1198
|
static void diff_tcl_lappend(Blob *pBlob, const char *z, int isFilename){ |
|
1199
|
if( strchr(z,'{')==0 && strchr(z,'}')==0 ){ |
|
1200
|
if( isFilename ){ |
|
1201
|
blob_appendf(pBlob, " {%/}", z); |
|
1202
|
}else{ |
|
1203
|
blob_appendf(pBlob, " {%s}", z); |
|
1204
|
} |
|
1205
|
}else{ |
|
1206
|
int j; |
|
1207
|
blob_append_char(pBlob, ' '); |
|
1208
|
for(j=0; z[j]; j++){ |
|
1209
|
if( fossil_isalnum(z[j]) ){ |
|
1210
|
blob_append_char(pBlob, z[j]); |
|
1211
|
}else{ |
|
1212
|
blob_appendf(pBlob, "\\%03o", (unsigned char)z[j]); |
|
1213
|
} |
|
1214
|
} |
|
1215
|
} |
|
1216
|
} |
|
1217
|
|
|
1218
|
/* |
|
1219
|
** Return TRUE (non-zero) if zArg is a valid option to "diff" or |
|
1220
|
** "gdiff" that takes an argument that might be confused for a |
|
1221
|
** HOST:PATH or USER@HOST:PATH filename. |
|
1222
|
*/ |
|
1223
|
static int isDiffOptionWithArg(const char *zArg){ |
|
1224
|
int i; |
|
1225
|
const char *az[] = { |
|
1226
|
"binary", |
|
1227
|
"branch", |
|
1228
|
"ci", "checkin", |
|
1229
|
"r", "from", |
|
1230
|
"to" |
|
1231
|
}; |
|
1232
|
if( zArg[0]!='-' ) return 0; |
|
1233
|
zArg++; |
|
1234
|
if( zArg[0]=='-' ) zArg++; |
|
1235
|
for(i=0; i<count(az); i++){ |
|
1236
|
if( fossil_strcmp(zArg, az[i])==0 ) return 1; |
|
1237
|
} |
|
1238
|
return 0; |
|
1239
|
} |
|
1240
|
|
|
1241
|
/* |
|
1242
|
** Show diff output in a Tcl/Tk window, in response to the --tk option |
|
1243
|
** to the diff command. |
|
1244
|
** |
|
1245
|
** If fossil has direct access to a Tcl interpreter (either loaded |
|
1246
|
** dynamically through stubs or linked in statically), we can use it |
|
1247
|
** directly. Otherwise: |
|
1248
|
** (1) Write the Tcl/Tk script used for rendering into a temp file. |
|
1249
|
** (2) Invoke "tclsh" on the temp file using fossil_system(). |
|
1250
|
** (3) Delete the temp file. |
|
1251
|
*/ |
|
1252
|
void diff_tk(const char *zSubCmd, int firstArg){ |
|
1253
|
int i; |
|
1254
|
Blob script; |
|
1255
|
const char *zTempFile = 0; |
|
1256
|
char *zCmd; |
|
1257
|
const char *zTclsh; |
|
1258
|
const char *zHost = 0; |
|
1259
|
const char *zDir = 0; |
|
1260
|
int bDebug = find_option("tkdebug",0,0)!=0; |
|
1261
|
int bDarkMode = find_option("dark",0,0)!=0; |
|
1262
|
(void)find_option("debug",0,0); |
|
1263
|
blob_zero(&script); |
|
1264
|
/* Caution: When this routine is called from the merge-info command, |
|
1265
|
** the --tcl argument requires an argument. But merge-info does not |
|
1266
|
** use -i, so we can take -i as that argument. This routine needs to |
|
1267
|
** always have -i after --tcl. |
|
1268
|
** CAUTION! |
|
1269
|
** vvvvvvv */ |
|
1270
|
blob_appendf(&script, "set fossilcmd {| \"%/\" %s -tcl -i -v", |
|
1271
|
g.nameOfExe, zSubCmd); |
|
1272
|
find_option("tcl",0,0); |
|
1273
|
find_option("html",0,0); |
|
1274
|
find_option("side-by-side","y",0); |
|
1275
|
find_option("internal","i",0); |
|
1276
|
find_option("verbose","v",0); |
|
1277
|
zTclsh = find_option("tclsh",0,1); |
|
1278
|
if( zTclsh==0 ){ |
|
1279
|
zTclsh = db_get("tclsh",0); |
|
1280
|
} |
|
1281
|
/* The undocumented --script FILENAME option causes the Tk script to |
|
1282
|
** be written into the FILENAME instead of being run. This is used |
|
1283
|
** for testing and debugging. */ |
|
1284
|
zTempFile = find_option("script",0,1); |
|
1285
|
for(i=firstArg; i<g.argc; i++){ |
|
1286
|
const char *z = g.argv[i]; |
|
1287
|
if( zHost==0 |
|
1288
|
&& (i==firstArg || !isDiffOptionWithArg(g.argv[i-1])) |
|
1289
|
&& !file_isfile_or_link(z) |
|
1290
|
&& (zDir = file_skip_userhost(z))!=0 |
|
1291
|
){ |
|
1292
|
zHost = mprintf("%.*s", (int)(zDir - z - 1), z); |
|
1293
|
continue; |
|
1294
|
} |
|
1295
|
diff_tcl_lappend(&script, z, 1); |
|
1296
|
} |
|
1297
|
blob_appendf(&script, "}\nset darkmode %d\n", bDarkMode); |
|
1298
|
blob_appendf(&script, "set debug %d\n", bDebug); |
|
1299
|
if( zHost ){ |
|
1300
|
blob_appendf(&script, "set remotehost"); |
|
1301
|
diff_tcl_lappend(&script, zHost, 0); |
|
1302
|
blob_appendf(&script, "\nset remotedir"); |
|
1303
|
diff_tcl_lappend(&script, zDir, 1); |
|
1304
|
blob_append_char(&script, '\n'); |
|
1305
|
} |
|
1306
|
blob_appendf(&script, "%s", builtin_file("diff.tcl", 0)); |
|
1307
|
if( zTempFile ){ |
|
1308
|
blob_write_to_file(&script, zTempFile); |
|
1309
|
fossil_print("To see diff, run: %s \"%s\"\n", zTclsh, zTempFile); |
|
1310
|
}else{ |
|
1311
|
#if defined(FOSSIL_ENABLE_TCL) |
|
1312
|
Th_FossilInit(TH_INIT_DEFAULT); |
|
1313
|
if( evaluateTclWithEvents(g.interp, &g.tcl, blob_str(&script), |
|
1314
|
blob_size(&script), 1, 1, 0)==TCL_OK ){ |
|
1315
|
blob_reset(&script); |
|
1316
|
return; |
|
1317
|
} |
|
1318
|
/* |
|
1319
|
* If evaluation of the Tcl script fails, the reason may be that Tk |
|
1320
|
* could not be found by the loaded Tcl, or that Tcl cannot be loaded |
|
1321
|
* dynamically (e.g. x64 Tcl with x86 Fossil). Therefore, fallback |
|
1322
|
* to using the external "tclsh", if available. |
|
1323
|
*/ |
|
1324
|
#endif |
|
1325
|
zTempFile = write_blob_to_temp_file(&script); |
|
1326
|
zCmd = mprintf("%$ %$", zTclsh, zTempFile); |
|
1327
|
fossil_system(zCmd); |
|
1328
|
file_delete(zTempFile); |
|
1329
|
fossil_free(zCmd); |
|
1330
|
} |
|
1331
|
blob_reset(&script); |
|
1332
|
} |
|
1333
|
|
|
1334
|
/* |
|
1335
|
** Returns the GLOB pattern for file names that should be treated as binary |
|
1336
|
** by the diff subsystem, if any. |
|
1337
|
*/ |
|
1338
|
const char *diff_get_binary_glob(void){ |
|
1339
|
const char *zBinGlob = find_option("binary", 0, 1); |
|
1340
|
if( zBinGlob==0 ) zBinGlob = db_get("binary-glob",0); |
|
1341
|
return zBinGlob; |
|
1342
|
} |
|
1343
|
|
|
1344
|
/* |
|
1345
|
** COMMAND: diff |
|
1346
|
** COMMAND: gdiff |
|
1347
|
** |
|
1348
|
** Usage: %fossil diff|gdiff ?OPTIONS? ?FILE1? ?FILE2 ...? |
|
1349
|
** |
|
1350
|
** Show the difference between the current version of each of the FILEs |
|
1351
|
** specified (as they exist on disk) and that same file as it was checked- |
|
1352
|
** out. Or if the FILE arguments are omitted, show all committed changes |
|
1353
|
** currently in the working check-out. The "gdiff" variant means to |
|
1354
|
** use a GUI diff specified by the "gdiff-command" setting. If |
|
1355
|
** gdiff-command is unset or is an empty string, then "gdiff" behaves |
|
1356
|
** like "diff --tk". |
|
1357
|
** |
|
1358
|
** The default output format is a "unified patch" (the same as the |
|
1359
|
** output of "diff -u" on most unix systems). Many alternative formats |
|
1360
|
** are available. A few of the more useful alternatives: |
|
1361
|
** |
|
1362
|
** --tk Pop up a Tcl/Tk-based GUI to show the diff |
|
1363
|
** --by Show a side-by-side diff in the default web browser |
|
1364
|
** -b Show a linear diff in the default web browser |
|
1365
|
** -y Show a text side-by-side diff |
|
1366
|
** --webpage Format output as HTML |
|
1367
|
** --webpage -y HTML output in the side-by-side format |
|
1368
|
** |
|
1369
|
** The "--from VERSION" option is used to specify the source check-in |
|
1370
|
** for the diff operation. If not specified, the source check-in is the |
|
1371
|
** base check-in for the current check-out. Similarly, the "--to VERSION" |
|
1372
|
** option specifies the check-in from which the second version of the file |
|
1373
|
** or files is taken. If there is no "--to" option then the (possibly edited) |
|
1374
|
** files in the current check-out are used. The "--checkin VERSION" option |
|
1375
|
** shows the changes made by check-in VERSION relative to its primary parent. |
|
1376
|
** The "--branch BRANCHNAME" shows all the changes on the branch BRANCHNAME. |
|
1377
|
** |
|
1378
|
** With the "--from VERSION" option, if VERSION is actually a directory name |
|
1379
|
** (not a tag or check-in hash) then the files under that directory are used |
|
1380
|
** as the baseline for the diff. |
|
1381
|
** |
|
1382
|
** The "-i" command-line option forces the use of Fossil's own internal |
|
1383
|
** diff logic rather than any external diff program that might be configured |
|
1384
|
** using the "setting" command. If no external diff program is configured, |
|
1385
|
** then the "-i" option is a no-op. The "-i" option converts "gdiff" into |
|
1386
|
** "diff". |
|
1387
|
** |
|
1388
|
** The "--diff-binary" option enables or disables the inclusion of binary files |
|
1389
|
** when using an external diff program. |
|
1390
|
** |
|
1391
|
** The "--binary" option causes files matching the glob PATTERN to be treated |
|
1392
|
** as binary when considering if they should be used with the external diff |
|
1393
|
** program. This option overrides the "binary-glob" setting. |
|
1394
|
** |
|
1395
|
** If the "--tk" option is used (or "gdiff" is used and no third-party |
|
1396
|
** graphical diff tool is specified) then an additional argument of |
|
1397
|
** the form "USER@HOST:DIRECTORY" or "HOST:DIRECTORY" can be added to specify |
|
1398
|
** a remote host and directory from which the diff should be taken. If this |
|
1399
|
** happens, Fossil will run the diff using SSH, capture the results, then |
|
1400
|
** display the results locally using the Tcl/Tk diff GUI. |
|
1401
|
** |
|
1402
|
** These commands show differences between managed files. Use the "fossil xdiff" |
|
1403
|
** command to see differences in unmanaged files. |
|
1404
|
** |
|
1405
|
** Options: |
|
1406
|
** --binary PATTERN Treat files that match the glob PATTERN |
|
1407
|
** as binary |
|
1408
|
** --branch BRANCH Show diff of all changes on BRANCH |
|
1409
|
** --brief Show filenames only |
|
1410
|
** -b|--browser Show the diff output in a web-browser |
|
1411
|
** --by Shorthand for "--browser -y" |
|
1412
|
** -ci|--checkin VERSION Show diff of all changes in VERSION |
|
1413
|
** --command PROG External diff program. Overrides "diff-command" |
|
1414
|
** -c|--context N Show N lines of context around each change, |
|
1415
|
** with negative N meaning show all content |
|
1416
|
** --dark Use dark mode for the Tcl/Tk-based GUI and HTML |
|
1417
|
** --diff-binary BOOL Include binary files with external commands |
|
1418
|
** --exec-abs-paths Force absolute path names on external commands |
|
1419
|
** --exec-rel-paths Force relative path names on external commands |
|
1420
|
** -r|--from VERSION Use VERSION as the baseline for the diff, or |
|
1421
|
** if VERSION is a directory name, use files in |
|
1422
|
** that directory as the baseline. |
|
1423
|
** -w|--ignore-all-space Ignore white space when comparing lines |
|
1424
|
** -i|--internal Use internal diff logic |
|
1425
|
** --invert Invert the diff |
|
1426
|
** --json Output formatted as JSON |
|
1427
|
** -n|--linenum Show line numbers |
|
1428
|
** -N|--new-file Alias for --verbose |
|
1429
|
** -s|--numstat Show the number of added and deleted lines per |
|
1430
|
** file, omitting the diff. When combined |
|
1431
|
** with --brief, show only the total row. |
|
1432
|
** -y|--side-by-side Side-by-side diff |
|
1433
|
** --strip-trailing-cr Strip trailing CR |
|
1434
|
** --tcl Tcl-formatted output used internally by --tk |
|
1435
|
** --tclsh PATH Tcl/Tk shell used for --tk (default: "tclsh") |
|
1436
|
** --tk Launch a Tcl/Tk GUI for display |
|
1437
|
** --to VERSION Select VERSION as target for the diff |
|
1438
|
** --undo Use the undo buffer as the baseline |
|
1439
|
** --unified Unified diff |
|
1440
|
** -v|--verbose Output complete text of added or deleted files |
|
1441
|
** -h|--versions Show compared versions in the diff header |
|
1442
|
** --webpage Format output as a stand-alone HTML webpage |
|
1443
|
** -W|--width N Width of lines in side-by-side diff |
|
1444
|
** -Z|--ignore-trailing-space Ignore changes to end-of-line whitespace |
|
1445
|
*/ |
|
1446
|
void diff_cmd(void){ |
|
1447
|
int isGDiff; /* True for gdiff. False for normal diff */ |
|
1448
|
const char *zFrom; /* Source version number */ |
|
1449
|
const char *zTo; /* Target version number */ |
|
1450
|
const char *zCheckin; /* Check-in version number */ |
|
1451
|
const char *zBranch; /* Branch to diff */ |
|
1452
|
int againstUndo = 0; /* Diff against files in the undo buffer */ |
|
1453
|
FileDirList *pFileDir = 0; /* Restrict the diff to these files */ |
|
1454
|
DiffConfig DCfg; /* Diff configuration object */ |
|
1455
|
int bFromIsDir = 0; /* True if zFrom is a directory name */ |
|
1456
|
|
|
1457
|
isGDiff = g.argv[1][0]=='g'; |
|
1458
|
if( find_option("tk",0,0)!=0 |
|
1459
|
|| has_option("tclsh") |
|
1460
|
/* || gdiff_using_tk(isGDiff) */ |
|
1461
|
){ |
|
1462
|
diff_tk("diff", 2); |
|
1463
|
return; |
|
1464
|
} |
|
1465
|
zFrom = find_option("from", "r", 1); |
|
1466
|
zTo = find_option("to", 0, 1); |
|
1467
|
zCheckin = find_option("checkin", "ci", 1); |
|
1468
|
zBranch = find_option("branch", 0, 1); |
|
1469
|
againstUndo = find_option("undo",0,0)!=0; |
|
1470
|
if( againstUndo && (zFrom!=0 || zTo!=0 || zCheckin!=0 || zBranch!=0) ){ |
|
1471
|
fossil_fatal("cannot use --undo together with --from, --to, --checkin," |
|
1472
|
" or --branch"); |
|
1473
|
} |
|
1474
|
if( zBranch ){ |
|
1475
|
if( zTo || zFrom || zCheckin ){ |
|
1476
|
fossil_fatal("cannot use --from, --to, or --checkin with --branch"); |
|
1477
|
} |
|
1478
|
zTo = zBranch; |
|
1479
|
zFrom = mprintf("merge-in:%s", zBranch); |
|
1480
|
zBranch = 0; |
|
1481
|
} |
|
1482
|
if( zCheckin!=0 && (zFrom!=0 || zTo!=0) ){ |
|
1483
|
fossil_fatal("cannot use --checkin together with --from or --to"); |
|
1484
|
} |
|
1485
|
if( 0==zCheckin ){ |
|
1486
|
if( zTo==0 || againstUndo ){ |
|
1487
|
db_must_be_within_tree(); |
|
1488
|
}else if( zFrom==0 ){ |
|
1489
|
fossil_fatal("must use --from if --to is present"); |
|
1490
|
}else{ |
|
1491
|
db_find_and_open_repository(0, 0); |
|
1492
|
} |
|
1493
|
}else{ |
|
1494
|
db_find_and_open_repository(0, 0); |
|
1495
|
} |
|
1496
|
determine_exec_relative_option(1); |
|
1497
|
if( zFrom!=file_tail(zFrom) |
|
1498
|
&& file_isdir(zFrom, ExtFILE)==1 |
|
1499
|
&& !db_exists("SELECT 1 FROM tag WHERE tagname='sym-%q'", zFrom) |
|
1500
|
){ |
|
1501
|
bFromIsDir = 1; |
|
1502
|
if( zTo ){ |
|
1503
|
fossil_fatal("cannot use --to together with \"--from PATH\""); |
|
1504
|
} |
|
1505
|
} |
|
1506
|
diff_options(&DCfg, isGDiff, 0); |
|
1507
|
verify_all_options(); |
|
1508
|
g.diffCnt[0] = g.diffCnt[1] = g.diffCnt[2] = 0; |
|
1509
|
if( g.argc>=3 ){ |
|
1510
|
int i; |
|
1511
|
Blob fname; |
|
1512
|
pFileDir = fossil_malloc( sizeof(*pFileDir) * (g.argc-1) ); |
|
1513
|
memset(pFileDir, 0, sizeof(*pFileDir) * (g.argc-1)); |
|
1514
|
for(i=2; i<g.argc; i++){ |
|
1515
|
file_tree_name(g.argv[i], &fname, 0, 1); |
|
1516
|
pFileDir[i-2].zName = fossil_strdup(blob_str(&fname)); |
|
1517
|
if( strcmp(pFileDir[i-2].zName,".")==0 ){ |
|
1518
|
pFileDir[0].zName[0] = '.'; |
|
1519
|
pFileDir[0].zName[1] = 0; |
|
1520
|
break; |
|
1521
|
} |
|
1522
|
pFileDir[i-2].nName = blob_size(&fname); |
|
1523
|
pFileDir[i-2].nUsed = 0; |
|
1524
|
blob_reset(&fname); |
|
1525
|
} |
|
1526
|
} |
|
1527
|
if( (DCfg.diffFlags & DIFF_NUMSTAT) && !(DCfg.diffFlags & DIFF_BRIEF) ){ |
|
1528
|
fossil_print("%10s %10s\n", "INSERTED", "DELETED"); |
|
1529
|
} |
|
1530
|
if( zCheckin!=0 ){ |
|
1531
|
int ridTo = name_to_typed_rid(zCheckin, "ci"); |
|
1532
|
zTo = zCheckin; |
|
1533
|
zFrom = db_text(0, |
|
1534
|
"SELECT uuid FROM blob, plink" |
|
1535
|
" WHERE plink.cid=%d AND plink.isprim AND plink.pid=blob.rid", |
|
1536
|
ridTo); |
|
1537
|
if( zFrom==0 ){ |
|
1538
|
fossil_fatal("check-in %s has no parent", zTo); |
|
1539
|
} |
|
1540
|
} |
|
1541
|
diff_begin(&DCfg); |
|
1542
|
if( bFromIsDir ){ |
|
1543
|
diff_externbase_to_checkout(zFrom, &DCfg, pFileDir); |
|
1544
|
}else if( againstUndo ){ |
|
1545
|
if( db_lget_int("undo_available",0)==0 ){ |
|
1546
|
fossil_print("No undo or redo is available\n"); |
|
1547
|
return; |
|
1548
|
} |
|
1549
|
diff_undo_to_checkout(&DCfg, pFileDir); |
|
1550
|
}else if( zTo==0 ){ |
|
1551
|
diff_version_to_checkout(zFrom, &DCfg, pFileDir, 0); |
|
1552
|
}else{ |
|
1553
|
diff_two_versions(zFrom, zTo, &DCfg, pFileDir); |
|
1554
|
} |
|
1555
|
if( pFileDir ){ |
|
1556
|
int i; |
|
1557
|
for(i=0; pFileDir[i].zName; i++){ |
|
1558
|
if( pFileDir[i].nUsed==0 |
|
1559
|
&& strcmp(pFileDir[0].zName,".")!=0 |
|
1560
|
&& !file_isdir(g.argv[i+2], ExtFILE) |
|
1561
|
){ |
|
1562
|
fossil_fatal("not found: '%s'", g.argv[i+2]); |
|
1563
|
} |
|
1564
|
fossil_free(pFileDir[i].zName); |
|
1565
|
} |
|
1566
|
fossil_free(pFileDir); |
|
1567
|
} |
|
1568
|
diff_end(&DCfg, 0); |
|
1569
|
if ( DCfg.diffFlags & DIFF_NUMSTAT ){ |
|
1570
|
fossil_print("%10d %10d TOTAL over %d changed file%s\n", |
|
1571
|
g.diffCnt[1], g.diffCnt[2], g.diffCnt[0], g.diffCnt[0]!=1 ? "s": ""); |
|
1572
|
} |
|
1573
|
} |
|
1574
|
|
|
1575
|
/* |
|
1576
|
** WEBPAGE: vpatch |
|
1577
|
** URL: /vpatch?from=FROM&to=TO |
|
1578
|
** |
|
1579
|
** Show a patch that goes from check-in FROM to check-in TO. |
|
1580
|
*/ |
|
1581
|
void vpatch_page(void){ |
|
1582
|
const char *zFrom = P("from"); |
|
1583
|
const char *zTo = P("to"); |
|
1584
|
DiffConfig DCfg; |
|
1585
|
cgi_check_for_malice(); |
|
1586
|
login_check_credentials(); |
|
1587
|
if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
|
1588
|
if( zFrom==0 || zTo==0 ) fossil_redirect_home(); |
|
1589
|
if( robot_restrict("diff") ) return; |
|
1590
|
|
|
1591
|
fossil_nice_default(); |
|
1592
|
cgi_set_content_type("text/plain"); |
|
1593
|
diff_config_init(&DCfg, DIFF_VERBOSE); |
|
1594
|
diff_two_versions(zFrom, zTo, &DCfg, 0); |
|
1595
|
} |
|
1596
|
|