git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
* [PATCH 0/5] Towards a Git to SVN bridge
@ 2011-03-29 18:13 Ramkumar Ramachandra
  2011-03-29 18:13 ` [PATCH 1/5] date: Expose the time_to_tm function Ramkumar Ramachandra
                   ` (4 more replies)
  0 siblings, 5 replies; 9+ messages in thread
From: Ramkumar Ramachandra @ 2011-03-29 18:13 UTC (permalink / raw
  To: Git List
  Cc: Peter Baumann, Jonathan Nieder, David Barr, Sverre Rabbelier,
	Erik Faye-Lund, Junio C Hamano

Hi,

Thanks to Jonathan, Junio, Peter, and Erik among others for reviewing
previous iterations of this series. This iteration should take care of
most error handling and design issues addressed in the previous
iterations- it also comes along with a battery of tests.

Small introduction: svn-fi's design is modelled after git-fast-import.
It aims to be more comprehensive and production-ready than the
existing alternative, git2svn.  It converts a fast-import stream
to a Subversion dumpfile which can be loaded using `svnadmin load` (or
alternatively using `svnrdump load`, which will feature in Subversion
1.7).  The shortcomings can be summarized as follows:
1. dir_cache is a very naive implementation using string_list.  This
will not scale well either in memory or time, and should be replaced
with a prefix tree in future.
2. It currently only supports one branch. For supporting multiple
branches, two things need to be implemented: a branch-specific
dir_cache, and a mapper that maps branch names to directories (if the
standard "trunk", "branches", "tags" layout is not to be hardcoded).
3. Tags are currently unsupported, although it should be trivial to
implement once a mapping is in place.
4. Merges are unsupported.  This requires more thought.
5. The `from` command in the fast-import stream is ignored.  This
should probably be implemented like branching.
6. The data can't make round trips back-and-fourth yet.  Although this
topic has been discussed at length, there is no implementation of this
yet.

Thanks for reading.

-- Ram

Ramkumar Ramachandra (5):
  date: Expose the time_to_tm function
  fast-export: Introduce --inline-blobs
  strbuf: Introduce strbuf_fwrite corresponding to strbuf_fread
  vcs-svn: Introduce svnload, a dumpfile producer
  t9012-svn-fi: Add tests for svn-fi

 .gitignore                        |    1 +
 Documentation/git-fast-export.txt |    5 +
 Makefile                          |    6 +-
 builtin/fast-export.c             |   23 ++-
 cache.h                           |    1 +
 contrib/svn-fe/.gitignore         |    1 +
 contrib/svn-fe/Makefile           |   23 ++-
 contrib/svn-fe/svn-fi.c           |   16 +
 contrib/svn-fe/svn-fi.txt         |   28 ++
 date.c                            |    2 +-
 strbuf.c                          |   11 +
 strbuf.h                          |    1 +
 t/t9012-svn-fi.sh                 |  705 +++++++++++++++++++++++++++++++++++++
 test-svn-fi.c                     |   17 +
 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 +
 20 files changed, 1591 insertions(+), 6 deletions(-)
 create mode 100644 contrib/svn-fe/svn-fi.c
 create mode 100644 contrib/svn-fe/svn-fi.txt
 create mode 100755 t/t9012-svn-fi.sh
 create mode 100644 test-svn-fi.c
 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

-- 
1.7.4.rc1.7.g2cf08.dirty

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH 1/5] date: Expose the time_to_tm function
  2011-03-29 18:13 [PATCH 0/5] Towards a Git to SVN bridge Ramkumar Ramachandra
@ 2011-03-29 18:13 ` Ramkumar Ramachandra
  2011-03-29 18:13 ` [PATCH 2/5] fast-export: Introduce --inline-blobs Ramkumar Ramachandra
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 9+ messages in thread
From: Ramkumar Ramachandra @ 2011-03-29 18:13 UTC (permalink / raw
  To: Git List
  Cc: Peter Baumann, Jonathan Nieder, David Barr, Sverre Rabbelier,
	Erik Faye-Lund, Junio C Hamano

Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>
---
 cache.h |    1 +
 date.c  |    2 +-
 2 files changed, 2 insertions(+), 1 deletions(-)

diff --git a/cache.h b/cache.h
index f765cf5..76a14c6 100644
--- a/cache.h
+++ b/cache.h
@@ -834,6 +834,7 @@ enum date_mode {
 	DATE_RAW
 };
 
+struct tm *time_to_tm(unsigned long time, int tz);
 const char *show_date(unsigned long time, int timezone, enum date_mode mode);
 const char *show_date_relative(unsigned long time, int tz,
 			       const struct timeval *now,
diff --git a/date.c b/date.c
index 00f9eb5..e601a50 100644
--- a/date.c
+++ b/date.c
@@ -54,7 +54,7 @@ static time_t gm_time_t(unsigned long time, int tz)
  * thing, which means that tz -0100 is passed in as the integer -100,
  * even though it means "sixty minutes off"
  */
-static struct tm *time_to_tm(unsigned long time, int tz)
+struct tm *time_to_tm(unsigned long time, int tz)
 {
 	time_t t = gm_time_t(time, tz);
 	return gmtime(&t);
-- 
1.7.4.rc1.7.g2cf08.dirty

^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 2/5] fast-export: Introduce --inline-blobs
  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 ` 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
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 9+ messages in thread
From: Ramkumar Ramachandra @ 2011-03-29 18:13 UTC (permalink / raw
  To: Git List
  Cc: Peter Baumann, Jonathan Nieder, David Barr, Sverre Rabbelier,
	Erik Faye-Lund, Junio C Hamano

Introduce a new command-line option --inline-blobs that always inlines
blobs instead of referring to them via marks or their original SHA-1
hash.

Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>
---
 Documentation/git-fast-export.txt |    5 +++++
 builtin/fast-export.c             |   23 +++++++++++++++++++++--
 contrib/svn-fe/.gitignore         |    1 +
 contrib/svn-fe/Makefile           |   23 +++++++++++++++++++++--
 contrib/svn-fe/svn-fi.c           |   16 ++++++++++++++++
 contrib/svn-fe/svn-fi.txt         |   28 ++++++++++++++++++++++++++++
 6 files changed, 92 insertions(+), 4 deletions(-)
 create mode 100644 contrib/svn-fe/svn-fi.c
 create mode 100644 contrib/svn-fe/svn-fi.txt

diff --git a/Documentation/git-fast-export.txt b/Documentation/git-fast-export.txt
index 781bd6e..363e768 100644
--- a/Documentation/git-fast-export.txt
+++ b/Documentation/git-fast-export.txt
@@ -90,6 +90,11 @@ marks the same across runs.
 	resulting stream can only be used by a repository which
 	already contains the necessary objects.
 
+--inline-blobs::
+	Inline all blobs, instead of referring to them via marks or
+	their original SHA-1 hash.  This is useful to parsers, as they
+	don't need to persist blobs.
+
 --full-tree::
 	This option will cause fast-export to issue a "deleteall"
 	directive for each commit followed by a full list of all files
diff --git a/builtin/fast-export.c b/builtin/fast-export.c
index daf1945..891ad5c 100644
--- a/builtin/fast-export.c
+++ b/builtin/fast-export.c
@@ -27,6 +27,7 @@ static enum { ABORT, VERBATIM, WARN, STRIP } signed_tag_mode = ABORT;
 static enum { ERROR, DROP, REWRITE } tag_of_filtered_mode = ABORT;
 static int fake_missing_tagger;
 static int no_data;
+static int inline_blobs;
 static int full_tree;
 
 static int parse_opt_signed_tag_mode(const struct option *opt,
@@ -118,7 +119,7 @@ static void handle_object(const unsigned char *sha1)
 	char *buf;
 	struct object *object;
 
-	if (no_data)
+	if (no_data || inline_blobs)
 		return;
 
 	if (is_null_sha1(sha1))
@@ -218,7 +219,23 @@ static void show_filemodify(struct diff_queue_struct *q,
 			if (no_data || S_ISGITLINK(spec->mode))
 				printf("M %06o %s %s\n", spec->mode,
 				       sha1_to_hex(spec->sha1), spec->path);
-			else {
+			else if (inline_blobs) {
+				unsigned long size;
+				enum object_type type;
+				char *buf;
+
+				buf = read_sha1_file(spec->sha1, &type, &size);
+				if (!buf)
+					die("Could not read blob %s",
+						sha1_to_hex(spec->sha1));
+				printf("M %06o inline %s\n", spec->mode, spec->path);
+				printf("data %lu\n", size);
+				if (size && fwrite(buf, size, 1, stdout) != 1)
+					die_errno("Could not write blob '%s'",
+						sha1_to_hex(spec->sha1));
+				printf("\n");
+
+			} else {
 				struct object *object = lookup_object(spec->sha1);
 				printf("M %06o :%d %s\n", spec->mode,
 				       get_object_mark(object), spec->path);
@@ -627,6 +644,8 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
 			     "Fake a tagger when tags lack one"),
 		OPT_BOOLEAN(0, "full-tree", &full_tree,
 			     "Output full tree for each commit"),
+		OPT_BOOLEAN(0, "inline-blobs", &inline_blobs,
+			     "Output all blobs inline"),
 		{ OPTION_NEGBIT, 0, "data", &no_data, NULL,
 			"Skip output of blob data",
 			PARSE_OPT_NOARG | PARSE_OPT_NEGHELP, NULL, 1 },
diff --git a/contrib/svn-fe/.gitignore b/contrib/svn-fe/.gitignore
index 02a7791..35f205d 100644
--- a/contrib/svn-fe/.gitignore
+++ b/contrib/svn-fe/.gitignore
@@ -2,3 +2,4 @@
 /*.1
 /*.html
 /svn-fe
+/svn-fi
diff --git a/contrib/svn-fe/Makefile b/contrib/svn-fe/Makefile
index 360d8da..555a8ff 100644
--- a/contrib/svn-fe/Makefile
+++ b/contrib/svn-fe/Makefile
@@ -37,7 +37,7 @@ svn-fe$X: svn-fe.o $(VCSSVN_LIB) $(GIT_LIB)
 	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ svn-fe.o \
 		$(ALL_LDFLAGS) $(LIBS)
 
-svn-fe.o: svn-fe.c ../../vcs-svn/svndump.h
+svn-fe.o: svn-fe.c ../../vcs-svn/svnload.h
 	$(QUIET_CC)$(CC) -I../../vcs-svn -o $*.o -c $(ALL_CFLAGS) $<
 
 svn-fe.html: svn-fe.txt
@@ -51,6 +51,24 @@ svn-fe.1: svn-fe.txt
 		../contrib/svn-fe/$@
 	$(MV) ../../Documentation/svn-fe.1 .
 
+svn-fi$X: svn-fi.o $(VCSSVN_LIB) $(GIT_LIB)
+	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ svn-fi.o \
+		$(ALL_LDFLAGS) $(LIBS)
+
+svn-fi.o: svn-fi.c ../../vcs-svn/svnload.h
+	$(QUIET_CC)$(CC) -I../../vcs-svn -o $*.o -c $(ALL_CFLAGS) $<
+
+svn-fi.html: svn-fi.txt
+	$(QUIET_SUBDIR0)../../Documentation $(QUIET_SUBDIR1) \
+		MAN_TXT=../contrib/svn-fe/svn-fi.txt \
+		../contrib/svn-fe/$@
+
+svn-fi.1: svn-fi.txt
+	$(QUIET_SUBDIR0)../../Documentation $(QUIET_SUBDIR1) \
+		MAN_TXT=../contrib/svn-fe/svn-fi.txt \
+		../contrib/svn-fe/$@
+	$(MV) ../../Documentation/svn-fi.1 .
+
 ../../vcs-svn/lib.a: FORCE
 	$(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) vcs-svn/lib.a
 
@@ -58,6 +76,7 @@ svn-fe.1: svn-fe.txt
 	$(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) libgit.a
 
 clean:
-	$(RM) svn-fe$X svn-fe.o svn-fe.html svn-fe.xml svn-fe.1
+	$(RM) svn-fe$X svn-fe.o svn-fe.html svn-fe.xml svn-fe.1 \
+	svn-fi$X svn-fi.o svn-fi.html svn-fi.xml svn-fi.1
 
 .PHONY: all clean FORCE
diff --git a/contrib/svn-fe/svn-fi.c b/contrib/svn-fe/svn-fi.c
new file mode 100644
index 0000000..81347b0
--- /dev/null
+++ b/contrib/svn-fe/svn-fi.c
@@ -0,0 +1,16 @@
+/*
+ * This file is in the public domain.
+ * You may freely use, modify, distribute, and relicense it.
+ */
+
+#include <stdlib.h>
+#include "svnload.h"
+
+int main(int argc, char **argv)
+{
+	if (svnload_init(NULL))
+		return 1;
+	svnload_read();
+	svnload_deinit();
+	return 0;
+}
diff --git a/contrib/svn-fe/svn-fi.txt b/contrib/svn-fe/svn-fi.txt
new file mode 100644
index 0000000..996a175
--- /dev/null
+++ b/contrib/svn-fe/svn-fi.txt
@@ -0,0 +1,28 @@
+svn-fe(1)
+=========
+
+NAME
+----
+svn-fi - convert fast-import stream to an SVN "dumpfile"
+
+SYNOPSIS
+--------
+[verse]
+svn-fi
+
+DESCRIPTION
+-----------
+
+Converts a git-fast-import(1) stream into a Subversion dumpfile.
+
+INPUT FORMAT
+-------------
+The fast-import format is documented by the git-fast-import(1)
+manual page.
+
+OUTPUT FORMAT
+------------
+Subversion's repository dump format is documented in full in
+`notes/dump-load-format.txt` from the Subversion source tree.
+Files in this format can be generated using the 'svnadmin dump' or
+'svk admin dump' command.
-- 
1.7.4.rc1.7.g2cf08.dirty

^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 3/5] strbuf: Introduce strbuf_fwrite corresponding to strbuf_fread
  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 18:13 ` Ramkumar Ramachandra
  2011-03-31  2:15   ` Jonathan Nieder
  2011-03-29 18:13 ` [PATCH 4/5] vcs-svn: Introduce svnload, a dumpfile producer Ramkumar Ramachandra
  2011-03-29 18:13 ` [PATCH 5/5] t9012-svn-fi: Add tests for svn-fi Ramkumar Ramachandra
  4 siblings, 1 reply; 9+ messages in thread
From: Ramkumar Ramachandra @ 2011-03-29 18:13 UTC (permalink / raw
  To: Git List
  Cc: Peter Baumann, Jonathan Nieder, David Barr, Sverre Rabbelier,
	Erik Faye-Lund, Junio C Hamano

Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>
---
 strbuf.c |   11 +++++++++++
 strbuf.h |    1 +
 2 files changed, 12 insertions(+), 0 deletions(-)

diff --git a/strbuf.c b/strbuf.c
index 77444a9..bdfd0d0 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -290,6 +290,17 @@ size_t strbuf_fread(struct strbuf *sb, size_t size, FILE *f)
 	return res;
 }
 
+size_t strbuf_fwrite(struct strbuf *sb, size_t size, FILE *f)
+{
+	size_t res;
+
+	if (size > sb->len)
+		res = fwrite(sb->buf, 1, sb->len, f);
+	else 
+		res = fwrite(sb->buf, 1, size, f);
+	return res;
+}
+
 ssize_t strbuf_read(struct strbuf *sb, int fd, size_t hint)
 {
 	size_t oldlen = sb->len;
diff --git a/strbuf.h b/strbuf.h
index f722331..c68361c 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -124,6 +124,7 @@ __attribute__((format (printf,2,0)))
 extern void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list ap);
 
 extern size_t strbuf_fread(struct strbuf *, size_t, FILE *);
+extern size_t strbuf_fwrite(struct strbuf *, size_t, FILE *);
 /* XXX: if read fails, any partial read is undone */
 extern ssize_t strbuf_read(struct strbuf *, int fd, size_t hint);
 extern int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint);
-- 
1.7.4.rc1.7.g2cf08.dirty

^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 4/5] vcs-svn: Introduce svnload, a dumpfile producer
  2011-03-29 18:13 [PATCH 0/5] Towards a Git to SVN bridge Ramkumar Ramachandra
                   ` (2 preceding siblings ...)
  2011-03-29 18:13 ` [PATCH 3/5] strbuf: Introduce strbuf_fwrite corresponding to strbuf_fread Ramkumar Ramachandra
@ 2011-03-29 18:13 ` Ramkumar Ramachandra
  2011-03-29 18:13 ` [PATCH 5/5] t9012-svn-fi: Add tests for svn-fi Ramkumar Ramachandra
  4 siblings, 0 replies; 9+ messages in thread
From: Ramkumar Ramachandra @ 2011-03-29 18:13 UTC (permalink / raw
  To: Git List
  Cc: Peter Baumann, Jonathan Nieder, David Barr, Sverre Rabbelier,
	Erik Faye-Lund, Junio C Hamano

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

^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 5/5] t9012-svn-fi: Add tests for svn-fi
  2011-03-29 18:13 [PATCH 0/5] Towards a Git to SVN bridge Ramkumar Ramachandra
                   ` (3 preceding siblings ...)
  2011-03-29 18:13 ` [PATCH 4/5] vcs-svn: Introduce svnload, a dumpfile producer Ramkumar Ramachandra
@ 2011-03-29 18:13 ` Ramkumar Ramachandra
  2011-03-31 23:23   ` Jonathan Nieder
  4 siblings, 1 reply; 9+ messages in thread
From: Ramkumar Ramachandra @ 2011-03-29 18:13 UTC (permalink / raw
  To: Git List
  Cc: Peter Baumann, Jonathan Nieder, David Barr, Sverre Rabbelier,
	Erik Faye-Lund, Junio C Hamano

Create a test-svn-fi in toplevel directory, add rules to build it, and
add some basic tests.

Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>
---
Verifying svn:date is very non-trivial, because of timezone conversion
issues. I tried fuzzing those out and checking only year and month,
but a timezone shift can cause a change in both of them as
well. Example: 31 Dec 2011 11:45 PM in one timezone can very well be 1
Jan 2012 in another timezone.

More tests? Suggestions for reduced testcases of complex revision
history tasks? I'll probably import some tests from svnrdump in the
next iteration.

 .gitignore        |    1 +
 Makefile          |    3 +
 t/t9012-svn-fi.sh |  705 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 test-svn-fi.c     |   17 ++
 4 files changed, 726 insertions(+), 0 deletions(-)
 create mode 100755 t/t9012-svn-fi.sh
 create mode 100644 test-svn-fi.c

diff --git a/.gitignore b/.gitignore
index c460c66..1cf145d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -181,6 +181,7 @@
 /test-subprocess
 /test-svn-fe
 /test-treap
+/test-svn-fi
 /common-cmds.h
 *.tar.gz
 *.dsc
diff --git a/Makefile b/Makefile
index 7206d35..7fc5a2c 100644
--- a/Makefile
+++ b/Makefile
@@ -435,6 +435,7 @@ TEST_PROGRAMS_NEED_X += test-string-pool
 TEST_PROGRAMS_NEED_X += test-subprocess
 TEST_PROGRAMS_NEED_X += test-svn-fe
 TEST_PROGRAMS_NEED_X += test-treap
+TEST_PROGRAMS_NEED_X += test-svn-fi
 TEST_PROGRAMS_NEED_X += test-index-version
 TEST_PROGRAMS_NEED_X += test-mktemp
 
@@ -2152,6 +2153,8 @@ test-string-pool$X: vcs-svn/lib.a
 
 test-svn-fe$X: vcs-svn/lib.a
 
+test-svn-fi$X: vcs-svn/lib.a
+
 .PRECIOUS: $(TEST_OBJS)
 
 test-%$X: test-%.o $(GITLIBS)
diff --git a/t/t9012-svn-fi.sh b/t/t9012-svn-fi.sh
new file mode 100755
index 0000000..8bc2075
--- /dev/null
+++ b/t/t9012-svn-fi.sh
@@ -0,0 +1,705 @@
+#!/bin/sh
+
+test_description='check svn dumpfile exporter'
+
+. ./test-lib.sh
+
+svnrepo="testsvn"
+
+if svnadmin --help >/dev/null 2>&1
+then
+    load_dump () {
+	svnadmin load "$1"
+    }
+    test_set_prereq SVN
+fi
+
+reinit_svn () {
+	rm -rf "$svnrepo" &&
+	rm -f stream &&
+	svnadmin create "$svnrepo" &&
+	printf "#!/bin/sh" > "$svnrepo"/hooks/pre-revprop-change &&
+	chmod +x "$svnrepo"/hooks/pre-revprop-change &&
+	mkfifo stream
+}
+
+svn_look () {
+	subcommand=$1 &&
+	shift &&
+	svnlook "$subcommand" "$svnrepo" "$@"
+}
+
+try_load () {
+	input=$1 &&
+	maybe_fail=${2:+test_$2} &&
+
+	{
+		$maybe_fail test-svn-fi "$input" >stream &
+	} &&
+	load_dump "$svnrepo" <stream &&
+	wait $!
+}
+
+test_expect_success SVN 'M: regular files' '
+	reinit_svn &&
+	test_tick &&
+	cat >expect.tree <<-\EOF &&
+	/
+	 foo
+	 bar
+	EOF
+	cat >expect.cat.foo <<-\EOF &&
+	nothing
+	EOF
+	cat >expect.cat.bar <<-\EOF &&
+	nothing again
+	EOF
+	cat >input <<-EOF &&
+	reset refs/heads/master
+	commit refs/heads/master
+	mark :1
+	author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	data 9
+	something
+	M 100644 inline foo
+	data 8
+	nothing
+	M 100644 inline bar
+	data 14
+	nothing again
+	EOF
+	try_load input &&
+	svn_look tree >actual.tree &&
+	svn_look cat foo >actual.cat.foo &&
+	svn_look cat bar >actual.cat.bar &&
+	test_cmp expect.tree actual.tree &&
+	test_cmp expect.cat.foo actual.cat.foo &&
+	test_cmp expect.cat.bar actual.cat.bar
+'
+
+test_expect_success SVN 'D: regular files' '
+	reinit_svn &&
+	test_tick &&
+	cat >expect.tree <<-\EOF &&
+	/
+	EOF
+	cat >input <<-EOF &&
+	reset refs/heads/master
+	commit refs/heads/master
+	mark :1
+	author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	data 9
+	something
+	M 100644 inline foo
+	data 7
+	nothing
+	commit refs/heads/master
+	author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	data 0
+	D foo
+	EOF
+	try_load input &&
+	svn_look tree >actual.tree &&
+	test_cmp expect.tree actual.tree
+'
+
+test_expect_success SVN 'D: directories' '
+	reinit_svn &&
+	test_tick &&
+	cat >expect.tree <<-\EOF &&
+	/
+	EOF
+	cat >input <<-EOF &&
+	reset refs/heads/master
+	commit refs/heads/master
+	mark :1
+	author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	data 9
+	something
+	M 100644 inline subdir/foo
+	data 7
+	nothing
+	commit refs/heads/master
+	author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	data 0
+	D subdir
+	EOF
+	try_load input &&
+	svn_look tree >actual.tree &&
+	test_cmp expect.tree actual.tree
+'
+
+test_expect_success SVN 'R: regular files' '
+	reinit_svn &&
+	test_tick &&
+	cat >expect.tree <<-\EOF &&
+	/
+	 bar
+	EOF
+	cat >expect.cat <<-\EOF &&
+	nothing
+	EOF
+	cat >input <<-EOF &&
+	reset refs/heads/master
+	commit refs/heads/master
+	mark :1
+	author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	data 9
+	something
+	M 100644 inline foo
+	data 8
+	nothing
+	commit refs/heads/master
+	author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	data 6
+	rename
+	R foo bar
+	EOF
+	try_load input &&
+	svn_look tree >actual.tree &&
+	svn_look cat bar >actual.cat &&
+	test_cmp expect.tree actual.tree &&
+	test_cmp expect.cat actual.cat
+'
+
+test_expect_success SVN 'R: directories' '
+	reinit_svn &&
+	test_tick &&
+	cat >expect.tree <<-\EOF &&
+	/
+	 subdir2/
+	  foo
+	  bar
+	EOF
+	cat >expect.cat.foo <<-\EOF &&
+	nothing
+	EOF
+	cat >expect.cat.bar <<-\EOF &&
+	nothing again
+	EOF
+	cat >input <<-EOF &&
+	reset refs/heads/master
+	commit refs/heads/master
+	mark :1
+	author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	data 9
+	something
+	M 100644 inline subdir/foo
+	data 8
+	nothing
+	M 100644 inline subdir/bar
+	data 14
+	nothing again
+	commit refs/heads/master
+	author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	data 12
+	replace root
+	R subdir subdir2
+	EOF
+	try_load input &&
+	svn_look tree >actual.tree &&
+	svn_look cat subdir2/foo >actual.cat.foo &&
+	svn_look cat subdir2/bar >actual.cat.bar &&
+	test_cmp expect.tree actual.tree &&
+	test_cmp expect.cat.foo actual.cat.foo &&
+	test_cmp expect.cat.bar actual.cat.bar
+'
+
+test_expect_success SVN 'C: regular files' '
+	reinit_svn &&
+	test_tick &&
+	cat >expect.tree <<-\EOF &&
+	/
+	 foo
+	 bar
+	EOF
+	cat >expect.cat <<-\EOF &&
+	nothing
+	EOF
+	cat >input <<-EOF &&
+	reset refs/heads/master
+	commit refs/heads/master
+	mark :1
+	author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	data 9
+	something
+	M 100644 inline foo
+	data 8
+	nothing
+	commit refs/heads/master
+	author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	data 6
+	rename
+	C foo bar
+	EOF
+	try_load input &&
+	svn_look tree >actual.tree &&
+	svn_look cat foo >actual.cat.foo &&
+	svn_look cat bar >actual.cat.bar &&
+	test_cmp expect.tree actual.tree &&
+	test_cmp expect.cat actual.cat.foo &&
+	test_cmp expect.cat actual.cat.bar
+'
+
+test_expect_success SVN 'C: directories' '
+	reinit_svn &&
+	test_tick &&
+	cat >expect.tree <<-\EOF &&
+	/
+	 subdir/
+	  foo
+	  bar
+	 subdir2/
+	  foo
+	  bar
+	EOF
+	cat >expect.cat.foo <<-\EOF &&
+	nothing
+	EOF
+	cat >expect.cat.bar <<-\EOF &&
+	nothing again
+	EOF
+	cat >input <<-EOF &&
+	reset refs/heads/master
+	commit refs/heads/master
+	mark :1
+	author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	data 9
+	something
+	M 100644 inline subdir/foo
+	data 8
+	nothing
+	M 100644 inline subdir/bar
+	data 14
+	nothing again
+	commit refs/heads/master
+	author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	data 12
+	replace root
+	C subdir subdir2
+	EOF
+	try_load input &&
+	svn_look tree >actual.tree &&
+	svn_look cat subdir2/foo >actual.cat.foo &&
+	svn_look cat subdir2/bar >actual.cat.bar &&
+	test_cmp expect.tree actual.tree &&
+	test_cmp expect.cat.foo actual.cat.foo &&
+	test_cmp expect.cat.bar actual.cat.bar
+'
+
+test_expect_success SVN 'ignore checkpoint, progress, feature, option' '
+	reinit_svn &&
+	test_tick &&
+	cat >expect.tree <<-\EOF &&
+	/
+	EOF
+	cat >input <<-EOF &&
+	checkpoint
+	progress 3
+	feature foo
+	option bar
+	EOF
+	try_load input &&
+	svn_look tree >actual.tree &&
+	test_cmp expect.tree actual.tree
+'
+
+test_expect_success SVN 'ignore tag' '
+	reinit_svn &&
+	test_tick &&
+	cat >expect.tree <<-\EOF &&
+	/
+	 foo
+	EOF
+	cat >input <<-EOF &&
+	reset refs/heads/master
+	commit refs/heads/master
+	mark :1
+	author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	data 9
+	something
+	M 644 inline foo
+	data 0
+	tag moo
+	from master
+	tagger <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	data 8
+	nothing
+	tag bar
+	from master
+	tagger $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	data 10
+	something
+	EOF
+	try_load input &&
+	svn_look tree >actual.tree &&
+	test_cmp expect.tree actual.tree
+'
+
+test_expect_success SVN 'ignore notemodify' '
+	reinit_svn &&
+	test_tick &&
+	cat >expect.tree <<-\EOF &&
+	/
+	EOF
+	cat >input <<-EOF &&
+	reset refs/heads/master
+	commit refs/heads/master
+	mark :1
+	author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	data 9
+	something
+	N moo :1
+	data 8
+	nothing
+	EOF
+	try_load input &&
+	svn_look tree >actual.tree &&
+	test_cmp expect.tree actual.tree
+'
+
+test_expect_success SVN 'svn:special and svn:executable' '
+	reinit_svn &&
+	test_tick &&
+	cat >input <<-EOF &&
+	reset refs/heads/master
+	commit refs/heads/master
+	mark :1
+	author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	data 7
+	nothing
+	M 644 inline baz
+	data 0
+	M 100755 inline foo
+	data 0
+	M 755 inline moo
+	data 0
+	M 120000 inline bar
+	data 0
+	EOF
+	try_load input &&
+	svn_look propget svn:executable foo &&
+	svn_look propget svn:executable moo &&
+	svn_look propget svn:special bar
+'
+
+test_expect_success SVN 'invalid toplevel command' '
+	reinit_svn &&
+	test_tick &&
+	cat >input <<-EOF &&
+	reset refs/heads/master
+	commit refs/heads/master
+	marks :1
+	author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	data 0
+	M 100644 inline foo
+	data 0
+	EOF
+	test_must_fail try_load input
+'
+
+test_expect_success SVN 'invalid command after commit' '
+	reinit_svn &&
+	test_tick &&
+	cat >input <<-EOF &&
+	reset refs/heads/master
+	commit refs/heads/master
+	mark :1
+	author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	datum 0
+	M 100644 inline foo
+	data 0
+	EOF
+	test_must_fail try_load input
+'
+
+test_expect_success SVN 'empty log, empty file' '
+	reinit_svn &&
+	test_tick &&
+	cat >expect.tree <<-\EOF &&
+	/
+	 foo
+	EOF
+	cat >input <<-EOF &&
+	reset refs/heads/master
+	commit refs/heads/master
+	mark :1
+	author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	data 0
+	M 100644 inline foo
+	data 0
+	EOF
+	try_load input &&
+	svn_look tree >actual.tree &&
+	test_cmp expect.tree actual.tree
+'
+
+test_expect_success SVN 'missing lf after data' '
+	reinit_svn &&
+	test_tick &&
+	cat >expect.log <<-\EOF &&
+	something
+	EOF
+	cat >input <<-EOF &&
+	reset refs/heads/master
+	commit refs/heads/master
+	mark :1
+	author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	data 9
+	somethingM 100644 inline foo
+	data 0
+	EOF
+	try_load input &&
+	svn_look log >actual.log &&
+	test_cmp expect.log actual.log
+'
+
+test_expect_success SVN 'revprops: svn:author, svn:log' '
+	reinit_svn &&
+	test_tick &&
+	echo "nothing" >expect.log &&
+	echo "author" >expect.author &&
+	cat >input <<-EOF &&
+	reset refs/heads/master
+	commit refs/heads/master
+	mark :1
+	author $GIT_AUTHOR_NAME <author@example.com> $GIT_AUTHOR_DATE
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	data 7
+	nothing
+	M 100644 inline foo
+	data 0
+	EOF
+	try_load input &&
+	svn_look log >actual.log &&
+	svn_look author >actual.author &&
+	test_cmp expect.log actual.log &&
+	test_cmp expect.author actual.author
+'
+
+test_expect_success SVN 'missing author line' '
+	reinit_svn &&
+	test_tick &&
+	cat >expect.tree <<-\EOF &&
+	/
+	 foo
+	EOF
+	cat >input <<-EOF &&
+	reset refs/heads/master
+	commit refs/heads/master
+	mark :1
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	data 0
+	M 100644 inline foo
+	data 0
+	EOF
+	try_load input &&
+	svn_look tree >actual.tree &&
+	test_cmp expect.tree actual.tree
+'
+
+test_expect_success SVN 'author email without @' '
+	reinit_svn &&
+	test_tick &&
+	cat >input <<-EOF &&
+	reset refs/heads/master
+	commit refs/heads/master
+	mark :1
+	author $GIT_AUTHOR_NAME <example> $GIT_AUTHOR_DATE
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	data 0
+	M 100644 inline foo
+	data 0
+	EOF
+	try_load input
+'
+
+test_expect_success SVN 'blob marks unsupported' '
+	reinit_svn &&
+	test_tick &&
+	cat >input <<-EOF &&
+	blob
+	mark :1
+	data 0
+	reset refs/heads/master
+	commit refs/heads/master
+	mark :2
+	author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	data 0
+	M 100644 :1 foo
+	data 0
+	EOF
+	test_must_fail try_load input
+'
+
+test_expect_success SVN 'malformed filemodify line' '
+	reinit_svn &&
+	test_tick &&
+	cat >input <<-EOF &&
+	reset refs/heads/master
+	commit refs/heads/master
+	mark :1
+	author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	data 0
+	M 100644 inline
+	data 0
+	EOF
+	test_must_fail try_load input
+'
+
+test_expect_success SVN 'malformed author line' '
+	reinit_svn &&
+	test_tick &&
+	cat >input <<-EOF &&
+	reset refs/heads/master
+	commit refs/heads/master
+	mark :1
+	author 2d3%*s&f#k|
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	data 0
+	M 100644 inline foo
+	data 0
+	EOF
+	test_must_fail try_load input
+'
+
+test_expect_success SVN 'missing committer line' '
+	reinit_svn &&
+	test_tick &&
+	cat >input <<-EOF &&
+	reset refs/heads/master
+	commit refs/heads/master
+	mark :1
+	author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	data 0
+	M 100644 inline foo
+	data 0
+	EOF
+	test_must_fail try_load input
+'
+
+test_expect_success SVN 'malformed data length' '
+	reinit_svn &&
+	test_tick &&
+	cat >input <<-EOF &&
+	reset refs/heads/master
+	commit refs/heads/master
+	mark :1
+	author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_AUTHOR_DATE
+	data 0
+	M 100644 inline foo
+	data 14238
+	EOF
+	test_must_fail try_load input
+'
+
+test_expect_success SVN 'recursive directory creation' '
+	reinit_svn &&
+	test_tick &&
+	cat >expect.tree <<-\EOF &&
+	/
+	 alpha/
+	  beta/
+	   gamma
+	EOF
+	cat >input <<-EOF &&
+	reset refs/heads/master
+	commit refs/heads/master
+	mark :1
+	author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	data 7
+	nothing
+	M 100644 inline alpha/beta/gamma
+	data 12
+	some content
+	EOF
+	try_load input &&
+	svn_look tree >actual.tree &&
+	test_cmp expect.tree actual.tree
+'
+
+test_expect_success SVN 'replace symlink with normal file' '
+	reinit_svn &&
+	test_tick &&
+	cat >expect.tree <<-\EOF &&
+	/
+	 alpha/
+	  beta/
+	   gamma
+	EOF
+	cat >input <<-EOF &&
+	reset refs/heads/master
+	commit refs/heads/master
+	mark :1
+	author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	data 7
+	nothing
+	M 120000 inline alpha/beta/gamma
+	data 0
+	commit refs/heads/master
+	mark :1
+	author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	data 7
+	nothing
+	M 100644 inline alpha/beta/gamma
+	data 0
+	EOF
+	try_load input &&
+	svn_look tree -r1 >actual.tree1 &&
+	svn_look tree -r2 >actual.tree2 &&
+	test_cmp expect.tree actual.tree1 &&
+	test_cmp expect.tree actual.tree2
+'
+
+test_expect_success SVN 'path includes symlink' '
+	reinit_svn &&
+	test_tick &&
+	cat >input <<-EOF &&
+	reset refs/heads/master
+	commit refs/heads/master
+	mark :1
+	author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	data 7
+	nothing
+	M 120000 inline alpha/beta/gamma
+	data 0
+	commit refs/heads/master
+	mark :1
+	author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
+	committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+	data 7
+	nothing
+	M 100644 inline alpha/beta/gamma/bar
+	data 0
+	EOF
+	test_must_fail try_load input
+'
+
+test_done
diff --git a/test-svn-fi.c b/test-svn-fi.c
new file mode 100644
index 0000000..cc6b312
--- /dev/null
+++ b/test-svn-fi.c
@@ -0,0 +1,17 @@
+/*
+ * test-svn-fe: Code to exercise the svn import lib
+ */
+
+#include "git-compat-util.h"
+#include "vcs-svn/svnload.h"
+
+int main(int argc, char *argv[])
+{
+	if (argc != 2)
+		usage("test-svn-fe <file>");
+	if (svnload_init(argv[1]))
+		return 1;
+	svnload_read();
+	svnload_deinit();
+	return 0;
+}
-- 
1.7.4.rc1.7.g2cf08.dirty

^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH 2/5] fast-export: Introduce --inline-blobs
  2011-03-29 18:13 ` [PATCH 2/5] fast-export: Introduce --inline-blobs Ramkumar Ramachandra
@ 2011-03-29 20:44   ` Jonathan Nieder
  0 siblings, 0 replies; 9+ messages in thread
From: Jonathan Nieder @ 2011-03-29 20:44 UTC (permalink / raw
  To: Ramkumar Ramachandra
  Cc: Git List, Peter Baumann, David Barr, Sverre Rabbelier,
	Erik Faye-Lund, Junio C Hamano

Hi,

Ramkumar Ramachandra wrote:

> Introduce a new command-line option --inline-blobs that always inlines
> blobs instead of referring to them via marks or their original SHA-1
> hash.

Could you expand on this?  What does it mean to inline a blob (is it the
same thing as the reference manual describes as using the inline data
format with the filemodify command)?  Why do we want to always do
that?  How would a person or script choose whether to use this option?
Are there any downsides?

I ask because I would be happy to use something like this. ;-)  Thanks
for working on it.

> --- a/Documentation/git-fast-export.txt
> +++ b/Documentation/git-fast-export.txt
> @@ -90,6 +90,11 @@ marks the same across runs.
>  	resulting stream can only be used by a repository which
>  	already contains the necessary objects.
>  
> +--inline-blobs::
> +	Inline all blobs, instead of referring to them via marks or
> +	their original SHA-1 hash.  This is useful to parsers, as they
> +	don't need to persist blobs.

This explanation leaves something out, I think.  If it is useful to
parsers, that means it simplifies the syntax, so a natural conclusion
would be that parsers do not need to learn about the non-inline
syntax.  But I think the last time[1] this came up, we decided that
one should not encourage that, because it moves away from a world in
which "git fast-export", "hg fast-export", and svn-fe use one standard
format and can be used interchangeably.

[1] http://thread.gmane.org/gmane.comp.version-control.git/165237/focus=165289

Perhaps it would be better to say something to the effect that "This
can decrease the memory footprint and complexity of the work some
fast-import backends have to do"?  In other words, it's just an
optimization.

To that end, if the same blob keeps on coming up again and again, I'd
be tempted to allow making a mark for it in the future, even when
--inline-blobs is specified.  In other words, I'd prefer (unless
real-world considerations prevent it) for --inline-blobs to be a hint
or a permission instead of something binding.

> --- a/builtin/fast-export.c
> +++ b/builtin/fast-export.c
[...]
> @@ -118,7 +119,7 @@ static void handle_object(const unsigned char *sha1)
>  	char *buf;
>  	struct object *object;
>  
> -	if (no_data)
> +	if (no_data || inline_blobs)
>  		return;

Maybe inline_blobs should imply no_data internally?

>  
>  	if (is_null_sha1(sha1))
> @@ -218,7 +219,23 @@ static void show_filemodify(struct diff_queue_struct *q,
>  			if (no_data || S_ISGITLINK(spec->mode))
>  				printf("M %06o %s %s\n", spec->mode,
>  				       sha1_to_hex(spec->sha1), spec->path);
> -			else {
> +			else if (inline_blobs) {

If so, this could be something like

	int inline_me = inline_blobs && !S_ISGITLINK(spec->mode);
	...

	if (no_data || S_ISGITLINK(spec->mode)) {
		const char *dataref =
			inline_me ? "inline" : sha1_to_hex(spec->sha1);
		printf("M %06o %s %s\n", spec->mode, dataref, spec->path);
	} else {
		struct object *object = lookup_object(spec->sha1);
		printf("M %06o :%d %s\n", spec->mode,
		       get_object_mark(object), spec->path);
	}

	if (inline_blob && export_data(spec->data, spec->size))
		die_errno("Could not write blob '%s'",
				sha1_to_hex(spec->sha1));

> --- a/contrib/svn-fe/.gitignore
> +++ b/contrib/svn-fe/.gitignore
[...]
> --- a/contrib/svn-fe/Makefile
> +++ b/contrib/svn-fe/Makefile
[...]
> --- /dev/null
> +++ b/contrib/svn-fe/svn-fi.c
[...]
> --- /dev/null
> +++ b/contrib/svn-fe/svn-fi.txt

Snuck in from another patch?

Hope that helps,
Jonathan

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 3/5] strbuf: Introduce strbuf_fwrite corresponding to strbuf_fread
  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
  0 siblings, 0 replies; 9+ messages in thread
From: Jonathan Nieder @ 2011-03-31  2:15 UTC (permalink / raw
  To: Ramkumar Ramachandra
  Cc: Git List, Peter Baumann, David Barr, Sverre Rabbelier,
	Erik Faye-Lund, Junio C Hamano

Ramkumar Ramachandra wrote:

> [Subject: strbuf: Introduce strbuf_fwrite corresponding to strbuf_fread]
>
> Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>

And why?  FWIW strbuf_fread takes care of (1) setting the size of sb
to accomodate the input, (2) starting reading at the end of the
current strbuf, and (3) freeing the buffer if nothing could be read,
so symmetry doesn't seem like a strong justification.

Which is not to say this is a bad change.  Just that it's hard to see
how it lives up to its goals without knowing what those goals are.

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 5/5] t9012-svn-fi: Add tests for svn-fi
  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
  0 siblings, 0 replies; 9+ messages in thread
From: Jonathan Nieder @ 2011-03-31 23:23 UTC (permalink / raw
  To: Ramkumar Ramachandra
  Cc: Git List, Peter Baumann, David Barr, Sverre Rabbelier,
	Erik Faye-Lund, Junio C Hamano

Ramkumar Ramachandra wrote:

> Verifying svn:date is very non-trivial, because of timezone conversion
> issues.

Maybe the TZ variable can help.

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2011-03-31 23:23 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 ` [PATCH 4/5] vcs-svn: Introduce svnload, a dumpfile producer Ramkumar Ramachandra
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

Code repositories for project(s) associated with this public inbox

	https://80x24.org/mirrors/git.git

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 read-only IMAP folder(s) and NNTP newsgroup(s).