diff options
Diffstat (limited to 'vcs-svn')
-rw-r--r-- | vcs-svn/LICENSE | 32 | ||||
-rw-r--r-- | vcs-svn/fast_export.c | 311 | ||||
-rw-r--r-- | vcs-svn/fast_export.h | 27 | ||||
-rw-r--r-- | vcs-svn/line_buffer.c | 126 | ||||
-rw-r--r-- | vcs-svn/line_buffer.h | 30 | ||||
-rw-r--r-- | vcs-svn/line_buffer.txt | 77 | ||||
-rw-r--r-- | vcs-svn/repo_tree.c | 48 | ||||
-rw-r--r-- | vcs-svn/repo_tree.h | 23 | ||||
-rw-r--r-- | vcs-svn/sliding_window.c | 79 | ||||
-rw-r--r-- | vcs-svn/sliding_window.h | 18 | ||||
-rw-r--r-- | vcs-svn/svndiff.c | 309 | ||||
-rw-r--r-- | vcs-svn/svndiff.h | 10 | ||||
-rw-r--r-- | vcs-svn/svndump.c | 509 | ||||
-rw-r--r-- | vcs-svn/svndump.h | 9 |
14 files changed, 1608 insertions, 0 deletions
diff --git a/vcs-svn/LICENSE b/vcs-svn/LICENSE new file mode 100644 index 0000000000..eb91858b82 --- /dev/null +++ b/vcs-svn/LICENSE @@ -0,0 +1,32 @@ +Copyright (C) 2010 David Barr <david.barr@cordelta.com>. +All rights reserved. + +Copyright (C) 2010 Jonathan Nieder <jrnieder@gmail.com>. + +Copyright (C) 2005 Stefan Hegny, hydrografix Consulting GmbH, +Frankfurt/Main, Germany +and others, see http://svn2cc.sarovar.org + +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(s), this list of conditions and the following disclaimer + unmodified other than the allowable addition of one or more + copyright notices. +2. Redistributions in binary form must reproduce the above copyright + notice(s), 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 COPYRIGHT HOLDER(S) ``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 COPYRIGHT HOLDER(S) 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. diff --git a/vcs-svn/fast_export.c b/vcs-svn/fast_export.c new file mode 100644 index 0000000000..1f04697866 --- /dev/null +++ b/vcs-svn/fast_export.c @@ -0,0 +1,311 @@ +/* + * Licensed under a two-clause BSD-style license. + * See LICENSE for details. + */ + +#include "git-compat-util.h" +#include "strbuf.h" +#include "quote.h" +#include "fast_export.h" +#include "repo_tree.h" +#include "strbuf.h" +#include "svndiff.h" +#include "sliding_window.h" +#include "line_buffer.h" + +#define MAX_GITSVN_LINE_LEN 4096 + +static uint32_t first_commit_done; +static struct line_buffer postimage = LINE_BUFFER_INIT; +static struct line_buffer report_buffer = LINE_BUFFER_INIT; + +/* NEEDSWORK: move to fast_export_init() */ +static int init_postimage(void) +{ + static int postimage_initialized; + if (postimage_initialized) + return 0; + postimage_initialized = 1; + return buffer_tmpfile_init(&postimage); +} + +void fast_export_init(int fd) +{ + first_commit_done = 0; + if (buffer_fdinit(&report_buffer, fd)) + die_errno("cannot read from file descriptor %d", fd); +} + +void fast_export_deinit(void) +{ + if (buffer_deinit(&report_buffer)) + die_errno("error closing fast-import feedback stream"); +} + +void fast_export_delete(const char *path) +{ + putchar('D'); + putchar(' '); + quote_c_style(path, NULL, stdout, 0); + putchar('\n'); +} + +static void fast_export_truncate(const char *path, uint32_t mode) +{ + fast_export_modify(path, mode, "inline"); + printf("data 0\n\n"); +} + +void fast_export_modify(const char *path, uint32_t mode, const char *dataref) +{ + /* Mode must be 100644, 100755, 120000, or 160000. */ + if (!dataref) { + fast_export_truncate(path, mode); + return; + } + printf("M %06"PRIo32" %s ", mode, dataref); + quote_c_style(path, NULL, stdout, 0); + putchar('\n'); +} + +static char gitsvnline[MAX_GITSVN_LINE_LEN]; +void fast_export_begin_commit(uint32_t revision, const char *author, + const struct strbuf *log, + const char *uuid, const char *url, + unsigned long timestamp) +{ + static const struct strbuf empty = STRBUF_INIT; + if (!log) + log = ∅ + if (*uuid && *url) { + snprintf(gitsvnline, MAX_GITSVN_LINE_LEN, + "\n\ngit-svn-id: %s@%"PRIu32" %s\n", + url, revision, uuid); + } else { + *gitsvnline = '\0'; + } + printf("commit refs/heads/master\n"); + printf("mark :%"PRIu32"\n", revision); + printf("committer %s <%s@%s> %ld +0000\n", + *author ? author : "nobody", + *author ? author : "nobody", + *uuid ? uuid : "local", timestamp); + printf("data %"PRIuMAX"\n", + (uintmax_t) (log->len + strlen(gitsvnline))); + fwrite(log->buf, log->len, 1, stdout); + printf("%s\n", gitsvnline); + if (!first_commit_done) { + if (revision > 1) + printf("from :%"PRIu32"\n", revision - 1); + first_commit_done = 1; + } +} + +void fast_export_end_commit(uint32_t revision) +{ + printf("progress Imported commit %"PRIu32".\n\n", revision); +} + +static void ls_from_rev(uint32_t rev, const char *path) +{ + /* ls :5 path/to/old/file */ + printf("ls :%"PRIu32" ", rev); + quote_c_style(path, NULL, stdout, 0); + putchar('\n'); + fflush(stdout); +} + +static void ls_from_active_commit(const char *path) +{ + /* ls "path/to/file" */ + printf("ls \""); + quote_c_style(path, NULL, stdout, 1); + printf("\"\n"); + fflush(stdout); +} + +static const char *get_response_line(void) +{ + const char *line = buffer_read_line(&report_buffer); + if (line) + return line; + if (buffer_ferror(&report_buffer)) + die_errno("error reading from fast-import"); + die("unexpected end of fast-import feedback"); +} + +static void die_short_read(struct line_buffer *input) +{ + if (buffer_ferror(input)) + die_errno("error reading dump file"); + die("invalid dump: unexpected end of file"); +} + +static int ends_with(const char *s, size_t len, const char *suffix) +{ + const size_t suffixlen = strlen(suffix); + if (len < suffixlen) + return 0; + return !memcmp(s + len - suffixlen, suffix, suffixlen); +} + +static int parse_cat_response_line(const char *header, off_t *len) +{ + size_t headerlen = strlen(header); + uintmax_t n; + const char *type; + const char *end; + + if (ends_with(header, headerlen, " missing")) + return error("cat-blob reports missing blob: %s", header); + type = strstr(header, " blob "); + if (!type) + return error("cat-blob header has wrong object type: %s", header); + n = strtoumax(type + strlen(" blob "), (char **) &end, 10); + if (end == type + strlen(" blob ")) + return error("cat-blob header does not contain length: %s", header); + if (memchr(type + strlen(" blob "), '-', end - type - strlen(" blob "))) + return error("cat-blob header contains negative length: %s", header); + if (n == UINTMAX_MAX || n > maximum_signed_value_of_type(off_t)) + return error("blob too large for current definition of off_t"); + *len = n; + if (*end) + return error("cat-blob header contains garbage after length: %s", header); + return 0; +} + +static void check_preimage_overflow(off_t a, off_t b) +{ + if (signed_add_overflows(a, b)) + die("blob too large for current definition of off_t"); +} + +static long apply_delta(off_t len, struct line_buffer *input, + const char *old_data, uint32_t old_mode) +{ + long ret; + struct sliding_view preimage = SLIDING_VIEW_INIT(&report_buffer, 0); + FILE *out; + + if (init_postimage() || !(out = buffer_tmpfile_rewind(&postimage))) + die("cannot open temporary file for blob retrieval"); + if (old_data) { + const char *response; + printf("cat-blob %s\n", old_data); + fflush(stdout); + response = get_response_line(); + if (parse_cat_response_line(response, &preimage.max_off)) + die("invalid cat-blob response: %s", response); + check_preimage_overflow(preimage.max_off, 1); + } + if (old_mode == REPO_MODE_LNK) { + strbuf_addstr(&preimage.buf, "link "); + check_preimage_overflow(preimage.max_off, strlen("link ")); + preimage.max_off += strlen("link "); + check_preimage_overflow(preimage.max_off, 1); + } + if (svndiff0_apply(input, len, &preimage, out)) + die("cannot apply delta"); + if (old_data) { + /* Read the remainder of preimage and trailing newline. */ + assert(!signed_add_overflows(preimage.max_off, 1)); + preimage.max_off++; /* room for newline */ + if (move_window(&preimage, preimage.max_off - 1, 1)) + die("cannot seek to end of input"); + if (preimage.buf.buf[0] != '\n') + die("missing newline after cat-blob response"); + } + ret = buffer_tmpfile_prepare_to_read(&postimage); + if (ret < 0) + die("cannot read temporary file for blob retrieval"); + strbuf_release(&preimage.buf); + return ret; +} + +void fast_export_data(uint32_t mode, off_t len, struct line_buffer *input) +{ + assert(len >= 0); + if (mode == REPO_MODE_LNK) { + /* svn symlink blobs start with "link " */ + if (len < 5) + die("invalid dump: symlink too short for \"link\" prefix"); + len -= 5; + if (buffer_skip_bytes(input, 5) != 5) + die_short_read(input); + } + printf("data %"PRIuMAX"\n", (uintmax_t) len); + if (buffer_copy_bytes(input, len) != len) + die_short_read(input); + fputc('\n', stdout); +} + +static int parse_ls_response(const char *response, uint32_t *mode, + struct strbuf *dataref) +{ + const char *tab; + const char *response_end; + + assert(response); + response_end = response + strlen(response); + + if (*response == 'm') { /* Missing. */ + errno = ENOENT; + return -1; + } + + /* Mode. */ + if (response_end - response < (signed) strlen("100644") || + response[strlen("100644")] != ' ') + die("invalid ls response: missing mode: %s", response); + *mode = 0; + for (; *response != ' '; response++) { + char ch = *response; + if (ch < '0' || ch > '7') + die("invalid ls response: mode is not octal: %s", response); + *mode *= 8; + *mode += ch - '0'; + } + + /* ' blob ' or ' tree ' */ + if (response_end - response < (signed) strlen(" blob ") || + (response[1] != 'b' && response[1] != 't')) + die("unexpected ls response: not a tree or blob: %s", response); + response += strlen(" blob "); + + /* Dataref. */ + tab = memchr(response, '\t', response_end - response); + if (!tab) + die("invalid ls response: missing tab: %s", response); + strbuf_add(dataref, response, tab - response); + return 0; +} + +int fast_export_ls_rev(uint32_t rev, const char *path, + uint32_t *mode, struct strbuf *dataref) +{ + ls_from_rev(rev, path); + return parse_ls_response(get_response_line(), mode, dataref); +} + +int fast_export_ls(const char *path, uint32_t *mode, struct strbuf *dataref) +{ + ls_from_active_commit(path); + return parse_ls_response(get_response_line(), mode, dataref); +} + +void fast_export_blob_delta(uint32_t mode, + uint32_t old_mode, const char *old_data, + off_t len, struct line_buffer *input) +{ + long postimage_len; + + assert(len >= 0); + postimage_len = apply_delta(len, input, old_data, old_mode); + if (mode == REPO_MODE_LNK) { + buffer_skip_bytes(&postimage, strlen("link ")); + postimage_len -= strlen("link "); + } + printf("data %ld\n", postimage_len); + buffer_copy_bytes(&postimage, postimage_len); + fputc('\n', stdout); +} diff --git a/vcs-svn/fast_export.h b/vcs-svn/fast_export.h new file mode 100644 index 0000000000..8823aca15c --- /dev/null +++ b/vcs-svn/fast_export.h @@ -0,0 +1,27 @@ +#ifndef FAST_EXPORT_H_ +#define FAST_EXPORT_H_ + +struct strbuf; +struct line_buffer; + +void fast_export_init(int fd); +void fast_export_deinit(void); + +void fast_export_delete(const char *path); +void fast_export_modify(const char *path, uint32_t mode, const char *dataref); +void fast_export_begin_commit(uint32_t revision, const char *author, + const struct strbuf *log, const char *uuid, + const char *url, unsigned long timestamp); +void fast_export_end_commit(uint32_t revision); +void fast_export_data(uint32_t mode, off_t len, struct line_buffer *input); +void fast_export_blob_delta(uint32_t mode, + uint32_t old_mode, const char *old_data, + off_t len, struct line_buffer *input); + +/* If there is no such file at that rev, returns -1, errno == ENOENT. */ +int fast_export_ls_rev(uint32_t rev, const char *path, + uint32_t *mode_out, struct strbuf *dataref_out); +int fast_export_ls(const char *path, + uint32_t *mode_out, struct strbuf *dataref_out); + +#endif diff --git a/vcs-svn/line_buffer.c b/vcs-svn/line_buffer.c new file mode 100644 index 0000000000..57cc1cec03 --- /dev/null +++ b/vcs-svn/line_buffer.c @@ -0,0 +1,126 @@ +/* + * Licensed under a two-clause BSD-style license. + * See LICENSE for details. + */ + +#include "git-compat-util.h" +#include "line_buffer.h" +#include "strbuf.h" + +#define COPY_BUFFER_LEN 4096 + +int buffer_init(struct line_buffer *buf, const char *filename) +{ + buf->infile = filename ? fopen(filename, "r") : stdin; + if (!buf->infile) + return -1; + return 0; +} + +int buffer_fdinit(struct line_buffer *buf, int fd) +{ + buf->infile = fdopen(fd, "r"); + if (!buf->infile) + return -1; + return 0; +} + +int buffer_tmpfile_init(struct line_buffer *buf) +{ + buf->infile = tmpfile(); + if (!buf->infile) + return -1; + return 0; +} + +int buffer_deinit(struct line_buffer *buf) +{ + int err; + if (buf->infile == stdin) + return ferror(buf->infile); + err = ferror(buf->infile); + err |= fclose(buf->infile); + return err; +} + +FILE *buffer_tmpfile_rewind(struct line_buffer *buf) +{ + rewind(buf->infile); + return buf->infile; +} + +long buffer_tmpfile_prepare_to_read(struct line_buffer *buf) +{ + long pos = ftell(buf->infile); + if (pos < 0) + return error("ftell error: %s", strerror(errno)); + if (fseek(buf->infile, 0, SEEK_SET)) + return error("seek error: %s", strerror(errno)); + return pos; +} + +int buffer_ferror(struct line_buffer *buf) +{ + return ferror(buf->infile); +} + +int buffer_read_char(struct line_buffer *buf) +{ + return fgetc(buf->infile); +} + +/* Read a line without trailing newline. */ +char *buffer_read_line(struct line_buffer *buf) +{ + char *end; + if (!fgets(buf->line_buffer, sizeof(buf->line_buffer), buf->infile)) + /* Error or data exhausted. */ + return NULL; + end = buf->line_buffer + strlen(buf->line_buffer); + if (end[-1] == '\n') + end[-1] = '\0'; + else if (feof(buf->infile)) + ; /* No newline at end of file. That's fine. */ + else + /* + * Line was too long. + * There is probably a saner way to deal with this, + * but for now let's return an error. + */ + return NULL; + return buf->line_buffer; +} + +size_t buffer_read_binary(struct line_buffer *buf, + struct strbuf *sb, size_t size) +{ + return strbuf_fread(sb, size, buf->infile); +} + +off_t buffer_copy_bytes(struct line_buffer *buf, off_t nbytes) +{ + char byte_buffer[COPY_BUFFER_LEN]; + off_t done = 0; + while (done < nbytes && !feof(buf->infile) && !ferror(buf->infile)) { + off_t len = nbytes - done; + size_t in = len < COPY_BUFFER_LEN ? len : COPY_BUFFER_LEN; + in = fread(byte_buffer, 1, in, buf->infile); + done += in; + fwrite(byte_buffer, 1, in, stdout); + if (ferror(stdout)) + return done + buffer_skip_bytes(buf, nbytes - done); + } + return done; +} + +off_t buffer_skip_bytes(struct line_buffer *buf, off_t nbytes) +{ + char byte_buffer[COPY_BUFFER_LEN]; + off_t done = 0; + while (done < nbytes && !feof(buf->infile) && !ferror(buf->infile)) { + off_t len = nbytes - done; + size_t in = len < COPY_BUFFER_LEN ? len : COPY_BUFFER_LEN; + done += fread(byte_buffer, 1, in, buf->infile); + } + return done; +} diff --git a/vcs-svn/line_buffer.h b/vcs-svn/line_buffer.h new file mode 100644 index 0000000000..ee23b4f490 --- /dev/null +++ b/vcs-svn/line_buffer.h @@ -0,0 +1,30 @@ +#ifndef LINE_BUFFER_H_ +#define LINE_BUFFER_H_ + +#include "strbuf.h" + +#define LINE_BUFFER_LEN 10000 + +struct line_buffer { + char line_buffer[LINE_BUFFER_LEN]; + FILE *infile; +}; +#define LINE_BUFFER_INIT { "", NULL } + +int buffer_init(struct line_buffer *buf, const char *filename); +int buffer_fdinit(struct line_buffer *buf, int fd); +int buffer_deinit(struct line_buffer *buf); + +int buffer_tmpfile_init(struct line_buffer *buf); +FILE *buffer_tmpfile_rewind(struct line_buffer *buf); /* prepare to write. */ +long buffer_tmpfile_prepare_to_read(struct line_buffer *buf); + +int buffer_ferror(struct line_buffer *buf); +char *buffer_read_line(struct line_buffer *buf); +int buffer_read_char(struct line_buffer *buf); +size_t buffer_read_binary(struct line_buffer *buf, struct strbuf *sb, size_t len); +/* Returns number of bytes read (not necessarily written). */ +off_t buffer_copy_bytes(struct line_buffer *buf, off_t len); +off_t buffer_skip_bytes(struct line_buffer *buf, off_t len); + +#endif diff --git a/vcs-svn/line_buffer.txt b/vcs-svn/line_buffer.txt new file mode 100644 index 0000000000..8e139eb22d --- /dev/null +++ b/vcs-svn/line_buffer.txt @@ -0,0 +1,77 @@ +line_buffer API +=============== + +The line_buffer library provides a convenient interface for +mostly-line-oriented input. + +Each line is not permitted to exceed 10000 bytes. The provided +functions are not thread-safe or async-signal-safe, and like +`fgets()`, they generally do not function correctly if interrupted +by a signal without SA_RESTART set. + +Calling sequence +---------------- + +The calling program: + + - initializes a `struct line_buffer` to LINE_BUFFER_INIT + - specifies a file to read with `buffer_init` + - processes input with `buffer_read_line`, `buffer_skip_bytes`, + and `buffer_copy_bytes` + - closes the file with `buffer_deinit`, perhaps to start over and + read another file. + +When finished, the caller can use `buffer_reset` to deallocate +resources. + +Using temporary files +--------------------- + +Temporary files provide a place to store data that should not outlive +the calling program. A program + + - initializes a `struct line_buffer` to LINE_BUFFER_INIT + - requests a temporary file with `buffer_tmpfile_init` + - acquires an output handle by calling `buffer_tmpfile_rewind` + - uses standard I/O functions like `fprintf` and `fwrite` to fill + the temporary file + - declares writing is over with `buffer_tmpfile_prepare_to_read` + - can re-read what was written with `buffer_read_line`, + `buffer_copy_bytes`, and so on + - can reuse the temporary file by calling `buffer_tmpfile_rewind` + again + - removes the temporary file with `buffer_deinit`, perhaps to + reuse the line_buffer for some other file. + +When finished, the calling program can use `buffer_reset` to deallocate +resources. + +Functions +--------- + +`buffer_init`, `buffer_fdinit`:: + Open the named file or file descriptor for input. + buffer_init(buf, NULL) prepares to read from stdin. + On failure, returns -1 (with errno indicating the nature + of the failure). + +`buffer_deinit`:: + Stop reading from the current file (closing it unless + it was stdin). Returns nonzero if `fclose` fails or + the error indicator was set. + +`buffer_read_line`:: + Read a line and strip off the trailing newline. + On failure or end of file, returns NULL. + +`buffer_copy_bytes`:: + Read `len` bytes of input and dump them to the standard output + stream. Returns early for error or end of file. + +`buffer_skip_bytes`:: + Discards `len` bytes from the input stream (stopping early + if necessary because of an error or eof). Return value is + the number of bytes successfully read. + +`buffer_reset`:: + Deallocates non-static buffers. diff --git a/vcs-svn/repo_tree.c b/vcs-svn/repo_tree.c new file mode 100644 index 0000000000..67d27f0b6c --- /dev/null +++ b/vcs-svn/repo_tree.c @@ -0,0 +1,48 @@ +/* + * Licensed under a two-clause BSD-style license. + * See LICENSE for details. + */ + +#include "git-compat-util.h" +#include "strbuf.h" +#include "repo_tree.h" +#include "fast_export.h" + +const char *repo_read_path(const char *path, uint32_t *mode_out) +{ + int err; + static struct strbuf buf = STRBUF_INIT; + + strbuf_reset(&buf); + err = fast_export_ls(path, mode_out, &buf); + if (err) { + if (errno != ENOENT) + die_errno("BUG: unexpected fast_export_ls error"); + /* Treat missing paths as directories. */ + *mode_out = REPO_MODE_DIR; + return NULL; + } + return buf.buf; +} + +void repo_copy(uint32_t revision, const char *src, const char *dst) +{ + int err; + uint32_t mode; + static struct strbuf data = STRBUF_INIT; + + strbuf_reset(&data); + err = fast_export_ls_rev(revision, src, &mode, &data); + if (err) { + if (errno != ENOENT) + die_errno("BUG: unexpected fast_export_ls_rev error"); + fast_export_delete(dst); + return; + } + fast_export_modify(dst, mode, data.buf); +} + +void repo_delete(const char *path) +{ + fast_export_delete(path); +} diff --git a/vcs-svn/repo_tree.h b/vcs-svn/repo_tree.h new file mode 100644 index 0000000000..889c6a3c95 --- /dev/null +++ b/vcs-svn/repo_tree.h @@ -0,0 +1,23 @@ +#ifndef REPO_TREE_H_ +#define REPO_TREE_H_ + +struct strbuf; + +#define REPO_MODE_DIR 0040000 +#define REPO_MODE_BLB 0100644 +#define REPO_MODE_EXE 0100755 +#define REPO_MODE_LNK 0120000 + +uint32_t next_blob_mark(void); +void repo_copy(uint32_t revision, const char *src, const char *dst); +void repo_add(const char *path, uint32_t mode, uint32_t blob_mark); +const char *repo_read_path(const char *path, uint32_t *mode_out); +void repo_delete(const char *path); +void repo_commit(uint32_t revision, const char *author, + const struct strbuf *log, const char *uuid, const char *url, + long unsigned timestamp); +void repo_diff(uint32_t r1, uint32_t r2); +void repo_init(void); +void repo_reset(void); + +#endif diff --git a/vcs-svn/sliding_window.c b/vcs-svn/sliding_window.c new file mode 100644 index 0000000000..f11d490995 --- /dev/null +++ b/vcs-svn/sliding_window.c @@ -0,0 +1,79 @@ +/* + * Licensed under a two-clause BSD-style license. + * See LICENSE for details. + */ + +#include "git-compat-util.h" +#include "sliding_window.h" +#include "line_buffer.h" +#include "strbuf.h" + +static int input_error(struct line_buffer *file) +{ + if (!buffer_ferror(file)) + return error("delta preimage ends early"); + return error("cannot read delta preimage: %s", strerror(errno)); +} + +static int skip_or_whine(struct line_buffer *file, off_t gap) +{ + if (buffer_skip_bytes(file, gap) != gap) + return input_error(file); + return 0; +} + +static int read_to_fill_or_whine(struct line_buffer *file, + struct strbuf *buf, size_t width) +{ + buffer_read_binary(file, buf, width - buf->len); + if (buf->len != width) + return input_error(file); + return 0; +} + +static int check_offset_overflow(off_t offset, uintmax_t len) +{ + if (len > maximum_signed_value_of_type(off_t)) + return error("unrepresentable length in delta: " + "%"PRIuMAX" > OFF_MAX", len); + if (signed_add_overflows(offset, (off_t) len)) + return error("unrepresentable offset in delta: " + "%"PRIuMAX" + %"PRIuMAX" > OFF_MAX", + (uintmax_t) offset, len); + return 0; +} + +int move_window(struct sliding_view *view, off_t off, size_t width) +{ + off_t file_offset; + assert(view); + assert(view->width <= view->buf.len); + assert(!check_offset_overflow(view->off, view->buf.len)); + + if (check_offset_overflow(off, width)) + return -1; + if (off < view->off || off + width < view->off + view->width) + return error("invalid delta: window slides left"); + if (view->max_off >= 0 && view->max_off < off + (off_t) width) + return error("delta preimage ends early"); + + file_offset = view->off + view->buf.len; + if (off < file_offset) { + /* Move the overlapping region into place. */ + strbuf_remove(&view->buf, 0, off - view->off); + } else { + /* Seek ahead to skip the gap. */ + if (skip_or_whine(view->file, off - file_offset)) + return -1; + strbuf_setlen(&view->buf, 0); + } + + if (view->buf.len > width) + ; /* Already read. */ + else if (read_to_fill_or_whine(view->file, &view->buf, width)) + return -1; + + view->off = off; + view->width = width; + return 0; +} diff --git a/vcs-svn/sliding_window.h b/vcs-svn/sliding_window.h new file mode 100644 index 0000000000..b43a825cba --- /dev/null +++ b/vcs-svn/sliding_window.h @@ -0,0 +1,18 @@ +#ifndef SLIDING_WINDOW_H_ +#define SLIDING_WINDOW_H_ + +#include "strbuf.h" + +struct sliding_view { + struct line_buffer *file; + off_t off; + size_t width; + off_t max_off; /* -1 means unlimited */ + struct strbuf buf; +}; + +#define SLIDING_VIEW_INIT(input, len) { (input), 0, 0, (len), STRBUF_INIT } + +extern int move_window(struct sliding_view *view, off_t off, size_t width); + +#endif diff --git a/vcs-svn/svndiff.c b/vcs-svn/svndiff.c new file mode 100644 index 0000000000..74c97c4543 --- /dev/null +++ b/vcs-svn/svndiff.c @@ -0,0 +1,309 @@ +/* + * Licensed under a two-clause BSD-style license. + * See LICENSE for details. + */ + +#include "git-compat-util.h" +#include "sliding_window.h" +#include "line_buffer.h" +#include "svndiff.h" + +/* + * svndiff0 applier + * + * See http://svn.apache.org/repos/asf/subversion/trunk/notes/svndiff. + * + * svndiff0 ::= 'SVN\0' window* + * window ::= int int int int int instructions inline_data; + * instructions ::= instruction*; + * instruction ::= view_selector int int + * | copyfrom_data int + * | packed_view_selector int + * | packed_copyfrom_data + * ; + * view_selector ::= copyfrom_source + * | copyfrom_target + * ; + * copyfrom_source ::= # binary 00 000000; + * copyfrom_target ::= # binary 01 000000; + * copyfrom_data ::= # binary 10 000000; + * packed_view_selector ::= # view_selector OR-ed with 6 bit value; + * packed_copyfrom_data ::= # copyfrom_data OR-ed with 6 bit value; + * int ::= highdigit* lowdigit; + * highdigit ::= # binary 1000 0000 OR-ed with 7 bit value; + * lowdigit ::= # 7 bit value; + */ + +#define INSN_MASK 0xc0 +#define INSN_COPYFROM_SOURCE 0x00 +#define INSN_COPYFROM_TARGET 0x40 +#define INSN_COPYFROM_DATA 0x80 +#define OPERAND_MASK 0x3f + +#define VLI_CONTINUE 0x80 +#define VLI_DIGIT_MASK 0x7f +#define VLI_BITS_PER_DIGIT 7 + +struct window { + struct sliding_view *in; + struct strbuf out; + struct strbuf instructions; + struct strbuf data; +}; + +#define WINDOW_INIT(w) { (w), STRBUF_INIT, STRBUF_INIT, STRBUF_INIT } + +static void window_release(struct window *ctx) +{ + strbuf_release(&ctx->out); + strbuf_release(&ctx->instructions); + strbuf_release(&ctx->data); +} + +static int write_strbuf(struct strbuf *sb, FILE *out) +{ + if (fwrite(sb->buf, 1, sb->len, out) == sb->len) /* Success. */ + return 0; + return error("cannot write delta postimage: %s", strerror(errno)); +} + +static int error_short_read(struct line_buffer *input) +{ + if (buffer_ferror(input)) + return error("error reading delta: %s", strerror(errno)); + return error("invalid delta: unexpected end of file"); +} + +static int read_chunk(struct line_buffer *delta, off_t *delta_len, + struct strbuf *buf, size_t len) +{ + assert(*delta_len >= 0); + strbuf_reset(buf); + if (len > (uintmax_t) *delta_len || + buffer_read_binary(delta, buf, len) != len) + return error_short_read(delta); + *delta_len -= buf->len; + return 0; +} + +static int read_magic(struct line_buffer *in, off_t *len) +{ + static const char magic[] = {'S', 'V', 'N', '\0'}; + struct strbuf sb = STRBUF_INIT; + + if (read_chunk(in, len, &sb, sizeof(magic))) { + strbuf_release(&sb); + return -1; + } + if (memcmp(sb.buf, magic, sizeof(magic))) { + strbuf_release(&sb); + return error("invalid delta: unrecognized file type"); + } + strbuf_release(&sb); + return 0; +} + +static int read_int(struct line_buffer *in, uintmax_t *result, off_t *len) +{ + uintmax_t rv = 0; + off_t sz; + for (sz = *len; sz; sz--) { + const int ch = buffer_read_char(in); + if (ch == EOF) + break; + + rv <<= VLI_BITS_PER_DIGIT; + rv += (ch & VLI_DIGIT_MASK); + if (ch & VLI_CONTINUE) + continue; + + *result = rv; + *len = sz - 1; + return 0; + } + return error_short_read(in); +} + +static int parse_int(const char **buf, size_t *result, const char *end) +{ + size_t rv = 0; + const char *pos; + for (pos = *buf; pos != end; pos++) { + unsigned char ch = *pos; + + rv <<= VLI_BITS_PER_DIGIT; + rv += (ch & VLI_DIGIT_MASK); + if (ch & VLI_CONTINUE) + continue; + + *result = rv; + *buf = pos + 1; + return 0; + } + return error("invalid delta: unexpected end of instructions section"); +} + +static int read_offset(struct line_buffer *in, off_t *result, off_t *len) +{ + uintmax_t val; + if (read_int(in, &val, len)) + return -1; + if (val > maximum_signed_value_of_type(off_t)) + return error("unrepresentable offset in delta: %"PRIuMAX"", val); + *result = val; + return 0; +} + +static int read_length(struct line_buffer *in, size_t *result, off_t *len) +{ + uintmax_t val; + if (read_int(in, &val, len)) + return -1; + if (val > SIZE_MAX) + return error("unrepresentable length in delta: %"PRIuMAX"", val); + *result = val; + return 0; +} + +static int copyfrom_source(struct window *ctx, const char **instructions, + size_t nbytes, const char *insns_end) +{ + size_t offset; + if (parse_int(instructions, &offset, insns_end)) + return -1; + if (unsigned_add_overflows(offset, nbytes) || + offset + nbytes > ctx->in->width) + return error("invalid delta: copies source data outside view"); + strbuf_add(&ctx->out, ctx->in->buf.buf + offset, nbytes); + return 0; +} + +static int copyfrom_target(struct window *ctx, const char **instructions, + size_t nbytes, const char *instructions_end) +{ + size_t offset; + if (parse_int(instructions, &offset, instructions_end)) + return -1; + if (offset >= ctx->out.len) + return error("invalid delta: copies from the future"); + for (; nbytes > 0; nbytes--) + strbuf_addch(&ctx->out, ctx->out.buf[offset++]); + return 0; +} + +static int copyfrom_data(struct window *ctx, size_t *data_pos, size_t nbytes) +{ + const size_t pos = *data_pos; + if (unsigned_add_overflows(pos, nbytes) || + pos + nbytes > ctx->data.len) + return error("invalid delta: copies unavailable inline data"); + strbuf_add(&ctx->out, ctx->data.buf + pos, nbytes); + *data_pos += nbytes; + return 0; +} + +static int parse_first_operand(const char **buf, size_t *out, const char *end) +{ + size_t result = (unsigned char) *(*buf)++ & OPERAND_MASK; + if (result) { /* immediate operand */ + *out = result; + return 0; + } + return parse_int(buf, out, end); +} + +static int execute_one_instruction(struct window *ctx, + const char **instructions, size_t *data_pos) +{ + unsigned int instruction; + const char *insns_end = ctx->instructions.buf + ctx->instructions.len; + size_t nbytes; + assert(ctx); + assert(instructions && *instructions); + assert(data_pos); + + instruction = (unsigned char) **instructions; + if (parse_first_operand(instructions, &nbytes, insns_end)) + return -1; + switch (instruction & INSN_MASK) { + case INSN_COPYFROM_SOURCE: + return copyfrom_source(ctx, instructions, nbytes, insns_end); + case INSN_COPYFROM_TARGET: + return copyfrom_target(ctx, instructions, nbytes, insns_end); + case INSN_COPYFROM_DATA: + return copyfrom_data(ctx, data_pos, nbytes); + default: + return error("invalid delta: unrecognized instruction"); + } +} + +static int apply_window_in_core(struct window *ctx) +{ + const char *instructions; + size_t data_pos = 0; + + /* + * Fill ctx->out.buf using data from the source, target, + * and inline data views. + */ + for (instructions = ctx->instructions.buf; + instructions != ctx->instructions.buf + ctx->instructions.len; + ) + if (execute_one_instruction(ctx, &instructions, &data_pos)) + return -1; + if (data_pos != ctx->data.len) + return error("invalid delta: does not copy all inline data"); + return 0; +} + +static int apply_one_window(struct line_buffer *delta, off_t *delta_len, + struct sliding_view *preimage, FILE *out) +{ + int rv = -1; + struct window ctx = WINDOW_INIT(preimage); + size_t out_len; + size_t instructions_len; + size_t data_len; + assert(delta_len); + + /* "source view" offset and length already handled; */ + if (read_length(delta, &out_len, delta_len) || + read_length(delta, &instructions_len, delta_len) || + read_length(delta, &data_len, delta_len) || + read_chunk(delta, delta_len, &ctx.instructions, instructions_len) || + read_chunk(delta, delta_len, &ctx.data, data_len)) + goto error_out; + strbuf_grow(&ctx.out, out_len); + if (apply_window_in_core(&ctx)) + goto error_out; + if (ctx.out.len != out_len) { + rv = error("invalid delta: incorrect postimage length"); + goto error_out; + } + if (write_strbuf(&ctx.out, out)) + goto error_out; + rv = 0; +error_out: + window_release(&ctx); + return rv; +} + +int svndiff0_apply(struct line_buffer *delta, off_t delta_len, + struct sliding_view *preimage, FILE *postimage) +{ + assert(delta && preimage && postimage && delta_len >= 0); + + if (read_magic(delta, &delta_len)) + return -1; + while (delta_len) { /* For each window: */ + off_t pre_off = -1; + size_t pre_len; + + if (read_offset(delta, &pre_off, &delta_len) || + read_length(delta, &pre_len, &delta_len) || + move_window(preimage, pre_off, pre_len) || + apply_one_window(delta, &delta_len, preimage, postimage)) + return -1; + } + return 0; +} diff --git a/vcs-svn/svndiff.h b/vcs-svn/svndiff.h new file mode 100644 index 0000000000..74eb464bab --- /dev/null +++ b/vcs-svn/svndiff.h @@ -0,0 +1,10 @@ +#ifndef SVNDIFF_H_ +#define SVNDIFF_H_ + +struct line_buffer; +struct sliding_view; + +extern int svndiff0_apply(struct line_buffer *delta, off_t delta_len, + struct sliding_view *preimage, FILE *postimage); + +#endif diff --git a/vcs-svn/svndump.c b/vcs-svn/svndump.c new file mode 100644 index 0000000000..2b168aee75 --- /dev/null +++ b/vcs-svn/svndump.c @@ -0,0 +1,509 @@ +/* + * Parse and rearrange a svnadmin dump. + * Create the dump with: + * svnadmin dump --incremental -r<startrev>:<endrev> <repository> >outfile + * + * Licensed under a two-clause BSD-style license. + * See LICENSE for details. + */ + +#include "cache.h" +#include "repo_tree.h" +#include "fast_export.h" +#include "line_buffer.h" +#include "strbuf.h" +#include "svndump.h" + +/* + * Compare start of string to literal of equal length; + * must be guarded by length test. + */ +#define constcmp(s, ref) memcmp(s, ref, sizeof(ref) - 1) + +#define REPORT_FILENO 3 + +#define NODEACT_REPLACE 4 +#define NODEACT_DELETE 3 +#define NODEACT_ADD 2 +#define NODEACT_CHANGE 1 +#define NODEACT_UNKNOWN 0 + +/* States: */ +#define DUMP_CTX 0 /* dump metadata */ +#define REV_CTX 1 /* revision metadata */ +#define NODE_CTX 2 /* node metadata */ +#define INTERNODE_CTX 3 /* between nodes */ + +#define DATE_RFC2822_LEN 31 + +static struct line_buffer input = LINE_BUFFER_INIT; + +static struct { + uint32_t action, srcRev, type; + off_t prop_length, text_length; + struct strbuf src, dst; + uint32_t text_delta, prop_delta; +} node_ctx; + +static struct { + uint32_t revision; + unsigned long timestamp; + struct strbuf log, author; +} rev_ctx; + +static struct { + uint32_t version; + struct strbuf uuid, url; +} dump_ctx; + +static void reset_node_ctx(char *fname) +{ + node_ctx.type = 0; + node_ctx.action = NODEACT_UNKNOWN; + node_ctx.prop_length = -1; + node_ctx.text_length = -1; + strbuf_reset(&node_ctx.src); + node_ctx.srcRev = 0; + strbuf_reset(&node_ctx.dst); + if (fname) + strbuf_addstr(&node_ctx.dst, fname); + node_ctx.text_delta = 0; + node_ctx.prop_delta = 0; +} + +static void reset_rev_ctx(uint32_t revision) +{ + rev_ctx.revision = revision; + rev_ctx.timestamp = 0; + strbuf_reset(&rev_ctx.log); + strbuf_reset(&rev_ctx.author); +} + +static void reset_dump_ctx(const char *url) +{ + strbuf_reset(&dump_ctx.url); + if (url) + strbuf_addstr(&dump_ctx.url, url); + dump_ctx.version = 1; + strbuf_reset(&dump_ctx.uuid); +} + +static void handle_property(const struct strbuf *key_buf, + struct strbuf *val, + uint32_t *type_set) +{ + const char *key = key_buf->buf; + size_t keylen = key_buf->len; + + switch (keylen + 1) { + case sizeof("svn:log"): + if (constcmp(key, "svn:log")) + break; + if (!val) + die("invalid dump: unsets svn:log"); + strbuf_swap(&rev_ctx.log, val); + break; + case sizeof("svn:author"): + if (constcmp(key, "svn:author")) + break; + if (!val) + strbuf_reset(&rev_ctx.author); + else + strbuf_swap(&rev_ctx.author, val); + break; + case sizeof("svn:date"): + if (constcmp(key, "svn:date")) + break; + if (!val) + die("invalid dump: unsets svn:date"); + if (parse_date_basic(val->buf, &rev_ctx.timestamp, NULL)) + warning("invalid timestamp: %s", val->buf); + break; + case sizeof("svn:executable"): + case sizeof("svn:special"): + if (keylen == strlen("svn:executable") && + constcmp(key, "svn:executable")) + break; + if (keylen == strlen("svn:special") && + constcmp(key, "svn:special")) + break; + if (*type_set) { + if (!val) + return; + die("invalid dump: sets type twice"); + } + if (!val) { + node_ctx.type = REPO_MODE_BLB; + return; + } + *type_set = 1; + node_ctx.type = keylen == strlen("svn:executable") ? + REPO_MODE_EXE : + REPO_MODE_LNK; + } +} + +static void die_short_read(void) +{ + if (buffer_ferror(&input)) + die_errno("error reading dump file"); + die("invalid dump: unexpected end of file"); +} + +static void read_props(void) +{ + static struct strbuf key = STRBUF_INIT; + static struct strbuf val = STRBUF_INIT; + const char *t; + /* + * NEEDSWORK: to support simple mode changes like + * K 11 + * svn:special + * V 1 + * * + * D 14 + * svn:executable + * we keep track of whether a mode has been set and reset to + * plain file only if not. We should be keeping track of the + * symlink and executable bits separately instead. + */ + uint32_t type_set = 0; + while ((t = buffer_read_line(&input)) && strcmp(t, "PROPS-END")) { + uint32_t len; + const char type = t[0]; + int ch; + + if (!type || t[1] != ' ') + die("invalid property line: %s", t); + len = atoi(&t[2]); + strbuf_reset(&val); + buffer_read_binary(&input, &val, len); + if (val.len < len) + die_short_read(); + + /* Discard trailing newline. */ + ch = buffer_read_char(&input); + if (ch == EOF) + die_short_read(); + if (ch != '\n') + die("invalid dump: expected newline after %s", val.buf); + + switch (type) { + case 'K': + strbuf_swap(&key, &val); + continue; + case 'D': + handle_property(&val, NULL, &type_set); + continue; + case 'V': + handle_property(&key, &val, &type_set); + strbuf_reset(&key); + continue; + default: + die("invalid property line: %s", t); + } + } +} + +static void handle_node(void) +{ + const uint32_t type = node_ctx.type; + const int have_props = node_ctx.prop_length != -1; + const int have_text = node_ctx.text_length != -1; + /* + * Old text for this node: + * NULL - directory or bug + * empty_blob - empty + * "<dataref>" - data retrievable from fast-import + */ + static const char *const empty_blob = "::empty::"; + const char *old_data = NULL; + uint32_t old_mode = REPO_MODE_BLB; + + if (node_ctx.action == NODEACT_DELETE) { + if (have_text || have_props || node_ctx.srcRev) + die("invalid dump: deletion node has " + "copyfrom info, text, or properties"); + repo_delete(node_ctx.dst.buf); + return; + } + if (node_ctx.action == NODEACT_REPLACE) { + repo_delete(node_ctx.dst.buf); + node_ctx.action = NODEACT_ADD; + } + if (node_ctx.srcRev) { + repo_copy(node_ctx.srcRev, node_ctx.src.buf, node_ctx.dst.buf); + if (node_ctx.action == NODEACT_ADD) + node_ctx.action = NODEACT_CHANGE; + } + if (have_text && type == REPO_MODE_DIR) + die("invalid dump: directories cannot have text attached"); + + /* + * Find old content (old_data) and decide on the new mode. + */ + if (node_ctx.action == NODEACT_CHANGE && !*node_ctx.dst.buf) { + if (type != REPO_MODE_DIR) + die("invalid dump: root of tree is not a regular file"); + old_data = NULL; + } else if (node_ctx.action == NODEACT_CHANGE) { + uint32_t mode; + old_data = repo_read_path(node_ctx.dst.buf, &mode); + if (mode == REPO_MODE_DIR && type != REPO_MODE_DIR) + die("invalid dump: cannot modify a directory into a file"); + if (mode != REPO_MODE_DIR && type == REPO_MODE_DIR) + die("invalid dump: cannot modify a file into a directory"); + node_ctx.type = mode; + old_mode = mode; + } else if (node_ctx.action == NODEACT_ADD) { + if (type == REPO_MODE_DIR) + old_data = NULL; + else if (have_text) + old_data = empty_blob; + else + die("invalid dump: adds node without text"); + } else { + die("invalid dump: Node-path block lacks Node-action"); + } + + /* + * Adjust mode to reflect properties. + */ + if (have_props) { + if (!node_ctx.prop_delta) + node_ctx.type = type; + if (node_ctx.prop_length) + read_props(); + } + + /* + * Save the result. + */ + if (type == REPO_MODE_DIR) /* directories are not tracked. */ + return; + assert(old_data); + if (old_data == empty_blob) + /* For the fast_export_* functions, NULL means empty. */ + old_data = NULL; + if (!have_text) { + fast_export_modify(node_ctx.dst.buf, node_ctx.type, old_data); + return; + } + if (!node_ctx.text_delta) { + fast_export_modify(node_ctx.dst.buf, node_ctx.type, "inline"); + fast_export_data(node_ctx.type, node_ctx.text_length, &input); + return; + } + fast_export_modify(node_ctx.dst.buf, node_ctx.type, "inline"); + fast_export_blob_delta(node_ctx.type, old_mode, old_data, + node_ctx.text_length, &input); +} + +static void begin_revision(void) +{ + if (!rev_ctx.revision) /* revision 0 gets no git commit. */ + return; + fast_export_begin_commit(rev_ctx.revision, rev_ctx.author.buf, + &rev_ctx.log, dump_ctx.uuid.buf, dump_ctx.url.buf, + rev_ctx.timestamp); +} + +static void end_revision(void) +{ + if (rev_ctx.revision) + fast_export_end_commit(rev_ctx.revision); +} + +void svndump_read(const char *url) +{ + char *val; + char *t; + uint32_t active_ctx = DUMP_CTX; + uint32_t len; + + reset_dump_ctx(url); + while ((t = buffer_read_line(&input))) { + val = strchr(t, ':'); + if (!val) + continue; + val++; + if (*val != ' ') + continue; + val++; + + /* strlen(key) + 1 */ + switch (val - t - 1) { + case sizeof("SVN-fs-dump-format-version"): + if (constcmp(t, "SVN-fs-dump-format-version")) + continue; + dump_ctx.version = atoi(val); + if (dump_ctx.version > 3) + die("expected svn dump format version <= 3, found %"PRIu32, + dump_ctx.version); + break; + case sizeof("UUID"): + if (constcmp(t, "UUID")) + continue; + strbuf_reset(&dump_ctx.uuid); + strbuf_addstr(&dump_ctx.uuid, val); + break; + case sizeof("Revision-number"): + if (constcmp(t, "Revision-number")) + continue; + if (active_ctx == NODE_CTX) + handle_node(); + if (active_ctx == REV_CTX) + begin_revision(); + if (active_ctx != DUMP_CTX) + end_revision(); + active_ctx = REV_CTX; + reset_rev_ctx(atoi(val)); + break; + case sizeof("Node-path"): + if (constcmp(t, "Node-")) + continue; + if (!constcmp(t + strlen("Node-"), "path")) { + if (active_ctx == NODE_CTX) + handle_node(); + if (active_ctx == REV_CTX) + begin_revision(); + active_ctx = NODE_CTX; + reset_node_ctx(val); + break; + } + if (constcmp(t + strlen("Node-"), "kind")) + continue; + if (!strcmp(val, "dir")) + node_ctx.type = REPO_MODE_DIR; + else if (!strcmp(val, "file")) + node_ctx.type = REPO_MODE_BLB; + else + fprintf(stderr, "Unknown node-kind: %s\n", val); + break; + case sizeof("Node-action"): + if (constcmp(t, "Node-action")) + continue; + if (!strcmp(val, "delete")) { + node_ctx.action = NODEACT_DELETE; + } else if (!strcmp(val, "add")) { + node_ctx.action = NODEACT_ADD; + } else if (!strcmp(val, "change")) { + node_ctx.action = NODEACT_CHANGE; + } else if (!strcmp(val, "replace")) { + node_ctx.action = NODEACT_REPLACE; + } else { + fprintf(stderr, "Unknown node-action: %s\n", val); + node_ctx.action = NODEACT_UNKNOWN; + } + break; + case sizeof("Node-copyfrom-path"): + if (constcmp(t, "Node-copyfrom-path")) + continue; + strbuf_reset(&node_ctx.src); + strbuf_addstr(&node_ctx.src, val); + break; + case sizeof("Node-copyfrom-rev"): + if (constcmp(t, "Node-copyfrom-rev")) + continue; + node_ctx.srcRev = atoi(val); + break; + case sizeof("Text-content-length"): + if (constcmp(t, "Text") && constcmp(t, "Prop")) + continue; + if (constcmp(t + 4, "-content-length")) + continue; + { + char *end; + uintmax_t len; + + len = strtoumax(val, &end, 10); + if (!isdigit(*val) || *end) + die("invalid dump: non-numeric length %s", val); + if (len > maximum_signed_value_of_type(off_t)) + die("unrepresentable length in dump: %s", val); + + if (*t == 'T') + node_ctx.text_length = (off_t) len; + else + node_ctx.prop_length = (off_t) len; + break; + } + case sizeof("Text-delta"): + if (!constcmp(t, "Text-delta")) { + node_ctx.text_delta = !strcmp(val, "true"); + break; + } + if (constcmp(t, "Prop-delta")) + continue; + node_ctx.prop_delta = !strcmp(val, "true"); + break; + case sizeof("Content-length"): + if (constcmp(t, "Content-length")) + continue; + len = atoi(val); + t = buffer_read_line(&input); + if (!t) + die_short_read(); + if (*t) + die("invalid dump: expected blank line after content length header"); + if (active_ctx == REV_CTX) { + read_props(); + } else if (active_ctx == NODE_CTX) { + handle_node(); + active_ctx = INTERNODE_CTX; + } else { + fprintf(stderr, "Unexpected content length header: %"PRIu32"\n", len); + if (buffer_skip_bytes(&input, len) != len) + die_short_read(); + } + } + } + if (buffer_ferror(&input)) + die_short_read(); + if (active_ctx == NODE_CTX) + handle_node(); + if (active_ctx == REV_CTX) + begin_revision(); + if (active_ctx != DUMP_CTX) + end_revision(); +} + +int svndump_init(const char *filename) +{ + if (buffer_init(&input, filename)) + return error("cannot open %s: %s", filename, strerror(errno)); + fast_export_init(REPORT_FILENO); + strbuf_init(&dump_ctx.uuid, 4096); + strbuf_init(&dump_ctx.url, 4096); + strbuf_init(&rev_ctx.log, 4096); + strbuf_init(&rev_ctx.author, 4096); + strbuf_init(&node_ctx.src, 4096); + strbuf_init(&node_ctx.dst, 4096); + reset_dump_ctx(NULL); + reset_rev_ctx(0); + reset_node_ctx(NULL); + return 0; +} + +void svndump_deinit(void) +{ + fast_export_deinit(); + reset_dump_ctx(NULL); + reset_rev_ctx(0); + reset_node_ctx(NULL); + strbuf_release(&rev_ctx.log); + strbuf_release(&node_ctx.src); + strbuf_release(&node_ctx.dst); + if (buffer_deinit(&input)) + fprintf(stderr, "Input error\n"); + if (ferror(stdout)) + fprintf(stderr, "Output error\n"); +} + +void svndump_reset(void) +{ + strbuf_release(&dump_ctx.uuid); + strbuf_release(&dump_ctx.url); + strbuf_release(&rev_ctx.log); + strbuf_release(&rev_ctx.author); +} diff --git a/vcs-svn/svndump.h b/vcs-svn/svndump.h new file mode 100644 index 0000000000..df9ceb0e8d --- /dev/null +++ b/vcs-svn/svndump.h @@ -0,0 +1,9 @@ +#ifndef SVNDUMP_H_ +#define SVNDUMP_H_ + +int svndump_init(const char *filename); +void svndump_read(const char *url); +void svndump_deinit(void); +void svndump_reset(void); + +#endif |