summaryrefslogtreecommitdiffstats
path: root/usr.bin/csup/rcsfile.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/csup/rcsfile.c')
-rw-r--r--usr.bin/csup/rcsfile.c1412
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);
+}
OpenPOWER on IntegriCloud