Fossil SCM

fossil-scm / src / diffcmd.c
Blame History Raw 1596 lines
1
/*
2
** Copyright (c) 2007 D. Richard Hipp
3
**
4
** This program is free software; you can redistribute it and/or
5
** modify it under the terms of the Simplified BSD License (also
6
** known as the "2-Clause License" or "FreeBSD License".)
7
8
** This program is distributed in the hope that it will be useful,
9
** but without any warranty; without even the implied warranty of
10
** merchantability or fitness for a particular purpose.
11
**
12
** Author contact information:
13
** [email protected]
14
** http://www.hwaci.com/drh/
15
**
16
*******************************************************************************
17
**
18
** This file contains code used to 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 &lrarr; %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

Keyboard Shortcuts

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