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
43 typedef long lineno_t;
47 #define MAXKEY LONG_MAX
49 /* Editing command and state. */
60 /* For convenience. */
61 struct keyword *keyword;
65 LIST_ENTRY(editcmd) next;
69 LIST_HEAD(, editcmd) dhead;
72 static int diff_geteditcmd(struct editcmd *, char *);
73 static int diff_copyln(struct editcmd *, lineno_t);
74 static int diff_ignoreln(struct editcmd *, lineno_t);
75 static void diff_write(struct editcmd *, void *, size_t);
76 static int diff_insert_edit(struct diffstart *, struct editcmd *);
77 static void diff_free(struct diffstart *);
80 diff_apply(struct stream *rd, struct stream *orig, struct stream *dest,
81 struct keyword *keyword, struct diffinfo *di, int comode)
87 int empty, error, noeol;
89 memset(&ec, 0, sizeof(ec));
96 line = stream_getln(rd, NULL);
97 while (line != NULL && strcmp(line, ".") != 0 &&
98 strcmp(line, ".+") != 0) {
100 * The server sends an empty line and then terminates
101 * with .+ for forced (and thus empty) commits.
107 line = stream_getln(rd, NULL);
110 error = diff_geteditcmd(&ec, line);
114 if (ec.cmd == EC_ADD) {
115 error = diff_copyln(&ec, ec.where);
118 for (i = 0; i < ec.count; i++) {
119 line = stream_getln(rd, &size);
122 if (comode && line[0] == '.') {
126 diff_write(&ec, line, size);
129 assert(ec.cmd == EC_DEL);
130 error = diff_copyln(&ec, ec.where - 1);
133 for (i = 0; i < ec.count; i++) {
134 line = stream_getln(orig, NULL);
140 line = stream_getln(rd, NULL);
142 if (comode && line == NULL)
144 /* If we got ".+", there's no ending newline. */
145 if (comode && strcmp(line, ".+") == 0 && !empty)
148 while ((line = stream_getln(orig, &size)) != NULL)
149 diff_write(&ec, line, size);
152 error = stream_truncate_rel(dest, -1);
154 warn("stream_truncate_rel");
162 * Reverse a diff using the same algorithm as in cvsup.
165 diff_write_reverse(struct stream *dest, struct diffstart *ds)
167 struct editcmd *ec, *nextec;
168 long editline, endline, firstoutputlinedeleted;
169 long num_added, num_deleted, startline;
172 nextec = LIST_FIRST(&ds->dhead);
175 while (nextec != NULL) {
177 nextec = LIST_NEXT(nextec, next);
183 num_deleted = ec->count;
184 num_added = num_deleted + nextec->offset - ec->offset;
185 if (num_deleted > 0) {
186 firstoutputlinedeleted = ec->key - num_deleted + 1;
187 stream_printf(dest, "d%ld %ld\n", firstoutputlinedeleted,
193 stream_printf(dest, "a%ld %ld\n", ec->key, num_added);
194 startline = ec->key - num_deleted + 1 + ec->offset;
195 endline = startline + num_added - 1;
197 /* Copy lines from original file. First ignore some. */
198 ec->editline = editline;
199 diff_ignoreln(ec, startline - 1);
200 diff_copyln(ec, endline);
201 editline = ec->editline;
208 * Insert a diff into the list sorted on key. Should perhaps use quicker
209 * algorithms than insertion sort, but do this for now.
212 diff_insert_edit(struct diffstart *ds, struct editcmd *ec)
214 struct editcmd *curec;
219 if (LIST_EMPTY(&ds->dhead)) {
220 LIST_INSERT_HEAD(&ds->dhead, ec, next);
224 /* Insertion sort based on key. */
225 LIST_FOREACH(curec, &ds->dhead, next) {
226 if (ec->key < curec->key) {
227 LIST_INSERT_BEFORE(curec, ec, next);
230 if (LIST_NEXT(curec, next) == NULL)
233 /* Just insert it after. */
234 LIST_INSERT_AFTER(curec, ec, next);
239 diff_free(struct diffstart *ds)
243 while(!LIST_EMPTY(&ds->dhead)) {
244 ec = LIST_FIRST(&ds->dhead);
245 LIST_REMOVE(ec, next);
251 * Write the reverse diff from the diff in rd, and original file into
252 * destination. This algorithm is the same as used in cvsup.
255 diff_reverse(struct stream *rd, struct stream *orig, struct stream *dest,
256 struct keyword *keyword, struct diffinfo *di)
259 struct editcmd ec, *addec, *delec;
264 memset(&ec, 0, sizeof(ec));
267 ec.keyword = keyword;
273 LIST_INIT(&ds.dhead);
275 /* Start with next since we need it. */
276 line = stream_getln(rd, NULL);
277 /* First we build up the list of diffs from input. */
278 while (line != NULL) {
279 error = diff_geteditcmd(&ec, line);
282 if (ec.cmd == EC_ADD) {
283 addec = xmalloc(sizeof(struct editcmd));
286 /* Ignore the lines we was supposed to add. */
287 for (i = 0; i < ec.count; i++) {
288 line = stream_getln(rd, NULL);
293 /* Get the next diff command if we have one. */
294 addec->key = addec->where + addec->count - offset;
296 delec->key == addec->key - addec->count) {
297 delec->key = addec->key;
298 delec->havetext = addec->havetext;
299 delec->count = addec->count;
300 diff_insert_edit(&ds, delec);
306 diff_insert_edit(&ds, delec);
309 addec->offset = offset;
310 diff_insert_edit(&ds, addec);
314 } else if (ec.cmd == EC_DEL) {
316 /* Update offset to our next. */
317 diff_insert_edit(&ds, delec);
320 delec = xmalloc(sizeof(struct editcmd));
322 delec->key = delec->where - 1 - offset;
323 delec->offset = offset;
326 /* Important to use the count we had before reset.*/
329 line = stream_getln(rd, NULL);
333 line = stream_getln(rd, NULL);
335 diff_insert_edit(&ds, delec);
339 addec = xmalloc(sizeof(struct editcmd));
340 /* Should be filesize, but we set it to max value. */
342 addec->offset = offset;
345 diff_insert_edit(&ds, addec);
347 diff_write_reverse(dest, &ds);
353 /* Get an editing command from the diff. */
355 diff_geteditcmd(struct editcmd *ec, char *line)
361 else if (line[0] == 'd')
366 ec->where = strtol(line + 1, &end, 10);
367 if (errno || ec->where < 0 || *end != ' ')
371 ec->count = strtol(line, &end, 10);
372 if (errno || ec->count <= 0 || *end != '\0')
374 if (ec->cmd == EC_ADD) {
375 if (ec->where < ec->lasta)
377 ec->lasta = ec->where + 1;
379 if (ec->where < ec->lasta || ec->where < ec->lastd)
381 ec->lasta = ec->where;
382 ec->lastd = ec->where + ec->count;
387 /* Copy lines from the original version of the file up to line "to". */
389 diff_copyln(struct editcmd *ec, lineno_t to)
394 while (ec->editline < to) {
395 line = stream_getln(ec->orig, &size);
399 diff_write(ec, line, size);
404 /* Ignore lines from the original version of the file up to line "to". */
406 diff_ignoreln(struct editcmd *ec, lineno_t to)
411 while (ec->editline < to) {
412 line = stream_getln(ec->orig, &size);
420 /* Write a new line to the file, expanding RCS keywords appropriately. */
422 diff_write(struct editcmd *ec, void *buf, size_t size)
425 char *line, *newline;
429 ret = keyword_expand(ec->keyword, ec->di, line, size,
432 stream_write(ec->dest, newline, newsize);
435 stream_write(ec->dest, buf, size);