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:
2026-07-05 22:29:19 +03:00
parent 7a2b0d5160
commit b8aac3c9bc
2226 changed files with 876572 additions and 2382 deletions
@@ -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