2 * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 #include <sys/limits.h>
44 typedef long lineno_t;
48 #define MAXKEY LONG_MAX
50 /* Editing command and state. */
61 /* For convenience. */
62 struct keyword *keyword;
66 LIST_ENTRY(editcmd) next;
70 LIST_HEAD(, editcmd) dhead;
73 static int diff_geteditcmd(struct editcmd *, char *);
74 static int diff_copyln(struct editcmd *, lineno_t);
75 static int diff_ignoreln(struct editcmd *, lineno_t);
76 static void diff_write(struct editcmd *, void *, size_t);
77 static int diff_insert_edit(struct diffstart *, struct editcmd *);
78 static void diff_free(struct diffstart *);
81 diff_apply(struct stream *rd, struct stream *orig, struct stream *dest,
82 struct keyword *keyword, struct diffinfo *di, int comode)
88 int empty, error, noeol;
90 memset(&ec, 0, sizeof(ec));
97 line = stream_getln(rd, NULL);
98 while (line != NULL && strcmp(line, ".") != 0 &&
99 strcmp(line, ".+") != 0) {
101 * The server sends an empty line and then terminates
102 * with .+ for forced (and thus empty) commits.
108 line = stream_getln(rd, NULL);
111 error = diff_geteditcmd(&ec, line);
115 if (ec.cmd == EC_ADD) {
116 error = diff_copyln(&ec, ec.where);
119 for (i = 0; i < ec.count; i++) {
120 line = stream_getln(rd, &size);
123 if (comode && line[0] == '.') {
127 diff_write(&ec, line, size);
130 assert(ec.cmd == EC_DEL);
131 error = diff_copyln(&ec, ec.where - 1);
134 for (i = 0; i < ec.count; i++) {
135 line = stream_getln(orig, NULL);
141 line = stream_getln(rd, NULL);
143 if (comode && line == NULL)
145 /* If we got ".+", there's no ending newline. */
146 if (comode && strcmp(line, ".+") == 0 && !empty)
149 while ((line = stream_getln(orig, &size)) != NULL)
150 diff_write(&ec, line, size);
153 error = stream_truncate_rel(dest, -1);
155 warn("stream_truncate_rel");
163 * Reverse a diff using the same algorithm as in cvsup.
166 diff_write_reverse(struct stream *dest, struct diffstart *ds)
168 struct editcmd *ec, *nextec;
169 long editline, endline, firstoutputlinedeleted;
170 long num_added, num_deleted, startline;
173 nextec = LIST_FIRST(&ds->dhead);
176 while (nextec != NULL) {
178 nextec = LIST_NEXT(nextec, next);
184 num_deleted = ec->count;
185 num_added = num_deleted + nextec->offset - ec->offset;
186 if (num_deleted > 0) {
187 firstoutputlinedeleted = ec->key - num_deleted + 1;
188 stream_printf(dest, "d%ld %ld\n", firstoutputlinedeleted,
194 stream_printf(dest, "a%ld %ld\n", ec->key, num_added);
195 startline = ec->key - num_deleted + 1 + ec->offset;
196 endline = startline + num_added - 1;
198 /* Copy lines from original file. First ignore some. */
199 ec->editline = editline;
200 diff_ignoreln(ec, startline - 1);
201 diff_copyln(ec, endline);
202 editline = ec->editline;
209 * Insert a diff into the list sorted on key. Should perhaps use quicker
210 * algorithms than insertion sort, but do this for now.
213 diff_insert_edit(struct diffstart *ds, struct editcmd *ec)
215 struct editcmd *curec;
220 if (LIST_EMPTY(&ds->dhead)) {
221 LIST_INSERT_HEAD(&ds->dhead, ec, next);
225 /* Insertion sort based on key. */
226 LIST_FOREACH(curec, &ds->dhead, next) {
227 if (ec->key < curec->key) {
228 LIST_INSERT_BEFORE(curec, ec, next);
231 if (LIST_NEXT(curec, next) == NULL)
234 /* Just insert it after. */
235 LIST_INSERT_AFTER(curec, ec, next);
240 diff_free(struct diffstart *ds)
244 while(!LIST_EMPTY(&ds->dhead)) {
245 ec = LIST_FIRST(&ds->dhead);
246 LIST_REMOVE(ec, next);
252 * Write the reverse diff from the diff in rd, and original file into
253 * destination. This algorithm is the same as used in cvsup.
256 diff_reverse(struct stream *rd, struct stream *orig, struct stream *dest,
257 struct keyword *keyword, struct diffinfo *di)
260 struct editcmd ec, *addec, *delec;
265 memset(&ec, 0, sizeof(ec));
268 ec.keyword = keyword;
274 LIST_INIT(&ds.dhead);
276 /* Start with next since we need it. */
277 line = stream_getln(rd, NULL);
278 /* First we build up the list of diffs from input. */
279 while (line != NULL) {
280 error = diff_geteditcmd(&ec, line);
283 if (ec.cmd == EC_ADD) {
284 addec = xmalloc(sizeof(struct editcmd));
287 /* Ignore the lines we was supposed to add. */
288 for (i = 0; i < ec.count; i++) {
289 line = stream_getln(rd, NULL);
294 /* Get the next diff command if we have one. */
295 addec->key = addec->where + addec->count - offset;
297 delec->key == addec->key - addec->count) {
298 delec->key = addec->key;
299 delec->havetext = addec->havetext;
300 delec->count = addec->count;
301 diff_insert_edit(&ds, delec);
307 diff_insert_edit(&ds, delec);
310 addec->offset = offset;
311 diff_insert_edit(&ds, addec);
315 } else if (ec.cmd == EC_DEL) {
317 /* Update offset to our next. */
318 diff_insert_edit(&ds, delec);
321 delec = xmalloc(sizeof(struct editcmd));
323 delec->key = delec->where - 1 - offset;
324 delec->offset = offset;
327 /* Important to use the count we had before reset.*/
330 line = stream_getln(rd, NULL);
334 line = stream_getln(rd, NULL);
336 diff_insert_edit(&ds, delec);
340 addec = xmalloc(sizeof(struct editcmd));
341 /* Should be filesize, but we set it to max value. */
343 addec->offset = offset;
346 diff_insert_edit(&ds, addec);
348 diff_write_reverse(dest, &ds);
354 /* Get an editing command from the diff. */
356 diff_geteditcmd(struct editcmd *ec, char *line)
362 else if (line[0] == 'd')
367 ec->where = strtol(line + 1, &end, 10);
368 if (errno || ec->where < 0 || *end != ' ')
372 ec->count = strtol(line, &end, 10);
373 if (errno || ec->count <= 0 || *end != '\0')
375 if (ec->cmd == EC_ADD) {
376 if (ec->where < ec->lasta)
378 ec->lasta = ec->where + 1;
380 if (ec->where < ec->lasta || ec->where < ec->lastd)
382 ec->lasta = ec->where;
383 ec->lastd = ec->where + ec->count;
388 /* Copy lines from the original version of the file up to line "to". */
390 diff_copyln(struct editcmd *ec, lineno_t to)
395 while (ec->editline < to) {
396 line = stream_getln(ec->orig, &size);
400 diff_write(ec, line, size);
405 /* Ignore lines from the original version of the file up to line "to". */
407 diff_ignoreln(struct editcmd *ec, lineno_t to)
412 while (ec->editline < to) {
413 line = stream_getln(ec->orig, &size);
421 /* Write a new line to the file, expanding RCS keywords appropriately. */
423 diff_write(struct editcmd *ec, void *buf, size_t size)
426 char *line, *newline;
430 ret = keyword_expand(ec->keyword, ec->di, line, size,
433 stream_write(ec->dest, newline, newsize);
436 stream_write(ec->dest, buf, size);