9178dc306f
- Instead of including protocol info in diffs, strip them before adding deltatext and take this into account when applying the diff later. - Don't use strlen when the string in the RCS file may contain garbage. This got caught in the checksumming before, but was not fixed until now. Instead of using strlen, pass the token length when adding log and text entries to a delta. Add an extra length parameter to duptext() to record the token length. - When adding new branches to a file, add them in at the tail instead of the head of the list to get correct ordering when writing out. - Input stream when diffing was opened twice. - Don't expand keywords in diffs between deltas.
1331 lines
32 KiB
C
1331 lines
32 KiB
C
/*-
|
|
* Copyright (c) 2007-2008, 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;
|
|
STAILQ_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. */
|
|
STAILQ_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;
|
|
struct branch *trunk; /* The tip delta. */
|
|
|
|
LIST_HEAD(, delta) deltatable;
|
|
LIST_HEAD(, delta) deltatable_dates;
|
|
|
|
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 void rcsdelta_writestring(char *, size_t, struct stream *);
|
|
|
|
|
|
/* 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";
|
|
|
|
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)
|
|
{
|
|
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;
|
|
|
|
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);
|
|
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;
|
|
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) {
|
|
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);
|
|
stream_printf(dest, "head%s%s;\n", head_space, d->revnum);
|
|
|
|
/* Write branch, if we have. */
|
|
if (rf->branch != NULL)
|
|
stream_printf(dest, "branch%s%s;\n", branch_space, rf->branch);
|
|
|
|
/* Write access. */
|
|
stream_printf(dest, "access");
|
|
#if 0
|
|
if (!STAILQ_EMPTY(&rf->accesslist)) {
|
|
/*
|
|
* XXX: Write out access. This doesn't seem to be necessary for
|
|
* the time being.
|
|
*/
|
|
}
|
|
#endif
|
|
stream_printf(dest, ";\n");
|
|
|
|
/* Write out taglist. */
|
|
stream_printf(dest, "symbols");
|
|
if (!STAILQ_EMPTY(&rf->taglist)) {
|
|
STAILQ_FOREACH(t, &rf->taglist, tag_next) {
|
|
stream_printf(dest, "\n%s%s:%s", tag_space, t->tag,
|
|
t->revnum);
|
|
}
|
|
}
|
|
stream_printf(dest, ";\n");
|
|
|
|
/* Write out locks and strict. */
|
|
stream_printf(dest, "locks;");
|
|
if (rf->strictlock)
|
|
stream_printf(dest, " strict;");
|
|
stream_printf(dest, "\n");
|
|
|
|
/* Write out the comment. */
|
|
if (rf->comment != NULL)
|
|
stream_printf(dest, "comment%s%s;\n", comment_space, rf->comment);
|
|
|
|
stream_printf(dest, "\n\n");
|
|
|
|
/*
|
|
* 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;
|
|
stream_printf(dest, "%s\n", d->revnum);
|
|
stream_printf(dest, "date%s%s;%sauthor %s;%sstate",
|
|
date_space, d->revdate, auth_space, d->author,
|
|
state_space);
|
|
if (d->state != NULL)
|
|
stream_printf(dest, " %s", d->state);
|
|
stream_printf(dest, ";\n");
|
|
stream_printf(dest, "branches");
|
|
/*
|
|
* Write out our branches. Add them to a reversed list for use
|
|
* later when we write out the text.
|
|
*/
|
|
STAILQ_INIT(&deltalist_inverted);
|
|
STAILQ_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)
|
|
err(1, "empty branch!");
|
|
stream_printf(dest, "\n%s%s", branches_space,
|
|
d_tmp->revnum);
|
|
}
|
|
stream_printf(dest, ";\n");
|
|
|
|
stream_printf(dest, "next%s", next_space);
|
|
/* Push next delta on stack. */
|
|
d_next = LIST_NEXT(d, delta_next);
|
|
if (d_next != NULL) {
|
|
stream_printf(dest, "%s", d_next->revnum);
|
|
STAILQ_INSERT_HEAD(&deltastack, d_next, stack_next);
|
|
}
|
|
stream_printf(dest, ";\n\n");
|
|
}
|
|
stream_printf(dest, "\n");
|
|
/* Write out desc. */
|
|
stream_printf(dest, "desc\n@@");
|
|
d = LIST_FIRST(&rf->trunk->deltalist);
|
|
|
|
/*
|
|
* XXX: We do not take as much care as cvsup to cope with hand-hacked
|
|
* RCS-files, and therefore we'll just let them be updated. If having
|
|
* them correct is important, it will be catched by the checksum anyway.
|
|
*/
|
|
|
|
/* Write out deltatexts. */
|
|
error = rcsfile_write_deltatext(rf, dest);
|
|
stream_printf(dest, "\n");
|
|
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);
|
|
stream_printf(dest, "\n\n\n%s\n", d->revnum);
|
|
stream_printf(dest, "log\n@");
|
|
in = stream_open_buf(d->log);
|
|
line = stream_getln(in, &size);
|
|
while (line != NULL) {
|
|
stream_write(dest, line, size);
|
|
line = stream_getln(in, &size);
|
|
}
|
|
stream_close(in);
|
|
stream_printf(dest, "@\n");
|
|
stream_printf(dest, "text\n@");
|
|
error = rcsfile_puttext(rf, dest, d, d->prev);
|
|
if (error)
|
|
return (error);
|
|
stream_printf(dest, "@");
|
|
|
|
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.
|
|
*/
|
|
STAILQ_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) {
|
|
stream_write(dest, line, size);
|
|
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) {
|
|
stream_write(dest, line, size);
|
|
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 >= 0)
|
|
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);
|
|
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. */
|
|
/* XXX: Is this ok? Since the branchpoint is removed, there is no good
|
|
* reason for the branch to exists, but we might still have deltas in
|
|
* these branches.
|
|
*/
|
|
while (!STAILQ_EMPTY(&d->branchlist)) {
|
|
b = STAILQ_FIRST(&d->branchlist);
|
|
STAILQ_REMOVE_HEAD(&d->branchlist, 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);
|
|
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)
|
|
{
|
|
|
|
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:
|
|
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;
|
|
|
|
STAILQ_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);
|
|
STAILQ_INSERT_TAIL(&d_bp->branchlist, b, branch_next);
|
|
}
|
|
|
|
/* 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 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);
|
|
STAILQ_INSERT_HEAD(&d_bp->branchlist, b, branch_next);
|
|
}
|
|
|
|
/* 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.
|
|
* XXX: this could perhaps be done at a better time.
|
|
*/
|
|
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);
|
|
STAILQ_FOREACH(b, &d->branchlist, branch_next) {
|
|
if(rcsnum_cmp(b->revnum, branchrev) == 0) {
|
|
free(branchrev);
|
|
return (b);
|
|
}
|
|
}
|
|
free(branchrev);
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* Insert a delta into the correct place in the table of the rcsfile. Sorted by
|
|
* date.
|
|
*/
|
|
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->revdate, d2->revdate) < 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;
|
|
|
|
assert(d != NULL);
|
|
/* Strip away '@' at beginning and end. */
|
|
log++;
|
|
len--;
|
|
log[len - 1] = '\0';
|
|
dest = stream_open_buf(d->log);
|
|
stream_write(dest, log, len - 1);
|
|
stream_close(dest);
|
|
return (0);
|
|
}
|
|
|
|
/* Add deltatext to a delta. Assume the delta already exists. */
|
|
int
|
|
rcsdelta_addtext(struct delta *d, char *text, int len)
|
|
{
|
|
struct stream *dest;
|
|
|
|
assert(d != NULL);
|
|
/* Strip away '@' at beginning and end. */
|
|
text++;
|
|
len--;
|
|
text[len - 1] = '\0';
|
|
|
|
dest = stream_open_buf(d->text);
|
|
stream_write(dest, text, len - 1);
|
|
stream_close(dest);
|
|
return (0);
|
|
}
|
|
|
|
/* Add a deltatext logline to a delta. */
|
|
void
|
|
rcsdelta_appendlog(struct delta *d, char *logline, size_t size)
|
|
{
|
|
struct stream *dest;
|
|
|
|
assert(d != NULL);
|
|
dest = stream_open_buf(d->log);
|
|
rcsdelta_writestring(logline, size, dest);
|
|
stream_close(dest);
|
|
}
|
|
|
|
/* Add a deltatext textline to a delta. */
|
|
void
|
|
rcsdelta_appendtext(struct delta *d, char *textline, size_t size)
|
|
{
|
|
struct stream *dest;
|
|
|
|
assert(d != NULL);
|
|
dest = stream_open_buf(d->text);
|
|
rcsdelta_writestring(textline, size, dest);
|
|
stream_close(dest);
|
|
}
|
|
|
|
static void
|
|
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;
|
|
}
|
|
stream_write(dest, buf, count);
|
|
}
|
|
}
|
|
|
|
/* 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);
|
|
}
|