freebsd-dev/contrib/csup/diff.c
Ulf Lilleengen 9178dc306f A few bugfixes:
- Instead of including protocol info in diffs, strip them before adding
  deltatext and take this into account when applying the diff later.
- Don't use strlen when the string in the RCS file may contain garbage. This got
  caught in the checksumming before, but was not fixed until now. Instead of
  using strlen, pass the token length when adding log and text entries to a
  delta. Add an extra length parameter to duptext() to record the token length.
- When adding new branches to a file, add them in at the tail instead of the
  head of the list to get correct ordering when writing out.
- Input stream when diffing was opened twice.
- Don't expand keywords in diffs between deltas.
2008-12-03 22:47:33 +00:00

439 lines
10 KiB
C

/*-
* Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#include <sys/limits.h>
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "diff.h"
#include "keyword.h"
#include "misc.h"
#include "stream.h"
#include "queue.h"
typedef long lineno_t;
#define EC_ADD 0
#define EC_DEL 1
#define MAXKEY LONG_MAX
/* Editing command and state. */
struct editcmd {
int cmd;
long key;
int havetext;
int offset;
lineno_t where;
lineno_t count;
lineno_t lasta;
lineno_t lastd;
lineno_t editline;
/* For convenience. */
struct keyword *keyword;
struct diffinfo *di;
struct stream *orig;
struct stream *dest;
LIST_ENTRY(editcmd) next;
};
struct diffstart {
LIST_HEAD(, editcmd) dhead;
};
static int diff_geteditcmd(struct editcmd *, char *);
static int diff_copyln(struct editcmd *, lineno_t);
static int diff_ignoreln(struct editcmd *, lineno_t);
static void diff_write(struct editcmd *, void *, size_t);
static int diff_insert_edit(struct diffstart *, struct editcmd *);
static void diff_free(struct diffstart *);
int
diff_apply(struct stream *rd, struct stream *orig, struct stream *dest,
struct keyword *keyword, struct diffinfo *di, int comode)
{
struct editcmd ec;
lineno_t i;
size_t size;
char *line;
int empty, error, noeol;
memset(&ec, 0, sizeof(ec));
empty = 0;
noeol = 0;
ec.di = di;
ec.keyword = keyword;
ec.orig = orig;
ec.dest = dest;
line = stream_getln(rd, NULL);
while (line != NULL && strcmp(line, ".") != 0 &&
strcmp(line, ".+") != 0) {
/*
* The server sends an empty line and then terminates
* with .+ for forced (and thus empty) commits.
*/
if (*line == '\0') {
if (empty)
return (-1);
empty = 1;
line = stream_getln(rd, NULL);
continue;
}
error = diff_geteditcmd(&ec, line);
if (error)
return (-1);
if (ec.cmd == EC_ADD) {
error = diff_copyln(&ec, ec.where);
if (error)
return (-1);
for (i = 0; i < ec.count; i++) {
line = stream_getln(rd, &size);
if (line == NULL)
return (-1);
if (comode && line[0] == '.') {
line++;
size--;
}
diff_write(&ec, line, size);
}
} else {
assert(ec.cmd == EC_DEL);
error = diff_copyln(&ec, ec.where - 1);
if (error)
return (-1);
for (i = 0; i < ec.count; i++) {
line = stream_getln(orig, NULL);
if (line == NULL)
return (-1);
ec.editline++;
}
}
line = stream_getln(rd, NULL);
}
if (comode && line == NULL)
return (-1);
/* If we got ".+", there's no ending newline. */
if (comode && strcmp(line, ".+") == 0 && !empty)
noeol = 1;
ec.where = 0;
while ((line = stream_getln(orig, &size)) != NULL)
diff_write(&ec, line, size);
stream_flush(dest);
if (noeol) {
error = stream_truncate_rel(dest, -1);
if (error) {
warn("stream_truncate_rel");
return (-1);
}
}
return (0);
}
/*
* Reverse a diff using the same algorithm as in cvsup.
*/
static int
diff_write_reverse(struct stream *dest, struct diffstart *ds)
{
struct editcmd *ec, *nextec;
long editline, endline, firstoutputlinedeleted;
long num_added, num_deleted, startline;
int num;
nextec = LIST_FIRST(&ds->dhead);
editline = 0;
num = 0;
while (nextec != NULL) {
ec = nextec;
nextec = LIST_NEXT(nextec, next);
if (nextec == NULL)
break;
num++;
num_deleted = 0;
if (ec->havetext)
num_deleted = ec->count;
num_added = num_deleted + nextec->offset - ec->offset;
if (num_deleted > 0) {
firstoutputlinedeleted = ec->key - num_deleted + 1;
stream_printf(dest, "d%ld %ld\n", firstoutputlinedeleted,
num_deleted);
if (num_added <= 0)
continue;
}
if (num_added > 0) {
stream_printf(dest, "a%ld %ld\n", ec->key, num_added);
startline = ec->key - num_deleted + 1 + ec->offset;
endline = startline + num_added - 1;
/* Copy lines from original file. First ignore some. */
ec->editline = editline;
diff_ignoreln(ec, startline - 1);
diff_copyln(ec, endline);
editline = ec->editline;
}
}
return (0);
}
/*
* Insert a diff into the list sorted on key. Should perhaps use quicker
* algorithms than insertion sort, but do this for now.
*/
static int
diff_insert_edit(struct diffstart *ds, struct editcmd *ec)
{
struct editcmd *curec;
if (ec == NULL)
return (0);
if (LIST_EMPTY(&ds->dhead)) {
LIST_INSERT_HEAD(&ds->dhead, ec, next);
return (0);
}
/* Insertion sort based on key. */
LIST_FOREACH(curec, &ds->dhead, next) {
if (ec->key < curec->key) {
LIST_INSERT_BEFORE(curec, ec, next);
return (0);
}
if (LIST_NEXT(curec, next) == NULL)
break;
}
/* Just insert it after. */
LIST_INSERT_AFTER(curec, ec, next);
return (0);
}
static void
diff_free(struct diffstart *ds)
{
struct editcmd *ec;
while(!LIST_EMPTY(&ds->dhead)) {
ec = LIST_FIRST(&ds->dhead);
LIST_REMOVE(ec, next);
free(ec);
}
}
/*
* Write the reverse diff from the diff in rd, and original file into
* destination. This algorithm is the same as used in cvsup.
*/
int
diff_reverse(struct stream *rd, struct stream *orig, struct stream *dest,
struct keyword *keyword, struct diffinfo *di)
{
struct diffstart ds;
struct editcmd ec, *addec, *delec;
lineno_t i;
char *line;
int error, offset;
memset(&ec, 0, sizeof(ec));
ec.orig = orig;
ec.dest = dest;
ec.keyword = keyword;
ec.di = di;
addec = NULL;
delec = NULL;
ec.havetext = 0;
offset = 0;
LIST_INIT(&ds.dhead);
/* Start with next since we need it. */
line = stream_getln(rd, NULL);
/* First we build up the list of diffs from input. */
while (line != NULL) {
error = diff_geteditcmd(&ec, line);
if (error)
break;
if (ec.cmd == EC_ADD) {
addec = xmalloc(sizeof(struct editcmd));
*addec = ec;
addec->havetext = 1;
/* Ignore the lines we was supposed to add. */
for (i = 0; i < ec.count; i++) {
line = stream_getln(rd, NULL);
if (line == NULL)
return (-1);
}
/* Get the next diff command if we have one. */
addec->key = addec->where + addec->count - offset;
if (delec != NULL &&
delec->key == addec->key - addec->count) {
delec->key = addec->key;
delec->havetext = addec->havetext;
delec->count = addec->count;
diff_insert_edit(&ds, delec);
free(addec);
delec = NULL;
addec = NULL;
} else {
if (delec != NULL) {
diff_insert_edit(&ds, delec);
}
delec = NULL;
addec->offset = offset;
diff_insert_edit(&ds, addec);
addec = NULL;
}
offset -= ec.count;
} else if (ec.cmd == EC_DEL) {
if (delec != NULL) {
/* Update offset to our next. */
diff_insert_edit(&ds, delec);
delec = NULL;
}
delec = xmalloc(sizeof(struct editcmd));
*delec = ec;
delec->key = delec->where - 1 - offset;
delec->offset = offset;
delec->count = 0;
delec->havetext = 0;
/* Important to use the count we had before reset.*/
offset += ec.count;
}
line = stream_getln(rd, NULL);
}
while (line != NULL)
line = stream_getln(rd, NULL);
if (delec != NULL) {
diff_insert_edit(&ds, delec);
delec = NULL;
}
addec = xmalloc(sizeof(struct editcmd));
/* Should be filesize, but we set it to max value. */
addec->key = MAXKEY;
addec->offset = offset;
addec->havetext = 0;
addec->count = 0;
diff_insert_edit(&ds, addec);
addec = NULL;
diff_write_reverse(dest, &ds);
diff_free(&ds);
stream_flush(dest);
return (0);
}
/* Get an editing command from the diff. */
static int
diff_geteditcmd(struct editcmd *ec, char *line)
{
char *end;
if (line[0] == 'a')
ec->cmd = EC_ADD;
else if (line[0] == 'd')
ec->cmd = EC_DEL;
else
return (-1);
errno = 0;
ec->where = strtol(line + 1, &end, 10);
if (errno || ec->where < 0 || *end != ' ')
return (-1);
line = end + 1;
errno = 0;
ec->count = strtol(line, &end, 10);
if (errno || ec->count <= 0 || *end != '\0')
return (-1);
if (ec->cmd == EC_ADD) {
if (ec->where < ec->lasta)
return (-1);
ec->lasta = ec->where + 1;
} else {
if (ec->where < ec->lasta || ec->where < ec->lastd)
return (-1);
ec->lasta = ec->where;
ec->lastd = ec->where + ec->count;
}
return (0);
}
/* Copy lines from the original version of the file up to line "to". */
static int
diff_copyln(struct editcmd *ec, lineno_t to)
{
size_t size;
char *line;
while (ec->editline < to) {
line = stream_getln(ec->orig, &size);
if (line == NULL)
return (-1);
ec->editline++;
diff_write(ec, line, size);
}
return (0);
}
/* Ignore lines from the original version of the file up to line "to". */
static int
diff_ignoreln(struct editcmd *ec, lineno_t to)
{
size_t size;
char *line;
while (ec->editline < to) {
line = stream_getln(ec->orig, &size);
if (line == NULL)
return (-1);
ec->editline++;
}
return (0);
}
/* Write a new line to the file, expanding RCS keywords appropriately. */
static void
diff_write(struct editcmd *ec, void *buf, size_t size)
{
size_t newsize;
char *line, *newline;
int ret;
line = buf;
ret = keyword_expand(ec->keyword, ec->di, line, size,
&newline, &newsize);
if (ret) {
stream_write(ec->dest, newline, newsize);
free(newline);
} else {
stream_write(ec->dest, buf, size);
}
}