From: Ramkumar Ramachandra <artagnon@gmail.com>
To: Git List <git@vger.kernel.org>
Cc: Peter Baumann <waste.manager@gmx.de>,
Jonathan Nieder <jrnieder@gmail.com>,
David Barr <david.barr@cordelta.com>,
Sverre Rabbelier <srabbelier@gmail.com>,
Erik Faye-Lund <kusmabite@gmail.com>,
Junio C Hamano <gitster@pobox.com>
Subject: [PATCH 4/5] vcs-svn: Introduce svnload, a dumpfile producer
Date: Tue, 29 Mar 2011 23:43:11 +0530 [thread overview]
Message-ID: <1301422392-21177-5-git-send-email-artagnon@gmail.com> (raw)
In-Reply-To: <1301422392-21177-1-git-send-email-artagnon@gmail.com>
Design-wise, svnload resembles svndump. Include a Makefile rule to
build it into vcs-svn/lib.a.
Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>
---
I'm not very happy with the parse_data and parse_data_len API. It's
probably too cryptic? See also: two skip_optional_lf() calls in the
parse_data function. It seems to be necessary- is the fast-import
documentation wrong about one optional lf?
Makefile | 3 +-
vcs-svn/dir_cache.c | 52 ++++++
vcs-svn/dir_cache.h | 11 +
vcs-svn/dump_export.c | 164 +++++++++++++++++
vcs-svn/dump_export.h | 26 +++
vcs-svn/svnload.c | 485 +++++++++++++++++++++++++++++++++++++++++++++++++
vcs-svn/svnload.h | 19 ++
7 files changed, 759 insertions(+), 1 deletions(-)
create mode 100644 vcs-svn/dir_cache.c
create mode 100644 vcs-svn/dir_cache.h
create mode 100644 vcs-svn/dump_export.c
create mode 100644 vcs-svn/dump_export.h
create mode 100644 vcs-svn/svnload.c
create mode 100644 vcs-svn/svnload.h
diff --git a/Makefile b/Makefile
index b0f155a..7206d35 100644
--- a/Makefile
+++ b/Makefile
@@ -1838,7 +1838,8 @@ endif
XDIFF_OBJS = xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \
xdiff/xmerge.o xdiff/xpatience.o
VCSSVN_OBJS = vcs-svn/string_pool.o vcs-svn/line_buffer.o \
- vcs-svn/repo_tree.o vcs-svn/fast_export.o vcs-svn/svndump.o
+ vcs-svn/repo_tree.o vcs-svn/fast_export.o vcs-svn/svndump.o \
+ vcs-svn/svnload.o vcs-svn/dump_export.o vcs-svn/dir_cache.o
VCSSVN_TEST_OBJS = test-obj-pool.o test-string-pool.o \
test-line-buffer.o test-treap.o
OBJECTS := $(GIT_OBJS) $(XDIFF_OBJS) $(VCSSVN_OBJS)
diff --git a/vcs-svn/dir_cache.c b/vcs-svn/dir_cache.c
new file mode 100644
index 0000000..551558a
--- /dev/null
+++ b/vcs-svn/dir_cache.c
@@ -0,0 +1,52 @@
+/*
+ * Licensed under a two-clause BSD-style license.
+ * See LICENSE for details.
+ */
+
+#include "cache.h"
+#include "git-compat-util.h"
+#include "string-list.h"
+#include "dir_cache.h"
+
+static struct string_list dirents = STRING_LIST_INIT_DUP;
+
+void dir_cache_add(const char *path, mode_t mode)
+{
+ struct string_list_item *dir;
+ dir = string_list_insert(&dirents, path);
+ dir->util = xmalloc(sizeof(uint16_t));
+ *((mode_t *)(dir->util)) = mode;
+}
+
+void dir_cache_remove(const char *path)
+{
+ struct string_list_item *dir;
+ dir = string_list_lookup(&dirents, path);
+ if (dir)
+ *((mode_t *)(dir->util)) = S_IFINVALID;
+}
+
+mode_t dir_cache_lookup(const char *path)
+{
+ struct string_list_item *dir;
+ dir = string_list_lookup(&dirents, path);
+ if (dir)
+ return *((mode_t *)(dir->util));
+ else
+ return S_IFINVALID;
+}
+
+void dir_cache_mkdir_p(const char *path) {
+ char *t, *p;
+
+ p = (char *) path;
+ while ((t = strchr(p, '/'))) {
+ *t = '\0';
+ if (dir_cache_lookup(path) == S_IFINVALID) {
+ dir_cache_add(path, S_IFDIR);
+ dump_export_mkdir(path);
+ }
+ *t = '/'; /* Change it back */
+ p = t + 1;
+ }
+}
diff --git a/vcs-svn/dir_cache.h b/vcs-svn/dir_cache.h
new file mode 100644
index 0000000..e7e83fb
--- /dev/null
+++ b/vcs-svn/dir_cache.h
@@ -0,0 +1,11 @@
+#ifndef DIR_CACHE_H_
+#define DIR_CACHE_H_
+
+#include "dump_export.h"
+
+void dir_cache_add(const char *path, mode_t mode);
+void dir_cache_remove(const char *path);
+mode_t dir_cache_lookup(const char *path);
+void dir_cache_mkdir_p(const char *path);
+
+#endif
diff --git a/vcs-svn/dump_export.c b/vcs-svn/dump_export.c
new file mode 100644
index 0000000..33ff852
--- /dev/null
+++ b/vcs-svn/dump_export.c
@@ -0,0 +1,164 @@
+/*
+ * Licensed under a two-clause BSD-style license.
+ * See LICENSE for details.
+ */
+
+#include "cache.h"
+#include "git-compat-util.h"
+#include "dump_export.h"
+#include "dir_cache.h"
+
+static struct strbuf props = STRBUF_INIT;
+static unsigned long revn = 0;
+
+/* Fills props and tells if the mode represents a file */
+static int populate_props(mode_t mode)
+{
+ int is_file = 1;
+
+ strbuf_reset(&props);
+ switch (mode) {
+ case S_IFINVALID:
+ break;
+ case 0644:
+ case S_IFREG | 0644:
+ break;
+ case 0755:
+ case S_IFREG | 0755:
+ strbuf_addf(&props, "K 14\nsvn:executable\nV 1\n*\n");
+ break;
+ case S_IFLNK:
+ strbuf_addf(&props, "K 11\nsvn:special\nV 1\n*\n");
+ break;
+ case S_IFGITLINK:
+ die("Gitlinks unsupported"); /* TODO */
+ case S_IFDIR:
+ is_file = 0;
+ break;
+ default:
+ die("Corrupt mode: %d", mode);
+ }
+ strbuf_add(&props, "PROPS-END\n", 10);
+ return is_file;
+}
+
+void dump_export_revision(struct strbuf *revprops)
+{
+ printf("Revision-number: %lu\n", ++ revn);
+ printf("Prop-content-length: %lu\n", revprops->len);
+ printf("Content-length: %lu\n\n", revprops->len);
+ printf("%s\n", revprops->buf);
+}
+
+static void dump_export_action(enum node_action action)
+{
+ switch (action) {
+ case NODE_ACTION_CHANGE:
+ printf("Node-action: change\n");
+ break;
+ case NODE_ACTION_ADD:
+ printf("Node-action: add\n");
+ break;
+ case NODE_ACTION_DELETE:
+ printf("Node-action: delete\n");
+ break;
+ case NODE_ACTION_REPLACE:
+ printf("Node-action: replace\n");
+ break;
+ default:
+ die("Corrupt action: %d", action);
+ }
+}
+
+void dump_export_node(const char *path, mode_t mode,
+ enum node_action action, size_t text_len,
+ const char *copyfrom_path)
+{
+ /* Node-path, Node-kind, and Node-action */
+ printf("Node-path: %s\n", path);
+ printf("Node-kind: %s\n", populate_props(mode) ? "file" : "dir");
+ dump_export_action(action);
+
+ if (copyfrom_path) {
+ printf("Node-copyfrom-rev: %lu\n", revn - 1);
+ printf("Node-copyfrom-path: %s\n", copyfrom_path);
+ }
+ if (props.len) {
+ printf("Prop-delta: false\n");
+ printf("Prop-content-length: %lu\n", props.len);
+ }
+ if (text_len) {
+ printf("Text-delta: false\n");
+ printf("Text-content-length: %lu\n", text_len);
+ }
+ if (text_len || props.len) {
+ printf("Content-length: %lu\n\n", text_len + props.len);
+ printf("%s", props.buf);
+ }
+
+ /* When no data is present, pad with two newlines */
+ if (!text_len)
+ printf("\n\n");
+}
+
+void dump_export_mkdir(const char *path)
+{
+ dump_export_node(path, S_IFDIR, NODE_ACTION_ADD, 0, NULL);
+}
+
+void dump_export_m(const char *path, mode_t mode, size_t text_len)
+{
+ enum node_action action = NODE_ACTION_CHANGE;
+ mode_t old_mode;
+
+ old_mode = dir_cache_lookup(path);
+
+ if (mode != old_mode) {
+ if (old_mode != S_IFINVALID) {
+ dump_export_d(path);
+ dir_cache_remove(path);
+ }
+ action = NODE_ACTION_ADD;
+ dir_cache_mkdir_p(path);
+ dir_cache_add(path, mode);
+ }
+
+ dump_export_node(path, mode, action, text_len, NULL);
+}
+
+void dump_export_d(const char *path)
+{
+ printf("Node-path: %s\n", path);
+ dump_export_action(NODE_ACTION_DELETE);
+ printf("\n\n");
+ dir_cache_remove(path);
+}
+
+void dump_export_cr(const char *path, const char *copyfrom_path,
+ int delete_old)
+{
+ enum node_action action = NODE_ACTION_REPLACE;
+ mode_t mode, old_mode;
+
+ mode = dir_cache_lookup(path);
+ old_mode = dir_cache_lookup(copyfrom_path);
+
+ if (old_mode == S_IFINVALID)
+ die("Can't copy from non-existant path: %s", copyfrom_path);
+ if (mode != old_mode) {
+ action = NODE_ACTION_ADD;
+ dir_cache_mkdir_p(path);
+ dir_cache_add(path, mode);
+ }
+ if (delete_old) {
+ dump_export_d(copyfrom_path);
+ dir_cache_remove(copyfrom_path);
+ }
+
+ dump_export_node(path, old_mode, action, 0, copyfrom_path);
+}
+
+void dump_export_init()
+{
+ printf("SVN-fs-dump-format-version: 3\n\n");
+}
diff --git a/vcs-svn/dump_export.h b/vcs-svn/dump_export.h
new file mode 100644
index 0000000..647701d
--- /dev/null
+++ b/vcs-svn/dump_export.h
@@ -0,0 +1,26 @@
+#ifndef DUMP_EXPORT_H_
+#define DUMP_EXPORT_H_
+
+#define MAX_GITSVN_LINE_LEN 4096
+#define SVN_INVALID_REV 0
+
+enum node_action {
+ NODE_ACTION_CHANGE,
+ NODE_ACTION_ADD,
+ NODE_ACTION_DELETE,
+ NODE_ACTION_REPLACE,
+ NODE_ACTION_COUNT
+};
+
+void dump_export_revision(struct strbuf *revprops);
+void dump_export_node(const char *path, mode_t mode,
+ enum node_action action, size_t text_len,
+ const char *copyfrom_path);
+void dump_export_mkdir(const char *path);
+void dump_export_m(const char *path, mode_t mode, size_t text_len);
+void dump_export_d(const char *path);
+void dump_export_cr(const char *path, const char *copyfrom_path,
+ int delete_old);
+void dump_export_init();
+
+#endif
diff --git a/vcs-svn/svnload.c b/vcs-svn/svnload.c
new file mode 100644
index 0000000..c07e475
--- /dev/null
+++ b/vcs-svn/svnload.c
@@ -0,0 +1,485 @@
+/*
+ * Produce a dumpfile v3 from a fast-import stream.
+ * Load the dump into the SVN repository with:
+ * svnrdump load <URL> <dumpfile
+ *
+ * Licensed under a two-clause BSD-style license.
+ * See LICENSE for details.
+ */
+
+#include "cache.h"
+#include "quote.h"
+#include "svnload.h"
+#include "dump_export.h"
+#include "dir_cache.h"
+
+static FILE *infile;
+static struct strbuf command_buf = STRBUF_INIT;
+static struct strbuf path_d = STRBUF_INIT;
+
+static int read_next_command(void)
+{
+ strbuf_reset(&command_buf);
+ return strbuf_getline(&command_buf, infile, '\n');
+}
+
+static void populate_revprops(struct strbuf *revprops,
+ struct ident *svn_ident, struct strbuf *log)
+{
+ strbuf_reset(revprops);
+ strbuf_addf(revprops, "K 10\nsvn:author\nV %lu\n%s\n",
+ svn_ident->name.len, svn_ident->name.buf);
+ strbuf_addf(revprops, "K 7\nsvn:log\nV %lu\n%s\n",
+ log->len, log->buf);
+ if (svn_ident->date)
+ /* SVN doesn't like an empty svn:date value */
+ strbuf_addf(revprops, "K 8\nsvn:date\nV %d\n%s\n",
+ SVN_DATE_LEN - 1, svn_ident->date);
+ strbuf_add(revprops, "PROPS-END\n", 10);
+}
+
+static void parse_ident(const char *buf, struct ident *identp)
+{
+ char *original_buf, *t, *tz_off;
+ int tz_off_buf;
+ const struct tm *tm_time;
+
+ /* John Doe <johndoe@email.com> 1170199019 +0530 */
+ strbuf_reset(&(identp->name));
+ strbuf_reset(&(identp->email));
+
+ original_buf = strdup(buf);
+ if (!(tz_off = strrchr(buf, ' ')))
+ goto error;
+ *tz_off++ = '\0';
+ if (!(t = strrchr(buf, ' ')))
+ goto error;
+ *(t - 1) = '\0'; /* Ignore '>' from email */
+ t++;
+ tz_off_buf = atoi(tz_off);
+
+ /* UTC -1200 to UTC +1400 are valid */
+ if (tz_off_buf > 1400 || tz_off_buf < -1200)
+ goto error;
+ tm_time = time_to_tm(strtoul(t, NULL, 10), tz_off_buf);
+ strftime(identp->date, SVN_DATE_LEN, SVN_DATE_FORMAT, tm_time);
+ if (!(t = strchr(buf, '<')))
+ goto error;
+ *(t - 1) = '\0'; /* Ignore ' <' from email */
+ t++;
+
+ strbuf_add(&(identp->email), t, strlen(t));
+ strbuf_add(&(identp->name), buf, strlen(buf));
+ free(original_buf);
+ return;
+error:
+ die("Malformed ident line: %s", original_buf);
+}
+
+static void skip_optional_lf(void)
+{
+ int term_char = fgetc(infile);
+ if (term_char != '\n' && term_char != EOF)
+ ungetc(term_char, infile);
+}
+
+/* Either sets term and returns terminator length or returns data
+ length after setting term to NULL */
+static size_t parse_data_len(char *term)
+{
+ uintmax_t length;
+
+ term = NULL;
+ if (prefixcmp(command_buf.buf, "data "))
+ die("Expected 'data n' command, found: %s", command_buf.buf);
+
+ if (!prefixcmp(command_buf.buf + 5, "<<")) {
+ term = xstrdup(command_buf.buf + 5 + 2);
+ if (!(command_buf.len - 5 - 2))
+ die("Missing delimeter after 'data <<' in: %s", command_buf.buf);
+ return (size_t) (command_buf.len - 5 - 2);
+ }
+
+ length = strtoumax(command_buf.buf + 5, NULL, 10);
+ if ((size_t) length < length)
+ die("Data is too large to use in this context");
+
+ return (size_t) length;
+}
+
+/* When term is filled in, nbytes refers to the size of the
+ terminator; otherwise, it refers to the size of the actual data.
+ The parsed data is written to dst and out, if they exist. */
+static void parse_data(char *term, size_t nbytes, struct strbuf *dst, FILE *out)
+{
+ size_t in;
+ size_t done = 0;
+
+ if (term) {
+ /* Read line-by-line until terminator is encountered */
+ while (1) {
+ if (read_next_command() == EOF)
+ die("Expected terminator '%s', found EOF", term);
+
+ /* If the terminator is encountered, stop reading */
+ if (nbytes == command_buf.len
+ && !memcmp(term, command_buf.buf, nbytes))
+ break;
+
+ if (dst) {
+ strbuf_addbuf(dst, &command_buf);
+ strbuf_addch(dst, '\n');
+ }
+ if (out) {
+ strbuf_fwrite(&command_buf, command_buf.len, out);
+ fprintf(out, "\n");
+ }
+ }
+ free(term);
+ goto END;
+ }
+
+ /* Read nbytes bytes in chunks */
+ while (done < nbytes && !feof(infile) && !ferror(infile)) {
+ in = (nbytes - done) < COPY_BUFFER_LEN ?
+ (nbytes - done) : COPY_BUFFER_LEN;
+ strbuf_reset(&command_buf);
+ in = strbuf_fread(&command_buf, in, infile);
+ done += in;
+ if (dst)
+ strbuf_addbuf(dst, &command_buf);
+ if (out)
+ strbuf_fwrite(&command_buf, command_buf.len, out);
+ }
+ if (done != nbytes)
+ die("Expected %lu bytes, read %lu bytes", nbytes, done);
+
+ if (out)
+ fprintf(out, "\n"); /* Hack: Incase data is not terminated with lf */
+END:
+ skip_optional_lf();
+ skip_optional_lf();
+}
+
+static const char *get_mode(const char *str, uint16_t *modep)
+{
+ unsigned char c;
+ uint16_t mode = 0;
+
+ while ((c = *str++) != ' ') {
+ if (c < '0' || c > '7')
+ return NULL;
+ mode = (mode << 3) + (c - '0');
+ }
+ *modep = mode;
+ return str;
+}
+
+static void file_change_m(const char *p)
+{
+ struct strbuf dst = STRBUF_INIT;
+ const char *endp;
+ uint16_t mode;
+ size_t nbytes;
+ char *term;
+
+ if (!p)
+ die("Missing mode after filemodify in: %s", command_buf.buf);
+
+ if (!(p = get_mode(p, &mode)))
+ die("Corrupt mode: %s", command_buf.buf);
+ if (!prefixcmp(p, "inline "))
+ p += 7;
+ else
+ die("Non-inlined data unsupported");
+
+ /* Parse out path into path_d */
+ strbuf_reset(&path_d);
+ if (!unquote_c_style(&path_d, p, &endp)) {
+ if (*endp)
+ die("Garbage after path in: %s", command_buf.buf);
+ } else
+ strbuf_addstr(&path_d, p);
+
+ read_next_command();
+ nbytes = parse_data_len(term);
+ if (term) {
+ strbuf_reset(&dst);
+ parse_data(term, nbytes, &dst, NULL);
+ dump_export_m(path_d.buf, mode, dst.len);
+ fwrite(&dst.buf, 1, dst.len, stdout);
+ return;
+ }
+ dump_export_m(path_d.buf, mode, nbytes);
+ parse_data(NULL, nbytes, NULL, stdout);
+}
+
+static void file_change_d(const char *p)
+{
+ const char *endp;
+
+ if (!p)
+ die("Missing path after filedelete in: %s", command_buf.buf);
+
+ strbuf_reset(&path_d);
+ if (!unquote_c_style(&path_d, p, &endp)) {
+ if (*endp)
+ die("Garbage after path in: %s", command_buf.buf);
+ } else
+ strbuf_addstr(&path_d, p);
+ dump_export_d(path_d.buf);
+}
+
+static void file_change_cr(const char *p, int delete_old)
+{
+ struct strbuf path_s = STRBUF_INIT;
+ const char *endp;
+
+ if (!p)
+ die("Missing source after %s in: %s",
+ delete_old ? "filerename" : "filecopy", command_buf.buf);
+
+ strbuf_reset(&path_s);
+ if (!unquote_c_style(&path_s, p, &endp)) {
+ if (*endp != ' ')
+ die("Missing destination after source in: %s", command_buf.buf);
+ } else {
+ endp = strchr(p, ' ');
+ if (!endp)
+ die("Missing destination after source in: %s", command_buf.buf);
+ strbuf_add(&path_s, p, endp - p);
+ }
+
+ endp++;
+ if (!*endp)
+ die("Missing destination in: %s", command_buf.buf);
+
+ p = endp;
+ strbuf_reset(&path_d);
+ if (!unquote_c_style(&path_d, p, &endp)) {
+ if (*endp)
+ die("Garbage after destination in: %s", command_buf.buf);
+ } else
+ strbuf_addstr(&path_d, p);
+
+ /* TODO: Check C "path/to/subdir" "" */
+ dump_export_cr(path_d.buf, path_s.buf, delete_old);
+}
+
+static void build_svn_ident(struct ident *svn_ident,
+ struct ident *author, struct ident *committer)
+{
+ char *t, *email;
+
+ strbuf_reset(&(svn_ident->name));
+ memcpy(svn_ident->date, committer->date, SVN_DATE_LEN);
+ email = author->email.len ? author->email.buf : committer->email.buf;
+ if ((t = strchr(email, '@')))
+ strbuf_add(&(svn_ident->name), email, t - email);
+ else
+ strbuf_addstr(&(svn_ident->name), email);
+}
+
+static void parse_ignore_notemodify(const char *p)
+{
+ char *term;
+ size_t nbytes;
+
+ if (!p)
+ die("Missing dataref after notemodify in: %s", command_buf.buf);
+ if (!(p = strchr(p, ' ')))
+ die ("Missing committish after dataref in: %s", command_buf.buf);
+
+ read_next_command();
+ term = NULL;
+ nbytes = parse_data_len(term);
+ parse_data(term, nbytes, NULL, NULL);
+}
+
+static void parse_commit(const char *p)
+{
+ static struct strbuf log = STRBUF_INIT;
+ static struct strbuf revprops = STRBUF_INIT;
+ static struct ident author = {STRBUF_INIT, STRBUF_INIT, ""};
+ static struct ident committer = {STRBUF_INIT, STRBUF_INIT, ""};
+ static struct ident svn_ident = {STRBUF_INIT, {0, 0, NULL}, ""};
+
+ char *ident_buf, *term;
+ size_t nbytes;
+
+ /* TODO: Parse and use branch */
+ if (!p)
+ die("Missing ref after commit in: %s", command_buf.buf);
+ read_next_command();
+
+ /* Parse and ignore mark line */
+ if (!prefixcmp(command_buf.buf, "mark :"))
+ read_next_command();
+
+ if (!prefixcmp(command_buf.buf, "author ")) {
+ ident_buf = strbuf_detach(&command_buf, &command_buf.len);
+ parse_ident(ident_buf + 7, &author);
+ free(ident_buf);
+ read_next_command();
+ }
+ if (!prefixcmp(command_buf.buf, "committer ")) {
+ ident_buf = strbuf_detach(&command_buf, &command_buf.len);
+ parse_ident(ident_buf + 10, &committer);
+ free(ident_buf);
+ read_next_command();
+ }
+ if (!committer.name.len)
+ die("Missing committer line in stream");
+
+ /* Parse the log */
+ strbuf_reset(&log);
+ term = NULL;
+ nbytes = parse_data_len(term);
+ parse_data(term, nbytes, &log, NULL);
+ read_next_command();
+
+ if (!prefixcmp(command_buf.buf, "from "))
+ /* TODO: Support copyfrom */
+ read_next_command();
+ while (!prefixcmp(command_buf.buf, "merge "))
+ /* TODO: Support merges */
+ read_next_command();
+
+ /* Translation from Git metadata to SVN metadata */
+ build_svn_ident(&svn_ident, &author, &committer);
+ populate_revprops(&revprops, &svn_ident, &log);
+ dump_export_revision(&revprops);
+
+ do {
+ if (!prefixcmp(command_buf.buf, "M "))
+ file_change_m(command_buf.buf + 2);
+ else if (!prefixcmp(command_buf.buf, "D "))
+ file_change_d(command_buf.buf + 2);
+ else if (!prefixcmp(command_buf.buf, "R "))
+ file_change_cr(command_buf.buf + 2, 1);
+ else if (!prefixcmp(command_buf.buf, "C "))
+ file_change_cr(command_buf.buf + 2, 0);
+ else if (!prefixcmp(command_buf.buf, "N "))
+ parse_ignore_notemodify(command_buf.buf + 2);
+ else if (!prefixcmp(command_buf.buf, "ls "))
+ goto error; /* TODO */
+ else if (!strcmp("deleteall", command_buf.buf))
+ goto error; /* TODO */
+ else
+ /* Unrecognized command is left on command_buf */
+ break;
+ } while (read_next_command() != EOF);
+
+ /* Eat up optional trailing lf */
+ if (!command_buf.len)
+ read_next_command();
+ return;
+error:
+ die("Unsupported command: %s", command_buf.buf);
+}
+
+static void parse_tag(const char *p)
+{
+ char *term;
+ size_t nbytes;
+
+ if (!p)
+ die("Missing name after tag in: %s", command_buf.buf);
+ read_next_command();
+
+ if (prefixcmp(command_buf.buf, "from "))
+ die("Expected 'from committish', found: %s", command_buf.buf);
+ p = command_buf.buf + 5;
+ if (!p)
+ die("Missing committish after from in: %s", command_buf.buf);
+ read_next_command();
+
+ if (prefixcmp(command_buf.buf, "tagger "))
+ die("Expected 'tagger (name?) email when', found: %s", command_buf.buf);
+ p = command_buf.buf + 7;
+ if (*p != '<')
+ p = strchr(p, '<');
+ if (!p)
+ die("Missing name or email after tagger in: %s", command_buf.buf);
+ if (!(p = strchr(p, '>')))
+ die("Malformed email in: %s", command_buf.buf);
+ if (!(++ p))
+ die("Missing when after email in: %s", command_buf.buf);
+ read_next_command();
+
+ term = NULL;
+ nbytes = parse_data_len(term);
+ parse_data(term, nbytes, NULL, NULL);
+ read_next_command();
+}
+
+void parse_reset_branch(const char *p)
+{
+ if (!p)
+ die("Missing ref after reset in: %s", command_buf.buf);
+ read_next_command();
+
+ if (!prefixcmp(command_buf.buf, "from ")) {
+ p = command_buf.buf + 5;
+ if (!p)
+ die("Missing committish after from in: %s", command_buf.buf);
+ read_next_command();
+ }
+
+ skip_optional_lf();
+}
+
+void svnload_read(void)
+{
+ read_next_command();
+
+ /* Every function in the loop keeps reading until it
+ encounteres EOF or an unrecognized command; the
+ unrecognized command is left on command_buf */
+ while (!feof(infile)) {
+ if (!strcmp("blob", command_buf.buf))
+ die("Non-inlined blobs unsupported");
+ else if (!prefixcmp(command_buf.buf, "ls "))
+ goto error; /* TODO */
+ else if (!prefixcmp(command_buf.buf, "cat-blob "))
+ goto error; /* TODO */
+ else if (!prefixcmp(command_buf.buf, "commit "))
+ parse_commit(command_buf.buf + 7);
+ else if (!prefixcmp(command_buf.buf, "tag "))
+ /* TODO: No-op */
+ parse_tag(command_buf.buf + 4);
+ else if (!prefixcmp(command_buf.buf, "reset "))
+ /* TODO: No-op */
+ parse_reset_branch(command_buf.buf + 6);
+ else if (!strcmp(command_buf.buf, "checkpoint")
+ || !prefixcmp(command_buf.buf, "progress ")) {
+ /* Ignored */
+ read_next_command();
+ skip_optional_lf();
+ }
+ else if (!prefixcmp(command_buf.buf, "feature ")
+ || !prefixcmp(command_buf.buf, "option "))
+ /* Ignored */
+ read_next_command();
+ else
+ goto error;
+ };
+ return;
+error:
+ die("Unsupported command: %s", command_buf.buf);
+}
+
+int svnload_init(const char *filename)
+{
+ infile = filename ? fopen(filename, "r") : stdin;
+ if (!infile)
+ die("Cannot open %s: %s", filename, strerror(errno));
+ dump_export_init();
+ return 0;
+}
+
+void svnload_deinit(void)
+{
+ strbuf_release(&command_buf);
+ strbuf_release(&path_d);
+}
diff --git a/vcs-svn/svnload.h b/vcs-svn/svnload.h
new file mode 100644
index 0000000..12bb559
--- /dev/null
+++ b/vcs-svn/svnload.h
@@ -0,0 +1,19 @@
+#ifndef SVNLOAD_H_
+#define SVNLOAD_H_
+
+#include "strbuf.h"
+
+#define SVN_DATE_FORMAT "%Y-%m-%dT%H:%M:%S.000000Z"
+#define SVN_DATE_LEN 28
+#define COPY_BUFFER_LEN 4096
+
+struct ident {
+ struct strbuf name, email;
+ char date[SVN_DATE_LEN];
+};
+
+int svnload_init(const char *filename);
+void svnload_deinit(void);
+void svnload_read(void);
+
+#endif
--
1.7.4.rc1.7.g2cf08.dirty
next prev parent reply other threads:[~2011-03-29 18:15 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2011-03-29 18:13 [PATCH 0/5] Towards a Git to SVN bridge Ramkumar Ramachandra
2011-03-29 18:13 ` [PATCH 1/5] date: Expose the time_to_tm function Ramkumar Ramachandra
2011-03-29 18:13 ` [PATCH 2/5] fast-export: Introduce --inline-blobs Ramkumar Ramachandra
2011-03-29 20:44 ` Jonathan Nieder
2011-03-29 18:13 ` [PATCH 3/5] strbuf: Introduce strbuf_fwrite corresponding to strbuf_fread Ramkumar Ramachandra
2011-03-31 2:15 ` Jonathan Nieder
2011-03-29 18:13 ` Ramkumar Ramachandra [this message]
2011-03-29 18:13 ` [PATCH 5/5] t9012-svn-fi: Add tests for svn-fi Ramkumar Ramachandra
2011-03-31 23:23 ` Jonathan Nieder
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1301422392-21177-5-git-send-email-artagnon@gmail.com \
--to=artagnon@gmail.com \
--cc=david.barr@cordelta.com \
--cc=git@vger.kernel.org \
--cc=gitster@pobox.com \
--cc=jrnieder@gmail.com \
--cc=kusmabite@gmail.com \
--cc=srabbelier@gmail.com \
--cc=waste.manager@gmx.de \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).