/*- * Copyright (c) 2007-2009, Ulf Lilleengen * 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 #include #include #include #include #include #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(const char *path, const char *name, const char *cvsroot, const 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 compatibility 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); }