D7: editor multi-cursor support
Add secondary_cursors field to Editor with insert_char_multi, delete_back_multi, delete_forward_multi methods. Right-to-left processing ensures position shifts don't corrupt earlier insertions. 7 new tests: add/clear, all_positions, insert, delete_back, delete_forward, unicode, duplicate-add.
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
# Automakefile for GNU diffutils programs.
|
||||
|
||||
# Copyright (C) 2001-2002, 2006, 2009-2013, 2015-2017 Free Software Foundation,
|
||||
# Inc.
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
bin_PROGRAMS = cmp diff diff3 sdiff
|
||||
|
||||
localedir = $(datadir)/locale
|
||||
|
||||
AM_CPPFLAGS = -I../lib -I$(top_srcdir)/lib
|
||||
AM_CFLAGS = $(WARN_CFLAGS) $(WERROR_CFLAGS)
|
||||
|
||||
LDADD = \
|
||||
libver.a \
|
||||
../lib/libdiffutils.a \
|
||||
$(LIBCSTACK) \
|
||||
$(LIBINTL) \
|
||||
$(LIBICONV) \
|
||||
$(LIBSIGSEGV) \
|
||||
$(LIB_CLOCK_GETTIME)
|
||||
|
||||
diff_LDADD = $(LDADD)
|
||||
cmp_LDADD = $(LDADD)
|
||||
sdiff_LDADD = $(LDADD)
|
||||
diff3_LDADD = $(LDADD)
|
||||
|
||||
cmp_SOURCES = cmp.c
|
||||
diff3_SOURCES = diff3.c
|
||||
sdiff_SOURCES = sdiff.c
|
||||
diff_SOURCES = \
|
||||
analyze.c context.c diff.c dir.c ed.c ifdef.c io.c \
|
||||
normal.c side.c util.c
|
||||
noinst_HEADERS = \
|
||||
die.h \
|
||||
diff.h \
|
||||
system.h
|
||||
|
||||
MOSTLYCLEANFILES = paths.h paths.ht
|
||||
|
||||
cmp.$(OBJEXT) diff3.$(OBJEXT) diff.$(OBJEXT) sdiff.$(OBJEXT): paths.h
|
||||
|
||||
gdiff = `echo diff|sed '$(transform)'`
|
||||
BUILT_SOURCES = paths.h
|
||||
paths.h: Makefile.am
|
||||
$(AM_V_GEN)(echo '#define DEFAULT_DIFF_PROGRAM "'$(gdiff)'"' && \
|
||||
echo '#define LOCALEDIR "$(localedir)"') >$@t && mv $@t $@
|
||||
|
||||
noinst_LIBRARIES = libver.a
|
||||
nodist_libver_a_SOURCES = version.c version.h
|
||||
|
||||
BUILT_SOURCES += version.c
|
||||
version.c: Makefile
|
||||
$(AM_V_GEN)rm -f $@
|
||||
$(AM_V_at)printf '#include <config.h>\n' > $@t
|
||||
$(AM_V_at)printf 'char const *Version = "$(PACKAGE_VERSION)";\n' >> $@t
|
||||
$(AM_V_at)chmod a-w $@t
|
||||
$(AM_V_at)mv $@t $@
|
||||
|
||||
BUILT_SOURCES += version.h
|
||||
version.h: Makefile
|
||||
$(AM_V_GEN)rm -f $@
|
||||
$(AM_V_at)printf 'extern char const *Version;\n' > $@t
|
||||
$(AM_V_at)chmod a-w $@t
|
||||
$(AM_V_at)mv $@t $@
|
||||
|
||||
DISTCLEANFILES = version.c version.h
|
||||
MAINTAINERCLEANFILES = $(BUILT_SOURCES)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,716 @@
|
||||
/* Analyze file differences for GNU DIFF.
|
||||
|
||||
Copyright (C) 1988-1989, 1992-1995, 1998, 2001-2002, 2004, 2006-2007,
|
||||
2009-2013, 2015-2017 Free Software Foundation, Inc.
|
||||
|
||||
This file is part of GNU DIFF.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
#include "diff.h"
|
||||
#include <cmpbuf.h>
|
||||
#include <error.h>
|
||||
#include <file-type.h>
|
||||
#include <xalloc.h>
|
||||
|
||||
/* The core of the Diff algorithm. */
|
||||
#define ELEMENT lin
|
||||
#define EQUAL(x,y) ((x) == (y))
|
||||
#define OFFSET lin
|
||||
#define EXTRA_CONTEXT_FIELDS /* none */
|
||||
#define NOTE_DELETE(c, xoff) (files[0].changed[files[0].realindexes[xoff]] = 1)
|
||||
#define NOTE_INSERT(c, yoff) (files[1].changed[files[1].realindexes[yoff]] = 1)
|
||||
#define USE_HEURISTIC 1
|
||||
#include <diffseq.h>
|
||||
|
||||
/* Discard lines from one file that have no matches in the other file.
|
||||
|
||||
A line which is discarded will not be considered by the actual
|
||||
comparison algorithm; it will be as if that line were not in the file.
|
||||
The file's 'realindexes' table maps virtual line numbers
|
||||
(which don't count the discarded lines) into real line numbers;
|
||||
this is how the actual comparison algorithm produces results
|
||||
that are comprehensible when the discarded lines are counted.
|
||||
|
||||
When we discard a line, we also mark it as a deletion or insertion
|
||||
so that it will be printed in the output. */
|
||||
|
||||
static void
|
||||
discard_confusing_lines (struct file_data filevec[])
|
||||
{
|
||||
int f;
|
||||
lin i;
|
||||
char *discarded[2];
|
||||
lin *equiv_count[2];
|
||||
lin *p;
|
||||
|
||||
/* Allocate our results. */
|
||||
p = xmalloc ((filevec[0].buffered_lines + filevec[1].buffered_lines)
|
||||
* (2 * sizeof *p));
|
||||
for (f = 0; f < 2; f++)
|
||||
{
|
||||
filevec[f].undiscarded = p; p += filevec[f].buffered_lines;
|
||||
filevec[f].realindexes = p; p += filevec[f].buffered_lines;
|
||||
}
|
||||
|
||||
/* Set up equiv_count[F][I] as the number of lines in file F
|
||||
that fall in equivalence class I. */
|
||||
|
||||
p = zalloc (filevec[0].equiv_max * (2 * sizeof *p));
|
||||
equiv_count[0] = p;
|
||||
equiv_count[1] = p + filevec[0].equiv_max;
|
||||
|
||||
for (i = 0; i < filevec[0].buffered_lines; ++i)
|
||||
++equiv_count[0][filevec[0].equivs[i]];
|
||||
for (i = 0; i < filevec[1].buffered_lines; ++i)
|
||||
++equiv_count[1][filevec[1].equivs[i]];
|
||||
|
||||
/* Set up tables of which lines are going to be discarded. */
|
||||
|
||||
discarded[0] = zalloc (filevec[0].buffered_lines
|
||||
+ filevec[1].buffered_lines);
|
||||
discarded[1] = discarded[0] + filevec[0].buffered_lines;
|
||||
|
||||
/* Mark to be discarded each line that matches no line of the other file.
|
||||
If a line matches many lines, mark it as provisionally discardable. */
|
||||
|
||||
for (f = 0; f < 2; f++)
|
||||
{
|
||||
size_t end = filevec[f].buffered_lines;
|
||||
char *discards = discarded[f];
|
||||
lin *counts = equiv_count[1 - f];
|
||||
lin *equivs = filevec[f].equivs;
|
||||
size_t many = 5;
|
||||
size_t tem = end / 64;
|
||||
|
||||
/* Multiply MANY by approximate square root of number of lines.
|
||||
That is the threshold for provisionally discardable lines. */
|
||||
while ((tem = tem >> 2) > 0)
|
||||
many *= 2;
|
||||
|
||||
for (i = 0; i < end; i++)
|
||||
{
|
||||
lin nmatch;
|
||||
if (equivs[i] == 0)
|
||||
continue;
|
||||
nmatch = counts[equivs[i]];
|
||||
if (nmatch == 0)
|
||||
discards[i] = 1;
|
||||
else if (nmatch > many)
|
||||
discards[i] = 2;
|
||||
}
|
||||
}
|
||||
|
||||
/* Don't really discard the provisional lines except when they occur
|
||||
in a run of discardables, with nonprovisionals at the beginning
|
||||
and end. */
|
||||
|
||||
for (f = 0; f < 2; f++)
|
||||
{
|
||||
lin end = filevec[f].buffered_lines;
|
||||
register char *discards = discarded[f];
|
||||
|
||||
for (i = 0; i < end; i++)
|
||||
{
|
||||
/* Cancel provisional discards not in middle of run of discards. */
|
||||
if (discards[i] == 2)
|
||||
discards[i] = 0;
|
||||
else if (discards[i] != 0)
|
||||
{
|
||||
/* We have found a nonprovisional discard. */
|
||||
register lin j;
|
||||
lin length;
|
||||
lin provisional = 0;
|
||||
|
||||
/* Find end of this run of discardable lines.
|
||||
Count how many are provisionally discardable. */
|
||||
for (j = i; j < end; j++)
|
||||
{
|
||||
if (discards[j] == 0)
|
||||
break;
|
||||
if (discards[j] == 2)
|
||||
++provisional;
|
||||
}
|
||||
|
||||
/* Cancel provisional discards at end, and shrink the run. */
|
||||
while (j > i && discards[j - 1] == 2)
|
||||
discards[--j] = 0, --provisional;
|
||||
|
||||
/* Now we have the length of a run of discardable lines
|
||||
whose first and last are not provisional. */
|
||||
length = j - i;
|
||||
|
||||
/* If 1/4 of the lines in the run are provisional,
|
||||
cancel discarding of all provisional lines in the run. */
|
||||
if (provisional * 4 > length)
|
||||
{
|
||||
while (j > i)
|
||||
if (discards[--j] == 2)
|
||||
discards[j] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
register lin consec;
|
||||
lin minimum = 1;
|
||||
lin tem = length >> 2;
|
||||
|
||||
/* MINIMUM is approximate square root of LENGTH/4.
|
||||
A subrun of two or more provisionals can stand
|
||||
when LENGTH is at least 16.
|
||||
A subrun of 4 or more can stand when LENGTH >= 64. */
|
||||
while (0 < (tem >>= 2))
|
||||
minimum <<= 1;
|
||||
minimum++;
|
||||
|
||||
/* Cancel any subrun of MINIMUM or more provisionals
|
||||
within the larger run. */
|
||||
for (j = 0, consec = 0; j < length; j++)
|
||||
if (discards[i + j] != 2)
|
||||
consec = 0;
|
||||
else if (minimum == ++consec)
|
||||
/* Back up to start of subrun, to cancel it all. */
|
||||
j -= consec;
|
||||
else if (minimum < consec)
|
||||
discards[i + j] = 0;
|
||||
|
||||
/* Scan from beginning of run
|
||||
until we find 3 or more nonprovisionals in a row
|
||||
or until the first nonprovisional at least 8 lines in.
|
||||
Until that point, cancel any provisionals. */
|
||||
for (j = 0, consec = 0; j < length; j++)
|
||||
{
|
||||
if (j >= 8 && discards[i + j] == 1)
|
||||
break;
|
||||
if (discards[i + j] == 2)
|
||||
consec = 0, discards[i + j] = 0;
|
||||
else if (discards[i + j] == 0)
|
||||
consec = 0;
|
||||
else
|
||||
consec++;
|
||||
if (consec == 3)
|
||||
break;
|
||||
}
|
||||
|
||||
/* I advances to the last line of the run. */
|
||||
i += length - 1;
|
||||
|
||||
/* Same thing, from end. */
|
||||
for (j = 0, consec = 0; j < length; j++)
|
||||
{
|
||||
if (j >= 8 && discards[i - j] == 1)
|
||||
break;
|
||||
if (discards[i - j] == 2)
|
||||
consec = 0, discards[i - j] = 0;
|
||||
else if (discards[i - j] == 0)
|
||||
consec = 0;
|
||||
else
|
||||
consec++;
|
||||
if (consec == 3)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Actually discard the lines. */
|
||||
for (f = 0; f < 2; f++)
|
||||
{
|
||||
char *discards = discarded[f];
|
||||
lin end = filevec[f].buffered_lines;
|
||||
lin j = 0;
|
||||
for (i = 0; i < end; ++i)
|
||||
if (minimal || discards[i] == 0)
|
||||
{
|
||||
filevec[f].undiscarded[j] = filevec[f].equivs[i];
|
||||
filevec[f].realindexes[j++] = i;
|
||||
}
|
||||
else
|
||||
filevec[f].changed[i] = 1;
|
||||
filevec[f].nondiscarded_lines = j;
|
||||
}
|
||||
|
||||
free (discarded[0]);
|
||||
free (equiv_count[0]);
|
||||
}
|
||||
|
||||
/* Adjust inserts/deletes of identical lines to join changes
|
||||
as much as possible.
|
||||
|
||||
We do something when a run of changed lines include a
|
||||
line at one end and have an excluded, identical line at the other.
|
||||
We are free to choose which identical line is included.
|
||||
'compareseq' usually chooses the one at the beginning,
|
||||
but usually it is cleaner to consider the following identical line
|
||||
to be the "change". */
|
||||
|
||||
static void
|
||||
shift_boundaries (struct file_data filevec[])
|
||||
{
|
||||
int f;
|
||||
|
||||
for (f = 0; f < 2; f++)
|
||||
{
|
||||
char *changed = filevec[f].changed;
|
||||
char *other_changed = filevec[1 - f].changed;
|
||||
lin const *equivs = filevec[f].equivs;
|
||||
lin i = 0;
|
||||
lin j = 0;
|
||||
lin i_end = filevec[f].buffered_lines;
|
||||
|
||||
while (1)
|
||||
{
|
||||
lin runlength, start, corresponding;
|
||||
|
||||
/* Scan forwards to find beginning of another run of changes.
|
||||
Also keep track of the corresponding point in the other file. */
|
||||
|
||||
while (i < i_end && !changed[i])
|
||||
{
|
||||
while (other_changed[j++])
|
||||
continue;
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i == i_end)
|
||||
break;
|
||||
|
||||
start = i;
|
||||
|
||||
/* Find the end of this run of changes. */
|
||||
|
||||
while (changed[++i])
|
||||
continue;
|
||||
while (other_changed[j])
|
||||
j++;
|
||||
|
||||
do
|
||||
{
|
||||
/* Record the length of this run of changes, so that
|
||||
we can later determine whether the run has grown. */
|
||||
runlength = i - start;
|
||||
|
||||
/* Move the changed region back, so long as the
|
||||
previous unchanged line matches the last changed one.
|
||||
This merges with previous changed regions. */
|
||||
|
||||
while (start && equivs[start - 1] == equivs[i - 1])
|
||||
{
|
||||
changed[--start] = 1;
|
||||
changed[--i] = 0;
|
||||
while (changed[start - 1])
|
||||
start--;
|
||||
while (other_changed[--j])
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Set CORRESPONDING to the end of the changed run, at the last
|
||||
point where it corresponds to a changed run in the other file.
|
||||
CORRESPONDING == I_END means no such point has been found. */
|
||||
corresponding = other_changed[j - 1] ? i : i_end;
|
||||
|
||||
/* Move the changed region forward, so long as the
|
||||
first changed line matches the following unchanged one.
|
||||
This merges with following changed regions.
|
||||
Do this second, so that if there are no merges,
|
||||
the changed region is moved forward as far as possible. */
|
||||
|
||||
while (i != i_end && equivs[start] == equivs[i])
|
||||
{
|
||||
changed[start++] = 0;
|
||||
changed[i++] = 1;
|
||||
while (changed[i])
|
||||
i++;
|
||||
while (other_changed[++j])
|
||||
corresponding = i;
|
||||
}
|
||||
}
|
||||
while (runlength != i - start);
|
||||
|
||||
/* If possible, move the fully-merged run of changes
|
||||
back to a corresponding run in the other file. */
|
||||
|
||||
while (corresponding < i)
|
||||
{
|
||||
changed[--start] = 1;
|
||||
changed[--i] = 0;
|
||||
while (other_changed[--j])
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Cons an additional entry onto the front of an edit script OLD.
|
||||
LINE0 and LINE1 are the first affected lines in the two files (origin 0).
|
||||
DELETED is the number of lines deleted here from file 0.
|
||||
INSERTED is the number of lines inserted here in file 1.
|
||||
|
||||
If DELETED is 0 then LINE0 is the number of the line before
|
||||
which the insertion was done; vice versa for INSERTED and LINE1. */
|
||||
|
||||
static struct change *
|
||||
add_change (lin line0, lin line1, lin deleted, lin inserted,
|
||||
struct change *old)
|
||||
{
|
||||
struct change *new = xmalloc (sizeof *new);
|
||||
|
||||
new->line0 = line0;
|
||||
new->line1 = line1;
|
||||
new->inserted = inserted;
|
||||
new->deleted = deleted;
|
||||
new->link = old;
|
||||
return new;
|
||||
}
|
||||
|
||||
/* Scan the tables of which lines are inserted and deleted,
|
||||
producing an edit script in reverse order. */
|
||||
|
||||
static struct change *
|
||||
build_reverse_script (struct file_data const filevec[])
|
||||
{
|
||||
struct change *script = 0;
|
||||
char *changed0 = filevec[0].changed;
|
||||
char *changed1 = filevec[1].changed;
|
||||
lin len0 = filevec[0].buffered_lines;
|
||||
lin len1 = filevec[1].buffered_lines;
|
||||
|
||||
/* Note that changedN[lenN] does exist, and is 0. */
|
||||
|
||||
lin i0 = 0, i1 = 0;
|
||||
|
||||
while (i0 < len0 || i1 < len1)
|
||||
{
|
||||
if (changed0[i0] | changed1[i1])
|
||||
{
|
||||
lin line0 = i0, line1 = i1;
|
||||
|
||||
/* Find # lines changed here in each file. */
|
||||
while (changed0[i0]) ++i0;
|
||||
while (changed1[i1]) ++i1;
|
||||
|
||||
/* Record this change. */
|
||||
script = add_change (line0, line1, i0 - line0, i1 - line1, script);
|
||||
}
|
||||
|
||||
/* We have reached lines in the two files that match each other. */
|
||||
i0++, i1++;
|
||||
}
|
||||
|
||||
return script;
|
||||
}
|
||||
|
||||
/* Scan the tables of which lines are inserted and deleted,
|
||||
producing an edit script in forward order. */
|
||||
|
||||
static struct change *
|
||||
build_script (struct file_data const filevec[])
|
||||
{
|
||||
struct change *script = 0;
|
||||
char *changed0 = filevec[0].changed;
|
||||
char *changed1 = filevec[1].changed;
|
||||
lin i0 = filevec[0].buffered_lines, i1 = filevec[1].buffered_lines;
|
||||
|
||||
/* Note that changedN[-1] does exist, and is 0. */
|
||||
|
||||
while (i0 >= 0 || i1 >= 0)
|
||||
{
|
||||
if (changed0[i0 - 1] | changed1[i1 - 1])
|
||||
{
|
||||
lin line0 = i0, line1 = i1;
|
||||
|
||||
/* Find # lines changed here in each file. */
|
||||
while (changed0[i0 - 1]) --i0;
|
||||
while (changed1[i1 - 1]) --i1;
|
||||
|
||||
/* Record this change. */
|
||||
script = add_change (i0, i1, line0 - i0, line1 - i1, script);
|
||||
}
|
||||
|
||||
/* We have reached lines in the two files that match each other. */
|
||||
i0--, i1--;
|
||||
}
|
||||
|
||||
return script;
|
||||
}
|
||||
|
||||
/* If CHANGES, briefly report that two files differed. */
|
||||
static void
|
||||
briefly_report (int changes, struct file_data const filevec[])
|
||||
{
|
||||
if (changes)
|
||||
message ((brief
|
||||
? _("Files %s and %s differ\n")
|
||||
: _("Binary files %s and %s differ\n")),
|
||||
file_label[0] ? file_label[0] : filevec[0].name,
|
||||
file_label[1] ? file_label[1] : filevec[1].name);
|
||||
}
|
||||
|
||||
/* Report the differences of two files. */
|
||||
int
|
||||
diff_2_files (struct comparison *cmp)
|
||||
{
|
||||
int f;
|
||||
struct change *e, *p;
|
||||
struct change *script;
|
||||
int changes;
|
||||
|
||||
|
||||
/* If we have detected that either file is binary,
|
||||
compare the two files as binary. This can happen
|
||||
only when the first chunk is read.
|
||||
Also, --brief without any --ignore-* options means
|
||||
we can speed things up by treating the files as binary. */
|
||||
|
||||
if (read_files (cmp->file, files_can_be_treated_as_binary))
|
||||
{
|
||||
/* Files with different lengths must be different. */
|
||||
if (cmp->file[0].stat.st_size != cmp->file[1].stat.st_size
|
||||
&& 0 < cmp->file[0].stat.st_size
|
||||
&& 0 < cmp->file[1].stat.st_size
|
||||
&& (cmp->file[0].desc < 0 || S_ISREG (cmp->file[0].stat.st_mode))
|
||||
&& (cmp->file[1].desc < 0 || S_ISREG (cmp->file[1].stat.st_mode)))
|
||||
changes = 1;
|
||||
|
||||
/* Standard input equals itself. */
|
||||
else if (cmp->file[0].desc == cmp->file[1].desc)
|
||||
changes = 0;
|
||||
|
||||
else
|
||||
/* Scan both files, a buffer at a time, looking for a difference. */
|
||||
{
|
||||
/* Allocate same-sized buffers for both files. */
|
||||
size_t lcm_max = PTRDIFF_MAX - 1;
|
||||
size_t buffer_size =
|
||||
buffer_lcm (sizeof (word),
|
||||
buffer_lcm (STAT_BLOCKSIZE (cmp->file[0].stat),
|
||||
STAT_BLOCKSIZE (cmp->file[1].stat),
|
||||
lcm_max),
|
||||
lcm_max);
|
||||
for (f = 0; f < 2; f++)
|
||||
cmp->file[f].buffer = xrealloc (cmp->file[f].buffer, buffer_size);
|
||||
|
||||
for (;; cmp->file[0].buffered = cmp->file[1].buffered = 0)
|
||||
{
|
||||
/* Read a buffer's worth from both files. */
|
||||
for (f = 0; f < 2; f++)
|
||||
if (0 <= cmp->file[f].desc)
|
||||
file_block_read (&cmp->file[f],
|
||||
buffer_size - cmp->file[f].buffered);
|
||||
|
||||
/* If the buffers differ, the files differ. */
|
||||
if (cmp->file[0].buffered != cmp->file[1].buffered
|
||||
|| memcmp (cmp->file[0].buffer,
|
||||
cmp->file[1].buffer,
|
||||
cmp->file[0].buffered))
|
||||
{
|
||||
changes = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
/* If we reach end of file, the files are the same. */
|
||||
if (cmp->file[0].buffered != buffer_size)
|
||||
{
|
||||
changes = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
briefly_report (changes, cmp->file);
|
||||
}
|
||||
else
|
||||
{
|
||||
struct context ctxt;
|
||||
lin diags;
|
||||
lin too_expensive;
|
||||
|
||||
/* Allocate vectors for the results of comparison:
|
||||
a flag for each line of each file, saying whether that line
|
||||
is an insertion or deletion.
|
||||
Allocate an extra element, always 0, at each end of each vector. */
|
||||
|
||||
size_t s = cmp->file[0].buffered_lines + cmp->file[1].buffered_lines + 4;
|
||||
char *flag_space = zalloc (s);
|
||||
cmp->file[0].changed = flag_space + 1;
|
||||
cmp->file[1].changed = flag_space + cmp->file[0].buffered_lines + 3;
|
||||
|
||||
/* Some lines are obviously insertions or deletions
|
||||
because they don't match anything. Detect them now, and
|
||||
avoid even thinking about them in the main comparison algorithm. */
|
||||
|
||||
discard_confusing_lines (cmp->file);
|
||||
|
||||
/* Now do the main comparison algorithm, considering just the
|
||||
undiscarded lines. */
|
||||
|
||||
ctxt.xvec = cmp->file[0].undiscarded;
|
||||
ctxt.yvec = cmp->file[1].undiscarded;
|
||||
diags = (cmp->file[0].nondiscarded_lines
|
||||
+ cmp->file[1].nondiscarded_lines + 3);
|
||||
ctxt.fdiag = xmalloc (diags * (2 * sizeof *ctxt.fdiag));
|
||||
ctxt.bdiag = ctxt.fdiag + diags;
|
||||
ctxt.fdiag += cmp->file[1].nondiscarded_lines + 1;
|
||||
ctxt.bdiag += cmp->file[1].nondiscarded_lines + 1;
|
||||
|
||||
ctxt.heuristic = speed_large_files;
|
||||
|
||||
/* Set TOO_EXPENSIVE to be the approximate square root of the
|
||||
input size, bounded below by 4096. 4096 seems to be good for
|
||||
circa-2016 CPUs; see Bug#16848 and Bug#24715. */
|
||||
too_expensive = 1;
|
||||
for (; diags != 0; diags >>= 2)
|
||||
too_expensive <<= 1;
|
||||
ctxt.too_expensive = MAX (4096, too_expensive);
|
||||
|
||||
files[0] = cmp->file[0];
|
||||
files[1] = cmp->file[1];
|
||||
|
||||
compareseq (0, cmp->file[0].nondiscarded_lines,
|
||||
0, cmp->file[1].nondiscarded_lines, minimal, &ctxt);
|
||||
|
||||
free (ctxt.fdiag - (cmp->file[1].nondiscarded_lines + 1));
|
||||
|
||||
/* Modify the results slightly to make them prettier
|
||||
in cases where that can validly be done. */
|
||||
|
||||
shift_boundaries (cmp->file);
|
||||
|
||||
/* Get the results of comparison in the form of a chain
|
||||
of 'struct change's -- an edit script. */
|
||||
|
||||
if (output_style == OUTPUT_ED)
|
||||
script = build_reverse_script (cmp->file);
|
||||
else
|
||||
script = build_script (cmp->file);
|
||||
|
||||
/* Set CHANGES if we had any diffs.
|
||||
If some changes are ignored, we must scan the script to decide. */
|
||||
if (ignore_blank_lines || ignore_regexp.fastmap)
|
||||
{
|
||||
struct change *next = script;
|
||||
changes = 0;
|
||||
|
||||
while (next && changes == 0)
|
||||
{
|
||||
struct change *this, *end;
|
||||
lin first0, last0, first1, last1;
|
||||
|
||||
/* Find a set of changes that belong together. */
|
||||
this = next;
|
||||
end = find_change (next);
|
||||
|
||||
/* Disconnect them from the rest of the changes, making them
|
||||
a hunk, and remember the rest for next iteration. */
|
||||
next = end->link;
|
||||
end->link = 0;
|
||||
|
||||
/* Determine whether this hunk is really a difference. */
|
||||
if (analyze_hunk (this, &first0, &last0, &first1, &last1))
|
||||
changes = 1;
|
||||
|
||||
/* Reconnect the script so it will all be freed properly. */
|
||||
end->link = next;
|
||||
}
|
||||
}
|
||||
else
|
||||
changes = (script != 0);
|
||||
|
||||
if (brief)
|
||||
briefly_report (changes, cmp->file);
|
||||
else
|
||||
{
|
||||
if (changes || !no_diff_means_no_output)
|
||||
{
|
||||
/* Record info for starting up output,
|
||||
to be used if and when we have some output to print. */
|
||||
setup_output (file_label[0] ? file_label[0] : cmp->file[0].name,
|
||||
file_label[1] ? file_label[1] : cmp->file[1].name,
|
||||
cmp->parent != 0);
|
||||
|
||||
switch (output_style)
|
||||
{
|
||||
case OUTPUT_CONTEXT:
|
||||
print_context_script (script, false);
|
||||
break;
|
||||
|
||||
case OUTPUT_UNIFIED:
|
||||
print_context_script (script, true);
|
||||
break;
|
||||
|
||||
case OUTPUT_ED:
|
||||
print_ed_script (script);
|
||||
break;
|
||||
|
||||
case OUTPUT_FORWARD_ED:
|
||||
pr_forward_ed_script (script);
|
||||
break;
|
||||
|
||||
case OUTPUT_RCS:
|
||||
print_rcs_script (script);
|
||||
break;
|
||||
|
||||
case OUTPUT_NORMAL:
|
||||
print_normal_script (script);
|
||||
break;
|
||||
|
||||
case OUTPUT_IFDEF:
|
||||
print_ifdef_script (script);
|
||||
break;
|
||||
|
||||
case OUTPUT_SDIFF:
|
||||
print_sdiff_script (script);
|
||||
break;
|
||||
|
||||
default:
|
||||
abort ();
|
||||
}
|
||||
|
||||
finish_output ();
|
||||
}
|
||||
}
|
||||
|
||||
free (cmp->file[0].undiscarded);
|
||||
|
||||
free (flag_space);
|
||||
|
||||
for (f = 0; f < 2; f++)
|
||||
{
|
||||
free (cmp->file[f].equivs);
|
||||
free (cmp->file[f].linbuf + cmp->file[f].linbuf_base);
|
||||
}
|
||||
|
||||
for (e = script; e; e = p)
|
||||
{
|
||||
p = e->link;
|
||||
free (e);
|
||||
}
|
||||
|
||||
if (! ROBUST_OUTPUT_STYLE (output_style))
|
||||
for (f = 0; f < 2; ++f)
|
||||
if (cmp->file[f].missing_newline)
|
||||
{
|
||||
error (0, 0, "%s: %s\n",
|
||||
file_label[f] ? file_label[f] : cmp->file[f].name,
|
||||
_("No newline at end of file"));
|
||||
changes = 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (cmp->file[0].buffer != cmp->file[1].buffer)
|
||||
free (cmp->file[0].buffer);
|
||||
free (cmp->file[1].buffer);
|
||||
|
||||
return changes;
|
||||
}
|
||||
@@ -0,0 +1,693 @@
|
||||
/* cmp - compare two files byte by byte
|
||||
|
||||
Copyright (C) 1990-1996, 1998, 2001-2002, 2004, 2006-2007, 2009-2013,
|
||||
2015-2017 Free Software Foundation, Inc.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
#include "system.h"
|
||||
#include "paths.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <c-stack.h>
|
||||
#include <cmpbuf.h>
|
||||
#include "die.h"
|
||||
#include <error.h>
|
||||
#include <exitfail.h>
|
||||
#include <file-type.h>
|
||||
#include <getopt.h>
|
||||
#include <hard-locale.h>
|
||||
#include <inttostr.h>
|
||||
#include <progname.h>
|
||||
#include <unlocked-io.h>
|
||||
#include <version-etc.h>
|
||||
#include <xalloc.h>
|
||||
#include <binary-io.h>
|
||||
#include <xstrtol.h>
|
||||
|
||||
/* The official name of this program (e.g., no 'g' prefix). */
|
||||
#define PROGRAM_NAME "cmp"
|
||||
|
||||
#define AUTHORS \
|
||||
proper_name_utf8 ("Torbjorn Granlund", "Torbj\303\266rn Granlund"), \
|
||||
proper_name ("David MacKenzie")
|
||||
|
||||
#if defined LC_MESSAGES && ENABLE_NLS
|
||||
# define hard_locale_LC_MESSAGES hard_locale (LC_MESSAGES)
|
||||
#else
|
||||
# define hard_locale_LC_MESSAGES 0
|
||||
#endif
|
||||
|
||||
static int cmp (void);
|
||||
static off_t file_position (int);
|
||||
static size_t block_compare (word const *, word const *) _GL_ATTRIBUTE_PURE;
|
||||
static size_t count_newlines (char *, size_t);
|
||||
static void sprintc (char *, unsigned char);
|
||||
|
||||
/* Filenames of the compared files. */
|
||||
static char const *file[2];
|
||||
|
||||
/* File descriptors of the files. */
|
||||
static int file_desc[2];
|
||||
|
||||
/* Status of the files. */
|
||||
static struct stat stat_buf[2];
|
||||
|
||||
/* Read buffers for the files. */
|
||||
static word *buffer[2];
|
||||
|
||||
/* Optimal block size for the files. */
|
||||
static size_t buf_size;
|
||||
|
||||
/* Initial prefix to ignore for each file. */
|
||||
static off_t ignore_initial[2];
|
||||
|
||||
/* Number of bytes to compare. */
|
||||
static uintmax_t bytes = UINTMAX_MAX;
|
||||
|
||||
/* Output format. */
|
||||
static enum comparison_type
|
||||
{
|
||||
type_first_diff, /* Print the first difference. */
|
||||
type_all_diffs, /* Print all differences. */
|
||||
type_no_stdout, /* Do not output to stdout; only stderr. */
|
||||
type_status /* Exit status only. */
|
||||
} comparison_type;
|
||||
|
||||
/* If nonzero, print values of bytes quoted like cat -t does. */
|
||||
static bool opt_print_bytes;
|
||||
|
||||
/* Values for long options that do not have single-letter equivalents. */
|
||||
enum
|
||||
{
|
||||
HELP_OPTION = CHAR_MAX + 1
|
||||
};
|
||||
|
||||
static struct option const long_options[] =
|
||||
{
|
||||
{"print-bytes", 0, 0, 'b'},
|
||||
{"print-chars", 0, 0, 'c'}, /* obsolescent as of diffutils 2.7.3 */
|
||||
{"ignore-initial", 1, 0, 'i'},
|
||||
{"verbose", 0, 0, 'l'},
|
||||
{"bytes", 1, 0, 'n'},
|
||||
{"silent", 0, 0, 's'},
|
||||
{"quiet", 0, 0, 's'},
|
||||
{"version", 0, 0, 'v'},
|
||||
{"help", 0, 0, HELP_OPTION},
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
static void try_help (char const *, char const *) __attribute__((noreturn));
|
||||
static void
|
||||
try_help (char const *reason_msgid, char const *operand)
|
||||
{
|
||||
if (reason_msgid)
|
||||
error (0, 0, _(reason_msgid), operand);
|
||||
die (EXIT_TROUBLE, 0,
|
||||
_("Try '%s --help' for more information."), program_name);
|
||||
}
|
||||
|
||||
static char const valid_suffixes[] = "kKMGTPEZY0";
|
||||
|
||||
/* Update ignore_initial[F] according to the result of parsing an
|
||||
*operand ARGPTR of --ignore-initial, updating *ARGPTR to point
|
||||
*after the operand. If DELIMITER is nonzero, the operand may be
|
||||
*followed by DELIMITER; otherwise it must be null-terminated. */
|
||||
static void
|
||||
specify_ignore_initial (int f, char **argptr, char delimiter)
|
||||
{
|
||||
uintmax_t val;
|
||||
char const *arg = *argptr;
|
||||
strtol_error e = xstrtoumax (arg, argptr, 0, &val, valid_suffixes);
|
||||
if (! (e == LONGINT_OK
|
||||
|| (e == LONGINT_INVALID_SUFFIX_CHAR && **argptr == delimiter))
|
||||
|| TYPE_MAXIMUM (off_t) < val)
|
||||
try_help ("invalid --ignore-initial value '%s'", arg);
|
||||
if (ignore_initial[f] < val)
|
||||
ignore_initial[f] = val;
|
||||
}
|
||||
|
||||
/* Specify the output format. */
|
||||
static void
|
||||
specify_comparison_type (enum comparison_type t)
|
||||
{
|
||||
if (comparison_type && comparison_type != t)
|
||||
try_help ("options -l and -s are incompatible", 0);
|
||||
comparison_type = t;
|
||||
}
|
||||
|
||||
static void
|
||||
check_stdout (void)
|
||||
{
|
||||
if (ferror (stdout))
|
||||
die (EXIT_TROUBLE, 0, "%s", _("write failed"));
|
||||
else if (fclose (stdout) != 0)
|
||||
die (EXIT_TROUBLE, errno, "%s", _("standard output"));
|
||||
}
|
||||
|
||||
static char const * const option_help_msgid[] = {
|
||||
N_("-b, --print-bytes print differing bytes"),
|
||||
N_("-i, --ignore-initial=SKIP skip first SKIP bytes of both inputs"),
|
||||
N_("-i, --ignore-initial=SKIP1:SKIP2 skip first SKIP1 bytes of FILE1 and\n"
|
||||
" first SKIP2 bytes of FILE2"),
|
||||
N_("-l, --verbose output byte numbers and differing byte values"),
|
||||
N_("-n, --bytes=LIMIT compare at most LIMIT bytes"),
|
||||
N_("-s, --quiet, --silent suppress all normal output"),
|
||||
N_(" --help display this help and exit"),
|
||||
N_("-v, --version output version information and exit"),
|
||||
0
|
||||
};
|
||||
|
||||
static void
|
||||
usage (void)
|
||||
{
|
||||
char const * const *p;
|
||||
|
||||
printf (_("Usage: %s [OPTION]... FILE1 [FILE2 [SKIP1 [SKIP2]]]\n"),
|
||||
program_name);
|
||||
printf ("%s\n", _("Compare two files byte by byte."));
|
||||
printf ("\n%s\n\n",
|
||||
_("The optional SKIP1 and SKIP2 specify the number of bytes to skip\n"
|
||||
"at the beginning of each file (zero by default)."));
|
||||
|
||||
fputs (_("\
|
||||
Mandatory arguments to long options are mandatory for short options too.\n\
|
||||
"), stdout);
|
||||
for (p = option_help_msgid; *p; p++)
|
||||
printf (" %s\n", _(*p));
|
||||
printf ("\n%s\n\n%s\n%s\n",
|
||||
_("SKIP values may be followed by the following multiplicative suffixes:\n\
|
||||
kB 1000, K 1024, MB 1,000,000, M 1,048,576,\n\
|
||||
GB 1,000,000,000, G 1,073,741,824, and so on for T, P, E, Z, Y."),
|
||||
_("If a FILE is '-' or missing, read standard input."),
|
||||
_("Exit status is 0 if inputs are the same, 1 if different, 2 if trouble."));
|
||||
emit_bug_reporting_address ();
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char **argv)
|
||||
{
|
||||
int c, f, exit_status;
|
||||
size_t words_per_buffer;
|
||||
|
||||
exit_failure = EXIT_TROUBLE;
|
||||
initialize_main (&argc, &argv);
|
||||
set_program_name (argv[0]);
|
||||
setlocale (LC_ALL, "");
|
||||
bindtextdomain (PACKAGE, LOCALEDIR);
|
||||
textdomain (PACKAGE);
|
||||
c_stack_action (0);
|
||||
|
||||
/* Parse command line options. */
|
||||
|
||||
while ((c = getopt_long (argc, argv, "bci:ln:sv", long_options, 0))
|
||||
!= -1)
|
||||
switch (c)
|
||||
{
|
||||
case 'b':
|
||||
case 'c': /* 'c' is obsolescent as of diffutils 2.7.3 */
|
||||
opt_print_bytes = true;
|
||||
break;
|
||||
|
||||
case 'i':
|
||||
specify_ignore_initial (0, &optarg, ':');
|
||||
if (*optarg++ == ':')
|
||||
specify_ignore_initial (1, &optarg, 0);
|
||||
else if (ignore_initial[1] < ignore_initial[0])
|
||||
ignore_initial[1] = ignore_initial[0];
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
specify_comparison_type (type_all_diffs);
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
{
|
||||
uintmax_t n;
|
||||
if (xstrtoumax (optarg, 0, 0, &n, valid_suffixes) != LONGINT_OK)
|
||||
try_help ("invalid --bytes value '%s'", optarg);
|
||||
if (n < bytes)
|
||||
bytes = n;
|
||||
}
|
||||
break;
|
||||
|
||||
case 's':
|
||||
specify_comparison_type (type_status);
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
version_etc (stdout, PROGRAM_NAME, PACKAGE_NAME, Version,
|
||||
AUTHORS, (char *) NULL);
|
||||
check_stdout ();
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
case HELP_OPTION:
|
||||
usage ();
|
||||
check_stdout ();
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
default:
|
||||
try_help (0, 0);
|
||||
}
|
||||
|
||||
if (optind == argc)
|
||||
try_help ("missing operand after '%s'", argv[argc - 1]);
|
||||
|
||||
file[0] = argv[optind++];
|
||||
file[1] = optind < argc ? argv[optind++] : "-";
|
||||
|
||||
for (f = 0; f < 2 && optind < argc; f++)
|
||||
{
|
||||
char *arg = argv[optind++];
|
||||
specify_ignore_initial (f, &arg, 0);
|
||||
}
|
||||
|
||||
if (optind < argc)
|
||||
try_help ("extra operand '%s'", argv[optind]);
|
||||
|
||||
for (f = 0; f < 2; f++)
|
||||
{
|
||||
/* If file[1] is "-", treat it first; this avoids a misdiagnostic if
|
||||
stdin is closed and opening file[0] yields file descriptor 0. */
|
||||
int f1 = f ^ (STREQ (file[1], "-"));
|
||||
|
||||
/* Two files with the same name and offset are identical.
|
||||
But wait until we open the file once, for proper diagnostics. */
|
||||
if (f && ignore_initial[0] == ignore_initial[1]
|
||||
&& file_name_cmp (file[0], file[1]) == 0)
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
if (STREQ (file[f1], "-"))
|
||||
{
|
||||
file_desc[f1] = STDIN_FILENO;
|
||||
if (O_BINARY && ! isatty (STDIN_FILENO))
|
||||
set_binary_mode (STDIN_FILENO, O_BINARY);
|
||||
}
|
||||
else
|
||||
file_desc[f1] = open (file[f1], O_RDONLY | O_BINARY, 0);
|
||||
|
||||
if (file_desc[f1] < 0 || fstat (file_desc[f1], stat_buf + f1) != 0)
|
||||
{
|
||||
if (file_desc[f1] < 0 && comparison_type == type_status)
|
||||
exit (EXIT_TROUBLE);
|
||||
else
|
||||
die (EXIT_TROUBLE, errno, "%s", file[f1]);
|
||||
}
|
||||
}
|
||||
|
||||
/* If the files are links to the same inode and have the same file position,
|
||||
they are identical. */
|
||||
|
||||
if (0 < same_file (&stat_buf[0], &stat_buf[1])
|
||||
&& same_file_attributes (&stat_buf[0], &stat_buf[1])
|
||||
&& file_position (0) == file_position (1))
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
/* If output is redirected to the null device, we can avoid some of
|
||||
the work. */
|
||||
|
||||
if (comparison_type != type_status)
|
||||
{
|
||||
struct stat outstat, nullstat;
|
||||
|
||||
if (fstat (STDOUT_FILENO, &outstat) == 0
|
||||
&& stat (NULL_DEVICE, &nullstat) == 0
|
||||
&& 0 < same_file (&outstat, &nullstat))
|
||||
comparison_type = type_no_stdout;
|
||||
}
|
||||
|
||||
/* If only a return code is needed,
|
||||
and if both input descriptors are associated with plain files,
|
||||
conclude that the files differ if they have different sizes
|
||||
and if more bytes will be compared than are in the smaller file. */
|
||||
|
||||
if (comparison_type == type_status
|
||||
&& S_ISREG (stat_buf[0].st_mode)
|
||||
&& S_ISREG (stat_buf[1].st_mode))
|
||||
{
|
||||
off_t s0 = stat_buf[0].st_size - file_position (0);
|
||||
off_t s1 = stat_buf[1].st_size - file_position (1);
|
||||
if (s0 < 0)
|
||||
s0 = 0;
|
||||
if (s1 < 0)
|
||||
s1 = 0;
|
||||
if (s0 != s1 && MIN (s0, s1) < bytes)
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* Get the optimal block size of the files. */
|
||||
|
||||
buf_size = buffer_lcm (STAT_BLOCKSIZE (stat_buf[0]),
|
||||
STAT_BLOCKSIZE (stat_buf[1]),
|
||||
PTRDIFF_MAX - sizeof (word));
|
||||
|
||||
/* Allocate word-aligned buffers, with space for sentinels at the end. */
|
||||
|
||||
words_per_buffer = (buf_size + 2 * sizeof (word) - 1) / sizeof (word);
|
||||
buffer[0] = xmalloc (2 * sizeof (word) * words_per_buffer);
|
||||
buffer[1] = buffer[0] + words_per_buffer;
|
||||
|
||||
exit_status = cmp ();
|
||||
|
||||
for (f = 0; f < 2; f++)
|
||||
if (close (file_desc[f]) != 0)
|
||||
die (EXIT_TROUBLE, errno, "%s", file[f]);
|
||||
if (exit_status != EXIT_SUCCESS && comparison_type < type_no_stdout)
|
||||
check_stdout ();
|
||||
exit (exit_status);
|
||||
return exit_status;
|
||||
}
|
||||
|
||||
/* Compare the two files already open on 'file_desc[0]' and 'file_desc[1]',
|
||||
using 'buffer[0]' and 'buffer[1]'.
|
||||
Return EXIT_SUCCESS if identical, EXIT_FAILURE if different,
|
||||
>1 if error. */
|
||||
|
||||
static int
|
||||
cmp (void)
|
||||
{
|
||||
bool at_line_start = true;
|
||||
off_t line_number = 1; /* Line number (1...) of difference. */
|
||||
off_t byte_number = 1; /* Byte number (1...) of difference. */
|
||||
uintmax_t remaining = bytes; /* Remaining number of bytes to compare. */
|
||||
size_t read0, read1; /* Number of bytes read from each file. */
|
||||
size_t first_diff; /* Offset (0...) in buffers of 1st diff. */
|
||||
size_t smaller; /* The lesser of 'read0' and 'read1'. */
|
||||
word *buffer0 = buffer[0];
|
||||
word *buffer1 = buffer[1];
|
||||
char *buf0 = (char *) buffer0;
|
||||
char *buf1 = (char *) buffer1;
|
||||
int differing = 0;
|
||||
int f;
|
||||
int offset_width IF_LINT (= 0);
|
||||
|
||||
if (comparison_type == type_all_diffs)
|
||||
{
|
||||
off_t byte_number_max = MIN (bytes, TYPE_MAXIMUM (off_t));
|
||||
|
||||
for (f = 0; f < 2; f++)
|
||||
if (S_ISREG (stat_buf[f].st_mode))
|
||||
{
|
||||
off_t file_bytes = stat_buf[f].st_size - file_position (f);
|
||||
if (file_bytes < byte_number_max)
|
||||
byte_number_max = file_bytes;
|
||||
}
|
||||
|
||||
for (offset_width = 1; (byte_number_max /= 10) != 0; offset_width++)
|
||||
continue;
|
||||
}
|
||||
|
||||
for (f = 0; f < 2; f++)
|
||||
{
|
||||
off_t ig = ignore_initial[f];
|
||||
if (ig && file_position (f) == -1)
|
||||
{
|
||||
/* lseek failed; read and discard the ignored initial prefix. */
|
||||
do
|
||||
{
|
||||
size_t bytes_to_read = MIN (ig, buf_size);
|
||||
size_t r = block_read (file_desc[f], buf0, bytes_to_read);
|
||||
if (r != bytes_to_read)
|
||||
{
|
||||
if (r == SIZE_MAX)
|
||||
die (EXIT_TROUBLE, errno, "%s", file[f]);
|
||||
break;
|
||||
}
|
||||
ig -= r;
|
||||
}
|
||||
while (ig);
|
||||
}
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
size_t bytes_to_read = buf_size;
|
||||
|
||||
if (remaining != UINTMAX_MAX)
|
||||
{
|
||||
if (remaining < bytes_to_read)
|
||||
bytes_to_read = remaining;
|
||||
remaining -= bytes_to_read;
|
||||
}
|
||||
|
||||
read0 = block_read (file_desc[0], buf0, bytes_to_read);
|
||||
if (read0 == SIZE_MAX)
|
||||
die (EXIT_TROUBLE, errno, "%s", file[0]);
|
||||
read1 = block_read (file_desc[1], buf1, bytes_to_read);
|
||||
if (read1 == SIZE_MAX)
|
||||
die (EXIT_TROUBLE, errno, "%s", file[1]);
|
||||
|
||||
smaller = MIN (read0, read1);
|
||||
|
||||
/* Optimize the common case where the buffers are the same. */
|
||||
if (memcmp (buf0, buf1, smaller) == 0)
|
||||
first_diff = smaller;
|
||||
else
|
||||
{
|
||||
/* Insert sentinels for the block compare. */
|
||||
buf0[read0] = ~buf1[read0];
|
||||
buf1[read1] = ~buf0[read1];
|
||||
|
||||
first_diff = block_compare (buffer0, buffer1);
|
||||
}
|
||||
|
||||
byte_number += first_diff;
|
||||
if (comparison_type == type_first_diff && first_diff != 0)
|
||||
{
|
||||
line_number += count_newlines (buf0, first_diff);
|
||||
at_line_start = buf0[first_diff - 1] == '\n';
|
||||
}
|
||||
|
||||
if (first_diff < smaller)
|
||||
{
|
||||
switch (comparison_type)
|
||||
{
|
||||
case type_first_diff:
|
||||
{
|
||||
char byte_buf[INT_BUFSIZE_BOUND (off_t)];
|
||||
char line_buf[INT_BUFSIZE_BOUND (off_t)];
|
||||
char const *byte_num = offtostr (byte_number, byte_buf);
|
||||
char const *line_num = offtostr (line_number, line_buf);
|
||||
if (!opt_print_bytes)
|
||||
{
|
||||
/* See POSIX for this format. This message is
|
||||
used only in the POSIX locale, so it need not
|
||||
be translated. */
|
||||
static char const char_message[] =
|
||||
"%s %s differ: char %s, line %s\n";
|
||||
|
||||
/* The POSIX rationale recommends using the word
|
||||
"byte" outside the POSIX locale. Some gettext
|
||||
implementations translate even in the POSIX
|
||||
locale if certain other environment variables
|
||||
are set, so use "byte" if a translation is
|
||||
available, or if outside the POSIX locale. */
|
||||
static char const byte_msgid[] =
|
||||
N_("%s %s differ: byte %s, line %s\n");
|
||||
char const *byte_message = _(byte_msgid);
|
||||
bool use_byte_message = (byte_message != byte_msgid
|
||||
|| hard_locale_LC_MESSAGES);
|
||||
|
||||
printf (use_byte_message ? byte_message : char_message,
|
||||
file[0], file[1], byte_num, line_num);
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned char c0 = buf0[first_diff];
|
||||
unsigned char c1 = buf1[first_diff];
|
||||
char s0[5];
|
||||
char s1[5];
|
||||
sprintc (s0, c0);
|
||||
sprintc (s1, c1);
|
||||
printf (_("%s %s differ: byte %s, line %s is %3o %s %3o %s\n"),
|
||||
file[0], file[1], byte_num, line_num,
|
||||
c0, s0, c1, s1);
|
||||
}
|
||||
}
|
||||
FALLTHROUGH;
|
||||
case type_status:
|
||||
return EXIT_FAILURE;
|
||||
|
||||
case type_all_diffs:
|
||||
do
|
||||
{
|
||||
unsigned char c0 = buf0[first_diff];
|
||||
unsigned char c1 = buf1[first_diff];
|
||||
if (c0 != c1)
|
||||
{
|
||||
char byte_buf[INT_BUFSIZE_BOUND (off_t)];
|
||||
char const *byte_num = offtostr (byte_number, byte_buf);
|
||||
if (!opt_print_bytes)
|
||||
{
|
||||
/* See POSIX for this format. */
|
||||
printf ("%*s %3o %3o\n",
|
||||
offset_width, byte_num, c0, c1);
|
||||
}
|
||||
else
|
||||
{
|
||||
char s0[5];
|
||||
char s1[5];
|
||||
sprintc (s0, c0);
|
||||
sprintc (s1, c1);
|
||||
printf ("%*s %3o %-4s %3o %s\n",
|
||||
offset_width, byte_num, c0, s0, c1, s1);
|
||||
}
|
||||
}
|
||||
byte_number++;
|
||||
first_diff++;
|
||||
}
|
||||
while (first_diff < smaller);
|
||||
differing = -1;
|
||||
break;
|
||||
|
||||
case type_no_stdout:
|
||||
differing = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (read0 != read1)
|
||||
{
|
||||
if (differing <= 0 && comparison_type != type_status)
|
||||
{
|
||||
char const *shorter_file = file[read1 < read0];
|
||||
|
||||
/* POSIX says that each of these format strings must be
|
||||
"cmp: EOF on %s", optionally followed by a blank and
|
||||
extra text sans newline, then terminated by "\n". */
|
||||
if (byte_number == 1)
|
||||
fprintf (stderr, _("cmp: EOF on %s which is empty\n"),
|
||||
shorter_file);
|
||||
else
|
||||
{
|
||||
char byte_buf[INT_BUFSIZE_BOUND (off_t)];
|
||||
char const *byte_num = offtostr (byte_number - 1, byte_buf);
|
||||
|
||||
if (comparison_type == type_first_diff)
|
||||
{
|
||||
char line_buf[INT_BUFSIZE_BOUND (off_t)];
|
||||
char const *line_num
|
||||
= offtostr (line_number - at_line_start, line_buf);
|
||||
fprintf (stderr,
|
||||
(at_line_start
|
||||
? _("cmp: EOF on %s after byte %s, line %s\n")
|
||||
: _("cmp: EOF on %s after byte %s,"
|
||||
" in line %s\n")),
|
||||
shorter_file, byte_num, line_num);
|
||||
}
|
||||
else
|
||||
fprintf (stderr,
|
||||
_("cmp: EOF on %s after byte %s\n"),
|
||||
shorter_file, byte_num);
|
||||
}
|
||||
}
|
||||
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
while (differing <= 0 && read0 == buf_size);
|
||||
|
||||
return differing == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* Compare two blocks of memory P0 and P1 until they differ.
|
||||
If the blocks are not guaranteed to be different, put sentinels at the ends
|
||||
of the blocks before calling this function.
|
||||
|
||||
Return the offset of the first byte that differs. */
|
||||
|
||||
static size_t
|
||||
block_compare (word const *p0, word const *p1)
|
||||
{
|
||||
word const *l0, *l1;
|
||||
char const *c0, *c1;
|
||||
|
||||
/* Find the rough position of the first difference by reading words,
|
||||
not bytes. */
|
||||
|
||||
for (l0 = p0, l1 = p1; *l0 == *l1; l0++, l1++)
|
||||
continue;
|
||||
|
||||
/* Find the exact differing position (endianness independent). */
|
||||
|
||||
for (c0 = (char const *) l0, c1 = (char const *) l1;
|
||||
*c0 == *c1;
|
||||
c0++, c1++)
|
||||
continue;
|
||||
|
||||
return c0 - (char const *) p0;
|
||||
}
|
||||
|
||||
/* Return the number of newlines in BUF, of size BUFSIZE,
|
||||
where BUF[NBYTES] is available for use as a sentinel. */
|
||||
|
||||
static size_t
|
||||
count_newlines (char *buf, size_t bufsize)
|
||||
{
|
||||
size_t count = 0;
|
||||
char *p;
|
||||
char *lim = buf + bufsize;
|
||||
*lim = '\n';
|
||||
for (p = buf; (p = rawmemchr (p, '\n')) != lim; p++)
|
||||
count++;
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Put into BUF the unsigned char C, making unprintable bytes
|
||||
visible by quoting like cat -t does. */
|
||||
|
||||
static void
|
||||
sprintc (char *buf, unsigned char c)
|
||||
{
|
||||
if (! isprint (c))
|
||||
{
|
||||
if (c >= 128)
|
||||
{
|
||||
*buf++ = 'M';
|
||||
*buf++ = '-';
|
||||
c -= 128;
|
||||
}
|
||||
if (c < 32)
|
||||
{
|
||||
*buf++ = '^';
|
||||
c += 64;
|
||||
}
|
||||
else if (c == 127)
|
||||
{
|
||||
*buf++ = '^';
|
||||
c = '?';
|
||||
}
|
||||
}
|
||||
|
||||
*buf++ = c;
|
||||
*buf = 0;
|
||||
}
|
||||
|
||||
/* Position file F to ignore_initial[F] bytes from its initial position,
|
||||
and yield its new position. Don't try more than once. */
|
||||
|
||||
static off_t
|
||||
file_position (int f)
|
||||
{
|
||||
static bool positioned[2];
|
||||
static off_t position[2];
|
||||
|
||||
if (! positioned[f])
|
||||
{
|
||||
positioned[f] = true;
|
||||
position[f] = lseek (file_desc[f], ignore_initial[f], SEEK_CUR);
|
||||
}
|
||||
return position[f];
|
||||
}
|
||||
@@ -0,0 +1,537 @@
|
||||
/* Context-format output routines for GNU DIFF.
|
||||
|
||||
Copyright (C) 1988-1989, 1991-1995, 1998, 2001-2002, 2004, 2006, 2009-2013,
|
||||
2015-2017 Free Software Foundation, Inc.
|
||||
|
||||
This file is part of GNU DIFF.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
#include "diff.h"
|
||||
#include "c-ctype.h"
|
||||
#include <stat-time.h>
|
||||
#include <strftime.h>
|
||||
|
||||
static char const *find_function (char const * const *, lin);
|
||||
static struct change *find_hunk (struct change *);
|
||||
static void mark_ignorable (struct change *);
|
||||
static void pr_context_hunk (struct change *);
|
||||
static void pr_unidiff_hunk (struct change *);
|
||||
|
||||
/* Last place find_function started searching from. */
|
||||
static lin find_function_last_search;
|
||||
|
||||
/* The value find_function returned when it started searching there. */
|
||||
static lin find_function_last_match;
|
||||
|
||||
/* Print a label for a context diff, with a file name and date or a label. */
|
||||
|
||||
static void
|
||||
print_context_label (char const *mark,
|
||||
struct file_data *inf,
|
||||
char const *name,
|
||||
char const *label)
|
||||
{
|
||||
if (label)
|
||||
fprintf (outfile, "%s %s\n", mark, label);
|
||||
else
|
||||
{
|
||||
char buf[MAX (INT_STRLEN_BOUND (int) + 32,
|
||||
INT_STRLEN_BOUND (time_t) + 11)];
|
||||
struct tm const *tm = localtime (&inf->stat.st_mtime);
|
||||
int nsec = get_stat_mtime_ns (&inf->stat);
|
||||
if (! (tm && nstrftime (buf, sizeof buf, time_format, tm, 0, nsec)))
|
||||
{
|
||||
verify (TYPE_IS_INTEGER (time_t));
|
||||
if (LONG_MIN <= TYPE_MINIMUM (time_t)
|
||||
&& TYPE_MAXIMUM (time_t) <= LONG_MAX)
|
||||
{
|
||||
long int sec = inf->stat.st_mtime;
|
||||
sprintf (buf, "%ld.%.9d", sec, nsec);
|
||||
}
|
||||
else if (TYPE_MAXIMUM (time_t) <= INTMAX_MAX)
|
||||
{
|
||||
intmax_t sec = inf->stat.st_mtime;
|
||||
sprintf (buf, "%"PRIdMAX".%.9d", sec, nsec);
|
||||
}
|
||||
else
|
||||
{
|
||||
uintmax_t sec = inf->stat.st_mtime;
|
||||
sprintf (buf, "%"PRIuMAX".%.9d", sec, nsec);
|
||||
}
|
||||
}
|
||||
fprintf (outfile, "%s %s\t%s\n", mark, name, buf);
|
||||
}
|
||||
}
|
||||
|
||||
/* Print a header for a context diff, with the file names and dates. */
|
||||
|
||||
void
|
||||
print_context_header (struct file_data inf[], char const *const *names, bool unidiff)
|
||||
{
|
||||
set_color_context (HEADER_CONTEXT);
|
||||
if (unidiff)
|
||||
{
|
||||
print_context_label ("---", &inf[0], names[0], file_label[0]);
|
||||
print_context_label ("+++", &inf[1], names[1], file_label[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
print_context_label ("***", &inf[0], names[0], file_label[0]);
|
||||
print_context_label ("---", &inf[1], names[1], file_label[1]);
|
||||
}
|
||||
set_color_context (RESET_CONTEXT);
|
||||
}
|
||||
|
||||
/* Print an edit script in context format. */
|
||||
|
||||
void
|
||||
print_context_script (struct change *script, bool unidiff)
|
||||
{
|
||||
if (ignore_blank_lines || ignore_regexp.fastmap)
|
||||
mark_ignorable (script);
|
||||
else
|
||||
{
|
||||
struct change *e;
|
||||
for (e = script; e; e = e->link)
|
||||
e->ignore = false;
|
||||
}
|
||||
|
||||
find_function_last_search = - files[0].prefix_lines;
|
||||
find_function_last_match = LIN_MAX;
|
||||
|
||||
if (unidiff)
|
||||
print_script (script, find_hunk, pr_unidiff_hunk);
|
||||
else
|
||||
print_script (script, find_hunk, pr_context_hunk);
|
||||
}
|
||||
|
||||
/* Print a pair of line numbers with a comma, translated for file FILE.
|
||||
If the second number is not greater, use the first in place of it.
|
||||
|
||||
Args A and B are internal line numbers.
|
||||
We print the translated (real) line numbers. */
|
||||
|
||||
static void
|
||||
print_context_number_range (struct file_data const *file, lin a, lin b)
|
||||
{
|
||||
printint trans_a, trans_b;
|
||||
translate_range (file, a, b, &trans_a, &trans_b);
|
||||
|
||||
/* We can have B <= A in the case of a range of no lines.
|
||||
In this case, we should print the line number before the range,
|
||||
which is B.
|
||||
|
||||
POSIX 1003.1-2001 requires two line numbers separated by a comma
|
||||
even if the line numbers are the same. However, this does not
|
||||
match existing practice and is surely an error in the
|
||||
specification. */
|
||||
|
||||
if (trans_b <= trans_a)
|
||||
fprintf (outfile, "%"pI"d", trans_b);
|
||||
else
|
||||
fprintf (outfile, "%"pI"d,%"pI"d", trans_a, trans_b);
|
||||
}
|
||||
|
||||
/* Print FUNCTION in a context header. */
|
||||
static void
|
||||
print_context_function (FILE *out, char const *function)
|
||||
{
|
||||
int i, j;
|
||||
putc (' ', out);
|
||||
for (i = 0; c_isspace ((unsigned char) function[i]) && function[i] != '\n'; i++)
|
||||
continue;
|
||||
for (j = i; j < i + 40 && function[j] != '\n'; j++)
|
||||
continue;
|
||||
while (i < j && c_isspace ((unsigned char) function[j - 1]))
|
||||
j--;
|
||||
fwrite (function + i, sizeof (char), j - i, out);
|
||||
}
|
||||
|
||||
/* Print a portion of an edit script in context format.
|
||||
HUNK is the beginning of the portion to be printed.
|
||||
The end is marked by a 'link' that has been nulled out.
|
||||
|
||||
Prints out lines from both files, and precedes each
|
||||
line with the appropriate flag-character. */
|
||||
|
||||
static void
|
||||
pr_context_hunk (struct change *hunk)
|
||||
{
|
||||
lin first0, last0, first1, last1, i;
|
||||
char const *prefix;
|
||||
char const *function;
|
||||
FILE *out;
|
||||
|
||||
/* Determine range of line numbers involved in each file. */
|
||||
|
||||
enum changes changes = analyze_hunk (hunk, &first0, &last0, &first1, &last1);
|
||||
if (! changes)
|
||||
return;
|
||||
|
||||
/* Include a context's width before and after. */
|
||||
|
||||
i = - files[0].prefix_lines;
|
||||
first0 = MAX (first0 - context, i);
|
||||
first1 = MAX (first1 - context, i);
|
||||
if (last0 < files[0].valid_lines - context)
|
||||
last0 += context;
|
||||
else
|
||||
last0 = files[0].valid_lines - 1;
|
||||
if (last1 < files[1].valid_lines - context)
|
||||
last1 += context;
|
||||
else
|
||||
last1 = files[1].valid_lines - 1;
|
||||
|
||||
/* If desired, find the preceding function definition line in file 0. */
|
||||
function = NULL;
|
||||
if (function_regexp.fastmap)
|
||||
function = find_function (files[0].linbuf, first0);
|
||||
|
||||
begin_output ();
|
||||
out = outfile;
|
||||
|
||||
fputs ("***************", out);
|
||||
|
||||
if (function)
|
||||
print_context_function (out, function);
|
||||
|
||||
putc ('\n', out);
|
||||
set_color_context (LINE_NUMBER_CONTEXT);
|
||||
fputs ("*** ", out);
|
||||
print_context_number_range (&files[0], first0, last0);
|
||||
fputs (" ****", out);
|
||||
set_color_context (RESET_CONTEXT);
|
||||
putc ('\n', out);
|
||||
|
||||
if (changes & OLD)
|
||||
{
|
||||
struct change *next = hunk;
|
||||
|
||||
if (first0 <= last0)
|
||||
set_color_context (DELETE_CONTEXT);
|
||||
|
||||
for (i = first0; i <= last0; i++)
|
||||
{
|
||||
/* Skip past changes that apply (in file 0)
|
||||
only to lines before line I. */
|
||||
|
||||
while (next && next->line0 + next->deleted <= i)
|
||||
next = next->link;
|
||||
|
||||
/* Compute the marking for line I. */
|
||||
|
||||
prefix = " ";
|
||||
if (next && next->line0 <= i)
|
||||
{
|
||||
/* The change NEXT covers this line.
|
||||
If lines were inserted here in file 1, this is "changed".
|
||||
Otherwise it is "deleted". */
|
||||
prefix = (next->inserted > 0 ? "!" : "-");
|
||||
}
|
||||
print_1_line_nl (prefix, &files[0].linbuf[i], true);
|
||||
if (i == last0)
|
||||
set_color_context (RESET_CONTEXT);
|
||||
if (files[0].linbuf[i + 1][-1] == '\n')
|
||||
putc ('\n', out);
|
||||
}
|
||||
}
|
||||
|
||||
set_color_context (LINE_NUMBER_CONTEXT);
|
||||
fputs ("--- ", out);
|
||||
print_context_number_range (&files[1], first1, last1);
|
||||
fputs (" ----", out);
|
||||
set_color_context (RESET_CONTEXT);
|
||||
putc ('\n', out);
|
||||
|
||||
if (changes & NEW)
|
||||
{
|
||||
struct change *next = hunk;
|
||||
|
||||
if (first1 <= last1)
|
||||
set_color_context (ADD_CONTEXT);
|
||||
|
||||
for (i = first1; i <= last1; i++)
|
||||
{
|
||||
/* Skip past changes that apply (in file 1)
|
||||
only to lines before line I. */
|
||||
|
||||
while (next && next->line1 + next->inserted <= i)
|
||||
next = next->link;
|
||||
|
||||
/* Compute the marking for line I. */
|
||||
|
||||
prefix = " ";
|
||||
if (next && next->line1 <= i)
|
||||
{
|
||||
/* The change NEXT covers this line.
|
||||
If lines were deleted here in file 0, this is "changed".
|
||||
Otherwise it is "inserted". */
|
||||
prefix = (next->deleted > 0 ? "!" : "+");
|
||||
}
|
||||
print_1_line_nl (prefix, &files[1].linbuf[i], true);
|
||||
if (i == last1)
|
||||
set_color_context (RESET_CONTEXT);
|
||||
if (files[1].linbuf[i + 1][-1] == '\n')
|
||||
putc ('\n', out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Print a pair of line numbers with a comma, translated for file FILE.
|
||||
If the second number is smaller, use the first in place of it.
|
||||
If the numbers are equal, print just one number.
|
||||
|
||||
Args A and B are internal line numbers.
|
||||
We print the translated (real) line numbers. */
|
||||
|
||||
static void
|
||||
print_unidiff_number_range (struct file_data const *file, lin a, lin b)
|
||||
{
|
||||
printint trans_a, trans_b;
|
||||
translate_range (file, a, b, &trans_a, &trans_b);
|
||||
|
||||
/* We can have B < A in the case of a range of no lines.
|
||||
In this case, we print the line number before the range,
|
||||
which is B. It would be more logical to print A, but
|
||||
'patch' expects B in order to detect diffs against empty files. */
|
||||
if (trans_b <= trans_a)
|
||||
fprintf (outfile, trans_b < trans_a ? "%"pI"d,0" : "%"pI"d", trans_b);
|
||||
else
|
||||
fprintf (outfile, "%"pI"d,%"pI"d", trans_a, trans_b - trans_a + 1);
|
||||
}
|
||||
|
||||
/* Print a portion of an edit script in unidiff format.
|
||||
HUNK is the beginning of the portion to be printed.
|
||||
The end is marked by a 'link' that has been nulled out.
|
||||
|
||||
Prints out lines from both files, and precedes each
|
||||
line with the appropriate flag-character. */
|
||||
|
||||
static void
|
||||
pr_unidiff_hunk (struct change *hunk)
|
||||
{
|
||||
lin first0, last0, first1, last1;
|
||||
lin i, j, k;
|
||||
struct change *next;
|
||||
char const *function;
|
||||
FILE *out;
|
||||
|
||||
/* Determine range of line numbers involved in each file. */
|
||||
|
||||
if (! analyze_hunk (hunk, &first0, &last0, &first1, &last1))
|
||||
return;
|
||||
|
||||
/* Include a context's width before and after. */
|
||||
|
||||
i = - files[0].prefix_lines;
|
||||
first0 = MAX (first0 - context, i);
|
||||
first1 = MAX (first1 - context, i);
|
||||
if (last0 < files[0].valid_lines - context)
|
||||
last0 += context;
|
||||
else
|
||||
last0 = files[0].valid_lines - 1;
|
||||
if (last1 < files[1].valid_lines - context)
|
||||
last1 += context;
|
||||
else
|
||||
last1 = files[1].valid_lines - 1;
|
||||
|
||||
/* If desired, find the preceding function definition line in file 0. */
|
||||
function = NULL;
|
||||
if (function_regexp.fastmap)
|
||||
function = find_function (files[0].linbuf, first0);
|
||||
|
||||
begin_output ();
|
||||
out = outfile;
|
||||
|
||||
set_color_context (LINE_NUMBER_CONTEXT);
|
||||
fputs ("@@ -", out);
|
||||
print_unidiff_number_range (&files[0], first0, last0);
|
||||
fputs (" +", out);
|
||||
print_unidiff_number_range (&files[1], first1, last1);
|
||||
fputs (" @@", out);
|
||||
set_color_context (RESET_CONTEXT);
|
||||
|
||||
if (function)
|
||||
print_context_function (out, function);
|
||||
|
||||
putc ('\n', out);
|
||||
|
||||
next = hunk;
|
||||
i = first0;
|
||||
j = first1;
|
||||
|
||||
while (i <= last0 || j <= last1)
|
||||
{
|
||||
|
||||
/* If the line isn't a difference, output the context from file 0. */
|
||||
|
||||
if (!next || i < next->line0)
|
||||
{
|
||||
char const *const *line = &files[0].linbuf[i++];
|
||||
if (! (suppress_blank_empty && **line == '\n'))
|
||||
putc (initial_tab ? '\t' : ' ', out);
|
||||
print_1_line (NULL, line);
|
||||
j++;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* For each difference, first output the deleted part. */
|
||||
|
||||
k = next->deleted;
|
||||
if (k)
|
||||
set_color_context (DELETE_CONTEXT);
|
||||
|
||||
while (k--)
|
||||
{
|
||||
char const * const *line = &files[0].linbuf[i++];
|
||||
putc ('-', out);
|
||||
if (initial_tab && ! (suppress_blank_empty && **line == '\n'))
|
||||
putc ('\t', out);
|
||||
print_1_line_nl (NULL, line, true);
|
||||
|
||||
if (!k)
|
||||
set_color_context (RESET_CONTEXT);
|
||||
|
||||
if (line[1][-1] == '\n')
|
||||
putc ('\n', out);
|
||||
}
|
||||
|
||||
/* Then output the inserted part. */
|
||||
|
||||
k = next->inserted;
|
||||
if (k)
|
||||
set_color_context (ADD_CONTEXT);
|
||||
|
||||
while (k--)
|
||||
{
|
||||
char const * const *line = &files[1].linbuf[j++];
|
||||
putc ('+', out);
|
||||
if (initial_tab && ! (suppress_blank_empty && **line == '\n'))
|
||||
putc ('\t', out);
|
||||
print_1_line_nl (NULL, line, true);
|
||||
|
||||
if (!k)
|
||||
set_color_context (RESET_CONTEXT);
|
||||
|
||||
if (line[1][-1] == '\n')
|
||||
putc ('\n', out);
|
||||
}
|
||||
|
||||
/* We're done with this hunk, so on to the next! */
|
||||
|
||||
next = next->link;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Scan a (forward-ordered) edit script for the first place that more than
|
||||
2*CONTEXT unchanged lines appear, and return a pointer
|
||||
to the 'struct change' for the last change before those lines. */
|
||||
|
||||
static struct change * _GL_ATTRIBUTE_PURE
|
||||
find_hunk (struct change *start)
|
||||
{
|
||||
struct change *prev;
|
||||
lin top0, top1;
|
||||
lin thresh;
|
||||
|
||||
/* Threshold distance is CONTEXT if the second change is ignorable,
|
||||
2 * CONTEXT + 1 otherwise. Integer overflow can't happen, due
|
||||
to CONTEXT_LIM. */
|
||||
lin ignorable_threshold = context;
|
||||
lin non_ignorable_threshold = 2 * context + 1;
|
||||
|
||||
do
|
||||
{
|
||||
/* Compute number of first line in each file beyond this changed. */
|
||||
top0 = start->line0 + start->deleted;
|
||||
top1 = start->line1 + start->inserted;
|
||||
prev = start;
|
||||
start = start->link;
|
||||
thresh = (start && start->ignore
|
||||
? ignorable_threshold
|
||||
: non_ignorable_threshold);
|
||||
/* It is not supposed to matter which file we check in the end-test.
|
||||
If it would matter, crash. */
|
||||
if (start && start->line0 - top0 != start->line1 - top1)
|
||||
abort ();
|
||||
} while (start
|
||||
/* Keep going if less than THRESH lines
|
||||
elapse before the affected line. */
|
||||
&& start->line0 - top0 < thresh);
|
||||
|
||||
return prev;
|
||||
}
|
||||
|
||||
/* Set the 'ignore' flag properly in each change in SCRIPT.
|
||||
It should be 1 if all the lines inserted or deleted in that change
|
||||
are ignorable lines. */
|
||||
|
||||
static void
|
||||
mark_ignorable (struct change *script)
|
||||
{
|
||||
while (script)
|
||||
{
|
||||
struct change *next = script->link;
|
||||
lin first0, last0, first1, last1;
|
||||
|
||||
/* Turn this change into a hunk: detach it from the others. */
|
||||
script->link = NULL;
|
||||
|
||||
/* Determine whether this change is ignorable. */
|
||||
script->ignore = ! analyze_hunk (script,
|
||||
&first0, &last0, &first1, &last1);
|
||||
|
||||
/* Reconnect the chain as before. */
|
||||
script->link = next;
|
||||
|
||||
/* Advance to the following change. */
|
||||
script = next;
|
||||
}
|
||||
}
|
||||
|
||||
/* Find the last function-header line in LINBUF prior to line number LINENUM.
|
||||
This is a line containing a match for the regexp in 'function_regexp'.
|
||||
Return the address of the text, or NULL if no function-header is found. */
|
||||
|
||||
static char const *
|
||||
find_function (char const * const *linbuf, lin linenum)
|
||||
{
|
||||
lin i = linenum;
|
||||
lin last = find_function_last_search;
|
||||
find_function_last_search = i;
|
||||
|
||||
while (last <= --i)
|
||||
{
|
||||
/* See if this line is what we want. */
|
||||
char const *line = linbuf[i];
|
||||
size_t linelen = linbuf[i + 1] - line - 1;
|
||||
|
||||
/* FIXME: re_search's size args should be size_t, not int. */
|
||||
int len = MIN (linelen, INT_MAX);
|
||||
|
||||
if (0 <= re_search (&function_regexp, line, len, 0, len, NULL))
|
||||
{
|
||||
find_function_last_match = i;
|
||||
return line;
|
||||
}
|
||||
}
|
||||
/* If we search back to where we started searching the previous time,
|
||||
find the line we found last time. */
|
||||
if (find_function_last_match != LIN_MAX)
|
||||
return linbuf[find_function_last_match];
|
||||
|
||||
return NULL;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/* Report an error and exit.
|
||||
Copyright 2016-2017 Free Software Foundation, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA
|
||||
02110-1301, USA. */
|
||||
|
||||
#ifndef DIE_H
|
||||
# define DIE_H
|
||||
|
||||
# include <error.h>
|
||||
# include <stdbool.h>
|
||||
# include <verify.h>
|
||||
|
||||
/* Like 'error (STATUS, ...)', except STATUS must be a nonzero constant.
|
||||
This may pacify the compiler or help it generate better code. */
|
||||
# define die(status, ...) \
|
||||
verify_expr (status, (error (status, __VA_ARGS__), assume (false)))
|
||||
|
||||
#endif /* DIE_H */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,423 @@
|
||||
/* Shared definitions for GNU DIFF
|
||||
|
||||
Copyright (C) 1988-1989, 1991-1995, 1998, 2001-2002, 2004, 2009-2013,
|
||||
2015-2017 Free Software Foundation, Inc.
|
||||
|
||||
This file is part of GNU DIFF.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
#include "system.h"
|
||||
#include <regex.h>
|
||||
#include <stdio.h>
|
||||
#include <unlocked-io.h>
|
||||
|
||||
/* What kind of changes a hunk contains. */
|
||||
enum changes
|
||||
{
|
||||
/* No changes: lines common to both files. */
|
||||
UNCHANGED,
|
||||
|
||||
/* Deletes only: lines taken from just the first file. */
|
||||
OLD,
|
||||
|
||||
/* Inserts only: lines taken from just the second file. */
|
||||
NEW,
|
||||
|
||||
/* Both deletes and inserts: a hunk containing both old and new lines. */
|
||||
CHANGED
|
||||
};
|
||||
|
||||
/* When colors should be used in the output. */
|
||||
enum colors_style
|
||||
{
|
||||
/* Never output colors. */
|
||||
NEVER,
|
||||
|
||||
/* Output colors if the output is a terminal. */
|
||||
AUTO,
|
||||
|
||||
/* Always output colors. */
|
||||
ALWAYS,
|
||||
};
|
||||
|
||||
/* Variables for command line options */
|
||||
|
||||
#ifndef GDIFF_MAIN
|
||||
# define XTERN extern
|
||||
#else
|
||||
# define XTERN
|
||||
#endif
|
||||
|
||||
enum output_style
|
||||
{
|
||||
/* No output style specified. */
|
||||
OUTPUT_UNSPECIFIED,
|
||||
|
||||
/* Default output style. */
|
||||
OUTPUT_NORMAL,
|
||||
|
||||
/* Output the differences with lines of context before and after (-c). */
|
||||
OUTPUT_CONTEXT,
|
||||
|
||||
/* Output the differences in a unified context diff format (-u). */
|
||||
OUTPUT_UNIFIED,
|
||||
|
||||
/* Output the differences as commands suitable for 'ed' (-e). */
|
||||
OUTPUT_ED,
|
||||
|
||||
/* Output the diff as a forward ed script (-f). */
|
||||
OUTPUT_FORWARD_ED,
|
||||
|
||||
/* Like -f, but output a count of changed lines in each "command" (-n). */
|
||||
OUTPUT_RCS,
|
||||
|
||||
/* Output merged #ifdef'd file (-D). */
|
||||
OUTPUT_IFDEF,
|
||||
|
||||
/* Output sdiff style (-y). */
|
||||
OUTPUT_SDIFF
|
||||
};
|
||||
|
||||
/* True for output styles that are robust,
|
||||
i.e. can handle a file that ends in a non-newline. */
|
||||
#define ROBUST_OUTPUT_STYLE(S) ((S) != OUTPUT_ED && (S) != OUTPUT_FORWARD_ED)
|
||||
|
||||
XTERN enum output_style output_style;
|
||||
|
||||
/* Define the current color context used to print a line. */
|
||||
XTERN enum colors_style colors_style;
|
||||
|
||||
/* Nonzero if output cannot be generated for identical files. */
|
||||
XTERN bool no_diff_means_no_output;
|
||||
|
||||
/* Number of lines of context to show in each set of diffs.
|
||||
This is zero when context is not to be shown. */
|
||||
XTERN lin context;
|
||||
|
||||
/* Consider all files as text files (-a).
|
||||
Don't interpret codes over 0177 as implying a "binary file". */
|
||||
XTERN bool text;
|
||||
|
||||
/* Number of lines to keep in identical prefix and suffix. */
|
||||
XTERN lin horizon_lines;
|
||||
|
||||
/* The significance of white space during comparisons. */
|
||||
enum DIFF_white_space
|
||||
{
|
||||
/* All white space is significant (the default). */
|
||||
IGNORE_NO_WHITE_SPACE,
|
||||
|
||||
/* Ignore changes due to tab expansion (-E). */
|
||||
IGNORE_TAB_EXPANSION,
|
||||
|
||||
/* Ignore changes in trailing horizontal white space (-Z). */
|
||||
IGNORE_TRAILING_SPACE,
|
||||
|
||||
/* IGNORE_TAB_EXPANSION and IGNORE_TRAILING_SPACE are a special case
|
||||
because they are independent and can be ORed together, yielding
|
||||
IGNORE_TAB_EXPANSION_AND_TRAILING_SPACE. */
|
||||
IGNORE_TAB_EXPANSION_AND_TRAILING_SPACE,
|
||||
|
||||
/* Ignore changes in horizontal white space (-b). */
|
||||
IGNORE_SPACE_CHANGE,
|
||||
|
||||
/* Ignore all horizontal white space (-w). */
|
||||
IGNORE_ALL_SPACE
|
||||
};
|
||||
XTERN enum DIFF_white_space ignore_white_space;
|
||||
|
||||
/* Ignore changes that affect only blank lines (-B). */
|
||||
XTERN bool ignore_blank_lines;
|
||||
|
||||
/* Files can be compared byte-by-byte, as if they were binary.
|
||||
This depends on various options. */
|
||||
XTERN bool files_can_be_treated_as_binary;
|
||||
|
||||
/* Ignore differences in case of letters (-i). */
|
||||
XTERN bool ignore_case;
|
||||
|
||||
/* Ignore differences in case of letters in file names. */
|
||||
XTERN bool ignore_file_name_case;
|
||||
|
||||
/* Act on symbolic links themselves rather than on their target
|
||||
(--no-dereference). */
|
||||
XTERN bool no_dereference_symlinks;
|
||||
|
||||
/* File labels for '-c' output headers (--label). */
|
||||
XTERN char *file_label[2];
|
||||
|
||||
/* Regexp to identify function-header lines (-F). */
|
||||
XTERN struct re_pattern_buffer function_regexp;
|
||||
|
||||
/* Ignore changes that affect only lines matching this regexp (-I). */
|
||||
XTERN struct re_pattern_buffer ignore_regexp;
|
||||
|
||||
/* Say only whether files differ, not how (-q). */
|
||||
XTERN bool brief;
|
||||
|
||||
/* Expand tabs in the output so the text lines up properly
|
||||
despite the characters added to the front of each line (-t). */
|
||||
XTERN bool expand_tabs;
|
||||
|
||||
/* Number of columns between tab stops. */
|
||||
XTERN size_t tabsize;
|
||||
|
||||
/* Use a tab in the output, rather than a space, before the text of an
|
||||
input line, so as to keep the proper alignment in the input line
|
||||
without changing the characters in it (-T). */
|
||||
XTERN bool initial_tab;
|
||||
|
||||
/* Do not output an initial space or tab before the text of an empty line. */
|
||||
XTERN bool suppress_blank_empty;
|
||||
|
||||
/* Remove trailing carriage returns from input. */
|
||||
XTERN bool strip_trailing_cr;
|
||||
|
||||
/* In directory comparison, specify file to start with (-S).
|
||||
This is used for resuming an aborted comparison.
|
||||
All file names less than this name are ignored. */
|
||||
XTERN char const *starting_file;
|
||||
|
||||
/* Pipe each file's output through pr (-l). */
|
||||
XTERN bool paginate;
|
||||
|
||||
/* Line group formats for unchanged, old, new, and changed groups. */
|
||||
XTERN char const *group_format[CHANGED + 1];
|
||||
|
||||
/* Line formats for unchanged, old, and new lines. */
|
||||
XTERN char const *line_format[NEW + 1];
|
||||
|
||||
/* If using OUTPUT_SDIFF print extra information to help the sdiff filter. */
|
||||
XTERN bool sdiff_merge_assist;
|
||||
|
||||
/* Tell OUTPUT_SDIFF to show only the left version of common lines. */
|
||||
XTERN bool left_column;
|
||||
|
||||
/* Tell OUTPUT_SDIFF to not show common lines. */
|
||||
XTERN bool suppress_common_lines;
|
||||
|
||||
/* The half line width and column 2 offset for OUTPUT_SDIFF. */
|
||||
XTERN size_t sdiff_half_width;
|
||||
XTERN size_t sdiff_column2_offset;
|
||||
|
||||
/* String containing all the command options diff received,
|
||||
with spaces between and at the beginning but none at the end.
|
||||
If there were no options given, this string is empty. */
|
||||
XTERN char *switch_string;
|
||||
|
||||
/* Use heuristics for better speed with large files with a small
|
||||
density of changes. */
|
||||
XTERN bool speed_large_files;
|
||||
|
||||
/* Patterns that match file names to be excluded. */
|
||||
XTERN struct exclude *excluded;
|
||||
|
||||
/* Don't discard lines. This makes things slower (sometimes much
|
||||
slower) but will find a guaranteed minimal set of changes. */
|
||||
XTERN bool minimal;
|
||||
|
||||
/* The strftime format to use for time strings. */
|
||||
XTERN char const *time_format;
|
||||
|
||||
/* The result of comparison is an "edit script": a chain of 'struct change'.
|
||||
Each 'struct change' represents one place where some lines are deleted
|
||||
and some are inserted.
|
||||
|
||||
LINE0 and LINE1 are the first affected lines in the two files (origin 0).
|
||||
DELETED is the number of lines deleted here from file 0.
|
||||
INSERTED is the number of lines inserted here in file 1.
|
||||
|
||||
If DELETED is 0 then LINE0 is the number of the line before
|
||||
which the insertion was done; vice versa for INSERTED and LINE1. */
|
||||
|
||||
struct change
|
||||
{
|
||||
struct change *link; /* Previous or next edit command */
|
||||
lin inserted; /* # lines of file 1 changed here. */
|
||||
lin deleted; /* # lines of file 0 changed here. */
|
||||
lin line0; /* Line number of 1st deleted line. */
|
||||
lin line1; /* Line number of 1st inserted line. */
|
||||
bool ignore; /* Flag used in context.c. */
|
||||
};
|
||||
|
||||
/* Structures that describe the input files. */
|
||||
|
||||
/* Data on one input file being compared. */
|
||||
|
||||
struct file_data {
|
||||
int desc; /* File descriptor */
|
||||
char const *name; /* File name */
|
||||
struct stat stat; /* File status */
|
||||
|
||||
/* Buffer in which text of file is read. */
|
||||
word *buffer;
|
||||
|
||||
/* Allocated size of buffer, in bytes. Always a multiple of
|
||||
sizeof *buffer. */
|
||||
size_t bufsize;
|
||||
|
||||
/* Number of valid bytes now in the buffer. */
|
||||
size_t buffered;
|
||||
|
||||
/* Array of pointers to lines in the file. */
|
||||
char const **linbuf;
|
||||
|
||||
/* linbuf_base <= buffered_lines <= valid_lines <= alloc_lines.
|
||||
linebuf[linbuf_base ... buffered_lines - 1] are possibly differing.
|
||||
linebuf[linbuf_base ... valid_lines - 1] contain valid data.
|
||||
linebuf[linbuf_base ... alloc_lines - 1] are allocated. */
|
||||
lin linbuf_base, buffered_lines, valid_lines, alloc_lines;
|
||||
|
||||
/* Pointer to end of prefix of this file to ignore when hashing. */
|
||||
char const *prefix_end;
|
||||
|
||||
/* Count of lines in the prefix.
|
||||
There are this many lines in the file before linbuf[0]. */
|
||||
lin prefix_lines;
|
||||
|
||||
/* Pointer to start of suffix of this file to ignore when hashing. */
|
||||
char const *suffix_begin;
|
||||
|
||||
/* Vector, indexed by line number, containing an equivalence code for
|
||||
each line. It is this vector that is actually compared with that
|
||||
of another file to generate differences. */
|
||||
lin *equivs;
|
||||
|
||||
/* Vector, like the previous one except that
|
||||
the elements for discarded lines have been squeezed out. */
|
||||
lin *undiscarded;
|
||||
|
||||
/* Vector mapping virtual line numbers (not counting discarded lines)
|
||||
to real ones (counting those lines). Both are origin-0. */
|
||||
lin *realindexes;
|
||||
|
||||
/* Total number of nondiscarded lines. */
|
||||
lin nondiscarded_lines;
|
||||
|
||||
/* Vector, indexed by real origin-0 line number,
|
||||
containing 1 for a line that is an insertion or a deletion.
|
||||
The results of comparison are stored here. */
|
||||
char *changed;
|
||||
|
||||
/* 1 if file ends in a line with no final newline. */
|
||||
bool missing_newline;
|
||||
|
||||
/* 1 if at end of file. */
|
||||
bool eof;
|
||||
|
||||
/* 1 more than the maximum equivalence value used for this or its
|
||||
sibling file. */
|
||||
lin equiv_max;
|
||||
};
|
||||
|
||||
/* The file buffer, considered as an array of bytes rather than
|
||||
as an array of words. */
|
||||
#define FILE_BUFFER(f) ((char *) (f)->buffer)
|
||||
|
||||
/* Data on two input files being compared. */
|
||||
|
||||
struct comparison
|
||||
{
|
||||
struct file_data file[2];
|
||||
struct comparison const *parent; /* parent, if a recursive comparison */
|
||||
};
|
||||
|
||||
/* Describe the two files currently being compared. */
|
||||
|
||||
XTERN struct file_data files[2];
|
||||
|
||||
/* Stdio stream to output diffs to. */
|
||||
|
||||
XTERN FILE *outfile;
|
||||
|
||||
/* Declare various functions. */
|
||||
|
||||
/* analyze.c */
|
||||
extern int diff_2_files (struct comparison *);
|
||||
|
||||
/* context.c */
|
||||
extern void print_context_header (struct file_data[], char const * const *, bool);
|
||||
extern void print_context_script (struct change *, bool);
|
||||
|
||||
/* dir.c */
|
||||
extern int diff_dirs (struct comparison const *,
|
||||
int (*) (struct comparison const *,
|
||||
char const *, char const *));
|
||||
extern char *find_dir_file_pathname (char const *, char const *);
|
||||
|
||||
/* ed.c */
|
||||
extern void print_ed_script (struct change *);
|
||||
extern void pr_forward_ed_script (struct change *);
|
||||
|
||||
/* ifdef.c */
|
||||
extern void print_ifdef_script (struct change *);
|
||||
|
||||
/* io.c */
|
||||
extern void file_block_read (struct file_data *, size_t);
|
||||
extern bool read_files (struct file_data[], bool);
|
||||
|
||||
/* normal.c */
|
||||
extern void print_normal_script (struct change *);
|
||||
|
||||
/* rcs.c */
|
||||
extern void print_rcs_script (struct change *);
|
||||
|
||||
/* side.c */
|
||||
extern void print_sdiff_script (struct change *);
|
||||
|
||||
/* util.c */
|
||||
extern char const change_letter[4];
|
||||
extern char const pr_program[];
|
||||
extern char *concat (char const *, char const *, char const *);
|
||||
extern bool lines_differ (char const *, char const *) _GL_ATTRIBUTE_PURE;
|
||||
extern lin translate_line_number (struct file_data const *, lin);
|
||||
extern struct change *find_change (struct change *);
|
||||
extern struct change *find_reverse_change (struct change *);
|
||||
extern void *zalloc (size_t);
|
||||
extern enum changes analyze_hunk (struct change *, lin *, lin *, lin *, lin *);
|
||||
extern void begin_output (void);
|
||||
extern void debug_script (struct change *);
|
||||
extern void fatal (char const *) __attribute__((noreturn));
|
||||
extern void finish_output (void);
|
||||
extern void message (char const *, char const *, char const *);
|
||||
extern void message5 (char const *, char const *, char const *,
|
||||
char const *, char const *);
|
||||
extern void output_1_line (char const *, char const *, char const *,
|
||||
char const *);
|
||||
extern void perror_with_name (char const *);
|
||||
extern void pfatal_with_name (char const *) __attribute__((noreturn));
|
||||
extern void print_1_line (char const *, char const * const *);
|
||||
extern void print_1_line_nl (char const *, char const * const *, bool);
|
||||
extern void print_message_queue (void);
|
||||
extern void print_number_range (char, struct file_data *, lin, lin);
|
||||
extern void print_script (struct change *, struct change * (*) (struct change *),
|
||||
void (*) (struct change *));
|
||||
extern void setup_output (char const *, char const *, bool);
|
||||
extern void translate_range (struct file_data const *, lin, lin,
|
||||
printint *, printint *);
|
||||
|
||||
enum color_context
|
||||
{
|
||||
HEADER_CONTEXT,
|
||||
ADD_CONTEXT,
|
||||
DELETE_CONTEXT,
|
||||
RESET_CONTEXT,
|
||||
LINE_NUMBER_CONTEXT,
|
||||
};
|
||||
|
||||
XTERN bool presume_output_tty;
|
||||
|
||||
extern void set_color_context (enum color_context color_context);
|
||||
extern void set_color_palette (char const *palette);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,385 @@
|
||||
/* Read, sort and compare two directories. Used for GNU DIFF.
|
||||
|
||||
Copyright (C) 1988-1989, 1992-1995, 1998, 2001-2002, 2004, 2006-2007,
|
||||
2009-2013, 2015-2017 Free Software Foundation, Inc.
|
||||
|
||||
This file is part of GNU DIFF.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
#include "diff.h"
|
||||
#include <error.h>
|
||||
#include <exclude.h>
|
||||
#include <filenamecat.h>
|
||||
#include <setjmp.h>
|
||||
#include <xalloc.h>
|
||||
|
||||
/* Read the directory named by DIR and store into DIRDATA a sorted vector
|
||||
of filenames for its contents. DIR->desc == -1 means this directory is
|
||||
known to be nonexistent, so set DIRDATA to an empty vector.
|
||||
Return -1 (setting errno) if error, 0 otherwise. */
|
||||
|
||||
struct dirdata
|
||||
{
|
||||
size_t nnames; /* Number of names. */
|
||||
char const **names; /* Sorted names of files in dir, followed by 0. */
|
||||
char *data; /* Allocated storage for file names. */
|
||||
};
|
||||
|
||||
/* Whether file names in directories should be compared with
|
||||
locale-specific sorting. */
|
||||
static bool locale_specific_sorting;
|
||||
|
||||
/* Where to go if locale-specific sorting fails. */
|
||||
static jmp_buf failed_locale_specific_sorting;
|
||||
|
||||
static bool dir_loop (struct comparison const *, int);
|
||||
|
||||
|
||||
/* Read a directory and get its vector of names. */
|
||||
|
||||
static bool
|
||||
dir_read (struct file_data const *dir, struct dirdata *dirdata)
|
||||
{
|
||||
register struct dirent *next;
|
||||
register size_t i;
|
||||
|
||||
/* Address of block containing the files that are described. */
|
||||
char const **names;
|
||||
|
||||
/* Number of files in directory. */
|
||||
size_t nnames;
|
||||
|
||||
/* Allocated and used storage for file name data. */
|
||||
char *data;
|
||||
size_t data_alloc, data_used;
|
||||
|
||||
dirdata->names = 0;
|
||||
dirdata->data = 0;
|
||||
nnames = 0;
|
||||
data = 0;
|
||||
|
||||
if (dir->desc != -1)
|
||||
{
|
||||
/* Open the directory and check for errors. */
|
||||
register DIR *reading = opendir (dir->name);
|
||||
if (!reading)
|
||||
return false;
|
||||
|
||||
/* Initialize the table of filenames. */
|
||||
|
||||
data_alloc = 512;
|
||||
data_used = 0;
|
||||
dirdata->data = data = xmalloc (data_alloc);
|
||||
|
||||
/* Read the directory entries, and insert the subfiles
|
||||
into the 'data' table. */
|
||||
|
||||
while ((errno = 0, (next = readdir (reading)) != 0))
|
||||
{
|
||||
char *d_name = next->d_name;
|
||||
size_t d_size = _D_EXACT_NAMLEN (next) + 1;
|
||||
|
||||
/* Ignore "." and "..". */
|
||||
if (d_name[0] == '.'
|
||||
&& (d_name[1] == 0 || (d_name[1] == '.' && d_name[2] == 0)))
|
||||
continue;
|
||||
|
||||
if (excluded_file_name (excluded, d_name))
|
||||
continue;
|
||||
|
||||
while (data_alloc < data_used + d_size)
|
||||
{
|
||||
if (PTRDIFF_MAX / 2 <= data_alloc)
|
||||
xalloc_die ();
|
||||
dirdata->data = data = xrealloc (data, data_alloc *= 2);
|
||||
}
|
||||
|
||||
memcpy (data + data_used, d_name, d_size);
|
||||
data_used += d_size;
|
||||
nnames++;
|
||||
}
|
||||
if (errno)
|
||||
{
|
||||
int e = errno;
|
||||
closedir (reading);
|
||||
errno = e;
|
||||
return false;
|
||||
}
|
||||
#if CLOSEDIR_VOID
|
||||
closedir (reading);
|
||||
#else
|
||||
if (closedir (reading) != 0)
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Create the 'names' table from the 'data' table. */
|
||||
if (PTRDIFF_MAX / sizeof *names - 1 <= nnames)
|
||||
xalloc_die ();
|
||||
dirdata->names = names = xmalloc ((nnames + 1) * sizeof *names);
|
||||
dirdata->nnames = nnames;
|
||||
for (i = 0; i < nnames; i++)
|
||||
{
|
||||
names[i] = data;
|
||||
data += strlen (data) + 1;
|
||||
}
|
||||
names[nnames] = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Compare strings in a locale-specific way, returning a value
|
||||
compatible with strcmp. */
|
||||
|
||||
static int
|
||||
compare_collated (char const *name1, char const *name2)
|
||||
{
|
||||
int r;
|
||||
errno = 0;
|
||||
if (ignore_file_name_case)
|
||||
r = strcasecoll (name1, name2);
|
||||
else
|
||||
r = strcoll (name1, name2);
|
||||
if (errno)
|
||||
{
|
||||
error (0, errno, _("cannot compare file names '%s' and '%s'"),
|
||||
name1, name2);
|
||||
longjmp (failed_locale_specific_sorting, 1);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/* Compare file names, returning a value compatible with strcmp. */
|
||||
|
||||
static int
|
||||
compare_names (char const *name1, char const *name2)
|
||||
{
|
||||
if (locale_specific_sorting)
|
||||
{
|
||||
int diff = compare_collated (name1, name2);
|
||||
if (diff || ignore_file_name_case)
|
||||
return diff;
|
||||
}
|
||||
return file_name_cmp (name1, name2);
|
||||
}
|
||||
|
||||
/* Compare names FILE1 and FILE2 when sorting a directory.
|
||||
Prefer filtered comparison, breaking ties with file_name_cmp. */
|
||||
|
||||
static int
|
||||
compare_names_for_qsort (void const *file1, void const *file2)
|
||||
{
|
||||
char const *const *f1 = file1;
|
||||
char const *const *f2 = file2;
|
||||
char const *name1 = *f1;
|
||||
char const *name2 = *f2;
|
||||
if (locale_specific_sorting)
|
||||
{
|
||||
int diff = compare_collated (name1, name2);
|
||||
if (diff)
|
||||
return diff;
|
||||
}
|
||||
return file_name_cmp (name1, name2);
|
||||
}
|
||||
|
||||
/* Compare the contents of two directories named in CMP.
|
||||
This is a top-level routine; it does everything necessary for diff
|
||||
on two directories.
|
||||
|
||||
CMP->file[0].desc == -1 says directory CMP->file[0] doesn't exist,
|
||||
but pretend it is empty. Likewise for CMP->file[1].
|
||||
|
||||
HANDLE_FILE is a caller-provided subroutine called to handle each file.
|
||||
It gets three operands: CMP, name of file in dir 0, name of file in dir 1.
|
||||
These names are relative to the original working directory.
|
||||
|
||||
For a file that appears in only one of the dirs, one of the name-args
|
||||
to HANDLE_FILE is zero.
|
||||
|
||||
Returns the maximum of all the values returned by HANDLE_FILE,
|
||||
or EXIT_TROUBLE if trouble is encountered in opening files. */
|
||||
|
||||
int
|
||||
diff_dirs (struct comparison const *cmp,
|
||||
int (*handle_file) (struct comparison const *,
|
||||
char const *, char const *))
|
||||
{
|
||||
struct dirdata dirdata[2];
|
||||
int volatile val = EXIT_SUCCESS;
|
||||
int i;
|
||||
|
||||
if ((cmp->file[0].desc == -1 || dir_loop (cmp, 0))
|
||||
&& (cmp->file[1].desc == -1 || dir_loop (cmp, 1)))
|
||||
{
|
||||
error (0, 0, _("%s: recursive directory loop"),
|
||||
cmp->file[cmp->file[0].desc == -1].name);
|
||||
return EXIT_TROUBLE;
|
||||
}
|
||||
|
||||
/* Get contents of both dirs. */
|
||||
for (i = 0; i < 2; i++)
|
||||
if (! dir_read (&cmp->file[i], &dirdata[i]))
|
||||
{
|
||||
perror_with_name (cmp->file[i].name);
|
||||
val = EXIT_TROUBLE;
|
||||
}
|
||||
|
||||
if (val == EXIT_SUCCESS)
|
||||
{
|
||||
char const **volatile names[2];
|
||||
names[0] = dirdata[0].names;
|
||||
names[1] = dirdata[1].names;
|
||||
|
||||
/* Use locale-specific sorting if possible, else native byte order. */
|
||||
locale_specific_sorting = true;
|
||||
if (setjmp (failed_locale_specific_sorting))
|
||||
locale_specific_sorting = false;
|
||||
|
||||
/* Sort the directories. */
|
||||
for (i = 0; i < 2; i++)
|
||||
qsort (names[i], dirdata[i].nnames, sizeof *dirdata[i].names,
|
||||
compare_names_for_qsort);
|
||||
|
||||
/* If '-S name' was given, and this is the topmost level of comparison,
|
||||
ignore all file names less than the specified starting name. */
|
||||
|
||||
if (starting_file && ! cmp->parent)
|
||||
{
|
||||
while (*names[0] && compare_names (*names[0], starting_file) < 0)
|
||||
names[0]++;
|
||||
while (*names[1] && compare_names (*names[1], starting_file) < 0)
|
||||
names[1]++;
|
||||
}
|
||||
|
||||
/* Loop while files remain in one or both dirs. */
|
||||
while (*names[0] || *names[1])
|
||||
{
|
||||
/* Compare next name in dir 0 with next name in dir 1.
|
||||
At the end of a dir,
|
||||
pretend the "next name" in that dir is very large. */
|
||||
int nameorder = (!*names[0] ? 1 : !*names[1] ? -1
|
||||
: compare_names (*names[0], *names[1]));
|
||||
|
||||
/* Prefer a file_name_cmp match if available. This algorithm is
|
||||
O(N**2), where N is the number of names in a directory
|
||||
that compare_names says are all equal, but in practice N
|
||||
is so small it's not worth tuning. */
|
||||
if (nameorder == 0 && ignore_file_name_case)
|
||||
{
|
||||
int raw_order = file_name_cmp (*names[0], *names[1]);
|
||||
if (raw_order != 0)
|
||||
{
|
||||
int greater_side = raw_order < 0;
|
||||
int lesser_side = 1 - greater_side;
|
||||
char const **lesser = names[lesser_side];
|
||||
char const *greater_name = *names[greater_side];
|
||||
char const **p;
|
||||
|
||||
for (p = lesser + 1;
|
||||
*p && compare_names (*p, greater_name) == 0;
|
||||
p++)
|
||||
{
|
||||
int c = file_name_cmp (*p, greater_name);
|
||||
if (0 <= c)
|
||||
{
|
||||
if (c == 0)
|
||||
{
|
||||
memmove (lesser + 1, lesser,
|
||||
(char *) p - (char *) lesser);
|
||||
*lesser = greater_name;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int v1 = (*handle_file) (cmp,
|
||||
0 < nameorder ? 0 : *names[0]++,
|
||||
nameorder < 0 ? 0 : *names[1]++);
|
||||
if (val < v1)
|
||||
val = v1;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < 2; i++)
|
||||
{
|
||||
free (dirdata[i].names);
|
||||
free (dirdata[i].data);
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
/* Return nonzero if CMP is looping recursively in argument I. */
|
||||
|
||||
static bool _GL_ATTRIBUTE_PURE
|
||||
dir_loop (struct comparison const *cmp, int i)
|
||||
{
|
||||
struct comparison const *p = cmp;
|
||||
while ((p = p->parent))
|
||||
if (0 < same_file (&p->file[i].stat, &cmp->file[i].stat))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Find a matching filename in a directory. */
|
||||
|
||||
char *
|
||||
find_dir_file_pathname (char const *dir, char const *file)
|
||||
{
|
||||
/* The 'IF_LINT (volatile)' works around what appears to be a bug in
|
||||
gcc 4.8.0 20120825; see
|
||||
<http://lists.gnu.org/archive/html/bug-diffutils/2012-08/msg00007.html>.
|
||||
*/
|
||||
char const * IF_LINT (volatile) match = file;
|
||||
|
||||
char *val;
|
||||
struct dirdata dirdata;
|
||||
dirdata.names = NULL;
|
||||
dirdata.data = NULL;
|
||||
|
||||
if (ignore_file_name_case)
|
||||
{
|
||||
struct file_data filedata;
|
||||
filedata.name = dir;
|
||||
filedata.desc = 0;
|
||||
|
||||
if (dir_read (&filedata, &dirdata))
|
||||
{
|
||||
locale_specific_sorting = true;
|
||||
if (setjmp (failed_locale_specific_sorting))
|
||||
match = file; /* longjmp may mess up MATCH. */
|
||||
else
|
||||
{
|
||||
for (char const **p = dirdata.names; *p; p++)
|
||||
if (compare_names (*p, file) == 0)
|
||||
{
|
||||
if (file_name_cmp (*p, file) == 0)
|
||||
{
|
||||
match = *p;
|
||||
break;
|
||||
}
|
||||
if (match == file)
|
||||
match = *p;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val = file_name_concat (dir, match, NULL);
|
||||
free (dirdata.names);
|
||||
free (dirdata.data);
|
||||
return val;
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/* Output routines for ed-script format.
|
||||
|
||||
Copyright (C) 1988-1989, 1991-1993, 1995, 1998, 2001, 2004, 2006, 2009-2013,
|
||||
2015-2017 Free Software Foundation, Inc.
|
||||
|
||||
This file is part of GNU DIFF.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
#include "diff.h"
|
||||
|
||||
static void print_ed_hunk (struct change *);
|
||||
static void print_rcs_hunk (struct change *);
|
||||
static void pr_forward_ed_hunk (struct change *);
|
||||
|
||||
/* Print our script as ed commands. */
|
||||
|
||||
void
|
||||
print_ed_script (struct change *script)
|
||||
{
|
||||
print_script (script, find_reverse_change, print_ed_hunk);
|
||||
}
|
||||
|
||||
/* Print a hunk of an ed diff */
|
||||
|
||||
static void
|
||||
print_ed_hunk (struct change *hunk)
|
||||
{
|
||||
lin f0, l0, f1, l1;
|
||||
enum changes changes;
|
||||
|
||||
#ifdef DEBUG
|
||||
debug_script (hunk);
|
||||
#endif
|
||||
|
||||
/* Determine range of line numbers involved in each file. */
|
||||
changes = analyze_hunk (hunk, &f0, &l0, &f1, &l1);
|
||||
if (!changes)
|
||||
return;
|
||||
|
||||
begin_output ();
|
||||
|
||||
/* Print out the line number header for this hunk */
|
||||
print_number_range (',', &files[0], f0, l0);
|
||||
fputc (change_letter[changes], outfile);
|
||||
fputc ('\n', outfile);
|
||||
|
||||
/* Print new/changed lines from second file, if needed */
|
||||
if (changes != OLD)
|
||||
{
|
||||
lin i;
|
||||
bool insert_mode = true;
|
||||
|
||||
for (i = f1; i <= l1; i++)
|
||||
{
|
||||
if (!insert_mode)
|
||||
{
|
||||
fputs ("a\n", outfile);
|
||||
insert_mode = true;
|
||||
}
|
||||
if (files[1].linbuf[i][0] == '.' && files[1].linbuf[i][1] == '\n')
|
||||
{
|
||||
/* The file's line is just a dot, and it would exit
|
||||
insert mode. Precede the dot with another dot, exit
|
||||
insert mode and remove the extra dot. */
|
||||
fputs ("..\n.\ns/.//\n", outfile);
|
||||
insert_mode = false;
|
||||
}
|
||||
else
|
||||
print_1_line ("", &files[1].linbuf[i]);
|
||||
}
|
||||
|
||||
if (insert_mode)
|
||||
fputs (".\n", outfile);
|
||||
}
|
||||
}
|
||||
|
||||
/* Print change script in the style of ed commands,
|
||||
but print the changes in the order they appear in the input files,
|
||||
which means that the commands are not truly useful with ed.
|
||||
Because of the issue with lines containing just a dot, the output
|
||||
is not even parseable. */
|
||||
|
||||
void
|
||||
pr_forward_ed_script (struct change *script)
|
||||
{
|
||||
print_script (script, find_change, pr_forward_ed_hunk);
|
||||
}
|
||||
|
||||
static void
|
||||
pr_forward_ed_hunk (struct change *hunk)
|
||||
{
|
||||
lin i, f0, l0, f1, l1;
|
||||
|
||||
/* Determine range of line numbers involved in each file. */
|
||||
enum changes changes = analyze_hunk (hunk, &f0, &l0, &f1, &l1);
|
||||
if (!changes)
|
||||
return;
|
||||
|
||||
begin_output ();
|
||||
|
||||
fputc (change_letter[changes], outfile);
|
||||
print_number_range (' ', files, f0, l0);
|
||||
fputc ('\n', outfile);
|
||||
|
||||
/* If deletion only, print just the number range. */
|
||||
|
||||
if (changes == OLD)
|
||||
return;
|
||||
|
||||
/* For insertion (with or without deletion), print the number range
|
||||
and the lines from file 2. */
|
||||
|
||||
for (i = f1; i <= l1; i++)
|
||||
print_1_line ("", &files[1].linbuf[i]);
|
||||
|
||||
fputs (".\n", outfile);
|
||||
}
|
||||
|
||||
/* Print in a format somewhat like ed commands
|
||||
except that each insert command states the number of lines it inserts.
|
||||
This format is used for RCS. */
|
||||
|
||||
void
|
||||
print_rcs_script (struct change *script)
|
||||
{
|
||||
print_script (script, find_change, print_rcs_hunk);
|
||||
}
|
||||
|
||||
/* Print a hunk of an RCS diff */
|
||||
|
||||
static void
|
||||
print_rcs_hunk (struct change *hunk)
|
||||
{
|
||||
lin i, f0, l0, f1, l1;
|
||||
printint tf0, tl0, tf1, tl1;
|
||||
|
||||
/* Determine range of line numbers involved in each file. */
|
||||
enum changes changes = analyze_hunk (hunk, &f0, &l0, &f1, &l1);
|
||||
if (!changes)
|
||||
return;
|
||||
|
||||
begin_output ();
|
||||
|
||||
translate_range (&files[0], f0, l0, &tf0, &tl0);
|
||||
|
||||
if (changes & OLD)
|
||||
{
|
||||
/* For deletion, print just the starting line number from file 0
|
||||
and the number of lines deleted. */
|
||||
fprintf (outfile, "d%"pI"d %"pI"d\n", tf0,
|
||||
tf0 <= tl0 ? tl0 - tf0 + 1 : 1);
|
||||
}
|
||||
|
||||
if (changes & NEW)
|
||||
{
|
||||
/* Take last-line-number from file 0 and # lines from file 1. */
|
||||
translate_range (&files[1], f1, l1, &tf1, &tl1);
|
||||
fprintf (outfile, "a%"pI"d %"pI"d\n", tl0,
|
||||
tf1 <= tl1 ? tl1 - tf1 + 1 : 1);
|
||||
|
||||
/* Print the inserted lines. */
|
||||
for (i = f1; i <= l1; i++)
|
||||
print_1_line ("", &files[1].linbuf[i]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
/* #ifdef-format output routines for GNU DIFF.
|
||||
|
||||
Copyright (C) 1989, 1991-1994, 2001-2002, 2004, 2006, 2009-2013, 2015-2017
|
||||
Free Software Foundation, Inc.
|
||||
|
||||
This file is part of GNU DIFF.
|
||||
|
||||
GNU DIFF is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY. No author or distributor
|
||||
accepts responsibility to anyone for the consequences of using it
|
||||
or for whether it serves any particular purpose or works at all,
|
||||
unless he says so in writing. Refer to the GNU General Public
|
||||
License for full details.
|
||||
|
||||
Everyone is granted permission to copy, modify and redistribute
|
||||
GNU DIFF, but only under the conditions described in the
|
||||
GNU General Public License. A copy of this license is
|
||||
supposed to have been given to you along with GNU DIFF so you
|
||||
can know your rights and responsibilities. It should be in a
|
||||
file named COPYING. Among other things, the copyright notice
|
||||
and this notice must be preserved on all copies. */
|
||||
|
||||
#include "diff.h"
|
||||
|
||||
#include <xalloc.h>
|
||||
|
||||
struct group
|
||||
{
|
||||
struct file_data const *file;
|
||||
lin from, upto; /* start and limit lines for this group of lines */
|
||||
};
|
||||
|
||||
static char const *format_group (FILE *, char const *, char,
|
||||
struct group const *);
|
||||
static char const *do_printf_spec (FILE *, char const *,
|
||||
struct file_data const *, lin,
|
||||
struct group const *);
|
||||
static char const *scan_char_literal (char const *, char *);
|
||||
static lin groups_letter_value (struct group const *, char);
|
||||
static void format_ifdef (char const *, lin, lin, lin, lin);
|
||||
static void print_ifdef_hunk (struct change *);
|
||||
static void print_ifdef_lines (FILE *, char const *, struct group const *);
|
||||
|
||||
static lin next_line0;
|
||||
static lin next_line1;
|
||||
|
||||
/* Print the edit-script SCRIPT as a merged #ifdef file. */
|
||||
|
||||
void
|
||||
print_ifdef_script (struct change *script)
|
||||
{
|
||||
next_line0 = next_line1 = - files[0].prefix_lines;
|
||||
print_script (script, find_change, print_ifdef_hunk);
|
||||
if (next_line0 < files[0].valid_lines
|
||||
|| next_line1 < files[1].valid_lines)
|
||||
{
|
||||
begin_output ();
|
||||
format_ifdef (group_format[UNCHANGED],
|
||||
next_line0, files[0].valid_lines,
|
||||
next_line1, files[1].valid_lines);
|
||||
}
|
||||
}
|
||||
|
||||
/* Print a hunk of an ifdef diff.
|
||||
This is a contiguous portion of a complete edit script,
|
||||
describing changes in consecutive lines. */
|
||||
|
||||
static void
|
||||
print_ifdef_hunk (struct change *hunk)
|
||||
{
|
||||
lin first0, last0, first1, last1;
|
||||
|
||||
/* Determine range of line numbers involved in each file. */
|
||||
enum changes changes = analyze_hunk (hunk, &first0, &last0, &first1, &last1);
|
||||
if (!changes)
|
||||
return;
|
||||
|
||||
begin_output ();
|
||||
|
||||
/* Print lines up to this change. */
|
||||
if (next_line0 < first0 || next_line1 < first1)
|
||||
format_ifdef (group_format[UNCHANGED],
|
||||
next_line0, first0,
|
||||
next_line1, first1);
|
||||
|
||||
/* Print this change. */
|
||||
next_line0 = last0 + 1;
|
||||
next_line1 = last1 + 1;
|
||||
format_ifdef (group_format[changes],
|
||||
first0, next_line0,
|
||||
first1, next_line1);
|
||||
}
|
||||
|
||||
/* Print a set of lines according to FORMAT.
|
||||
Lines BEG0 up to END0 are from the first file;
|
||||
lines BEG1 up to END1 are from the second file. */
|
||||
|
||||
static void
|
||||
format_ifdef (char const *format, lin beg0, lin end0, lin beg1, lin end1)
|
||||
{
|
||||
struct group groups[2];
|
||||
|
||||
groups[0].file = &files[0];
|
||||
groups[0].from = beg0;
|
||||
groups[0].upto = end0;
|
||||
groups[1].file = &files[1];
|
||||
groups[1].from = beg1;
|
||||
groups[1].upto = end1;
|
||||
format_group (outfile, format, 0, groups);
|
||||
}
|
||||
|
||||
/* Print to file OUT a set of lines according to FORMAT.
|
||||
The format ends at the first free instance of ENDCHAR.
|
||||
Yield the address of the terminating character.
|
||||
GROUPS specifies which lines to print.
|
||||
If OUT is zero, do not actually print anything; just scan the format. */
|
||||
|
||||
static char const *
|
||||
format_group (register FILE *out, char const *format, char endchar,
|
||||
struct group const *groups)
|
||||
{
|
||||
register char c;
|
||||
register char const *f = format;
|
||||
|
||||
while ((c = *f) != endchar && c != 0)
|
||||
{
|
||||
char const *f1 = ++f;
|
||||
if (c == '%')
|
||||
switch ((c = *f++))
|
||||
{
|
||||
case '%':
|
||||
break;
|
||||
|
||||
case '(':
|
||||
/* Print if-then-else format e.g. '%(n=1?thenpart:elsepart)'. */
|
||||
{
|
||||
int i;
|
||||
uintmax_t value[2];
|
||||
FILE *thenout, *elseout;
|
||||
|
||||
for (i = 0; i < 2; i++)
|
||||
{
|
||||
if (ISDIGIT (*f))
|
||||
{
|
||||
char *fend;
|
||||
errno = 0;
|
||||
value[i] = strtoumax (f, &fend, 10);
|
||||
if (errno)
|
||||
goto bad_format;
|
||||
f = fend;
|
||||
}
|
||||
else
|
||||
{
|
||||
value[i] = groups_letter_value (groups, *f);
|
||||
if (value[i] == -1)
|
||||
goto bad_format;
|
||||
f++;
|
||||
}
|
||||
if (*f++ != "=?"[i])
|
||||
goto bad_format;
|
||||
}
|
||||
if (value[0] == value[1])
|
||||
thenout = out, elseout = 0;
|
||||
else
|
||||
thenout = 0, elseout = out;
|
||||
f = format_group (thenout, f, ':', groups);
|
||||
if (*f)
|
||||
{
|
||||
f = format_group (elseout, f + 1, ')', groups);
|
||||
if (*f)
|
||||
f++;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
|
||||
case '<':
|
||||
/* Print lines deleted from first file. */
|
||||
print_ifdef_lines (out, line_format[OLD], &groups[0]);
|
||||
continue;
|
||||
|
||||
case '=':
|
||||
/* Print common lines. */
|
||||
print_ifdef_lines (out, line_format[UNCHANGED], &groups[0]);
|
||||
continue;
|
||||
|
||||
case '>':
|
||||
/* Print lines inserted from second file. */
|
||||
print_ifdef_lines (out, line_format[NEW], &groups[1]);
|
||||
continue;
|
||||
|
||||
default:
|
||||
f = do_printf_spec (out, f - 2, 0, 0, groups);
|
||||
if (f)
|
||||
continue;
|
||||
/* Fall through. */
|
||||
bad_format:
|
||||
c = '%';
|
||||
f = f1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (out)
|
||||
putc (c, out);
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
/* For the line group pair G, return the number corresponding to LETTER.
|
||||
Return -1 if LETTER is not a group format letter. */
|
||||
static lin
|
||||
groups_letter_value (struct group const *g, char letter)
|
||||
{
|
||||
switch (letter)
|
||||
{
|
||||
case 'E': letter = 'e'; g++; break;
|
||||
case 'F': letter = 'f'; g++; break;
|
||||
case 'L': letter = 'l'; g++; break;
|
||||
case 'M': letter = 'm'; g++; break;
|
||||
case 'N': letter = 'n'; g++; break;
|
||||
}
|
||||
|
||||
switch (letter)
|
||||
{
|
||||
case 'e': return translate_line_number (g->file, g->from) - 1;
|
||||
case 'f': return translate_line_number (g->file, g->from);
|
||||
case 'l': return translate_line_number (g->file, g->upto) - 1;
|
||||
case 'm': return translate_line_number (g->file, g->upto);
|
||||
case 'n': return g->upto - g->from;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Print to file OUT, using FORMAT to print the line group GROUP.
|
||||
But do nothing if OUT is zero. */
|
||||
static void
|
||||
print_ifdef_lines (register FILE *out, char const *format,
|
||||
struct group const *group)
|
||||
{
|
||||
struct file_data const *file = group->file;
|
||||
char const * const *linbuf = file->linbuf;
|
||||
lin from = group->from, upto = group->upto;
|
||||
|
||||
if (!out)
|
||||
return;
|
||||
|
||||
/* If possible, use a single fwrite; it's faster. */
|
||||
if (!expand_tabs && format[0] == '%')
|
||||
{
|
||||
if (format[1] == 'l' && format[2] == '\n' && !format[3] && from < upto)
|
||||
{
|
||||
fwrite (linbuf[from], sizeof (char),
|
||||
linbuf[upto] + (linbuf[upto][-1] != '\n') - linbuf[from],
|
||||
out);
|
||||
return;
|
||||
}
|
||||
if (format[1] == 'L' && !format[2])
|
||||
{
|
||||
fwrite (linbuf[from], sizeof (char),
|
||||
linbuf[upto] - linbuf[from], out);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (; from < upto; from++)
|
||||
{
|
||||
register char c;
|
||||
register char const *f = format;
|
||||
|
||||
while ((c = *f++) != 0)
|
||||
{
|
||||
char const *f1 = f;
|
||||
if (c == '%')
|
||||
switch ((c = *f++))
|
||||
{
|
||||
case '%':
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
output_1_line (linbuf[from],
|
||||
(linbuf[from + 1]
|
||||
- (linbuf[from + 1][-1] == '\n')),
|
||||
0, 0);
|
||||
continue;
|
||||
|
||||
case 'L':
|
||||
output_1_line (linbuf[from], linbuf[from + 1], 0, 0);
|
||||
continue;
|
||||
|
||||
default:
|
||||
f = do_printf_spec (out, f - 2, file, from, 0);
|
||||
if (f)
|
||||
continue;
|
||||
c = '%';
|
||||
f = f1;
|
||||
break;
|
||||
}
|
||||
|
||||
putc (c, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static char const *
|
||||
do_printf_spec (FILE *out, char const *spec,
|
||||
struct file_data const *file, lin n,
|
||||
struct group const *groups)
|
||||
{
|
||||
char const *f = spec;
|
||||
char c;
|
||||
char c1;
|
||||
|
||||
/* Scan printf-style SPEC of the form %[-'0]*[0-9]*(.[0-9]*)?[cdoxX]. */
|
||||
/* assert (*f == '%'); */
|
||||
f++;
|
||||
while ((c = *f++) == '-' || c == '\'' || c == '0')
|
||||
continue;
|
||||
while (ISDIGIT (c))
|
||||
c = *f++;
|
||||
if (c == '.')
|
||||
while (ISDIGIT (c = *f++))
|
||||
continue;
|
||||
c1 = *f++;
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case 'c':
|
||||
if (c1 != '\'')
|
||||
return 0;
|
||||
else
|
||||
{
|
||||
char value IF_LINT (= 0);
|
||||
f = scan_char_literal (f, &value);
|
||||
if (!f)
|
||||
return 0;
|
||||
if (out)
|
||||
putc (value, out);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'd': case 'o': case 'x': case 'X':
|
||||
{
|
||||
lin value;
|
||||
|
||||
if (file)
|
||||
{
|
||||
if (c1 != 'n')
|
||||
return 0;
|
||||
value = translate_line_number (file, n);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = groups_letter_value (groups, c1);
|
||||
if (value < 0)
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (out)
|
||||
{
|
||||
/* For example, if the spec is "%3xn" and pI is "l", use the printf
|
||||
format spec "%3lx". Here the spec prefix is "%3". */
|
||||
printint print_value = value;
|
||||
size_t spec_prefix_len = f - spec - 2;
|
||||
size_t pI_len = sizeof pI - 1;
|
||||
#if 0
|
||||
char format[spec_prefix_len + pI_len + 2];
|
||||
#else
|
||||
char *format = xmalloc (spec_prefix_len + pI_len + 2);
|
||||
#endif
|
||||
char *p = format + spec_prefix_len + pI_len;
|
||||
memcpy (format, spec, spec_prefix_len);
|
||||
memcpy (format + spec_prefix_len, pI, pI_len);
|
||||
*p++ = c;
|
||||
*p = '\0';
|
||||
fprintf (out, format, print_value);
|
||||
#if ! HAVE_C_VARARRAYS
|
||||
free (format);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
/* Scan the character literal represented in the string LIT; LIT points just
|
||||
after the initial apostrophe. Put the literal's value into *VALPTR.
|
||||
Yield the address of the first character after the closing apostrophe,
|
||||
or a null pointer if the literal is ill-formed. */
|
||||
static char const *
|
||||
scan_char_literal (char const *lit, char *valptr)
|
||||
{
|
||||
register char const *p = lit;
|
||||
char value;
|
||||
ptrdiff_t digits;
|
||||
char c = *p++;
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case 0:
|
||||
case '\'':
|
||||
return NULL;
|
||||
|
||||
case '\\':
|
||||
value = 0;
|
||||
while ((c = *p++) != '\'')
|
||||
{
|
||||
unsigned int digit = c - '0';
|
||||
if (8 <= digit)
|
||||
return NULL;
|
||||
value = 8 * value + digit;
|
||||
}
|
||||
digits = p - lit - 2;
|
||||
if (! (1 <= digits && digits <= 3))
|
||||
return NULL;
|
||||
break;
|
||||
|
||||
default:
|
||||
value = c;
|
||||
if (*p++ != '\'')
|
||||
return NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
*valptr = value;
|
||||
return p;
|
||||
}
|
||||
@@ -0,0 +1,830 @@
|
||||
/* File I/O for GNU DIFF.
|
||||
|
||||
Copyright (C) 1988-1989, 1992-1995, 1998, 2001-2002, 2004, 2006, 2009-2013,
|
||||
2015-2017 Free Software Foundation, Inc.
|
||||
|
||||
This file is part of GNU DIFF.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
#include "diff.h"
|
||||
#include <binary-io.h>
|
||||
#include <cmpbuf.h>
|
||||
#include <file-type.h>
|
||||
#include <xalloc.h>
|
||||
|
||||
/* Rotate an unsigned value to the left. */
|
||||
#define ROL(v, n) ((v) << (n) | (v) >> (sizeof (v) * CHAR_BIT - (n)))
|
||||
|
||||
/* Given a hash value and a new character, return a new hash value. */
|
||||
#define HASH(h, c) ((c) + ROL (h, 7))
|
||||
|
||||
/* The type of a hash value. */
|
||||
typedef size_t hash_value;
|
||||
verify (! TYPE_SIGNED (hash_value));
|
||||
|
||||
/* Lines are put into equivalence classes of lines that match in lines_differ.
|
||||
Each equivalence class is represented by one of these structures,
|
||||
but only while the classes are being computed.
|
||||
Afterward, each class is represented by a number. */
|
||||
struct equivclass
|
||||
{
|
||||
lin next; /* Next item in this bucket. */
|
||||
hash_value hash; /* Hash of lines in this class. */
|
||||
char const *line; /* A line that fits this class. */
|
||||
size_t length; /* That line's length, not counting its newline. */
|
||||
};
|
||||
|
||||
/* Hash-table: array of buckets, each being a chain of equivalence classes.
|
||||
buckets[-1] is reserved for incomplete lines. */
|
||||
static lin *buckets;
|
||||
|
||||
/* Number of buckets in the hash table array, not counting buckets[-1]. */
|
||||
static size_t nbuckets;
|
||||
|
||||
/* Array in which the equivalence classes are allocated.
|
||||
The bucket-chains go through the elements in this array.
|
||||
The number of an equivalence class is its index in this array. */
|
||||
static struct equivclass *equivs;
|
||||
|
||||
/* Index of first free element in the array 'equivs'. */
|
||||
static lin equivs_index;
|
||||
|
||||
/* Number of elements allocated in the array 'equivs'. */
|
||||
static lin equivs_alloc;
|
||||
|
||||
/* Read a block of data into a file buffer, checking for EOF and error. */
|
||||
|
||||
void
|
||||
file_block_read (struct file_data *current, size_t size)
|
||||
{
|
||||
if (size && ! current->eof)
|
||||
{
|
||||
size_t s = block_read (current->desc,
|
||||
FILE_BUFFER (current) + current->buffered, size);
|
||||
if (s == SIZE_MAX)
|
||||
pfatal_with_name (current->name);
|
||||
current->buffered += s;
|
||||
current->eof = s < size;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check for binary files and compare them for exact identity. */
|
||||
|
||||
/* Return 1 if BUF contains a non text character.
|
||||
SIZE is the number of characters in BUF. */
|
||||
|
||||
#define binary_file_p(buf, size) (memchr (buf, 0, size) != 0)
|
||||
|
||||
/* Get ready to read the current file.
|
||||
Return nonzero if SKIP_TEST is zero,
|
||||
and if it appears to be a binary file. */
|
||||
|
||||
static bool
|
||||
sip (struct file_data *current, bool skip_test)
|
||||
{
|
||||
/* If we have a nonexistent file at this stage, treat it as empty. */
|
||||
if (current->desc < 0)
|
||||
{
|
||||
/* Leave room for a sentinel. */
|
||||
current->bufsize = sizeof (word);
|
||||
current->buffer = xmalloc (current->bufsize);
|
||||
}
|
||||
else
|
||||
{
|
||||
current->bufsize = buffer_lcm (sizeof (word),
|
||||
STAT_BLOCKSIZE (current->stat),
|
||||
PTRDIFF_MAX - 2 * sizeof (word));
|
||||
current->buffer = xmalloc (current->bufsize);
|
||||
|
||||
#ifdef __KLIBC__
|
||||
/* Skip test if seek is not possible */
|
||||
skip_test = skip_test
|
||||
|| (lseek (current->desc, 0, SEEK_CUR) < 0
|
||||
&& errno == ESPIPE);
|
||||
#endif
|
||||
|
||||
if (! skip_test)
|
||||
{
|
||||
/* Check first part of file to see if it's a binary file. */
|
||||
|
||||
int prev_mode = set_binary_mode (current->desc, O_BINARY);
|
||||
off_t buffered;
|
||||
file_block_read (current, current->bufsize);
|
||||
buffered = current->buffered;
|
||||
|
||||
if (prev_mode != O_BINARY)
|
||||
{
|
||||
/* Revert to text mode and seek back to the start to reread
|
||||
the file. Use relative seek, since file descriptors
|
||||
like stdin might not start at offset zero. */
|
||||
if (lseek (current->desc, - buffered, SEEK_CUR) < 0)
|
||||
pfatal_with_name (current->name);
|
||||
set_binary_mode (current->desc, prev_mode);
|
||||
current->buffered = 0;
|
||||
current->eof = false;
|
||||
}
|
||||
|
||||
return binary_file_p (current->buffer, buffered);
|
||||
}
|
||||
}
|
||||
|
||||
current->buffered = 0;
|
||||
current->eof = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Slurp the rest of the current file completely into memory. */
|
||||
|
||||
static void
|
||||
slurp (struct file_data *current)
|
||||
{
|
||||
size_t cc;
|
||||
|
||||
if (current->desc < 0)
|
||||
{
|
||||
/* The file is nonexistent. */
|
||||
return;
|
||||
}
|
||||
|
||||
if (S_ISREG (current->stat.st_mode))
|
||||
{
|
||||
/* It's a regular file; slurp in the rest all at once. */
|
||||
|
||||
/* Get the size out of the stat block.
|
||||
Allocate just enough room for appended newline plus word sentinel,
|
||||
plus word-alignment since we want the buffer word-aligned. */
|
||||
size_t file_size = current->stat.st_size;
|
||||
cc = file_size + 2 * sizeof (word) - file_size % sizeof (word);
|
||||
if (file_size != current->stat.st_size || cc < file_size
|
||||
|| PTRDIFF_MAX <= cc)
|
||||
xalloc_die ();
|
||||
|
||||
if (current->bufsize < cc)
|
||||
{
|
||||
current->bufsize = cc;
|
||||
current->buffer = xrealloc (current->buffer, cc);
|
||||
}
|
||||
|
||||
/* Try to read at least 1 more byte than the size indicates, to
|
||||
detect whether the file is growing. This is a nicety for
|
||||
users who run 'diff' on files while they are changing. */
|
||||
|
||||
if (current->buffered <= file_size)
|
||||
{
|
||||
file_block_read (current, file_size + 1 - current->buffered);
|
||||
if (current->buffered <= file_size)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* It's not a regular file, or it's a growing regular file; read it,
|
||||
growing the buffer as needed. */
|
||||
|
||||
file_block_read (current, current->bufsize - current->buffered);
|
||||
|
||||
if (current->buffered)
|
||||
{
|
||||
while (current->buffered == current->bufsize)
|
||||
{
|
||||
if (PTRDIFF_MAX / 2 - sizeof (word) < current->bufsize)
|
||||
xalloc_die ();
|
||||
current->bufsize *= 2;
|
||||
current->buffer = xrealloc (current->buffer, current->bufsize);
|
||||
file_block_read (current, current->bufsize - current->buffered);
|
||||
}
|
||||
|
||||
/* Allocate just enough room for appended newline plus word
|
||||
sentinel, plus word-alignment. */
|
||||
cc = current->buffered + 2 * sizeof (word);
|
||||
current->bufsize = cc - cc % sizeof (word);
|
||||
current->buffer = xrealloc (current->buffer, current->bufsize);
|
||||
}
|
||||
}
|
||||
|
||||
/* Split the file into lines, simultaneously computing the equivalence
|
||||
class for each line. */
|
||||
|
||||
static void
|
||||
find_and_hash_each_line (struct file_data *current)
|
||||
{
|
||||
char const *p = current->prefix_end;
|
||||
lin i, *bucket;
|
||||
size_t length;
|
||||
|
||||
/* Cache often-used quantities in local variables to help the compiler. */
|
||||
char const **linbuf = current->linbuf;
|
||||
lin alloc_lines = current->alloc_lines;
|
||||
lin line = 0;
|
||||
lin linbuf_base = current->linbuf_base;
|
||||
lin *cureqs = xmalloc (alloc_lines * sizeof *cureqs);
|
||||
struct equivclass *eqs = equivs;
|
||||
lin eqs_index = equivs_index;
|
||||
lin eqs_alloc = equivs_alloc;
|
||||
char const *suffix_begin = current->suffix_begin;
|
||||
char const *bufend = FILE_BUFFER (current) + current->buffered;
|
||||
bool ig_case = ignore_case;
|
||||
enum DIFF_white_space ig_white_space = ignore_white_space;
|
||||
bool diff_length_compare_anyway =
|
||||
ig_white_space != IGNORE_NO_WHITE_SPACE;
|
||||
bool same_length_diff_contents_compare_anyway =
|
||||
diff_length_compare_anyway | ig_case;
|
||||
|
||||
while (p < suffix_begin)
|
||||
{
|
||||
char const *ip = p;
|
||||
hash_value h = 0;
|
||||
unsigned char c;
|
||||
|
||||
/* Hash this line until we find a newline. */
|
||||
switch (ig_white_space)
|
||||
{
|
||||
case IGNORE_ALL_SPACE:
|
||||
while ((c = *p++) != '\n')
|
||||
if (! isspace (c))
|
||||
h = HASH (h, ig_case ? tolower (c) : c);
|
||||
break;
|
||||
|
||||
case IGNORE_SPACE_CHANGE:
|
||||
while ((c = *p++) != '\n')
|
||||
{
|
||||
if (isspace (c))
|
||||
{
|
||||
do
|
||||
if ((c = *p++) == '\n')
|
||||
goto hashing_done;
|
||||
while (isspace (c));
|
||||
|
||||
h = HASH (h, ' ');
|
||||
}
|
||||
|
||||
/* C is now the first non-space. */
|
||||
h = HASH (h, ig_case ? tolower (c) : c);
|
||||
}
|
||||
break;
|
||||
|
||||
case IGNORE_TAB_EXPANSION:
|
||||
case IGNORE_TAB_EXPANSION_AND_TRAILING_SPACE:
|
||||
case IGNORE_TRAILING_SPACE:
|
||||
{
|
||||
size_t column = 0;
|
||||
while ((c = *p++) != '\n')
|
||||
{
|
||||
if (ig_white_space & IGNORE_TRAILING_SPACE
|
||||
&& isspace (c))
|
||||
{
|
||||
char const *p1 = p;
|
||||
unsigned char c1;
|
||||
do
|
||||
if ((c1 = *p1++) == '\n')
|
||||
{
|
||||
p = p1;
|
||||
goto hashing_done;
|
||||
}
|
||||
while (isspace (c1));
|
||||
}
|
||||
|
||||
size_t repetitions = 1;
|
||||
|
||||
if (ig_white_space & IGNORE_TAB_EXPANSION)
|
||||
switch (c)
|
||||
{
|
||||
case '\b':
|
||||
column -= 0 < column;
|
||||
break;
|
||||
|
||||
case '\t':
|
||||
c = ' ';
|
||||
repetitions = tabsize - column % tabsize;
|
||||
column = (column + repetitions < column
|
||||
? 0
|
||||
: column + repetitions);
|
||||
break;
|
||||
|
||||
case '\r':
|
||||
column = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
column++;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ig_case)
|
||||
c = tolower (c);
|
||||
|
||||
do
|
||||
h = HASH (h, c);
|
||||
while (--repetitions != 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (ig_case)
|
||||
while ((c = *p++) != '\n')
|
||||
h = HASH (h, tolower (c));
|
||||
else
|
||||
while ((c = *p++) != '\n')
|
||||
h = HASH (h, c);
|
||||
break;
|
||||
}
|
||||
|
||||
hashing_done:;
|
||||
|
||||
bucket = &buckets[h % nbuckets];
|
||||
length = p - ip - 1;
|
||||
|
||||
if (p == bufend
|
||||
&& current->missing_newline
|
||||
&& ROBUST_OUTPUT_STYLE (output_style))
|
||||
{
|
||||
/* The last line is incomplete and we do not silently
|
||||
complete lines. If the line cannot compare equal to any
|
||||
complete line, put it into buckets[-1] so that it can
|
||||
compare equal only to the other file's incomplete line
|
||||
(if one exists). */
|
||||
if (ig_white_space < IGNORE_TRAILING_SPACE)
|
||||
bucket = &buckets[-1];
|
||||
}
|
||||
|
||||
for (i = *bucket; ; i = eqs[i].next)
|
||||
if (!i)
|
||||
{
|
||||
/* Create a new equivalence class in this bucket. */
|
||||
i = eqs_index++;
|
||||
if (i == eqs_alloc)
|
||||
{
|
||||
if (PTRDIFF_MAX / (2 * sizeof *eqs) <= eqs_alloc)
|
||||
xalloc_die ();
|
||||
eqs_alloc *= 2;
|
||||
eqs = xrealloc (eqs, eqs_alloc * sizeof *eqs);
|
||||
}
|
||||
eqs[i].next = *bucket;
|
||||
eqs[i].hash = h;
|
||||
eqs[i].line = ip;
|
||||
eqs[i].length = length;
|
||||
*bucket = i;
|
||||
break;
|
||||
}
|
||||
else if (eqs[i].hash == h)
|
||||
{
|
||||
char const *eqline = eqs[i].line;
|
||||
|
||||
/* Reuse existing class if lines_differ reports the lines
|
||||
equal. */
|
||||
if (eqs[i].length == length)
|
||||
{
|
||||
/* Reuse existing equivalence class if the lines are identical.
|
||||
This detects the common case of exact identity
|
||||
faster than lines_differ would. */
|
||||
if (memcmp (eqline, ip, length) == 0)
|
||||
break;
|
||||
if (!same_length_diff_contents_compare_anyway)
|
||||
continue;
|
||||
}
|
||||
else if (!diff_length_compare_anyway)
|
||||
continue;
|
||||
|
||||
if (! lines_differ (eqline, ip))
|
||||
break;
|
||||
}
|
||||
|
||||
/* Maybe increase the size of the line table. */
|
||||
if (line == alloc_lines)
|
||||
{
|
||||
/* Double (alloc_lines - linbuf_base) by adding to alloc_lines. */
|
||||
if (PTRDIFF_MAX / 3 <= alloc_lines
|
||||
|| PTRDIFF_MAX / sizeof *cureqs <= 2 * alloc_lines - linbuf_base
|
||||
|| PTRDIFF_MAX / sizeof *linbuf <= alloc_lines - linbuf_base)
|
||||
xalloc_die ();
|
||||
alloc_lines = 2 * alloc_lines - linbuf_base;
|
||||
cureqs = xrealloc (cureqs, alloc_lines * sizeof *cureqs);
|
||||
linbuf += linbuf_base;
|
||||
linbuf = xrealloc (linbuf,
|
||||
(alloc_lines - linbuf_base) * sizeof *linbuf);
|
||||
linbuf -= linbuf_base;
|
||||
}
|
||||
linbuf[line] = ip;
|
||||
cureqs[line] = i;
|
||||
++line;
|
||||
}
|
||||
|
||||
current->buffered_lines = line;
|
||||
|
||||
for (i = 0; ; i++)
|
||||
{
|
||||
/* Record the line start for lines in the suffix that we care about.
|
||||
Record one more line start than lines,
|
||||
so that we can compute the length of any buffered line. */
|
||||
if (line == alloc_lines)
|
||||
{
|
||||
/* Double (alloc_lines - linbuf_base) by adding to alloc_lines. */
|
||||
if (PTRDIFF_MAX / 3 <= alloc_lines
|
||||
|| PTRDIFF_MAX / sizeof *cureqs <= 2 * alloc_lines - linbuf_base
|
||||
|| PTRDIFF_MAX / sizeof *linbuf <= alloc_lines - linbuf_base)
|
||||
xalloc_die ();
|
||||
alloc_lines = 2 * alloc_lines - linbuf_base;
|
||||
linbuf += linbuf_base;
|
||||
linbuf = xrealloc (linbuf,
|
||||
(alloc_lines - linbuf_base) * sizeof *linbuf);
|
||||
linbuf -= linbuf_base;
|
||||
}
|
||||
linbuf[line] = p;
|
||||
|
||||
if (p == bufend)
|
||||
{
|
||||
/* If the last line is incomplete and we do not silently
|
||||
complete lines, don't count its appended newline. */
|
||||
if (current->missing_newline && ROBUST_OUTPUT_STYLE (output_style))
|
||||
linbuf[line]--;
|
||||
break;
|
||||
}
|
||||
|
||||
if (context <= i && no_diff_means_no_output)
|
||||
break;
|
||||
|
||||
line++;
|
||||
|
||||
while (*p++ != '\n')
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Done with cache in local variables. */
|
||||
current->linbuf = linbuf;
|
||||
current->valid_lines = line;
|
||||
current->alloc_lines = alloc_lines;
|
||||
current->equivs = cureqs;
|
||||
equivs = eqs;
|
||||
equivs_alloc = eqs_alloc;
|
||||
equivs_index = eqs_index;
|
||||
}
|
||||
|
||||
/* Prepare the text. Make sure the text end is initialized.
|
||||
Make sure text ends in a newline,
|
||||
but remember that we had to add one.
|
||||
Strip trailing CRs, if that was requested. */
|
||||
|
||||
static void
|
||||
prepare_text (struct file_data *current)
|
||||
{
|
||||
size_t buffered = current->buffered;
|
||||
char *p = FILE_BUFFER (current);
|
||||
|
||||
if (buffered == 0 || p[buffered - 1] == '\n')
|
||||
current->missing_newline = false;
|
||||
else
|
||||
{
|
||||
p[buffered++] = '\n';
|
||||
current->missing_newline = true;
|
||||
}
|
||||
|
||||
if (!p)
|
||||
return;
|
||||
|
||||
/* Don't use uninitialized storage when planting or using sentinels. */
|
||||
memset (p + buffered, 0, sizeof (word));
|
||||
|
||||
if (strip_trailing_cr)
|
||||
{
|
||||
char *dst;
|
||||
char *srclim = p + buffered;
|
||||
*srclim = '\r';
|
||||
dst = rawmemchr (p, '\r');
|
||||
|
||||
if (dst != srclim)
|
||||
{
|
||||
char const *src = dst;
|
||||
do
|
||||
{
|
||||
*dst = *src++;
|
||||
dst += ! (*dst == '\r' && *src == '\n');
|
||||
}
|
||||
while (src < srclim);
|
||||
|
||||
buffered -= src - dst;
|
||||
}
|
||||
}
|
||||
|
||||
current->buffered = buffered;
|
||||
}
|
||||
|
||||
/* We have found N lines in a buffer of size S; guess the
|
||||
proportionate number of lines that will be found in a buffer of
|
||||
size T. However, do not guess a number of lines so large that the
|
||||
resulting line table might cause overflow in size calculations. */
|
||||
static lin
|
||||
guess_lines (lin n, size_t s, size_t t)
|
||||
{
|
||||
size_t guessed_bytes_per_line = n < 10 ? 32 : s / (n - 1);
|
||||
lin guessed_lines = MAX (1, t / guessed_bytes_per_line);
|
||||
return MIN (guessed_lines, PTRDIFF_MAX / (2 * sizeof (char *) + 1) - 5) + 5;
|
||||
}
|
||||
|
||||
/* Given a vector of two file_data objects, find the identical
|
||||
prefixes and suffixes of each object. */
|
||||
|
||||
static void
|
||||
find_identical_ends (struct file_data filevec[])
|
||||
{
|
||||
word *w0, *w1;
|
||||
char *p0, *p1, *buffer0, *buffer1;
|
||||
char const *end0, *beg0;
|
||||
char const **linbuf0, **linbuf1;
|
||||
lin i, lines;
|
||||
size_t n0, n1;
|
||||
lin alloc_lines0, alloc_lines1;
|
||||
bool prefix_needed;
|
||||
lin buffered_prefix, prefix_count, prefix_mask;
|
||||
lin middle_guess, suffix_guess;
|
||||
|
||||
slurp (&filevec[0]);
|
||||
prepare_text (&filevec[0]);
|
||||
if (filevec[0].desc != filevec[1].desc)
|
||||
{
|
||||
slurp (&filevec[1]);
|
||||
prepare_text (&filevec[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
filevec[1].buffer = filevec[0].buffer;
|
||||
filevec[1].bufsize = filevec[0].bufsize;
|
||||
filevec[1].buffered = filevec[0].buffered;
|
||||
filevec[1].missing_newline = filevec[0].missing_newline;
|
||||
}
|
||||
|
||||
/* Find identical prefix. */
|
||||
|
||||
w0 = filevec[0].buffer;
|
||||
w1 = filevec[1].buffer;
|
||||
p0 = buffer0 = (char *) w0;
|
||||
p1 = buffer1 = (char *) w1;
|
||||
n0 = filevec[0].buffered;
|
||||
n1 = filevec[1].buffered;
|
||||
|
||||
if (p0 == p1)
|
||||
/* The buffers are the same; sentinels won't work. */
|
||||
p0 = p1 += n1;
|
||||
else
|
||||
{
|
||||
/* Insert end sentinels, in this case characters that are guaranteed
|
||||
to make the equality test false, and thus terminate the loop. */
|
||||
|
||||
if (n0 < n1)
|
||||
p0[n0] = ~p1[n0];
|
||||
else
|
||||
p1[n1] = ~p0[n1];
|
||||
|
||||
/* Loop until first mismatch, or to the sentinel characters. */
|
||||
|
||||
/* Compare a word at a time for speed. */
|
||||
while (*w0 == *w1)
|
||||
w0++, w1++;
|
||||
|
||||
/* Do the last few bytes of comparison a byte at a time. */
|
||||
p0 = (char *) w0;
|
||||
p1 = (char *) w1;
|
||||
while (*p0 == *p1)
|
||||
p0++, p1++;
|
||||
|
||||
/* Don't mistakenly count missing newline as part of prefix. */
|
||||
if (ROBUST_OUTPUT_STYLE (output_style)
|
||||
&& ((buffer0 + n0 - filevec[0].missing_newline < p0)
|
||||
!=
|
||||
(buffer1 + n1 - filevec[1].missing_newline < p1)))
|
||||
p0--, p1--;
|
||||
}
|
||||
|
||||
/* Now P0 and P1 point at the first nonmatching characters. */
|
||||
|
||||
/* Skip back to last line-beginning in the prefix,
|
||||
and then discard up to HORIZON_LINES lines from the prefix. */
|
||||
i = horizon_lines;
|
||||
while (p0 != buffer0 && (p0[-1] != '\n' || i--))
|
||||
p0--, p1--;
|
||||
|
||||
/* Record the prefix. */
|
||||
filevec[0].prefix_end = p0;
|
||||
filevec[1].prefix_end = p1;
|
||||
|
||||
/* Find identical suffix. */
|
||||
|
||||
/* P0 and P1 point beyond the last chars not yet compared. */
|
||||
p0 = buffer0 + n0;
|
||||
p1 = buffer1 + n1;
|
||||
|
||||
if (! ROBUST_OUTPUT_STYLE (output_style)
|
||||
|| filevec[0].missing_newline == filevec[1].missing_newline)
|
||||
{
|
||||
end0 = p0; /* Addr of last char in file 0. */
|
||||
|
||||
/* Get value of P0 at which we should stop scanning backward:
|
||||
this is when either P0 or P1 points just past the last char
|
||||
of the identical prefix. */
|
||||
beg0 = filevec[0].prefix_end + (n0 < n1 ? 0 : n0 - n1);
|
||||
|
||||
/* Scan back until chars don't match or we reach that point. */
|
||||
while (p0 != beg0)
|
||||
if (*--p0 != *--p1)
|
||||
{
|
||||
/* Point at the first char of the matching suffix. */
|
||||
++p0, ++p1;
|
||||
beg0 = p0;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Are we at a line-beginning in both files? If not, add the rest of
|
||||
this line to the main body. Discard up to HORIZON_LINES lines from
|
||||
the identical suffix. Also, discard one extra line,
|
||||
because shift_boundaries may need it. */
|
||||
i = horizon_lines + !((buffer0 == p0 || p0[-1] == '\n')
|
||||
&&
|
||||
(buffer1 == p1 || p1[-1] == '\n'));
|
||||
while (i-- && p0 != end0)
|
||||
while (*p0++ != '\n')
|
||||
continue;
|
||||
|
||||
p1 += p0 - beg0;
|
||||
}
|
||||
|
||||
/* Record the suffix. */
|
||||
filevec[0].suffix_begin = p0;
|
||||
filevec[1].suffix_begin = p1;
|
||||
|
||||
/* Calculate number of lines of prefix to save.
|
||||
|
||||
prefix_count == 0 means save the whole prefix;
|
||||
we need this for options like -D that output the whole file,
|
||||
or for enormous contexts (to avoid worrying about arithmetic overflow).
|
||||
We also need it for options like -F that output some preceding line;
|
||||
at least we will need to find the last few lines,
|
||||
but since we don't know how many, it's easiest to find them all.
|
||||
|
||||
Otherwise, prefix_count != 0. Save just prefix_count lines at start
|
||||
of the line buffer; they'll be moved to the proper location later.
|
||||
Handle 1 more line than the context says (because we count 1 too many),
|
||||
rounded up to the next power of 2 to speed index computation. */
|
||||
|
||||
if (no_diff_means_no_output && ! function_regexp.fastmap
|
||||
&& context < LIN_MAX / 4 && context < n0)
|
||||
{
|
||||
middle_guess = guess_lines (0, 0, p0 - filevec[0].prefix_end);
|
||||
suffix_guess = guess_lines (0, 0, buffer0 + n0 - p0);
|
||||
for (prefix_count = 1; prefix_count <= context; prefix_count *= 2)
|
||||
continue;
|
||||
alloc_lines0 = (prefix_count + middle_guess
|
||||
+ MIN (context, suffix_guess));
|
||||
}
|
||||
else
|
||||
{
|
||||
prefix_count = 0;
|
||||
alloc_lines0 = guess_lines (0, 0, n0);
|
||||
}
|
||||
|
||||
prefix_mask = prefix_count - 1;
|
||||
lines = 0;
|
||||
linbuf0 = xmalloc (alloc_lines0 * sizeof *linbuf0);
|
||||
prefix_needed = ! (no_diff_means_no_output
|
||||
&& filevec[0].prefix_end == p0
|
||||
&& filevec[1].prefix_end == p1);
|
||||
p0 = buffer0;
|
||||
|
||||
/* If the prefix is needed, find the prefix lines. */
|
||||
if (prefix_needed)
|
||||
{
|
||||
end0 = filevec[0].prefix_end;
|
||||
while (p0 != end0)
|
||||
{
|
||||
lin l = lines++ & prefix_mask;
|
||||
if (l == alloc_lines0)
|
||||
{
|
||||
if (PTRDIFF_MAX / (2 * sizeof *linbuf0) <= alloc_lines0)
|
||||
xalloc_die ();
|
||||
alloc_lines0 *= 2;
|
||||
linbuf0 = xrealloc (linbuf0, alloc_lines0 * sizeof *linbuf0);
|
||||
}
|
||||
linbuf0[l] = p0;
|
||||
while (*p0++ != '\n')
|
||||
continue;
|
||||
}
|
||||
}
|
||||
buffered_prefix = prefix_count && context < lines ? context : lines;
|
||||
|
||||
/* Allocate line buffer 1. */
|
||||
|
||||
middle_guess = guess_lines (lines, p0 - buffer0, p1 - filevec[1].prefix_end);
|
||||
suffix_guess = guess_lines (lines, p0 - buffer0, buffer1 + n1 - p1);
|
||||
alloc_lines1 = buffered_prefix + middle_guess + MIN (context, suffix_guess);
|
||||
if (alloc_lines1 < buffered_prefix
|
||||
|| PTRDIFF_MAX / sizeof *linbuf1 <= alloc_lines1)
|
||||
xalloc_die ();
|
||||
linbuf1 = xmalloc (alloc_lines1 * sizeof *linbuf1);
|
||||
|
||||
if (buffered_prefix != lines)
|
||||
{
|
||||
/* Rotate prefix lines to proper location. */
|
||||
for (i = 0; i < buffered_prefix; i++)
|
||||
linbuf1[i] = linbuf0[(lines - context + i) & prefix_mask];
|
||||
for (i = 0; i < buffered_prefix; i++)
|
||||
linbuf0[i] = linbuf1[i];
|
||||
}
|
||||
|
||||
/* Initialize line buffer 1 from line buffer 0. */
|
||||
for (i = 0; i < buffered_prefix; i++)
|
||||
linbuf1[i] = linbuf0[i] - buffer0 + buffer1;
|
||||
|
||||
/* Record the line buffer, adjusted so that
|
||||
linbuf[0] points at the first differing line. */
|
||||
filevec[0].linbuf = linbuf0 + buffered_prefix;
|
||||
filevec[1].linbuf = linbuf1 + buffered_prefix;
|
||||
filevec[0].linbuf_base = filevec[1].linbuf_base = - buffered_prefix;
|
||||
filevec[0].alloc_lines = alloc_lines0 - buffered_prefix;
|
||||
filevec[1].alloc_lines = alloc_lines1 - buffered_prefix;
|
||||
filevec[0].prefix_lines = filevec[1].prefix_lines = lines;
|
||||
}
|
||||
|
||||
/* If 1 < k, then (2**k - prime_offset[k]) is the largest prime less
|
||||
than 2**k. This table is derived from Chris K. Caldwell's list
|
||||
<http://www.utm.edu/research/primes/lists/2small/>. */
|
||||
|
||||
static unsigned char const prime_offset[] =
|
||||
{
|
||||
0, 0, 1, 1, 3, 1, 3, 1, 5, 3, 3, 9, 3, 1, 3, 19, 15, 1, 5, 1, 3, 9, 3,
|
||||
15, 3, 39, 5, 39, 57, 3, 35, 1, 5, 9, 41, 31, 5, 25, 45, 7, 87, 21,
|
||||
11, 57, 17, 55, 21, 115, 59, 81, 27, 129, 47, 111, 33, 55, 5, 13, 27,
|
||||
55, 93, 1, 57, 25
|
||||
};
|
||||
|
||||
/* Verify that this host's size_t is not too wide for the above table. */
|
||||
|
||||
verify (sizeof (size_t) * CHAR_BIT <= sizeof prime_offset);
|
||||
|
||||
/* Given a vector of two file_data objects, read the file associated
|
||||
with each one, and build the table of equivalence classes.
|
||||
Return nonzero if either file appears to be a binary file.
|
||||
If PRETEND_BINARY is nonzero, pretend they are binary regardless. */
|
||||
|
||||
bool
|
||||
read_files (struct file_data filevec[], bool pretend_binary)
|
||||
{
|
||||
int i;
|
||||
bool skip_test = text | pretend_binary;
|
||||
bool appears_binary = pretend_binary | sip (&filevec[0], skip_test);
|
||||
|
||||
if (filevec[0].desc != filevec[1].desc)
|
||||
appears_binary |= sip (&filevec[1], skip_test | appears_binary);
|
||||
else
|
||||
{
|
||||
filevec[1].buffer = filevec[0].buffer;
|
||||
filevec[1].bufsize = filevec[0].bufsize;
|
||||
filevec[1].buffered = filevec[0].buffered;
|
||||
}
|
||||
if (appears_binary)
|
||||
{
|
||||
set_binary_mode (filevec[0].desc, O_BINARY);
|
||||
set_binary_mode (filevec[1].desc, O_BINARY);
|
||||
return true;
|
||||
}
|
||||
|
||||
find_identical_ends (filevec);
|
||||
|
||||
equivs_alloc = filevec[0].alloc_lines + filevec[1].alloc_lines + 1;
|
||||
if (PTRDIFF_MAX / sizeof *equivs <= equivs_alloc)
|
||||
xalloc_die ();
|
||||
equivs = xmalloc (equivs_alloc * sizeof *equivs);
|
||||
/* Equivalence class 0 is permanently safe for lines that were not
|
||||
hashed. Real equivalence classes start at 1. */
|
||||
equivs_index = 1;
|
||||
|
||||
/* Allocate (one plus) a prime number of hash buckets. Use a prime
|
||||
number between 1/3 and 2/3 of the value of equiv_allocs,
|
||||
approximately. */
|
||||
for (i = 9; (size_t) 1 << i < equivs_alloc / 3; i++)
|
||||
continue;
|
||||
nbuckets = ((size_t) 1 << i) - prime_offset[i];
|
||||
if (PTRDIFF_MAX / sizeof *buckets <= nbuckets)
|
||||
xalloc_die ();
|
||||
buckets = zalloc ((nbuckets + 1) * sizeof *buckets);
|
||||
buckets++;
|
||||
|
||||
for (i = 0; i < 2; i++)
|
||||
find_and_hash_each_line (&filevec[i]);
|
||||
|
||||
filevec[0].equiv_max = filevec[1].equiv_max = equivs_index;
|
||||
|
||||
free (equivs);
|
||||
free (buckets - 1);
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/* Normal-format output routines for GNU DIFF.
|
||||
|
||||
Copyright (C) 1988-1989, 1993, 1995, 1998, 2001, 2006, 2009-2013, 2015-2017
|
||||
Free Software Foundation, Inc.
|
||||
|
||||
This file is part of GNU DIFF.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
#include "diff.h"
|
||||
|
||||
static void print_normal_hunk (struct change *);
|
||||
|
||||
/* Print the edit-script SCRIPT as a normal diff.
|
||||
INF points to an array of descriptions of the two files. */
|
||||
|
||||
void
|
||||
print_normal_script (struct change *script)
|
||||
{
|
||||
print_script (script, find_change, print_normal_hunk);
|
||||
}
|
||||
|
||||
/* Print a hunk of a normal diff.
|
||||
This is a contiguous portion of a complete edit script,
|
||||
describing changes in consecutive lines. */
|
||||
|
||||
static void
|
||||
print_normal_hunk (struct change *hunk)
|
||||
{
|
||||
lin first0, last0, first1, last1;
|
||||
register lin i;
|
||||
|
||||
/* Determine range of line numbers involved in each file. */
|
||||
enum changes changes = analyze_hunk (hunk, &first0, &last0, &first1, &last1);
|
||||
if (!changes)
|
||||
return;
|
||||
|
||||
begin_output ();
|
||||
|
||||
/* Print out the line number header for this hunk */
|
||||
set_color_context (LINE_NUMBER_CONTEXT);
|
||||
print_number_range (',', &files[0], first0, last0);
|
||||
fputc (change_letter[changes], outfile);
|
||||
print_number_range (',', &files[1], first1, last1);
|
||||
set_color_context (RESET_CONTEXT);
|
||||
fputc ('\n', outfile);
|
||||
|
||||
/* Print the lines that the first file has. */
|
||||
if (changes & OLD)
|
||||
{
|
||||
if (first0 <= last0)
|
||||
set_color_context (DELETE_CONTEXT);
|
||||
for (i = first0; i <= last0; i++)
|
||||
{
|
||||
print_1_line_nl ("<", &files[0].linbuf[i], true);
|
||||
if (i == last0)
|
||||
set_color_context (RESET_CONTEXT);
|
||||
if (files[0].linbuf[i + 1][-1] == '\n')
|
||||
putc ('\n', outfile);
|
||||
}
|
||||
}
|
||||
|
||||
if (changes == CHANGED)
|
||||
fputs ("---\n", outfile);
|
||||
|
||||
/* Print the lines that the second file has. */
|
||||
if (changes & NEW)
|
||||
{
|
||||
if (first1 <= last1)
|
||||
set_color_context (ADD_CONTEXT);
|
||||
for (i = first1; i <= last1; i++)
|
||||
{
|
||||
print_1_line_nl (">", &files[1].linbuf[i], true);
|
||||
if (i == last1)
|
||||
set_color_context (RESET_CONTEXT);
|
||||
if (files[1].linbuf[i + 1][-1] == '\n')
|
||||
putc ('\n', outfile);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,335 @@
|
||||
/* sdiff-format output routines for GNU DIFF.
|
||||
|
||||
Copyright (C) 1991-1993, 1998, 2001-2002, 2004, 2009-2013, 2015-2017 Free
|
||||
Software Foundation, Inc.
|
||||
|
||||
This file is part of GNU DIFF.
|
||||
|
||||
GNU DIFF is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY. No author or distributor
|
||||
accepts responsibility to anyone for the consequences of using it
|
||||
or for whether it serves any particular purpose or works at all,
|
||||
unless he says so in writing. Refer to the GNU General Public
|
||||
License for full details.
|
||||
|
||||
Everyone is granted permission to copy, modify and redistribute
|
||||
GNU DIFF, but only under the conditions described in the
|
||||
GNU General Public License. A copy of this license is
|
||||
supposed to have been given to you along with GNU DIFF so you
|
||||
can know your rights and responsibilities. It should be in a
|
||||
file named COPYING. Among other things, the copyright notice
|
||||
and this notice must be preserved on all copies. */
|
||||
|
||||
#include "diff.h"
|
||||
|
||||
#include <wchar.h>
|
||||
|
||||
static void print_sdiff_common_lines (lin, lin);
|
||||
static void print_sdiff_hunk (struct change *);
|
||||
|
||||
/* Next line number to be printed in the two input files. */
|
||||
static lin next0, next1;
|
||||
|
||||
/* Print the edit-script SCRIPT as a sdiff style output. */
|
||||
|
||||
void
|
||||
print_sdiff_script (struct change *script)
|
||||
{
|
||||
begin_output ();
|
||||
|
||||
next0 = next1 = - files[0].prefix_lines;
|
||||
print_script (script, find_change, print_sdiff_hunk);
|
||||
|
||||
print_sdiff_common_lines (files[0].valid_lines, files[1].valid_lines);
|
||||
}
|
||||
|
||||
/* Tab from column FROM to column TO, where FROM <= TO. Yield TO. */
|
||||
|
||||
static size_t
|
||||
tab_from_to (size_t from, size_t to)
|
||||
{
|
||||
FILE *out = outfile;
|
||||
size_t tab;
|
||||
size_t tab_size = tabsize;
|
||||
|
||||
if (!expand_tabs)
|
||||
for (tab = from + tab_size - from % tab_size; tab <= to; tab += tab_size)
|
||||
{
|
||||
putc ('\t', out);
|
||||
from = tab;
|
||||
}
|
||||
while (from++ < to)
|
||||
putc (' ', out);
|
||||
return to;
|
||||
}
|
||||
|
||||
/* Print the text for half an sdiff line. This means truncate to
|
||||
width observing tabs, and trim a trailing newline. Return the
|
||||
last column written (not the number of chars). */
|
||||
|
||||
static size_t
|
||||
print_half_line (char const *const *line, size_t indent, size_t out_bound)
|
||||
{
|
||||
FILE *out = outfile;
|
||||
register size_t in_position = 0;
|
||||
register size_t out_position = 0;
|
||||
register char const *text_pointer = line[0];
|
||||
register char const *text_limit = line[1];
|
||||
mbstate_t mbstate = { 0 };
|
||||
|
||||
while (text_pointer < text_limit)
|
||||
{
|
||||
char const *tp0 = text_pointer;
|
||||
register char c = *text_pointer++;
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case '\t':
|
||||
{
|
||||
size_t spaces = tabsize - in_position % tabsize;
|
||||
if (in_position == out_position)
|
||||
{
|
||||
size_t tabstop = out_position + spaces;
|
||||
if (expand_tabs)
|
||||
{
|
||||
if (out_bound < tabstop)
|
||||
tabstop = out_bound;
|
||||
for (; out_position < tabstop; out_position++)
|
||||
putc (' ', out);
|
||||
}
|
||||
else
|
||||
if (tabstop < out_bound)
|
||||
{
|
||||
out_position = tabstop;
|
||||
putc (c, out);
|
||||
}
|
||||
}
|
||||
in_position += spaces;
|
||||
}
|
||||
break;
|
||||
|
||||
case '\r':
|
||||
{
|
||||
putc (c, out);
|
||||
tab_from_to (0, indent);
|
||||
in_position = out_position = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case '\b':
|
||||
if (in_position != 0 && --in_position < out_bound)
|
||||
{
|
||||
if (out_position <= in_position)
|
||||
/* Add spaces to make up for suppressed tab past out_bound. */
|
||||
for (; out_position < in_position; out_position++)
|
||||
putc (' ', out);
|
||||
else
|
||||
{
|
||||
out_position = in_position;
|
||||
putc (c, out);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
wchar_t wc;
|
||||
size_t bytes = mbrtowc (&wc, tp0, text_limit - tp0, &mbstate);
|
||||
|
||||
if (0 < bytes && bytes < (size_t) -2)
|
||||
{
|
||||
int width = wcwidth (wc);
|
||||
if (0 < width)
|
||||
in_position += width;
|
||||
if (in_position <= out_bound)
|
||||
{
|
||||
out_position = in_position;
|
||||
fwrite (tp0, 1, bytes, stdout);
|
||||
}
|
||||
text_pointer = tp0 + bytes;
|
||||
break;
|
||||
}
|
||||
}
|
||||
FALLTHROUGH;
|
||||
case '\f':
|
||||
case '\v':
|
||||
if (in_position < out_bound)
|
||||
putc (c, out);
|
||||
break;
|
||||
|
||||
case ' ': case '!': case '"': case '#': case '%':
|
||||
case '&': case '\'': case '(': case ')': case '*':
|
||||
case '+': case ',': case '-': case '.': case '/':
|
||||
case '0': case '1': case '2': case '3': case '4':
|
||||
case '5': case '6': case '7': case '8': case '9':
|
||||
case ':': case ';': case '<': case '=': case '>':
|
||||
case '?':
|
||||
case 'A': case 'B': case 'C': case 'D': case 'E':
|
||||
case 'F': case 'G': case 'H': case 'I': case 'J':
|
||||
case 'K': case 'L': case 'M': case 'N': case 'O':
|
||||
case 'P': case 'Q': case 'R': case 'S': case 'T':
|
||||
case 'U': case 'V': case 'W': case 'X': case 'Y':
|
||||
case 'Z':
|
||||
case '[': case '\\': case ']': case '^': case '_':
|
||||
case 'a': case 'b': case 'c': case 'd': case 'e':
|
||||
case 'f': case 'g': case 'h': case 'i': case 'j':
|
||||
case 'k': case 'l': case 'm': case 'n': case 'o':
|
||||
case 'p': case 'q': case 'r': case 's': case 't':
|
||||
case 'u': case 'v': case 'w': case 'x': case 'y':
|
||||
case 'z': case '{': case '|': case '}': case '~':
|
||||
/* These characters are printable ASCII characters. */
|
||||
if (in_position++ < out_bound)
|
||||
{
|
||||
out_position = in_position;
|
||||
putc (c, out);
|
||||
}
|
||||
break;
|
||||
|
||||
case '\n':
|
||||
return out_position;
|
||||
}
|
||||
}
|
||||
|
||||
return out_position;
|
||||
}
|
||||
|
||||
/* Print side by side lines with a separator in the middle.
|
||||
0 parameters are taken to indicate white space text.
|
||||
Blank lines that can easily be caught are reduced to a single newline. */
|
||||
|
||||
static void
|
||||
print_1sdiff_line (char const *const *left, char sep,
|
||||
char const *const *right)
|
||||
{
|
||||
FILE *out = outfile;
|
||||
size_t hw = sdiff_half_width;
|
||||
size_t c2o = sdiff_column2_offset;
|
||||
size_t col = 0;
|
||||
bool put_newline = false;
|
||||
bool color_to_reset = false;
|
||||
|
||||
if (sep == '<')
|
||||
{
|
||||
set_color_context (DELETE_CONTEXT);
|
||||
color_to_reset = true;
|
||||
}
|
||||
else if (sep == '>')
|
||||
{
|
||||
set_color_context (ADD_CONTEXT);
|
||||
color_to_reset = true;
|
||||
}
|
||||
|
||||
if (left)
|
||||
{
|
||||
put_newline |= left[1][-1] == '\n';
|
||||
col = print_half_line (left, 0, hw);
|
||||
}
|
||||
|
||||
if (sep != ' ')
|
||||
{
|
||||
col = tab_from_to (col, (hw + c2o - 1) / 2) + 1;
|
||||
if (sep == '|' && put_newline != (right[1][-1] == '\n'))
|
||||
sep = put_newline ? '/' : '\\';
|
||||
putc (sep, out);
|
||||
}
|
||||
|
||||
if (right)
|
||||
{
|
||||
put_newline |= right[1][-1] == '\n';
|
||||
if (**right != '\n')
|
||||
{
|
||||
col = tab_from_to (col, c2o);
|
||||
print_half_line (right, col, hw);
|
||||
}
|
||||
}
|
||||
|
||||
if (put_newline)
|
||||
putc ('\n', out);
|
||||
|
||||
if (color_to_reset)
|
||||
set_color_context (RESET_CONTEXT);
|
||||
}
|
||||
|
||||
/* Print lines common to both files in side-by-side format. */
|
||||
static void
|
||||
print_sdiff_common_lines (lin limit0, lin limit1)
|
||||
{
|
||||
lin i0 = next0, i1 = next1;
|
||||
|
||||
if (!suppress_common_lines && (i0 != limit0 || i1 != limit1))
|
||||
{
|
||||
if (sdiff_merge_assist)
|
||||
{
|
||||
printint len0 = limit0 - i0;
|
||||
printint len1 = limit1 - i1;
|
||||
fprintf (outfile, "i%"pI"d,%"pI"d\n", len0, len1);
|
||||
}
|
||||
|
||||
if (!left_column)
|
||||
{
|
||||
while (i0 != limit0 && i1 != limit1)
|
||||
print_1sdiff_line (&files[0].linbuf[i0++], ' ',
|
||||
&files[1].linbuf[i1++]);
|
||||
while (i1 != limit1)
|
||||
print_1sdiff_line (0, ')', &files[1].linbuf[i1++]);
|
||||
}
|
||||
while (i0 != limit0)
|
||||
print_1sdiff_line (&files[0].linbuf[i0++], '(', 0);
|
||||
}
|
||||
|
||||
next0 = limit0;
|
||||
next1 = limit1;
|
||||
}
|
||||
|
||||
/* Print a hunk of an sdiff diff.
|
||||
This is a contiguous portion of a complete edit script,
|
||||
describing changes in consecutive lines. */
|
||||
|
||||
static void
|
||||
print_sdiff_hunk (struct change *hunk)
|
||||
{
|
||||
lin first0, last0, first1, last1;
|
||||
register lin i, j;
|
||||
|
||||
/* Determine range of line numbers involved in each file. */
|
||||
enum changes changes =
|
||||
analyze_hunk (hunk, &first0, &last0, &first1, &last1);
|
||||
if (!changes)
|
||||
return;
|
||||
|
||||
/* Print out lines up to this change. */
|
||||
print_sdiff_common_lines (first0, first1);
|
||||
|
||||
if (sdiff_merge_assist)
|
||||
{
|
||||
printint len0 = last0 - first0 + 1;
|
||||
printint len1 = last1 - first1 + 1;
|
||||
fprintf (outfile, "c%"pI"d,%"pI"d\n", len0, len1);
|
||||
}
|
||||
|
||||
/* Print "xxx | xxx " lines. */
|
||||
if (changes == CHANGED)
|
||||
{
|
||||
for (i = first0, j = first1; i <= last0 && j <= last1; i++, j++)
|
||||
print_1sdiff_line (&files[0].linbuf[i], '|', &files[1].linbuf[j]);
|
||||
changes = (i <= last0 ? OLD : 0) + (j <= last1 ? NEW : 0);
|
||||
next0 = first0 = i;
|
||||
next1 = first1 = j;
|
||||
}
|
||||
|
||||
/* Print " > xxx " lines. */
|
||||
if (changes & NEW)
|
||||
{
|
||||
for (j = first1; j <= last1; ++j)
|
||||
print_1sdiff_line (0, '>', &files[1].linbuf[j]);
|
||||
next1 = j;
|
||||
}
|
||||
|
||||
/* Print "xxx < " lines. */
|
||||
if (changes & OLD)
|
||||
{
|
||||
for (i = first0; i <= last0; ++i)
|
||||
print_1sdiff_line (&files[0].linbuf[i], '<', 0);
|
||||
next0 = i;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
/* System dependent declarations.
|
||||
|
||||
Copyright (C) 1988-1989, 1992-1995, 1998, 2001-2002, 2004, 2006, 2009-2013,
|
||||
2015-2017 Free Software Foundation, Inc.
|
||||
|
||||
This file is part of GNU DIFF.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
#include <config.h>
|
||||
|
||||
/* Use this to suppress gcc's "...may be used before initialized" warnings. */
|
||||
#ifdef lint
|
||||
# define IF_LINT(Code) Code
|
||||
#else
|
||||
# define IF_LINT(Code) /* empty */
|
||||
#endif
|
||||
|
||||
/* Define '__attribute__' and 'volatile' first
|
||||
so that they're used consistently in all system includes. */
|
||||
#if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 6) || __STRICT_ANSI__
|
||||
# define __attribute__(x)
|
||||
#endif
|
||||
|
||||
#include <verify.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include "stat-macros.h"
|
||||
|
||||
#ifndef STAT_BLOCKSIZE
|
||||
# if HAVE_STRUCT_STAT_ST_BLKSIZE
|
||||
# define STAT_BLOCKSIZE(s) ((s).st_blksize)
|
||||
# else
|
||||
# define STAT_BLOCKSIZE(s) (8 * 1024)
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <dirent.h>
|
||||
#ifndef _D_EXACT_NAMLEN
|
||||
# define _D_EXACT_NAMLEN(dp) strlen ((dp)->d_name)
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#define EXIT_TROUBLE 2
|
||||
|
||||
#include <limits.h>
|
||||
#include <locale.h>
|
||||
#include <stddef.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <string.h>
|
||||
#if ! HAVE_STRCASECOLL
|
||||
# if HAVE_STRICOLL || defined stricoll
|
||||
# define strcasecoll(a, b) stricoll (a, b)
|
||||
# else
|
||||
# define strcasecoll(a, b) strcasecmp (a, b) /* best we can do */
|
||||
# endif
|
||||
#endif
|
||||
#if ! (HAVE_STRCASECMP || defined strcasecmp)
|
||||
int strcasecmp (char const *, char const *);
|
||||
#endif
|
||||
|
||||
#include <gettext.h>
|
||||
#if ! ENABLE_NLS
|
||||
# undef textdomain
|
||||
# define textdomain(Domainname) /* empty */
|
||||
# undef bindtextdomain
|
||||
# define bindtextdomain(Domainname, Dirname) /* empty */
|
||||
#endif
|
||||
|
||||
#define _(msgid) gettext (msgid)
|
||||
#define N_(msgid) msgid
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
/* ISDIGIT differs from isdigit, as follows:
|
||||
- Its arg may be any int or unsigned int; it need not be an unsigned char.
|
||||
- It's guaranteed to evaluate its argument exactly once.
|
||||
- It's typically faster.
|
||||
POSIX 1003.1-2001 says that only '0' through '9' are digits.
|
||||
Prefer ISDIGIT to isdigit unless it's important to use the locale's
|
||||
definition of 'digit' even when the host does not conform to POSIX. */
|
||||
#define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include <signal.h>
|
||||
#if !defined SIGCHLD && defined SIGCLD
|
||||
# define SIGCHLD SIGCLD
|
||||
#endif
|
||||
|
||||
#undef MIN
|
||||
#undef MAX
|
||||
#define MIN(a, b) ((a) <= (b) ? (a) : (b))
|
||||
#define MAX(a, b) ((a) >= (b) ? (a) : (b))
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <intprops.h>
|
||||
#include "propername.h"
|
||||
#include "version.h"
|
||||
|
||||
/* Type used for fast comparison of several bytes at a time.
|
||||
This used to be uintmax_t, but changing it to size_t
|
||||
made plain 'cmp' 90% faster (GCC 4.8.1, x86). */
|
||||
|
||||
#ifndef word
|
||||
# define word size_t
|
||||
#endif
|
||||
|
||||
/* The signed integer type of a line number. Since files are read
|
||||
into main memory, ptrdiff_t should be wide enough. */
|
||||
|
||||
typedef ptrdiff_t lin;
|
||||
#define LIN_MAX PTRDIFF_MAX
|
||||
|
||||
/* The signed integer type for printing line numbers, and its printf
|
||||
length modifier. This is not simply ptrdiff_t, to cater to older
|
||||
and/or nonstandard C libraries where "l" works but "ll" and "t" do
|
||||
not, or where 'long' is too narrow and "ll" works but "t" does not. */
|
||||
|
||||
#if LIN_MAX <= LONG_MAX
|
||||
typedef long int printint;
|
||||
# define pI "l"
|
||||
#elif LIN_MAX <= LLONG_MAX
|
||||
typedef long long int printint;
|
||||
# define pI "ll"
|
||||
#else
|
||||
typedef ptrdiff_t printint;
|
||||
# define pI "t"
|
||||
#endif
|
||||
|
||||
verify (TYPE_SIGNED (lin));
|
||||
verify (TYPE_SIGNED (printint));
|
||||
verify (LIN_MAX == TYPE_MAXIMUM (lin));
|
||||
verify (LIN_MAX <= TYPE_MAXIMUM (printint));
|
||||
|
||||
/* Limit so that 2 * CONTEXT + 1 does not overflow. */
|
||||
|
||||
#define CONTEXT_MAX ((LIN_MAX - 1) / 2)
|
||||
|
||||
|
||||
/* This section contains POSIX-compliant defaults for macros
|
||||
that are meant to be overridden by hand in config.h as needed. */
|
||||
|
||||
#ifndef file_name_cmp
|
||||
# define file_name_cmp strcmp
|
||||
#endif
|
||||
|
||||
#ifndef initialize_main
|
||||
# define initialize_main(argcp, argvp)
|
||||
#endif
|
||||
|
||||
#ifndef NULL_DEVICE
|
||||
# define NULL_DEVICE "/dev/null"
|
||||
#endif
|
||||
|
||||
/* Do struct stat *S, *T describe the same special file? */
|
||||
#ifndef same_special_file
|
||||
# if HAVE_STRUCT_STAT_ST_RDEV && defined S_ISBLK && defined S_ISCHR
|
||||
# define same_special_file(s, t) \
|
||||
(((S_ISBLK ((s)->st_mode) && S_ISBLK ((t)->st_mode)) \
|
||||
|| (S_ISCHR ((s)->st_mode) && S_ISCHR ((t)->st_mode))) \
|
||||
&& (s)->st_rdev == (t)->st_rdev)
|
||||
# else
|
||||
# define same_special_file(s, t) 0
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/* Do struct stat *S, *T describe the same file? Answer -1 if unknown. */
|
||||
#ifndef same_file
|
||||
# define same_file(s, t) \
|
||||
((((s)->st_ino == (t)->st_ino) && ((s)->st_dev == (t)->st_dev)) \
|
||||
|| same_special_file (s, t))
|
||||
#endif
|
||||
|
||||
/* Do struct stat *S, *T have the same file attributes?
|
||||
|
||||
POSIX says that two files are identical if st_ino and st_dev are
|
||||
the same, but many file systems incorrectly assign the same (device,
|
||||
inode) pair to two distinct files, including:
|
||||
|
||||
- GNU/Linux NFS servers that export all local file systems as a
|
||||
single NFS file system, if a local device number (st_dev) exceeds
|
||||
255, or if a local inode number (st_ino) exceeds 16777215.
|
||||
|
||||
- Network Appliance NFS servers in snapshot directories; see
|
||||
Network Appliance bug #195.
|
||||
|
||||
- ClearCase MVFS; see bug id ATRia04618.
|
||||
|
||||
Check whether two files that purport to be the same have the same
|
||||
attributes, to work around instances of this common bug. Do not
|
||||
inspect all attributes, only attributes useful in checking for this
|
||||
bug.
|
||||
|
||||
It's possible for two distinct files on a buggy file system to have
|
||||
the same attributes, but it's not worth slowing down all
|
||||
implementations (or complicating the configuration) to cater to
|
||||
these rare cases in buggy implementations. */
|
||||
|
||||
#ifndef same_file_attributes
|
||||
# define same_file_attributes(s, t) \
|
||||
((s)->st_mode == (t)->st_mode \
|
||||
&& (s)->st_nlink == (t)->st_nlink \
|
||||
&& (s)->st_uid == (t)->st_uid \
|
||||
&& (s)->st_gid == (t)->st_gid \
|
||||
&& (s)->st_size == (t)->st_size \
|
||||
&& (s)->st_mtime == (t)->st_mtime \
|
||||
&& (s)->st_ctime == (t)->st_ctime)
|
||||
#endif
|
||||
|
||||
#define STREQ(a, b) (strcmp (a, b) == 0)
|
||||
|
||||
#ifndef FALLTHROUGH
|
||||
# if __GNUC__ < 7
|
||||
# define FALLTHROUGH ((void) 0)
|
||||
# else
|
||||
# define FALLTHROUGH __attribute__ ((__fallthrough__))
|
||||
# endif
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user