diff options
Diffstat (limited to 'usr.bin/csup/rcsfile.c')
-rw-r--r-- | usr.bin/csup/rcsfile.c | 1412 |
1 files changed, 1412 insertions, 0 deletions
diff --git a/usr.bin/csup/rcsfile.c b/usr.bin/csup/rcsfile.c new file mode 100644 index 0000000..1f3ede1 --- /dev/null +++ b/usr.bin/csup/rcsfile.c @@ -0,0 +1,1412 @@ +/*- + * Copyright (c) 2007-2009, Ulf Lilleengen <lulf@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 <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 "proto.h" +#include "queue.h" +#include "rcsfile.h" +#include "rcsparse.h" +#include "stream.h" + +#define BUF_SIZE_DEFAULT 128 + +/* + * RCS parser library. This is the part of the library that handles the + * importing, editing and exporting of RCS files. It currently supports only the + * part of the RCS file specification that is needed for csup (for instance, + * newphrases are not supported), and assumes that you can store the whole RCS + * file in memory. + */ + +/* + * Linked list for string tokens. + */ +struct string { + char *str; + STAILQ_ENTRY(string) string_next; +}; + +/* + * Linked list of tags and revision numbers, in the RCS file header. + */ +struct tag { + char *tag; + char *revnum; + STAILQ_ENTRY(tag) tag_next; +}; + +/* + * A RCS delta. The delta is identified by a revision number, and contains the + * most important RCS attributes that is needed by csup. It also contains + * pointers to other nodes in the RCS file delta structure. + */ +struct delta { + char *revdate; + char *revnum; + char *author; + char *state; + struct buf *log; + struct buf *text; + int placeholder; + struct delta *diffbase; + struct delta *prev; + + LIST_ENTRY(delta) delta_next; + STAILQ_ENTRY(delta) delta_prev; + LIST_ENTRY(delta) table_next; + STAILQ_ENTRY(delta) stack_next; + LIST_HEAD(, branch) branchlist; + LIST_ENTRY(delta) branch_next_date; +}; + +/* + * A branch data structure containing information about deltas in the branch as + * well as a base revision number. + */ +struct branch { + char *revnum; + LIST_HEAD(, delta) deltalist; /* Next delta in our branch. */ + LIST_ENTRY(branch) branch_next; +}; + +/* + * The rcsfile structure is the "main" structure of the RCS parser library. It + * contains administrative data as well as pointers to the deltas within the + * file. + */ +struct rcsfile { + char *name; + char *head; + char *branch; /* Default branch. */ + char *cvsroot; + char *colltag; + STAILQ_HEAD(, string) accesslist; + STAILQ_HEAD(, tag) taglist; + int strictlock; + char *comment; + int expand; + int ro; + struct branch *trunk; /* The tip delta. */ + + LIST_HEAD(, delta) deltatable; + + char *desc; +}; + +static void rcsfile_freedelta(struct delta *); +static void rcsfile_insertdelta(struct branch *, struct delta *, + int); +static struct delta *rcsfile_createdelta(char *); +static int rcsfile_write_deltatext(struct rcsfile *, + struct stream *); +static int rcsfile_puttext(struct rcsfile *, struct stream *, + struct delta *, struct delta *); +static struct branch *rcsfile_getbranch(struct rcsfile *, char *); +static void rcsfile_insertsorteddelta(struct rcsfile *, + struct delta *); +static struct stream *rcsfile_getdeltatext(struct rcsfile *, struct delta *, + struct buf **); +static int rcsdelta_writestring(char *, size_t, struct stream *); +static void rcsdelta_insertbranch(struct delta *, struct branch *); + +/* Space formatting of RCS file. */ +const char *head_space = "\t"; +const char *branch_space = "\t"; +const char *tag_space = "\t"; +const char *date_space = "\t"; +const char *auth_space = "\t"; +const char *state_space = "\t"; +const char *next_space = "\t"; +const char *branches_space = "\t"; +const char *comment_space ="\t"; +const char *expand_space = "\t"; + +void print_stream(struct stream *); + +/* Print the contents of a stream, for debugging. */ +void +print_stream(struct stream *s) +{ + char *line; + + line = stream_getln(s, NULL); + while (line != NULL) { + lprintf(-1, "%s\n", line); + line = stream_getln(s, NULL); + } + lprintf(-1, "\n"); +} + +/* + * Parse rcsfile from path and return a pointer to it. + */ +struct rcsfile * +rcsfile_frompath(char *path, char *name, char *cvsroot, char *colltag, int ro) +{ + struct rcsfile *rf; + FILE *infp; + int error; + + if (path == NULL || name == NULL || cvsroot == NULL || colltag == NULL) + return (NULL); + + rf = xmalloc(sizeof(struct rcsfile)); + rf->name = xstrdup(name); + rf->cvsroot = xstrdup(cvsroot); + rf->colltag = xstrdup(colltag); + + /* Initialize head branch. */ + rf->trunk = xmalloc(sizeof(struct branch)); + rf->trunk->revnum = xstrdup("1"); + LIST_INIT(&rf->trunk->deltalist); + /* Initialize delta list. */ + LIST_INIT(&rf->deltatable); + /* Initialize tag list. */ + STAILQ_INIT(&rf->taglist); + /* Initialize accesslist. */ + STAILQ_INIT(&rf->accesslist); + + /* Initialize all fields. */ + rf->head = NULL; + rf->branch = NULL; + rf->strictlock = 0; + rf->comment = NULL; + rf->expand = EXPAND_DEFAULT; + rf->desc = NULL; + rf->ro = ro; + + infp = fopen(path, "r"); + if (infp == NULL) { + lprintf(-1, "Cannot open \"%s\": %s\n", path, strerror(errno)); + rcsfile_free(rf); + return (NULL); + } + error = rcsparse_run(rf, infp, ro); + fclose(infp); + if (error) { + lprintf(-1, "Error parsing \"%s\"\n", name); + rcsfile_free(rf); + return (NULL); + } + return (rf); +} + +/* + * Write content of rcsfile to server. Assumes we have a complete RCS file + * loaded. + */ +int +rcsfile_send_details(struct rcsfile *rf, struct stream *wr) +{ + struct delta *d; + struct tag *t; + const char *keyword; + int error; + + assert(rf != NULL); + + error = proto_printf(wr, "V %s\n", rf->name); + if (error) + return(error); + + /* Write default branch. */ + if (rf->branch == NULL) + error = proto_printf(wr, "b\n"); + else + error = proto_printf(wr, "B %s\n", rf->branch); + if (error) + return(error); + + /* Write deltas to server. */ + error = proto_printf(wr, "D\n"); + if (error) + return(error); + + LIST_FOREACH(d, &rf->deltatable, table_next) { + error = proto_printf(wr, "%s %s\n", d->revnum, d->revdate); + if (error) + return(error); + } + error = proto_printf(wr, ".\n"); + + if (error) + return(error); + /* Write expand. */ + if (rf->expand != EXPAND_DEFAULT) { + keyword = keyword_encode_expand(rf->expand); + if (keyword != NULL) { + error = proto_printf(wr, "E %s\n", + keyword_encode_expand(rf->expand)); + if (error) + return(error); + } + } + + /* Write tags to server. */ + error = proto_printf(wr, "T\n"); + if (error) + return(error); + STAILQ_FOREACH(t, &rf->taglist, tag_next) { + error = proto_printf(wr, "%s %s\n", t->tag, t->revnum); + if (error) + return(error); + } + error = proto_printf(wr, ".\n"); + if (error) + return(error); + error = proto_printf(wr, ".\n"); + return (error); +} + +/* + * Write a RCS file to disk represented by the destination stream. Keep track of + * deltas with a stack and an inverted stack. + */ +int +rcsfile_write(struct rcsfile *rf, struct stream *dest) +{ + STAILQ_HEAD(, delta) deltastack; + STAILQ_HEAD(, delta) deltalist_inverted; + struct tag *t; + struct branch *b; + struct delta *d, *d_tmp, *d_next; + int error; + + /* First write head. */ + d = LIST_FIRST(&rf->trunk->deltalist); + if (stream_printf(dest, "head%s%s;\n", head_space, d->revnum) < 0) + return (-1); + + /* Write branch, if we have. */ + if (rf->branch != NULL) { + if (stream_printf(dest, "branch%s%s;\n", branch_space, + rf->branch) < 0) + return (-1); + } + + /* Write access. */ + if (stream_printf(dest, "access") < 0) + return (-1); +#if 0 + if (!STAILQ_EMPTY(&rf->accesslist)) { + /* + * XXX: Write out access. This doesn't seem to be necessary for + * the time being. + */ + } +#endif + if (stream_printf(dest, ";\n") < 0) + return (-1); + + /* Write out taglist. */ + if (stream_printf(dest, "symbols") < 0) + return (-1); + if (!STAILQ_EMPTY(&rf->taglist)) { + STAILQ_FOREACH(t, &rf->taglist, tag_next) { + if (stream_printf(dest, "\n%s%s:%s", tag_space, t->tag, + t->revnum) < 0) + return (-1); + } + } + + /* Write out locks and strict. */ + if (stream_printf(dest, ";\nlocks;") < 0) + return (-1); + if (rf->strictlock) { + if (stream_printf(dest, " strict;") < 0) + return (-1); + } + if (stream_printf(dest, "\n") < 0) + return (-1); + + /* Write out the comment. */ + if (rf->comment != NULL) { + if (stream_printf(dest, "comment%s%s;\n", comment_space, + rf->comment) < 0) + return (-1); + } + if (rf->expand != EXPAND_DEFAULT) { + if (stream_printf(dest, "expand%s@%s@;\n", expand_space, + keyword_encode_expand(rf->expand)) < 0) + return (-1); + } + + if (stream_printf(dest, "\n\n") < 0) + return (-1); + + /* + * Write out deltas. We use a stack where we push the appropriate deltas + * that is to be written out during the loop. + */ + STAILQ_INIT(&deltastack); + d = LIST_FIRST(&rf->trunk->deltalist); + STAILQ_INSERT_HEAD(&deltastack, d, stack_next); + while (!STAILQ_EMPTY(&deltastack)) { + d = STAILQ_FIRST(&deltastack); + STAILQ_REMOVE_HEAD(&deltastack, stack_next); + /* Do not write out placeholders just to be safe. */ + if (d->placeholder) + continue; + if (stream_printf(dest, "%s\n", d->revnum) < 0) + return (-1); + if (stream_printf(dest, "date%s%s;%sauthor %s;%sstate", + date_space, d->revdate, auth_space, d->author, + state_space) < 0) + return (-1); + if (d->state != NULL) { + if (stream_printf(dest, " %s", d->state) < 0) + return (-1); + } + if (stream_printf(dest, ";\nbranches") < 0) + return (-1); + /* + * Write out our branches. Add them to a reversed list for use + * later when we write out the text. + */ + STAILQ_INIT(&deltalist_inverted); + LIST_FOREACH(b, &d->branchlist, branch_next) { + d_tmp = LIST_FIRST(&b->deltalist); + STAILQ_INSERT_HEAD(&deltalist_inverted, d_tmp, delta_prev); + STAILQ_INSERT_HEAD(&deltastack, d_tmp, stack_next); + } + + /* Push branch heads on stack. */ + STAILQ_FOREACH(d_tmp, &deltalist_inverted, delta_prev) { + if (d_tmp == NULL) { + lprintf(2, "Empty branch!\n"); + return (-1); + } + if (stream_printf(dest, "\n%s%s", branches_space, + d_tmp->revnum) < 0) + return (-1); + } + + if (stream_printf(dest, ";\nnext%s", next_space) < 0) + return (-1); + /* Push next delta on stack. */ + d_next = LIST_NEXT(d, delta_next); + if (d_next != NULL) { + if (stream_printf(dest, "%s", d_next->revnum) < 0) + return (-1); + STAILQ_INSERT_HEAD(&deltastack, d_next, stack_next); + } + if (stream_printf(dest, ";\n\n") < 0) + return (-1); + } + /* Write out desc. */ + if (stream_printf(dest, "\ndesc\n@@") < 0) + return (-1); + d = LIST_FIRST(&rf->trunk->deltalist); + + /* Write out deltatexts. */ + error = rcsfile_write_deltatext(rf, dest); + if (stream_printf(dest, "\n") < 0) + return (-1); + return (error); +} + +/* + * Write out deltatexts of a delta and it's subbranches recursively. + */ +int +rcsfile_write_deltatext(struct rcsfile *rf, struct stream *dest) +{ + STAILQ_HEAD(, delta) deltastack; + LIST_HEAD(, delta) branchlist_datesorted; + struct delta *d, *d_tmp, *d_next, *d_tmp2, *d_tmp3; + struct stream *in; + struct branch *b; + size_t size; + char *line; + int error; + + error = 0; + STAILQ_INIT(&deltastack); + d = LIST_FIRST(&rf->trunk->deltalist); + d->prev = NULL; + STAILQ_INSERT_HEAD(&deltastack, d, stack_next); + while (!STAILQ_EMPTY(&deltastack)) { + d = STAILQ_FIRST(&deltastack); + STAILQ_REMOVE_HEAD(&deltastack, stack_next); + /* Do not write out placeholders just to be safe. */ + if (d->placeholder) + return (0); + if (stream_printf(dest, "\n\n\n%s\n", d->revnum) < 0) + return (-1); + if (stream_printf(dest, "log\n@") < 0) + return (-1); + in = stream_open_buf(d->log); + line = stream_getln(in, &size); + while (line != NULL) { + if (stream_write(dest, line, size) == -1) + return (-1); + line = stream_getln(in, &size); + } + stream_close(in); + if (stream_printf(dest, "@\ntext\n@") < 0) + return (-1); + error = rcsfile_puttext(rf, dest, d, d->prev); + if (error) + return (error); + if (stream_printf(dest, "@") < 0) + return (-1); + + LIST_INIT(&branchlist_datesorted); + d_next = LIST_NEXT(d, delta_next); + if (d_next != NULL) { + d_next->prev = d; + /* + * If it's trunk, treat it like the oldest, if not treat + * it like a child. + */ + if (rcsrev_istrunk(d_next->revnum)) + STAILQ_INSERT_HEAD(&deltastack, d_next, + stack_next); + else + LIST_INSERT_HEAD(&branchlist_datesorted, d_next, + branch_next_date); + } + + /* + * First, we need to sort our branches based on their date to + * take into account some self-hacked RCS files. + */ + LIST_FOREACH(b, &d->branchlist, branch_next) { + d_tmp = LIST_FIRST(&b->deltalist); + if (LIST_EMPTY(&branchlist_datesorted)) { + LIST_INSERT_HEAD(&branchlist_datesorted, d_tmp, + branch_next_date); + continue; + } + + d_tmp2 = LIST_FIRST(&branchlist_datesorted); + if (rcsnum_cmp(d_tmp->revdate, d_tmp2->revdate) <= 0) { + LIST_INSERT_BEFORE(d_tmp2, d_tmp, + branch_next_date); + continue; + } + while ((d_tmp3 = LIST_NEXT(d_tmp2, branch_next_date)) + != NULL) { + if (rcsnum_cmp(d_tmp->revdate, d_tmp3->revdate) + <= 0) + break; + d_tmp2 = d_tmp3; + } + LIST_INSERT_AFTER(d_tmp2, d_tmp, branch_next_date); + } + /* + * Invert the deltalist of a branch, since we're writing them + * the opposite way. + */ + LIST_FOREACH(d_tmp, &branchlist_datesorted, branch_next_date) { + d_tmp->prev = d; + STAILQ_INSERT_HEAD(&deltastack, d_tmp, stack_next); + } + } + return (0); +} + +/* + * Generates text given a delta and a diffbase. + */ +static int +rcsfile_puttext(struct rcsfile *rf, struct stream *dest, struct delta *d, + struct delta *diffbase) +{ + struct stream *in, *rd, *orig; + struct keyword *k; + struct diffinfo dibuf, *di; + struct buf *b; + size_t size; + char *line; + int error; + + di = &dibuf; + b = NULL; + error = 0; + + /* Write if the diffbase is the previous */ + if (d->diffbase == diffbase) { + + /* Write out the text. */ + in = stream_open_buf(d->text); + line = stream_getln(in, &size); + while (line != NULL) { + if (stream_write(dest, line, size) == -1) { + error = -1; + goto cleanup; + } + line = stream_getln(in, &size); + } + stream_close(in); + /* We need to apply diff to produce text, this is probably HEAD. */ + } else if (diffbase == NULL) { + /* Apply diff. */ + orig = rcsfile_getdeltatext(rf, d, &b); + if (orig == NULL) { + error = -1; + goto cleanup; + } + line = stream_getln(orig, &size); + while (line != NULL) { + if (stream_write(dest, line, size) == -1) { + error = -1; + goto cleanup; + } + line = stream_getln(orig, &size); + } + stream_close(orig); + /* + * A new head was probably added, and now the previous HEAD must be + * changed to include the diff instead. + */ + } else if (diffbase->diffbase == d) { + /* Get reverse diff. */ + orig = rcsfile_getdeltatext(rf, d, &b); + if (orig == NULL) { + error = -1; + goto cleanup; + } + di->di_rcsfile = rf->name; + di->di_cvsroot = rf->cvsroot; + di->di_revnum = d->revnum; + di->di_revdate = d->revdate; + di->di_author = d->author; + di->di_tag = rf->colltag; + di->di_state = d->state; + di->di_expand = EXPAND_OLD; + k = keyword_new(); + + rd = stream_open_buf(diffbase->text); + error = diff_reverse(rd, orig, dest, k, di); + if (error) { + lprintf(-1, "Error applying reverse diff: %d\n", error); + goto cleanup; + } + keyword_free(k); + stream_close(rd); + stream_close(orig); + } +cleanup: + if (b != NULL) + buf_free(b); + return (error); +} + +/* + * Return a stream with an applied diff of a delta. + * XXX: extra overhead on the last apply. Could write directly to file, but + * makes things complicated though. + */ +static struct stream * +rcsfile_getdeltatext(struct rcsfile *rf, struct delta *d, struct buf **buf_dest) +{ + struct diffinfo dibuf, *di; + struct stream *orig, *dest, *rd; + struct buf *buf_orig; + struct keyword *k; + int error; + + buf_orig = NULL; + error = 0; + + /* + * If diffbase is NULL or we are head (the old head), we have a normal + * complete deltatext. + */ + if (d->diffbase == NULL && !strcmp(rf->head, d->revnum)) { + orig = stream_open_buf(d->text); + return (orig); + } + + di = &dibuf; + /* If not, we need to apply our diff to that of our diffbase. */ + orig = rcsfile_getdeltatext(rf, d->diffbase, &buf_orig); + if (orig == NULL) + return (NULL); + + /* + * Now that we are sure we have a complete deltatext in ret, let's apply + * our diff to it. + */ + *buf_dest = buf_new(BUF_SIZE_DEFAULT); + dest = stream_open_buf(*buf_dest); + + di->di_rcsfile = rf->name; + di->di_cvsroot = rf->cvsroot; + di->di_revnum = d->revnum; + di->di_revdate = d->revdate; + di->di_author = d->author; + di->di_tag = rf->colltag; + di->di_state = d->state; + di->di_expand = EXPAND_OLD; + rd = stream_open_buf(d->text); + k = keyword_new(); + error = diff_apply(rd, orig, dest, k, di, 0); + stream_flush(dest); + stream_close(rd); + stream_close(orig); + stream_close(dest); + keyword_free(k); + if (buf_orig != NULL) + buf_free(buf_orig); + if (error) { + lprintf(-1, "Error applying diff: %d\n", error); + return (NULL); + } + + /* Now reopen the stream for the reading. */ + dest = stream_open_buf(*buf_dest); + return (dest); +} + +/* Print content of rcsfile. Useful for debugging. */ +void +rcsfile_print(struct rcsfile *rf) +{ + struct delta *d; + struct tag *t; + struct string *s; + struct stream *in; + char *line; + + lprintf(1, "\n"); + if (rf->name != NULL) + lprintf(1, "name: '%s'\n", rf->name); + if (rf->head != NULL) + lprintf(1, "head: '%s'\n", rf->head); + if (rf->branch != NULL) + lprintf(1, "branch: '%s'\n", rf->branch); + lprintf(1, "Access: "); + STAILQ_FOREACH(s, &rf->accesslist, string_next) + lprintf(1, "'%s' ", s->str); + lprintf(1, "\n"); + + /* Print all tags. */ + STAILQ_FOREACH(t, &rf->taglist, tag_next) { + lprintf(1, "Tag: "); + if (t->tag != NULL) + lprintf(1, "name: %s ", t->tag); + if (t->revnum != NULL) + lprintf(1, "rev: %s", t->revnum); + lprintf(1, "\n"); + } + + if (rf->strictlock) + lprintf(1, "Strict!\n"); + if (rf->comment != NULL) + lprintf(1, "comment: '%s'\n", rf->comment); + if (rf->expand != EXPAND_DEFAULT) + lprintf(1, "expand: '%s'\n", keyword_encode_expand(rf->expand)); + + /* Print all deltas. */ + LIST_FOREACH(d, &rf->deltatable, table_next) { + lprintf(1, "Delta: "); + if (d->revdate != NULL) + lprintf(1, "date: %s ", d->revdate); + if (d->revnum != NULL) + lprintf(1, "rev: %s", d->revnum); + if (d->author != NULL) + lprintf(1, "author: %s", d->author); + if (d->state != NULL) + lprintf(1, "state: %s", d->state); + + lprintf(1, "Text:\n"); + in = stream_open_buf(d->text); + line = stream_getln(in, NULL); + while (line != NULL) { + lprintf(1, "TEXT: %s\n", line); + line = stream_getln(in, NULL); + } + stream_close(in); + lprintf(1, "\n"); + } + + if (rf->desc != NULL) + lprintf(1, "desc: '%s'\n", rf->desc); +} + +/* Free all memory associated with a struct rcsfile. */ +void +rcsfile_free(struct rcsfile *rf) +{ + struct delta *d; + struct tag *t; + struct string *s; + + if (rf->name != NULL) + free(rf->name); + if (rf->head != NULL) + free(rf->head); + if (rf->branch != NULL) + free(rf->branch); + if (rf->cvsroot != NULL) + free(rf->cvsroot); + if (rf->colltag != NULL) + free(rf->colltag); + + /* Free all access ids. */ + while (!STAILQ_EMPTY(&rf->accesslist)) { + s = STAILQ_FIRST(&rf->accesslist); + STAILQ_REMOVE_HEAD(&rf->accesslist, string_next); + if (s->str != NULL) + free(s->str); + free(s); + } + + /* Free all tags. */ + while (!STAILQ_EMPTY(&rf->taglist)) { + t = STAILQ_FIRST(&rf->taglist); + STAILQ_REMOVE_HEAD(&rf->taglist, tag_next); + if (t->tag != NULL) + free(t->tag); + if (t->revnum != NULL) + free(t->revnum); + free(t); + } + + if (rf->comment != NULL) + free(rf->comment); + + /* Free all deltas in global list */ + while (!LIST_EMPTY(&rf->deltatable)) { + d = LIST_FIRST(&rf->deltatable); + if (!rf->ro) + LIST_REMOVE(d, delta_next); + LIST_REMOVE(d, table_next); + rcsfile_freedelta(d); + } + + /* Free global branch. */ + if (rf->trunk->revnum != NULL) + free(rf->trunk->revnum); + free(rf->trunk); + + if (rf->desc != NULL) + free(rf->desc); + + free(rf); +} + +/* + * Free a RCS delta. + */ +static void +rcsfile_freedelta(struct delta *d) +{ + struct branch *b; + + if (d->revdate != NULL) + free(d->revdate); + if (d->revnum != NULL) + free(d->revnum); + if (d->author != NULL) + free(d->author); + if (d->state != NULL) + free(d->state); + if (d->log != NULL) + buf_free(d->log); + if (d->text != NULL) + buf_free(d->text); + + /* Free all subbranches of a delta. */ + while (!LIST_EMPTY(&d->branchlist)) { + b = LIST_FIRST(&d->branchlist); + LIST_REMOVE(b, branch_next); + free(b->revnum); + free(b); + } + free(d); +} + +/* + * Functions for editing RCS deltas. + */ + +/* Add a new entry to the access list. */ +void +rcsfile_addaccess(struct rcsfile *rf, char *id) +{ + struct string *s; + + s = xmalloc(sizeof(struct string)); + s->str = xstrdup(id); + STAILQ_INSERT_TAIL(&rf->accesslist, s, string_next); +} + +/* Add a tag to a RCS file. */ +void +rcsfile_addtag(struct rcsfile *rf, char *tag, char *revnum) +{ + struct tag *t; + + t = xmalloc(sizeof(struct tag)); + t->tag = xstrdup(tag); + t->revnum = xstrdup(revnum); + + STAILQ_INSERT_HEAD(&rf->taglist, t, tag_next); +} + +/* Import a tag to a RCS file. */ +void +rcsfile_importtag(struct rcsfile *rf, char *tag, char *revnum) +{ + struct tag *t; + + t = xmalloc(sizeof(struct tag)); + t->tag = xstrdup(tag); + t->revnum = xstrdup(revnum); + + STAILQ_INSERT_TAIL(&rf->taglist, t, tag_next); +} + +/* + * Delete a revision from the global delta list and the branch it is in. Csup + * will tell us to delete the tags involved. + */ +void +rcsfile_deleterev(struct rcsfile *rf, char *revname) +{ + struct delta *d; + + d = rcsfile_getdelta(rf, revname); + if (!rf->ro) + LIST_REMOVE(d, delta_next); + LIST_REMOVE(d, table_next); + rcsfile_freedelta(d); +} + +/* Delete a tag from the tag list. */ +void +rcsfile_deletetag(struct rcsfile *rf, char *tag, char *revnum) +{ + struct tag *t; + + STAILQ_FOREACH(t, &rf->taglist, tag_next) { + if ((strcmp(tag, t->tag) == 0) && + (strcmp(revnum, t->revnum) == 0)) { + STAILQ_REMOVE(&rf->taglist, t, tag, tag_next); + free(t->tag); + free(t->revnum); + free(t); + return; + } + } +} + +/* + * Searches the global deltalist for a delta. + */ +struct delta * +rcsfile_getdelta(struct rcsfile *rf, char *revnum) +{ + struct delta *d; + + LIST_FOREACH(d, &rf->deltatable, table_next) { + if (strcmp(revnum, d->revnum) == 0) + return (d); + } + return (NULL); +} + +/* Set rcsfile head. */ +void +rcsfile_setval(struct rcsfile *rf, int field, char *val) +{ + size_t len; + + switch (field) { + case RCSFILE_HEAD: + if (rf->head != NULL) + free(rf->head); + rf->head = xstrdup(val); + break; + case RCSFILE_BRANCH: + if (rf->branch != NULL) + free(rf->branch); + rf->branch = (val == NULL) ? NULL : xstrdup(val); + break; + case RCSFILE_STRICT: + if (val != NULL) + rf->strictlock = 1; + break; + case RCSFILE_COMMENT: + if (rf->comment != NULL) + free(rf->comment); + rf->comment = xstrdup(val); + break; + case RCSFILE_EXPAND: + len = strlen(val) - 1; + val++; + val[len - 1] = '\0'; + rf->expand = keyword_decode_expand(val); + break; + case RCSFILE_DESC: + if (rf->desc != NULL) + free(rf->desc); + rf->desc = xstrdup(val); + break; + default: + lprintf(-1, "Setting invalid RCSfile value.\n"); + break; + } +} + +/* Create and initialize a delta. */ +static struct delta * +rcsfile_createdelta(char *revnum) +{ + struct delta *d; + + d = xmalloc(sizeof(struct delta)); + d->revnum = xstrdup(revnum); + d->revdate = NULL; + d->state = NULL; + d->author = NULL; + d->log = buf_new(BUF_SIZE_DEFAULT); + d->text = buf_new(BUF_SIZE_DEFAULT); + d->diffbase = NULL; + + LIST_INIT(&d->branchlist); + return (d); +} + +/* Add a delta to a imported delta tree. Used by the updater. */ +struct delta * +rcsfile_addelta(struct rcsfile *rf, char *revnum, char *revdate, char *author, + char *diffbase) +{ + struct branch *b; + struct delta *d, *d_bp, *d_next; + char *brev, *bprev; + int trunk; + + d_next = NULL; + d = rcsfile_getdelta(rf, revnum); + if (d != NULL) { + lprintf(-1, "Delta %s already exists!\n", revnum); + return (NULL); + } + d = rcsfile_createdelta(revnum); + d->placeholder = 0; + d->revdate = xstrdup(revdate); + d->author = xstrdup(author); + d->diffbase = rcsfile_getdelta(rf, diffbase); + + /* If it's trunk, insert it in the head branch list. */ + b = rcsrev_istrunk(d->revnum) ? rf->trunk : + rcsfile_getbranch(rf, d->revnum); + + /* + * We didn't find a branch, check if we can find a branchpoint and + * create a branch there. + */ + if (b == NULL) { + brev = rcsrev_prefix(d->revnum); + bprev = rcsrev_prefix(brev); + + d_bp = rcsfile_getdelta(rf, bprev); + free(bprev); + if (d_bp == NULL) { + lprintf(-1, "No branch point for adding delta %s\n", + d->revnum); + return (NULL); + } + + /* Create the branch and insert in delta. */ + b = xmalloc(sizeof(struct branch)); + b->revnum = brev; + LIST_INIT(&b->deltalist); + rcsdelta_insertbranch(d_bp, b); + } + + /* Insert both into the tree, and into the lookup list. */ + trunk = rcsrev_istrunk(d->revnum); + rcsfile_insertdelta(b, d, trunk); + rcsfile_insertsorteddelta(rf, d); + return (d); +} + +/* Adds a delta to a rcsfile struct. Used by the parser. */ +void +rcsfile_importdelta(struct rcsfile *rf, char *revnum, char *revdate, char *author, + char *state, char *next) +{ + struct branch *b; + struct delta *d, *d_bp, *d_next; + char *brev, *bprev; + int trunk; + + d_next = NULL; + d = rcsfile_getdelta(rf, revnum); + + if (d == NULL) { + /* If not, we'll just create a new entry. */ + d = rcsfile_createdelta(revnum); + d->placeholder = 0; + } else { + if (d->placeholder == 0) { + lprintf(-1, "Trying to import already existing delta\n"); + return; + } + } + /* + * If already exists, assume that only revnum is filled out, and set the + * rest of the fields. This should be an OK assumption given that we can + * be sure internally that the structure is sufficiently initialized so + * we won't have any unfreed memory. + */ + d->revdate = xstrdup(revdate); + d->author = xstrdup(author); + if (state != NULL) + d->state = xstrdup(state); + + /* If we have a next, create a placeholder for it. */ + if (next != NULL) { + d_next = rcsfile_createdelta(next); + d_next->placeholder = 1; + /* Diffbase should be the previous. */ + d_next->diffbase = d; + } + + /* If we're opening read-only, do minimal work. */ + if (rf->ro) { + if (!d->placeholder) + rcsfile_insertsorteddelta(rf, d); + else + d->placeholder = 0; + if (d_next != NULL) + rcsfile_insertsorteddelta(rf, d_next); + return; + } + + /* If it's trunk, insert it in the head branch list. */ + b = rcsrev_istrunk(d->revnum) ? rf->trunk : rcsfile_getbranch(rf, + d->revnum); + + /* + * We didn't find a branch, check if we can find a branchpoint and + * create a branch there. + */ + if (b == NULL) { + brev = rcsrev_prefix(d->revnum); + bprev = rcsrev_prefix(brev); + + d_bp = rcsfile_getdelta(rf, bprev); + free(bprev); + if (d_bp == NULL) { + lprintf(-1, "No branch point for adding delta %s\n", + d->revnum); + return; + } + + /* Create the branch and insert in delta. */ + b = xmalloc(sizeof(struct branch)); + b->revnum = brev; + LIST_INIT(&b->deltalist); + rcsdelta_insertbranch(d_bp, b); + } + + /* Insert if not a placeholder. */ + if (!d->placeholder) { + /* Insert both into the tree, and into the lookup list. */ + if (rcsrev_istrunk(d->revnum)) + rcsfile_insertdelta(b, d, 1); + else { + rcsfile_insertdelta(b, d, 0); + /* + * On import we need to set the diffbase to our + * branchpoint for writing out later. + */ + if (LIST_FIRST(&b->deltalist) == d) { + brev = rcsrev_prefix(d->revnum); + bprev = rcsrev_prefix(brev); + d_bp = rcsfile_getdelta(rf, bprev); + /* This should really not happen. */ + assert(d_bp != NULL); + d->diffbase = d_bp; + free(brev); + free(bprev); + } + } + rcsfile_insertsorteddelta(rf, d); + } else /* Not a placeholder anymore. */ { + d->placeholder = 0; + /* Put it into the tree. */ + trunk = rcsrev_istrunk(d->revnum); + rcsfile_insertdelta(b, d, trunk); + } + + /* If we have a next, insert the placeholder into the lookup list. */ + if (d_next != NULL) + rcsfile_insertsorteddelta(rf, d_next); +} + +/* + * Find the branch of a revision number. + */ +static struct branch * +rcsfile_getbranch(struct rcsfile *rf, char *revnum) +{ + struct branch *b; + struct delta *d; + char *branchrev, *bprev; + + branchrev = rcsrev_prefix(revnum); + bprev = rcsrev_prefix(branchrev); + d = rcsfile_getdelta(rf, bprev); + free(bprev); + LIST_FOREACH(b, &d->branchlist, branch_next) { + if(rcsnum_cmp(b->revnum, branchrev) == 0) { + free(branchrev); + return (b); + } + } + free(branchrev); + return (NULL); +} + +/* Insert a branch into a delta, sorted by branch revision date. */ +static void +rcsdelta_insertbranch(struct delta *d, struct branch *b) +{ + struct branch *b_iter; + + /* If it's empty, insert into head. */ + if (LIST_EMPTY(&d->branchlist)) { + LIST_INSERT_HEAD(&d->branchlist, b, branch_next); + return; + } + + /* Just put it in before the revdate that is lower. */ + LIST_FOREACH(b_iter, &d->branchlist, branch_next) { + if (rcsnum_cmp(b->revnum, b_iter->revnum) > 0) { + LIST_INSERT_BEFORE(b_iter, b, branch_next); + return; + } + if (LIST_NEXT(b_iter, branch_next) == NULL) + break; + } + /* Insert after last element. */ + LIST_INSERT_AFTER(b_iter, b, branch_next); +} + +/* Insert a delta into the correct place in the table of the rcsfile. */ +static void +rcsfile_insertsorteddelta(struct rcsfile *rf, struct delta *d) +{ + struct delta *d2; + + /* If it's empty, insert into head. */ + if (LIST_EMPTY(&rf->deltatable)) { + LIST_INSERT_HEAD(&rf->deltatable, d, table_next); + return; + } + + /* Just put it in before the revdate that is lower. */ + LIST_FOREACH(d2, &rf->deltatable, table_next) { + if (rcsnum_cmp(d->revnum, d2->revnum) <= 0) { + LIST_INSERT_BEFORE(d2, d, table_next); + return; + } + if (LIST_NEXT(d2, table_next) == NULL) + break; + } + /* Insert after last element. */ + LIST_INSERT_AFTER(d2, d, table_next); +} + +/* + * Insert a delta into the correct place in branch. A trunk branch will have + * different ordering scheme and be sorted by revision number, but a normal + * branch will be sorted by date to maintain compability with branches that is + * "hand-hacked". + */ +static void +rcsfile_insertdelta(struct branch *b, struct delta *d, int trunk) +{ + struct delta *d2; + + /* If it's empty, insert into head. */ + if (LIST_EMPTY(&b->deltalist)) { + LIST_INSERT_HEAD(&b->deltalist, d, delta_next); + return; + } + + /* + * Just put it in before the revnum that is lower. Sort trunk branch by + * branchnum but the subbranches after deltadate. + */ + LIST_FOREACH(d2, &b->deltalist, delta_next) { + if (trunk) { + if (rcsnum_cmp(d->revnum, d2->revnum) >= 0) { + LIST_INSERT_BEFORE(d2, d, delta_next); + return; + } + } else { + /* XXX: here we depend on the date being set, but it + * should be before this is called anyway. */ + if (rcsnum_cmp(d->revnum, d2->revnum) < 0) { + LIST_INSERT_BEFORE(d2, d, delta_next); + return; + } + } + if (LIST_NEXT(d2, delta_next) == NULL) + break; + } + /* Insert after last element. */ + LIST_INSERT_AFTER(d2, d, delta_next); +} + + +/* Add logtext to a delta. Assume the delta already exists. */ +int +rcsdelta_addlog(struct delta *d, char *log, int len) +{ + struct stream *dest; + int nbytes; + + assert(d != NULL); + /* Strip away '@' at beginning and end. */ + log++; + len--; + log[len - 1] = '\0'; + dest = stream_open_buf(d->log); + nbytes = stream_write(dest, log, len - 1); + stream_close(dest); + return ((nbytes == -1) ? -1 : 0); +} + +/* Add deltatext to a delta. Assume the delta already exists. */ +int +rcsdelta_addtext(struct delta *d, char *text, int len) +{ + struct stream *dest; + int nbytes; + + assert(d != NULL); + /* Strip away '@' at beginning and end. */ + text++; + len--; + text[len - 1] = '\0'; + + dest = stream_open_buf(d->text); + nbytes = stream_write(dest, text, len - 1); + stream_close(dest); + return ((nbytes == -1) ? -1 : 0); +} + +/* Add a deltatext logline to a delta. */ +int +rcsdelta_appendlog(struct delta *d, char *logline, size_t size) +{ + struct stream *dest; + int error; + + assert(d != NULL); + dest = stream_open_buf(d->log); + error = rcsdelta_writestring(logline, size, dest); + stream_close(dest); + return (error); +} + +/* Add a deltatext textline to a delta. */ +int +rcsdelta_appendtext(struct delta *d, char *textline, size_t size) +{ + struct stream *dest; + int error; + + assert(d != NULL); + dest = stream_open_buf(d->text); + error = rcsdelta_writestring(textline, size, dest); + stream_close(dest); + return (error); +} + +static int +rcsdelta_writestring(char *textline, size_t size, struct stream *dest) +{ + char buf[3]; + size_t i; + int count; + + for (i = 0; i < size; i++) { + buf[0] = textline[i]; + buf[1] = '\0'; + count = 1; + /* Expand @'s */ + if (buf[0] == '@') { + buf[1] = '@'; + buf[2] = '\0'; + count = 2; + } + if (stream_write(dest, buf, count) == -1) + return (-1); + } + return (0); +} + +/* Set delta state. */ +void +rcsdelta_setstate(struct delta *d, char *state) +{ + + if (d->state != NULL) + free(state); + if (state != NULL) { + d->state = xstrdup(state); + return; + } + d->state = NULL; +} + +/* Truncate the deltalog with a certain offset. */ +void +rcsdelta_truncatelog(struct delta *d, off_t offset) +{ + + stream_truncate_buf(d->log, offset); +} + +/* Truncate the deltatext with a certain offset. */ +void +rcsdelta_truncatetext(struct delta *d, off_t offset) +{ + + stream_truncate_buf(d->text, offset); +} |