git/xdiff/xutils.c
Junio C Hamano 3b5ef0e216 xutils: Fix xdl_recmatch() on incomplete lines
Thell Fowler noticed that various "ignore whitespace" options to git diff
do not work well on an incomplete line.

The loop control of the function responsible for these bugs was extremely
difficult to follow.  This patch restructures the loops for three variants
of "ignore whitespace" logic.

The basic idea of the re-written logic is:

 - A loop runs while the characters from both strings we are looking at
   match.  We declare unmatch immediately when we find something that does
   not match and return false from the function.  We break out of the loop
   if we ran out of either side of the string.

   The way we skip spaces inside this loop varies depending on the style
   of ignoring whitespaces.

 - After the above loop breaks, we know that the parts of the strings we
   inspected so far match, ignoring the whitespaces.  The lines can match
   only if the remainder consists of nothing but whitespaces.  This part
   of the logic is shared across all three styles.

The new code is more obvious and should be much easier to follow.

Tested-by: Thell Fowler <git@tbfowler.name>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2009-08-23 14:38:43 -07:00

403 lines
8.1 KiB
C

/*
* LibXDiff by Davide Libenzi ( File Differential Library )
* Copyright (C) 2003 Davide Libenzi
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Davide Libenzi <davidel@xmailserver.org>
*
*/
#include "xinclude.h"
#define XDL_GUESS_NLINES 256
long xdl_bogosqrt(long n) {
long i;
/*
* Classical integer square root approximation using shifts.
*/
for (i = 1; n > 0; n >>= 2)
i <<= 1;
return i;
}
int xdl_emit_diffrec(char const *rec, long size, char const *pre, long psize,
xdemitcb_t *ecb) {
int i = 2;
mmbuffer_t mb[3];
mb[0].ptr = (char *) pre;
mb[0].size = psize;
mb[1].ptr = (char *) rec;
mb[1].size = size;
if (size > 0 && rec[size - 1] != '\n') {
mb[2].ptr = (char *) "\n\\ No newline at end of file\n";
mb[2].size = strlen(mb[2].ptr);
i++;
}
if (ecb->outf(ecb->priv, mb, i) < 0) {
return -1;
}
return 0;
}
void *xdl_mmfile_first(mmfile_t *mmf, long *size)
{
*size = mmf->size;
return mmf->ptr;
}
void *xdl_mmfile_next(mmfile_t *mmf, long *size)
{
return NULL;
}
long xdl_mmfile_size(mmfile_t *mmf)
{
return mmf->size;
}
int xdl_cha_init(chastore_t *cha, long isize, long icount) {
cha->head = cha->tail = NULL;
cha->isize = isize;
cha->nsize = icount * isize;
cha->ancur = cha->sncur = NULL;
cha->scurr = 0;
return 0;
}
void xdl_cha_free(chastore_t *cha) {
chanode_t *cur, *tmp;
for (cur = cha->head; (tmp = cur) != NULL;) {
cur = cur->next;
xdl_free(tmp);
}
}
void *xdl_cha_alloc(chastore_t *cha) {
chanode_t *ancur;
void *data;
if (!(ancur = cha->ancur) || ancur->icurr == cha->nsize) {
if (!(ancur = (chanode_t *) xdl_malloc(sizeof(chanode_t) + cha->nsize))) {
return NULL;
}
ancur->icurr = 0;
ancur->next = NULL;
if (cha->tail)
cha->tail->next = ancur;
if (!cha->head)
cha->head = ancur;
cha->tail = ancur;
cha->ancur = ancur;
}
data = (char *) ancur + sizeof(chanode_t) + ancur->icurr;
ancur->icurr += cha->isize;
return data;
}
void *xdl_cha_first(chastore_t *cha) {
chanode_t *sncur;
if (!(cha->sncur = sncur = cha->head))
return NULL;
cha->scurr = 0;
return (char *) sncur + sizeof(chanode_t) + cha->scurr;
}
void *xdl_cha_next(chastore_t *cha) {
chanode_t *sncur;
if (!(sncur = cha->sncur))
return NULL;
cha->scurr += cha->isize;
if (cha->scurr == sncur->icurr) {
if (!(sncur = cha->sncur = sncur->next))
return NULL;
cha->scurr = 0;
}
return (char *) sncur + sizeof(chanode_t) + cha->scurr;
}
long xdl_guess_lines(mmfile_t *mf) {
long nl = 0, size, tsize = 0;
char const *data, *cur, *top;
if ((cur = data = xdl_mmfile_first(mf, &size)) != NULL) {
for (top = data + size; nl < XDL_GUESS_NLINES;) {
if (cur >= top) {
tsize += (long) (cur - data);
if (!(cur = data = xdl_mmfile_next(mf, &size)))
break;
top = data + size;
}
nl++;
if (!(cur = memchr(cur, '\n', top - cur)))
cur = top;
else
cur++;
}
tsize += (long) (cur - data);
}
if (nl && tsize)
nl = xdl_mmfile_size(mf) / (tsize / nl);
return nl + 1;
}
int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
{
int i1, i2;
if (!(flags & XDF_WHITESPACE_FLAGS))
return s1 == s2 && !memcmp(l1, l2, s1);
i1 = 0;
i2 = 0;
/*
* -w matches everything that matches with -b, and -b in turn
* matches everything that matches with --ignore-space-at-eol.
*
* Each flavor of ignoring needs different logic to skip whitespaces
* while we have both sides to compare.
*/
if (flags & XDF_IGNORE_WHITESPACE) {
goto skip_ws;
while (i1 < s1 && i2 < s2) {
if (l1[i1++] != l2[i2++])
return 0;
skip_ws:
while (i1 < s1 && isspace(l1[i1]))
i1++;
while (i2 < s2 && isspace(l2[i2]))
i2++;
}
} else if (flags & XDF_IGNORE_WHITESPACE_CHANGE) {
while (i1 < s1 && i2 < s2) {
if (isspace(l1[i1]) && isspace(l2[i2])) {
/* Skip matching spaces and try again */
while (i1 < s1 && isspace(l1[i1]))
i1++;
while (i2 < s2 && isspace(l2[i2]))
i2++;
continue;
}
if (l1[i1++] != l2[i2++])
return 0;
}
} else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL) {
while (i1 < s1 && i2 < s2 && l1[i1++] == l2[i2++])
; /* keep going */
}
/*
* After running out of one side, the remaining side must have
* nothing but whitespace for the lines to match. Note that
* ignore-whitespace-at-eol case may break out of the loop
* while there still are characters remaining on both lines.
*/
if (i1 < s1) {
while (i1 < s1 && isspace(l1[i1]))
i1++;
if (s1 != i1)
return 0;
}
if (i2 < s2) {
while (i2 < s2 && isspace(l2[i2]))
i2++;
return (s2 == i2);
}
return 1;
}
static unsigned long xdl_hash_record_with_whitespace(char const **data,
char const *top, long flags) {
unsigned long ha = 5381;
char const *ptr = *data;
for (; ptr < top && *ptr != '\n'; ptr++) {
if (isspace(*ptr)) {
const char *ptr2 = ptr;
int at_eol;
while (ptr + 1 < top && isspace(ptr[1])
&& ptr[1] != '\n')
ptr++;
at_eol = (top <= ptr + 1 || ptr[1] == '\n');
if (flags & XDF_IGNORE_WHITESPACE)
; /* already handled */
else if (flags & XDF_IGNORE_WHITESPACE_CHANGE
&& !at_eol) {
ha += (ha << 5);
ha ^= (unsigned long) ' ';
}
else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL
&& !at_eol) {
while (ptr2 != ptr + 1) {
ha += (ha << 5);
ha ^= (unsigned long) *ptr2;
ptr2++;
}
}
continue;
}
ha += (ha << 5);
ha ^= (unsigned long) *ptr;
}
*data = ptr < top ? ptr + 1: ptr;
return ha;
}
unsigned long xdl_hash_record(char const **data, char const *top, long flags) {
unsigned long ha = 5381;
char const *ptr = *data;
if (flags & XDF_WHITESPACE_FLAGS)
return xdl_hash_record_with_whitespace(data, top, flags);
for (; ptr < top && *ptr != '\n'; ptr++) {
ha += (ha << 5);
ha ^= (unsigned long) *ptr;
}
*data = ptr < top ? ptr + 1: ptr;
return ha;
}
unsigned int xdl_hashbits(unsigned int size) {
unsigned int val = 1, bits = 0;
for (; val < size && bits < CHAR_BIT * sizeof(unsigned int); val <<= 1, bits++);
return bits ? bits: 1;
}
int xdl_num_out(char *out, long val) {
char *ptr, *str = out;
char buf[32];
ptr = buf + sizeof(buf) - 1;
*ptr = '\0';
if (val < 0) {
*--ptr = '-';
val = -val;
}
for (; val && ptr > buf; val /= 10)
*--ptr = "0123456789"[val % 10];
if (*ptr)
for (; *ptr; ptr++, str++)
*str = *ptr;
else
*str++ = '0';
*str = '\0';
return str - out;
}
long xdl_atol(char const *str, char const **next) {
long val, base;
char const *top;
for (top = str; XDL_ISDIGIT(*top); top++);
if (next)
*next = top;
for (val = 0, base = 1, top--; top >= str; top--, base *= 10)
val += base * (long)(*top - '0');
return val;
}
int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2,
const char *func, long funclen, xdemitcb_t *ecb) {
int nb = 0;
mmbuffer_t mb;
char buf[128];
memcpy(buf, "@@ -", 4);
nb += 4;
nb += xdl_num_out(buf + nb, c1 ? s1: s1 - 1);
if (c1 != 1) {
memcpy(buf + nb, ",", 1);
nb += 1;
nb += xdl_num_out(buf + nb, c1);
}
memcpy(buf + nb, " +", 2);
nb += 2;
nb += xdl_num_out(buf + nb, c2 ? s2: s2 - 1);
if (c2 != 1) {
memcpy(buf + nb, ",", 1);
nb += 1;
nb += xdl_num_out(buf + nb, c2);
}
memcpy(buf + nb, " @@", 3);
nb += 3;
if (func && funclen) {
buf[nb++] = ' ';
if (funclen > sizeof(buf) - nb - 1)
funclen = sizeof(buf) - nb - 1;
memcpy(buf + nb, func, funclen);
nb += funclen;
}
buf[nb++] = '\n';
mb.ptr = buf;
mb.size = nb;
if (ecb->outf(ecb->priv, &mb, 1) < 0)
return -1;
return 0;
}