{"thread":{"id":"26913","subject":"[PATCH 0/5] Towards a Git to SVN bridge","startedAt":"2011-03-29T18:13:07Z","lastAt":"2011-03-31T23:23:07Z","messageCount":9,"participants":["Ramkumar Ramachandra","Jonathan Nieder"],"isPatch":true,"patchVersion":1,"patchTotal":5},"messages":[{"id":"164594","messageId":"1301422392-21177-1-git-send-email-artagnon@gmail.com","threadId":"26913","inReplyTo":null,"subject":"[PATCH 0/5] Towards a Git to SVN bridge","fromName":"Ramkumar Ramachandra","fromEmail":"artagnon@gmail.com","sentAt":"2011-03-29T18:13:07Z","receivedAt":"2011-03-29T18:13:07Z","isPatch":true,"sender":{"key":"r@artagnon.com","avatar":"https://avatars.githubusercontent.com/u/37226?v=4"},"body":"Hi,\n\nThanks to Jonathan, Junio, Peter, and Erik among others for reviewing\nprevious iterations of this series. This iteration should take care of\nmost error handling and design issues addressed in the previous\niterations- it also comes along with a battery of tests.\n\nSmall introduction: svn-fi's design is modelled after git-fast-import.\nIt aims to be more comprehensive and production-ready than the\nexisting alternative, git2svn.  It converts a fast-import stream\nto a Subversion dumpfile which can be loaded using `svnadmin load` (or\nalternatively using `svnrdump load`, which will feature in Subversion\n1.7).  The shortcomings can be summarized as follows:\n1. dir_cache is a very naive implementation using string_list.  This\nwill not scale well either in memory or time, and should be replaced\nwith a prefix tree in future.\n2. It currently only supports one branch. For supporting multiple\nbranches, two things need to be implemented: a branch-specific\ndir_cache, and a mapper that maps branch names to directories (if the\nstandard \"trunk\", \"branches\", \"tags\" layout is not to be hardcoded).\n3. Tags are currently unsupported, although it should be trivial to\nimplement once a mapping is in place.\n4. Merges are unsupported.  This requires more thought.\n5. The `from` command in the fast-import stream is ignored.  This\nshould probably be implemented like branching.\n6. The data can't make round trips back-and-fourth yet.  Although this\ntopic has been discussed at length, there is no implementation of this\nyet.\n\nThanks for reading.\n\n-- Ram\n\nRamkumar Ramachandra (5):\n  date: Expose the time_to_tm function\n  fast-export: Introduce --inline-blobs\n  strbuf: Introduce strbuf_fwrite corresponding to strbuf_fread\n  vcs-svn: Introduce svnload, a dumpfile producer\n  t9012-svn-fi: Add tests for svn-fi\n\n .gitignore                        |    1 +\n Documentation/git-fast-export.txt |    5 +\n Makefile                          |    6 +-\n builtin/fast-export.c             |   23 ++-\n cache.h                           |    1 +\n contrib/svn-fe/.gitignore         |    1 +\n contrib/svn-fe/Makefile           |   23 ++-\n contrib/svn-fe/svn-fi.c           |   16 +\n contrib/svn-fe/svn-fi.txt         |   28 ++\n date.c                            |    2 +-\n strbuf.c                          |   11 +\n strbuf.h                          |    1 +\n t/t9012-svn-fi.sh                 |  705 +++++++++++++++++++++++++++++++++++++\n test-svn-fi.c                     |   17 +\n vcs-svn/dir_cache.c               |   52 +++\n vcs-svn/dir_cache.h               |   11 +\n vcs-svn/dump_export.c             |  164 +++++++++\n vcs-svn/dump_export.h             |   26 ++\n vcs-svn/svnload.c                 |  485 +++++++++++++++++++++++++\n vcs-svn/svnload.h                 |   19 +\n 20 files changed, 1591 insertions(+), 6 deletions(-)\n create mode 100644 contrib/svn-fe/svn-fi.c\n create mode 100644 contrib/svn-fe/svn-fi.txt\n create mode 100755 t/t9012-svn-fi.sh\n create mode 100644 test-svn-fi.c\n create mode 100644 vcs-svn/dir_cache.c\n create mode 100644 vcs-svn/dir_cache.h\n create mode 100644 vcs-svn/dump_export.c\n create mode 100644 vcs-svn/dump_export.h\n create mode 100644 vcs-svn/svnload.c\n create mode 100644 vcs-svn/svnload.h\n\n-- \n1.7.4.rc1.7.g2cf08.dirty\n"},{"id":"164595","messageId":"1301422392-21177-2-git-send-email-artagnon@gmail.com","threadId":"26913","inReplyTo":"1301422392-21177-1-git-send-email-artagnon@gmail.com","subject":"[PATCH 1/5] date: Expose the time_to_tm function","fromName":"Ramkumar Ramachandra","fromEmail":"artagnon@gmail.com","sentAt":"2011-03-29T18:13:08Z","receivedAt":"2011-03-29T18:13:08Z","isPatch":true,"sender":{"key":"r@artagnon.com","avatar":"https://avatars.githubusercontent.com/u/37226?v=4"},"body":"Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>\n---\n cache.h |    1 +\n date.c  |    2 +-\n 2 files changed, 2 insertions(+), 1 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex f765cf5..76a14c6 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -834,6 +834,7 @@ enum date_mode {\n \tDATE_RAW\n };\n \n+struct tm *time_to_tm(unsigned long time, int tz);\n const char *show_date(unsigned long time, int timezone, enum date_mode mode);\n const char *show_date_relative(unsigned long time, int tz,\n \t\t\t       const struct timeval *now,\ndiff --git a/date.c b/date.c\nindex 00f9eb5..e601a50 100644\n--- a/date.c\n+++ b/date.c\n@@ -54,7 +54,7 @@ static time_t gm_time_t(unsigned long time, int tz)\n  * thing, which means that tz -0100 is passed in as the integer -100,\n  * even though it means \"sixty minutes off\"\n  */\n-static struct tm *time_to_tm(unsigned long time, int tz)\n+struct tm *time_to_tm(unsigned long time, int tz)\n {\n \ttime_t t = gm_time_t(time, tz);\n \treturn gmtime(&t);\n-- \n1.7.4.rc1.7.g2cf08.dirty\n"},{"id":"164596","messageId":"1301422392-21177-3-git-send-email-artagnon@gmail.com","threadId":"26913","inReplyTo":"1301422392-21177-1-git-send-email-artagnon@gmail.com","subject":"[PATCH 2/5] fast-export: Introduce --inline-blobs","fromName":"Ramkumar Ramachandra","fromEmail":"artagnon@gmail.com","sentAt":"2011-03-29T18:13:09Z","receivedAt":"2011-03-29T18:13:09Z","isPatch":true,"sender":{"key":"r@artagnon.com","avatar":"https://avatars.githubusercontent.com/u/37226?v=4"},"body":"Introduce a new command-line option --inline-blobs that always inlines\nblobs instead of referring to them via marks or their original SHA-1\nhash.\n\nSigned-off-by: Ramkumar Ramachandra <artagnon@gmail.com>\n---\n Documentation/git-fast-export.txt |    5 +++++\n builtin/fast-export.c             |   23 +++++++++++++++++++++--\n contrib/svn-fe/.gitignore         |    1 +\n contrib/svn-fe/Makefile           |   23 +++++++++++++++++++++--\n contrib/svn-fe/svn-fi.c           |   16 ++++++++++++++++\n contrib/svn-fe/svn-fi.txt         |   28 ++++++++++++++++++++++++++++\n 6 files changed, 92 insertions(+), 4 deletions(-)\n create mode 100644 contrib/svn-fe/svn-fi.c\n create mode 100644 contrib/svn-fe/svn-fi.txt\n\ndiff --git a/Documentation/git-fast-export.txt b/Documentation/git-fast-export.txt\nindex 781bd6e..363e768 100644\n--- a/Documentation/git-fast-export.txt\n+++ b/Documentation/git-fast-export.txt\n@@ -90,6 +90,11 @@ marks the same across runs.\n \tresulting stream can only be used by a repository which\n \talready contains the necessary objects.\n \n+--inline-blobs::\n+\tInline all blobs, instead of referring to them via marks or\n+\ttheir original SHA-1 hash.  This is useful to parsers, as they\n+\tdon't need to persist blobs.\n+\n --full-tree::\n \tThis option will cause fast-export to issue a \"deleteall\"\n \tdirective for each commit followed by a full list of all files\ndiff --git a/builtin/fast-export.c b/builtin/fast-export.c\nindex daf1945..891ad5c 100644\n--- a/builtin/fast-export.c\n+++ b/builtin/fast-export.c\n@@ -27,6 +27,7 @@ static enum { ABORT, VERBATIM, WARN, STRIP } signed_tag_mode = ABORT;\n static enum { ERROR, DROP, REWRITE } tag_of_filtered_mode = ABORT;\n static int fake_missing_tagger;\n static int no_data;\n+static int inline_blobs;\n static int full_tree;\n \n static int parse_opt_signed_tag_mode(const struct option *opt,\n@@ -118,7 +119,7 @@ static void handle_object(const unsigned char *sha1)\n \tchar *buf;\n \tstruct object *object;\n \n-\tif (no_data)\n+\tif (no_data || inline_blobs)\n \t\treturn;\n \n \tif (is_null_sha1(sha1))\n@@ -218,7 +219,23 @@ static void show_filemodify(struct diff_queue_struct *q,\n \t\t\tif (no_data || S_ISGITLINK(spec->mode))\n \t\t\t\tprintf(\"M %06o %s %s\\n\", spec->mode,\n \t\t\t\t       sha1_to_hex(spec->sha1), spec->path);\n-\t\t\telse {\n+\t\t\telse if (inline_blobs) {\n+\t\t\t\tunsigned long size;\n+\t\t\t\tenum object_type type;\n+\t\t\t\tchar *buf;\n+\n+\t\t\t\tbuf = read_sha1_file(spec->sha1, &type, &size);\n+\t\t\t\tif (!buf)\n+\t\t\t\t\tdie(\"Could not read blob %s\",\n+\t\t\t\t\t\tsha1_to_hex(spec->sha1));\n+\t\t\t\tprintf(\"M %06o inline %s\\n\", spec->mode, spec->path);\n+\t\t\t\tprintf(\"data %lu\\n\", size);\n+\t\t\t\tif (size && fwrite(buf, size, 1, stdout) != 1)\n+\t\t\t\t\tdie_errno(\"Could not write blob '%s'\",\n+\t\t\t\t\t\tsha1_to_hex(spec->sha1));\n+\t\t\t\tprintf(\"\\n\");\n+\n+\t\t\t} else {\n \t\t\t\tstruct object *object = lookup_object(spec->sha1);\n \t\t\t\tprintf(\"M %06o :%d %s\\n\", spec->mode,\n \t\t\t\t       get_object_mark(object), spec->path);\n@@ -627,6 +644,8 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)\n \t\t\t     \"Fake a tagger when tags lack one\"),\n \t\tOPT_BOOLEAN(0, \"full-tree\", &full_tree,\n \t\t\t     \"Output full tree for each commit\"),\n+\t\tOPT_BOOLEAN(0, \"inline-blobs\", &inline_blobs,\n+\t\t\t     \"Output all blobs inline\"),\n \t\t{ OPTION_NEGBIT, 0, \"data\", &no_data, NULL,\n \t\t\t\"Skip output of blob data\",\n \t\t\tPARSE_OPT_NOARG | PARSE_OPT_NEGHELP, NULL, 1 },\ndiff --git a/contrib/svn-fe/.gitignore b/contrib/svn-fe/.gitignore\nindex 02a7791..35f205d 100644\n--- a/contrib/svn-fe/.gitignore\n+++ b/contrib/svn-fe/.gitignore\n@@ -2,3 +2,4 @@\n /*.1\n /*.html\n /svn-fe\n+/svn-fi\ndiff --git a/contrib/svn-fe/Makefile b/contrib/svn-fe/Makefile\nindex 360d8da..555a8ff 100644\n--- a/contrib/svn-fe/Makefile\n+++ b/contrib/svn-fe/Makefile\n@@ -37,7 +37,7 @@ svn-fe$X: svn-fe.o $(VCSSVN_LIB) $(GIT_LIB)\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ svn-fe.o \\\n \t\t$(ALL_LDFLAGS) $(LIBS)\n \n-svn-fe.o: svn-fe.c ../../vcs-svn/svndump.h\n+svn-fe.o: svn-fe.c ../../vcs-svn/svnload.h\n \t$(QUIET_CC)$(CC) -I../../vcs-svn -o $*.o -c $(ALL_CFLAGS) $<\n \n svn-fe.html: svn-fe.txt\n@@ -51,6 +51,24 @@ svn-fe.1: svn-fe.txt\n \t\t../contrib/svn-fe/$@\n \t$(MV) ../../Documentation/svn-fe.1 .\n \n+svn-fi$X: svn-fi.o $(VCSSVN_LIB) $(GIT_LIB)\n+\t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ svn-fi.o \\\n+\t\t$(ALL_LDFLAGS) $(LIBS)\n+\n+svn-fi.o: svn-fi.c ../../vcs-svn/svnload.h\n+\t$(QUIET_CC)$(CC) -I../../vcs-svn -o $*.o -c $(ALL_CFLAGS) $<\n+\n+svn-fi.html: svn-fi.txt\n+\t$(QUIET_SUBDIR0)../../Documentation $(QUIET_SUBDIR1) \\\n+\t\tMAN_TXT=../contrib/svn-fe/svn-fi.txt \\\n+\t\t../contrib/svn-fe/$@\n+\n+svn-fi.1: svn-fi.txt\n+\t$(QUIET_SUBDIR0)../../Documentation $(QUIET_SUBDIR1) \\\n+\t\tMAN_TXT=../contrib/svn-fe/svn-fi.txt \\\n+\t\t../contrib/svn-fe/$@\n+\t$(MV) ../../Documentation/svn-fi.1 .\n+\n ../../vcs-svn/lib.a: FORCE\n \t$(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) vcs-svn/lib.a\n \n@@ -58,6 +76,7 @@ svn-fe.1: svn-fe.txt\n \t$(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) libgit.a\n \n clean:\n-\t$(RM) svn-fe$X svn-fe.o svn-fe.html svn-fe.xml svn-fe.1\n+\t$(RM) svn-fe$X svn-fe.o svn-fe.html svn-fe.xml svn-fe.1 \\\n+\tsvn-fi$X svn-fi.o svn-fi.html svn-fi.xml svn-fi.1\n \n .PHONY: all clean FORCE\ndiff --git a/contrib/svn-fe/svn-fi.c b/contrib/svn-fe/svn-fi.c\nnew file mode 100644\nindex 0000000..81347b0\n--- /dev/null\n+++ b/contrib/svn-fe/svn-fi.c\n@@ -0,0 +1,16 @@\n+/*\n+ * This file is in the public domain.\n+ * You may freely use, modify, distribute, and relicense it.\n+ */\n+\n+#include <stdlib.h>\n+#include \"svnload.h\"\n+\n+int main(int argc, char **argv)\n+{\n+\tif (svnload_init(NULL))\n+\t\treturn 1;\n+\tsvnload_read();\n+\tsvnload_deinit();\n+\treturn 0;\n+}\ndiff --git a/contrib/svn-fe/svn-fi.txt b/contrib/svn-fe/svn-fi.txt\nnew file mode 100644\nindex 0000000..996a175\n--- /dev/null\n+++ b/contrib/svn-fe/svn-fi.txt\n@@ -0,0 +1,28 @@\n+svn-fe(1)\n+=========\n+\n+NAME\n+----\n+svn-fi - convert fast-import stream to an SVN \"dumpfile\"\n+\n+SYNOPSIS\n+--------\n+[verse]\n+svn-fi\n+\n+DESCRIPTION\n+-----------\n+\n+Converts a git-fast-import(1) stream into a Subversion dumpfile.\n+\n+INPUT FORMAT\n+-------------\n+The fast-import format is documented by the git-fast-import(1)\n+manual page.\n+\n+OUTPUT FORMAT\n+------------\n+Subversion's repository dump format is documented in full in\n+`notes/dump-load-format.txt` from the Subversion source tree.\n+Files in this format can be generated using the 'svnadmin dump' or\n+'svk admin dump' command.\n-- \n1.7.4.rc1.7.g2cf08.dirty\n"},{"id":"164597","messageId":"1301422392-21177-4-git-send-email-artagnon@gmail.com","threadId":"26913","inReplyTo":"1301422392-21177-1-git-send-email-artagnon@gmail.com","subject":"[PATCH 3/5] strbuf: Introduce strbuf_fwrite corresponding to strbuf_fread","fromName":"Ramkumar Ramachandra","fromEmail":"artagnon@gmail.com","sentAt":"2011-03-29T18:13:10Z","receivedAt":"2011-03-29T18:13:10Z","isPatch":true,"sender":{"key":"r@artagnon.com","avatar":"https://avatars.githubusercontent.com/u/37226?v=4"},"body":"Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>\n---\n strbuf.c |   11 +++++++++++\n strbuf.h |    1 +\n 2 files changed, 12 insertions(+), 0 deletions(-)\n\ndiff --git a/strbuf.c b/strbuf.c\nindex 77444a9..bdfd0d0 100644\n--- a/strbuf.c\n+++ b/strbuf.c\n@@ -290,6 +290,17 @@ size_t strbuf_fread(struct strbuf *sb, size_t size, FILE *f)\n \treturn res;\n }\n \n+size_t strbuf_fwrite(struct strbuf *sb, size_t size, FILE *f)\n+{\n+\tsize_t res;\n+\n+\tif (size > sb->len)\n+\t\tres = fwrite(sb->buf, 1, sb->len, f);\n+\telse \n+\t\tres = fwrite(sb->buf, 1, size, f);\n+\treturn res;\n+}\n+\n ssize_t strbuf_read(struct strbuf *sb, int fd, size_t hint)\n {\n \tsize_t oldlen = sb->len;\ndiff --git a/strbuf.h b/strbuf.h\nindex f722331..c68361c 100644\n--- a/strbuf.h\n+++ b/strbuf.h\n@@ -124,6 +124,7 @@ __attribute__((format (printf,2,0)))\n extern void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list ap);\n \n extern size_t strbuf_fread(struct strbuf *, size_t, FILE *);\n+extern size_t strbuf_fwrite(struct strbuf *, size_t, FILE *);\n /* XXX: if read fails, any partial read is undone */\n extern ssize_t strbuf_read(struct strbuf *, int fd, size_t hint);\n extern int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint);\n-- \n1.7.4.rc1.7.g2cf08.dirty\n"},{"id":"164598","messageId":"1301422392-21177-5-git-send-email-artagnon@gmail.com","threadId":"26913","inReplyTo":"1301422392-21177-1-git-send-email-artagnon@gmail.com","subject":"[PATCH 4/5] vcs-svn: Introduce svnload, a dumpfile producer","fromName":"Ramkumar Ramachandra","fromEmail":"artagnon@gmail.com","sentAt":"2011-03-29T18:13:11Z","receivedAt":"2011-03-29T18:13:11Z","isPatch":true,"sender":{"key":"r@artagnon.com","avatar":"https://avatars.githubusercontent.com/u/37226?v=4"},"body":"Design-wise, svnload resembles svndump. Include a Makefile rule to\nbuild it into vcs-svn/lib.a.\n\nSigned-off-by: Ramkumar Ramachandra <artagnon@gmail.com>\n---\nI'm not very happy with the parse_data and parse_data_len API.  It's\nprobably too cryptic? See also: two skip_optional_lf() calls in the\nparse_data function.  It seems to be necessary- is the fast-import\ndocumentation wrong about one optional lf?\n\n Makefile              |    3 +-\n vcs-svn/dir_cache.c   |   52 ++++++\n vcs-svn/dir_cache.h   |   11 +\n vcs-svn/dump_export.c |  164 +++++++++++++++++\n vcs-svn/dump_export.h |   26 +++\n vcs-svn/svnload.c     |  485 +++++++++++++++++++++++++++++++++++++++++++++++++\n vcs-svn/svnload.h     |   19 ++\n 7 files changed, 759 insertions(+), 1 deletions(-)\n create mode 100644 vcs-svn/dir_cache.c\n create mode 100644 vcs-svn/dir_cache.h\n create mode 100644 vcs-svn/dump_export.c\n create mode 100644 vcs-svn/dump_export.h\n create mode 100644 vcs-svn/svnload.c\n create mode 100644 vcs-svn/svnload.h\n\ndiff --git a/Makefile b/Makefile\nindex b0f155a..7206d35 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1838,7 +1838,8 @@ endif\n XDIFF_OBJS = xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \\\n \txdiff/xmerge.o xdiff/xpatience.o\n VCSSVN_OBJS = vcs-svn/string_pool.o vcs-svn/line_buffer.o \\\n-\tvcs-svn/repo_tree.o vcs-svn/fast_export.o vcs-svn/svndump.o\n+\tvcs-svn/repo_tree.o vcs-svn/fast_export.o vcs-svn/svndump.o \\\n+\tvcs-svn/svnload.o vcs-svn/dump_export.o vcs-svn/dir_cache.o\n VCSSVN_TEST_OBJS = test-obj-pool.o test-string-pool.o \\\n \ttest-line-buffer.o test-treap.o\n OBJECTS := $(GIT_OBJS) $(XDIFF_OBJS) $(VCSSVN_OBJS)\ndiff --git a/vcs-svn/dir_cache.c b/vcs-svn/dir_cache.c\nnew file mode 100644\nindex 0000000..551558a\n--- /dev/null\n+++ b/vcs-svn/dir_cache.c\n@@ -0,0 +1,52 @@\n+/*\n+ * Licensed under a two-clause BSD-style license.\n+ * See LICENSE for details.\n+ */\n+\n+#include \"cache.h\"\n+#include \"git-compat-util.h\"\n+#include \"string-list.h\"\n+#include \"dir_cache.h\"\n+\n+static struct string_list dirents = STRING_LIST_INIT_DUP;\n+\n+void dir_cache_add(const char *path, mode_t mode)\n+{\n+\tstruct string_list_item *dir;\n+\tdir = string_list_insert(&dirents, path);\n+\tdir->util = xmalloc(sizeof(uint16_t));\n+\t*((mode_t *)(dir->util)) = mode;\n+}\n+\n+void dir_cache_remove(const char *path)\n+{\n+\tstruct string_list_item *dir;\n+\tdir = string_list_lookup(&dirents, path);\n+\tif (dir)\n+\t\t*((mode_t *)(dir->util)) = S_IFINVALID;\n+}\n+\n+mode_t dir_cache_lookup(const char *path)\n+{\n+\tstruct string_list_item *dir;\n+\tdir = string_list_lookup(&dirents, path);\n+\tif (dir)\n+\t\treturn *((mode_t *)(dir->util));\n+\telse\n+\t\treturn S_IFINVALID;\n+}\n+\n+void dir_cache_mkdir_p(const char *path) {\n+\tchar *t, *p;\n+\n+\tp = (char *) path;\n+\twhile ((t = strchr(p, '/'))) {\n+\t\t\t*t = '\\0';\n+\t\t\tif (dir_cache_lookup(path) == S_IFINVALID) {\n+\t\t\t\tdir_cache_add(path, S_IFDIR);\n+\t\t\t\tdump_export_mkdir(path);\n+\t\t\t}\n+\t\t\t*t = '/';   /* Change it back */\n+\t\t\tp = t + 1;\n+\t}\n+}\ndiff --git a/vcs-svn/dir_cache.h b/vcs-svn/dir_cache.h\nnew file mode 100644\nindex 0000000..e7e83fb\n--- /dev/null\n+++ b/vcs-svn/dir_cache.h\n@@ -0,0 +1,11 @@\n+#ifndef DIR_CACHE_H_\n+#define DIR_CACHE_H_\n+\n+#include \"dump_export.h\"\n+\n+void dir_cache_add(const char *path, mode_t mode);\n+void dir_cache_remove(const char *path);\n+mode_t dir_cache_lookup(const char *path);\n+void dir_cache_mkdir_p(const char *path);\n+\n+#endif\ndiff --git a/vcs-svn/dump_export.c b/vcs-svn/dump_export.c\nnew file mode 100644\nindex 0000000..33ff852\n--- /dev/null\n+++ b/vcs-svn/dump_export.c\n@@ -0,0 +1,164 @@\n+/*\n+ * Licensed under a two-clause BSD-style license.\n+ * See LICENSE for details.\n+ */\n+\n+#include \"cache.h\"\n+#include \"git-compat-util.h\"\n+#include \"dump_export.h\"\n+#include \"dir_cache.h\"\n+\n+static struct strbuf props = STRBUF_INIT;\n+static unsigned long revn = 0;\n+\n+/* Fills props and tells if the mode represents a file */\n+static int populate_props(mode_t mode)\n+{\n+\tint is_file = 1;\n+\n+\tstrbuf_reset(&props);\n+\tswitch (mode) {\n+\tcase S_IFINVALID:\n+\t\tbreak;\n+\tcase 0644:\n+\tcase S_IFREG | 0644:\n+\t\tbreak;\n+\tcase 0755:\n+\tcase S_IFREG | 0755:\n+\t\tstrbuf_addf(&props, \"K 14\\nsvn:executable\\nV 1\\n*\\n\");\n+\t\tbreak;\n+\tcase S_IFLNK:\n+\t\tstrbuf_addf(&props, \"K 11\\nsvn:special\\nV 1\\n*\\n\");\n+\t\tbreak;\n+\tcase S_IFGITLINK:\n+\t\tdie(\"Gitlinks unsupported\"); /* TODO */\n+\tcase S_IFDIR:\n+\t\tis_file = 0;\n+\t\tbreak;\n+\tdefault:\n+\t\tdie(\"Corrupt mode: %d\", mode);\n+\t}\n+\tstrbuf_add(&props, \"PROPS-END\\n\", 10);\n+\treturn is_file;\n+}\n+\n+void dump_export_revision(struct strbuf *revprops)\n+{\n+\tprintf(\"Revision-number: %lu\\n\", ++ revn);\n+\tprintf(\"Prop-content-length: %lu\\n\", revprops->len);\n+\tprintf(\"Content-length: %lu\\n\\n\", revprops->len);\n+\tprintf(\"%s\\n\", revprops->buf);\n+}\n+\n+static void dump_export_action(enum node_action action)\n+{\n+\tswitch (action) {\n+\tcase NODE_ACTION_CHANGE:\n+\t\tprintf(\"Node-action: change\\n\");\n+\t\tbreak;\n+\tcase NODE_ACTION_ADD:\n+\t\tprintf(\"Node-action: add\\n\");\n+\t\tbreak;\n+\tcase NODE_ACTION_DELETE:\n+\t\tprintf(\"Node-action: delete\\n\");\n+\t\tbreak;\n+\tcase NODE_ACTION_REPLACE:\n+\t\tprintf(\"Node-action: replace\\n\");\n+\t\tbreak;\n+\tdefault:\n+\t\tdie(\"Corrupt action: %d\", action);\n+\t}\n+}\n+\n+void dump_export_node(const char *path, mode_t mode,\n+\t\tenum node_action action, size_t text_len,\n+\t\tconst char *copyfrom_path)\n+{\n+\t/* Node-path, Node-kind, and Node-action */\n+\tprintf(\"Node-path: %s\\n\", path);\n+\tprintf(\"Node-kind: %s\\n\", populate_props(mode) ? \"file\" : \"dir\");\n+\tdump_export_action(action);\n+\n+\tif (copyfrom_path) {\n+\t\tprintf(\"Node-copyfrom-rev: %lu\\n\", revn - 1);\n+\t\tprintf(\"Node-copyfrom-path: %s\\n\", copyfrom_path);\n+\t}\n+\tif (props.len) {\n+\t\tprintf(\"Prop-delta: false\\n\");\n+\t\tprintf(\"Prop-content-length: %lu\\n\", props.len);\n+\t}\n+\tif (text_len) {\n+\t\tprintf(\"Text-delta: false\\n\");\n+\t\tprintf(\"Text-content-length: %lu\\n\", text_len);\n+\t}\n+\tif (text_len || props.len) {\n+\t\tprintf(\"Content-length: %lu\\n\\n\", text_len + props.len);\n+\t\tprintf(\"%s\", props.buf);\n+\t}\n+\n+\t/* When no data is present, pad with two newlines */\n+\tif (!text_len)\n+\t\tprintf(\"\\n\\n\");\n+}\n+\n+void dump_export_mkdir(const char *path)\n+{\n+\tdump_export_node(path, S_IFDIR, NODE_ACTION_ADD, 0, NULL);\n+}\n+\n+void dump_export_m(const char *path, mode_t mode, size_t text_len)\n+{\n+\tenum node_action action = NODE_ACTION_CHANGE;\n+\tmode_t old_mode;\n+\n+\told_mode = dir_cache_lookup(path);\n+\n+\tif (mode != old_mode) {\n+\t\tif (old_mode != S_IFINVALID) {\n+\t\t\tdump_export_d(path);\n+\t\t\tdir_cache_remove(path);\n+\t\t}\n+\t\taction = NODE_ACTION_ADD;\n+\t\tdir_cache_mkdir_p(path);\n+\t\tdir_cache_add(path, mode);\n+\t}\n+\n+\tdump_export_node(path, mode, action, text_len, NULL);\n+}\n+\n+void dump_export_d(const char *path)\n+{\n+\tprintf(\"Node-path: %s\\n\", path);\n+\tdump_export_action(NODE_ACTION_DELETE);\n+\tprintf(\"\\n\\n\");\n+\tdir_cache_remove(path);\n+}\n+\n+void dump_export_cr(const char *path, const char *copyfrom_path,\n+\t\tint delete_old)\n+{\n+\tenum node_action action = NODE_ACTION_REPLACE;\n+\tmode_t mode, old_mode;\n+\n+\tmode = dir_cache_lookup(path);\n+\told_mode = dir_cache_lookup(copyfrom_path);\n+\n+\tif (old_mode == S_IFINVALID)\n+\t\tdie(\"Can't copy from non-existant path: %s\", copyfrom_path);\n+\tif (mode != old_mode) {\n+\t\taction = NODE_ACTION_ADD;\n+\t\tdir_cache_mkdir_p(path);\n+\t\tdir_cache_add(path, mode);\n+\t}\n+\tif (delete_old) {\n+\t\tdump_export_d(copyfrom_path);\n+\t\tdir_cache_remove(copyfrom_path);\n+\t}\n+\n+\tdump_export_node(path, old_mode, action, 0, copyfrom_path);\n+}\n+\n+void dump_export_init()\n+{\n+\tprintf(\"SVN-fs-dump-format-version: 3\\n\\n\");\n+}\ndiff --git a/vcs-svn/dump_export.h b/vcs-svn/dump_export.h\nnew file mode 100644\nindex 0000000..647701d\n--- /dev/null\n+++ b/vcs-svn/dump_export.h\n@@ -0,0 +1,26 @@\n+#ifndef DUMP_EXPORT_H_\n+#define DUMP_EXPORT_H_\n+\n+#define MAX_GITSVN_LINE_LEN 4096\n+#define SVN_INVALID_REV 0\n+\n+enum node_action {\n+\tNODE_ACTION_CHANGE,\n+\tNODE_ACTION_ADD,\n+\tNODE_ACTION_DELETE,\n+\tNODE_ACTION_REPLACE,\n+\tNODE_ACTION_COUNT\n+};\n+\n+void dump_export_revision(struct strbuf *revprops);\n+void dump_export_node(const char *path, mode_t mode,\n+\t\tenum node_action action, size_t text_len,\n+\t\tconst char *copyfrom_path);\n+void dump_export_mkdir(const char *path);\n+void dump_export_m(const char *path, mode_t mode, size_t text_len);\n+void dump_export_d(const char *path);\n+void dump_export_cr(const char *path, const char *copyfrom_path,\n+\t\tint delete_old);\n+void dump_export_init();\n+\n+#endif\ndiff --git a/vcs-svn/svnload.c b/vcs-svn/svnload.c\nnew file mode 100644\nindex 0000000..c07e475\n--- /dev/null\n+++ b/vcs-svn/svnload.c\n@@ -0,0 +1,485 @@\n+/*\n+ * Produce a dumpfile v3 from a fast-import stream.\n+ * Load the dump into the SVN repository with:\n+ * svnrdump load <URL> <dumpfile\n+ *\n+ * Licensed under a two-clause BSD-style license.\n+ * See LICENSE for details.\n+ */\n+\n+#include \"cache.h\"\n+#include \"quote.h\"\n+#include \"svnload.h\"\n+#include \"dump_export.h\"\n+#include \"dir_cache.h\"\n+\n+static FILE *infile;\n+static struct strbuf command_buf = STRBUF_INIT;\n+static struct strbuf path_d = STRBUF_INIT;\n+\n+static int read_next_command(void)\n+{\n+\tstrbuf_reset(&command_buf);\n+\treturn strbuf_getline(&command_buf, infile, '\\n');\n+}\n+\n+static void populate_revprops(struct strbuf *revprops,\n+\t\t\tstruct ident *svn_ident, struct strbuf *log)\n+{\n+\tstrbuf_reset(revprops);\n+\tstrbuf_addf(revprops, \"K 10\\nsvn:author\\nV %lu\\n%s\\n\",\n+\t\tsvn_ident->name.len, svn_ident->name.buf);\n+\tstrbuf_addf(revprops, \"K 7\\nsvn:log\\nV %lu\\n%s\\n\",\n+\t\tlog->len, log->buf);\n+\tif (svn_ident->date)\n+\t\t/* SVN doesn't like an empty svn:date value */\n+\t\tstrbuf_addf(revprops, \"K 8\\nsvn:date\\nV %d\\n%s\\n\",\n+\t\t\tSVN_DATE_LEN - 1, svn_ident->date);\n+\tstrbuf_add(revprops, \"PROPS-END\\n\", 10);\n+}\n+\n+static void parse_ident(const char *buf, struct ident *identp)\n+{\n+\tchar *original_buf, *t, *tz_off;\n+\tint tz_off_buf;\n+\tconst struct tm *tm_time;\n+\n+\t/* John Doe <johndoe@email.com> 1170199019 +0530 */\n+\tstrbuf_reset(&(identp->name));\n+\tstrbuf_reset(&(identp->email));\n+\n+\toriginal_buf = strdup(buf);\n+\tif (!(tz_off = strrchr(buf, ' ')))\n+\t\tgoto error;\n+\t*tz_off++ = '\\0';\n+\tif (!(t = strrchr(buf, ' ')))\n+\t\tgoto error;\n+\t*(t - 1) = '\\0'; /* Ignore '>' from email */\n+\tt++;\n+\ttz_off_buf = atoi(tz_off);\n+\n+\t/* UTC -1200 to UTC +1400 are valid */\n+\tif (tz_off_buf > 1400 || tz_off_buf < -1200)\n+\t\tgoto error;\n+\ttm_time = time_to_tm(strtoul(t, NULL, 10), tz_off_buf);\n+\tstrftime(identp->date, SVN_DATE_LEN, SVN_DATE_FORMAT, tm_time);\n+\tif (!(t = strchr(buf, '<')))\n+\t\tgoto error;\n+\t*(t - 1) = '\\0'; /* Ignore ' <' from email */\n+\tt++;\n+\n+\tstrbuf_add(&(identp->email), t, strlen(t));\n+\tstrbuf_add(&(identp->name), buf, strlen(buf));\n+\tfree(original_buf);\n+\treturn;\n+error:\n+\tdie(\"Malformed ident line: %s\", original_buf);\n+}\n+\n+static void skip_optional_lf(void)\n+{\n+\tint term_char = fgetc(infile);\n+\tif (term_char != '\\n' && term_char != EOF)\n+\t\tungetc(term_char, infile);\n+}\n+\n+/* Either sets term and returns terminator length or returns data\n+   length after setting term to NULL */\n+static size_t parse_data_len(char *term)\n+{\n+\tuintmax_t length;\n+\n+\tterm = NULL;\n+\tif (prefixcmp(command_buf.buf, \"data \"))\n+\t\tdie(\"Expected 'data n' command, found: %s\", command_buf.buf);\n+\n+\tif (!prefixcmp(command_buf.buf + 5, \"<<\")) {\n+\t\tterm = xstrdup(command_buf.buf + 5 + 2);\n+\t\tif (!(command_buf.len - 5 - 2))\n+\t\t\tdie(\"Missing delimeter after 'data <<' in: %s\", command_buf.buf);\n+\t\treturn (size_t) (command_buf.len - 5 - 2);\n+\t}\n+\n+\tlength = strtoumax(command_buf.buf + 5, NULL, 10);\n+\tif ((size_t) length < length)\n+\t\tdie(\"Data is too large to use in this context\");\n+\n+\treturn (size_t) length;\n+}\n+\n+/* When term is filled in, nbytes refers to the size of the\n+   terminator; otherwise, it refers to the size of the actual data.\n+   The parsed data is written to dst and out, if they exist. */\n+static void parse_data(char *term, size_t nbytes, struct strbuf *dst, FILE *out)\n+{\n+\tsize_t in;\n+\tsize_t done = 0;\n+\n+\tif (term) {\n+\t\t/* Read line-by-line until terminator is encountered */\n+\t\twhile (1) {\n+\t\t\tif (read_next_command() == EOF)\n+\t\t\t\tdie(\"Expected terminator '%s', found EOF\", term);\n+\n+\t\t\t/* If the terminator is encountered, stop reading */\n+\t\t\tif (nbytes == command_buf.len\n+\t\t\t\t&& !memcmp(term, command_buf.buf, nbytes))\n+\t\t\t\tbreak;\n+\n+\t\t\tif (dst) {\n+\t\t\t\tstrbuf_addbuf(dst, &command_buf);\n+\t\t\t\tstrbuf_addch(dst, '\\n');\n+\t\t\t}\n+\t\t\tif (out) {\n+\t\t\t\tstrbuf_fwrite(&command_buf, command_buf.len, out);\n+\t\t\t\tfprintf(out, \"\\n\");\n+\t\t\t}\n+\t\t}\n+\t\tfree(term);\n+\t\tgoto END;\n+\t}\n+\n+\t/*  Read nbytes bytes in chunks */\n+\twhile (done < nbytes && !feof(infile) && !ferror(infile)) {\n+\t\tin = (nbytes - done) < COPY_BUFFER_LEN ?\n+\t\t\t(nbytes - done) : COPY_BUFFER_LEN;\n+\t\tstrbuf_reset(&command_buf);\n+\t\tin = strbuf_fread(&command_buf, in, infile);\n+\t\tdone += in;\n+\t\tif (dst)\n+\t\t\tstrbuf_addbuf(dst, &command_buf);\n+\t\tif (out)\n+\t\t\tstrbuf_fwrite(&command_buf, command_buf.len, out);\n+\t}\n+\tif (done != nbytes)\n+\t\tdie(\"Expected %lu bytes, read %lu bytes\", nbytes, done);\n+\n+\tif (out)\n+\t\tfprintf(out, \"\\n\"); /* Hack: Incase data is not terminated with lf */\n+END:\n+\tskip_optional_lf();\n+\tskip_optional_lf();\n+}\n+\n+static const char *get_mode(const char *str, uint16_t *modep)\n+{\n+\tunsigned char c;\n+\tuint16_t mode = 0;\n+\n+\twhile ((c = *str++) != ' ') {\n+\t\tif (c < '0' || c > '7')\n+\t\t\treturn NULL;\n+\t\tmode = (mode << 3) + (c - '0');\n+\t}\n+\t*modep = mode;\n+\treturn str;\n+}\n+\n+static void file_change_m(const char *p)\n+{\n+\tstruct strbuf dst = STRBUF_INIT;\n+\tconst char *endp;\n+\tuint16_t mode;\n+\tsize_t nbytes;\n+\tchar *term;\n+\n+\tif (!p)\n+\t\tdie(\"Missing mode after filemodify in: %s\", command_buf.buf);\n+\n+\tif (!(p = get_mode(p, &mode)))\n+\t\tdie(\"Corrupt mode: %s\", command_buf.buf);\n+\tif (!prefixcmp(p, \"inline \"))\n+\t\tp += 7;\n+\telse\n+\t\tdie(\"Non-inlined data unsupported\");\n+\n+\t/* Parse out path into path_d */\n+\tstrbuf_reset(&path_d);\n+\tif (!unquote_c_style(&path_d, p, &endp)) {\n+\t\tif (*endp)\n+\t\t\tdie(\"Garbage after path in: %s\", command_buf.buf);\n+\t} else\n+\t\tstrbuf_addstr(&path_d, p);\n+\n+\tread_next_command();\n+\tnbytes = parse_data_len(term);\n+\tif (term) {\n+\t\tstrbuf_reset(&dst);\n+\t\tparse_data(term, nbytes, &dst, NULL);\n+\t\tdump_export_m(path_d.buf, mode, dst.len);\n+\t\tfwrite(&dst.buf, 1, dst.len, stdout);\n+\t\treturn;\n+\t}\n+\tdump_export_m(path_d.buf, mode, nbytes);\n+\tparse_data(NULL, nbytes, NULL, stdout);\n+}\n+\n+static void file_change_d(const char *p)\n+{\n+\tconst char *endp;\n+\n+\tif (!p)\n+\t\tdie(\"Missing path after filedelete in: %s\", command_buf.buf);\n+\n+\tstrbuf_reset(&path_d);\n+\tif (!unquote_c_style(&path_d, p, &endp)) {\n+\t\tif (*endp)\n+\t\t\tdie(\"Garbage after path in: %s\", command_buf.buf);\n+\t} else\n+\t\tstrbuf_addstr(&path_d, p);\n+\tdump_export_d(path_d.buf);\n+}\n+\n+static void file_change_cr(const char *p, int delete_old)\n+{\n+\tstruct strbuf path_s = STRBUF_INIT;\n+\tconst char *endp;\n+\n+\tif (!p)\n+\t\tdie(\"Missing source after %s in: %s\",\n+\t\t\tdelete_old ? \"filerename\" : \"filecopy\", command_buf.buf);\n+\n+\tstrbuf_reset(&path_s);\n+\tif (!unquote_c_style(&path_s, p, &endp)) {\n+\t\tif (*endp != ' ')\n+\t\t\tdie(\"Missing destination after source in: %s\", command_buf.buf);\n+\t} else {\n+\t\tendp = strchr(p, ' ');\n+\t\tif (!endp)\n+\t\t\tdie(\"Missing destination after source in: %s\", command_buf.buf);\n+\t\tstrbuf_add(&path_s, p, endp - p);\n+\t}\n+\n+\tendp++;\n+\tif (!*endp)\n+\t\tdie(\"Missing destination in: %s\", command_buf.buf);\n+\n+\tp = endp;\n+\tstrbuf_reset(&path_d);\n+\tif (!unquote_c_style(&path_d, p, &endp)) {\n+\t\tif (*endp)\n+\t\t\tdie(\"Garbage after destination in: %s\", command_buf.buf);\n+\t} else\n+\t\tstrbuf_addstr(&path_d, p);\n+\n+\t/* TODO: Check C \"path/to/subdir\" \"\" */\n+\tdump_export_cr(path_d.buf, path_s.buf, delete_old);\n+}\n+\n+static void build_svn_ident(struct ident *svn_ident,\n+\t\t\tstruct ident *author, struct ident *committer)\n+{\n+\tchar *t, *email;\n+\n+\tstrbuf_reset(&(svn_ident->name));\n+\tmemcpy(svn_ident->date, committer->date, SVN_DATE_LEN);\n+\temail = author->email.len ? author->email.buf : committer->email.buf;\n+\tif ((t = strchr(email, '@')))\n+\t\tstrbuf_add(&(svn_ident->name), email, t - email);\n+\telse\n+\t\tstrbuf_addstr(&(svn_ident->name), email);\n+}\n+\n+static void parse_ignore_notemodify(const char *p)\n+{\n+\tchar *term;\n+\tsize_t nbytes;\n+\n+\tif (!p)\n+\t\tdie(\"Missing dataref after notemodify in: %s\", command_buf.buf);\n+\tif (!(p = strchr(p, ' ')))\n+\t\tdie (\"Missing committish after dataref in: %s\", command_buf.buf);\n+\n+\tread_next_command();\n+\tterm = NULL;\n+\tnbytes = parse_data_len(term);\n+\tparse_data(term, nbytes, NULL, NULL);\n+}\n+\n+static void parse_commit(const char *p)\n+{\n+\tstatic struct strbuf log = STRBUF_INIT;\n+\tstatic struct strbuf revprops = STRBUF_INIT;\n+\tstatic struct ident author = {STRBUF_INIT, STRBUF_INIT, \"\"};\n+\tstatic struct ident committer = {STRBUF_INIT, STRBUF_INIT, \"\"};\n+\tstatic struct ident svn_ident = {STRBUF_INIT, {0, 0, NULL}, \"\"};\n+\n+\tchar *ident_buf, *term;\n+\tsize_t nbytes;\n+\n+\t/* TODO: Parse and use branch */\n+\tif (!p)\n+\t\tdie(\"Missing ref after commit in: %s\", command_buf.buf);\n+\tread_next_command();\n+\n+\t/* Parse and ignore mark line */\n+\tif (!prefixcmp(command_buf.buf, \"mark :\"))\n+\t\tread_next_command();\n+\n+\tif (!prefixcmp(command_buf.buf, \"author \")) {\n+\t\tident_buf = strbuf_detach(&command_buf, &command_buf.len);\n+\t\tparse_ident(ident_buf + 7, &author);\n+\t\tfree(ident_buf);\n+\t\tread_next_command();\n+\t}\n+\tif (!prefixcmp(command_buf.buf, \"committer \")) {\n+\t\tident_buf = strbuf_detach(&command_buf, &command_buf.len);\n+\t\tparse_ident(ident_buf + 10, &committer);\n+\t\tfree(ident_buf);\n+\t\tread_next_command();\n+\t}\n+\tif (!committer.name.len)\n+\t\tdie(\"Missing committer line in stream\");\n+\n+\t/* Parse the log */\n+\tstrbuf_reset(&log);\n+\tterm = NULL;\n+\tnbytes = parse_data_len(term);\n+\tparse_data(term, nbytes, &log, NULL);\n+\tread_next_command();\n+\n+\tif (!prefixcmp(command_buf.buf, \"from \"))\n+\t\t/* TODO: Support copyfrom */\n+\t\tread_next_command();\n+\twhile (!prefixcmp(command_buf.buf, \"merge \"))\n+\t\t/* TODO: Support merges */\n+\t\tread_next_command();\n+\n+\t/* Translation from Git metadata to SVN metadata */\n+\tbuild_svn_ident(&svn_ident, &author, &committer);\n+\tpopulate_revprops(&revprops, &svn_ident, &log);\n+\tdump_export_revision(&revprops);\n+\n+\tdo {\n+\t\tif (!prefixcmp(command_buf.buf, \"M \"))\n+\t\t\tfile_change_m(command_buf.buf + 2);\n+\t\telse if (!prefixcmp(command_buf.buf, \"D \"))\n+\t\t\tfile_change_d(command_buf.buf + 2);\n+\t\telse if (!prefixcmp(command_buf.buf, \"R \"))\n+\t\t\tfile_change_cr(command_buf.buf + 2, 1);\n+\t\telse if (!prefixcmp(command_buf.buf, \"C \"))\n+\t\t\tfile_change_cr(command_buf.buf + 2, 0);\n+\t\telse if (!prefixcmp(command_buf.buf, \"N \"))\n+\t\t\tparse_ignore_notemodify(command_buf.buf + 2);\n+\t\telse if (!prefixcmp(command_buf.buf, \"ls \"))\n+\t\t\tgoto error; /* TODO */\n+\t\telse if (!strcmp(\"deleteall\", command_buf.buf))\n+\t\t\tgoto error; /* TODO */\n+\t\telse\n+\t\t\t/* Unrecognized command is left on command_buf */\n+\t\t\tbreak;\n+\t} while (read_next_command() != EOF);\n+\n+\t/* Eat up optional trailing lf */\n+\tif (!command_buf.len)\n+\t\tread_next_command();\n+\treturn;\n+error:\n+\tdie(\"Unsupported command: %s\", command_buf.buf);\n+}\n+\n+static void parse_tag(const char *p)\n+{\n+\tchar *term;\n+\tsize_t nbytes;\n+\n+\tif (!p)\n+\t\tdie(\"Missing name after tag in: %s\", command_buf.buf);\n+\tread_next_command();\n+\n+\tif (prefixcmp(command_buf.buf, \"from \"))\n+\t\tdie(\"Expected 'from committish', found: %s\", command_buf.buf);\n+\tp = command_buf.buf + 5;\n+\tif (!p)\n+\t\tdie(\"Missing committish after from in: %s\", command_buf.buf);\n+\tread_next_command();\n+\n+\tif (prefixcmp(command_buf.buf, \"tagger \"))\n+\t\tdie(\"Expected 'tagger (name?) email when', found: %s\", command_buf.buf);\n+\tp = command_buf.buf + 7;\n+\tif (*p != '<')\n+\t\tp = strchr(p, '<');\n+\tif (!p)\n+\t\tdie(\"Missing name or email after tagger in: %s\", command_buf.buf);\n+\tif (!(p = strchr(p, '>')))\n+\t\tdie(\"Malformed email in: %s\", command_buf.buf);\n+\tif (!(++ p))\n+\t\tdie(\"Missing when after email in: %s\", command_buf.buf);\n+\tread_next_command();\n+\n+\tterm = NULL;\n+\tnbytes = parse_data_len(term);\n+\tparse_data(term, nbytes, NULL, NULL);\n+\tread_next_command();\n+}\n+\n+void parse_reset_branch(const char *p)\n+{\n+\tif (!p)\n+\t\tdie(\"Missing ref after reset in: %s\", command_buf.buf);\n+\tread_next_command();\n+\n+\tif (!prefixcmp(command_buf.buf, \"from \")) {\n+\t\tp = command_buf.buf + 5;\n+\t\tif (!p)\n+\t\t\tdie(\"Missing committish after from in: %s\", command_buf.buf);\n+\t\tread_next_command();\n+\t}\n+\n+\tskip_optional_lf();\n+}\n+\n+void svnload_read(void)\n+{\n+\tread_next_command();\n+\n+\t/* Every function in the loop keeps reading until it\n+\t   encounteres EOF or an unrecognized command; the\n+\t   unrecognized command is left on command_buf */\n+\twhile (!feof(infile)) {\n+\t\tif (!strcmp(\"blob\", command_buf.buf))\n+\t\t\tdie(\"Non-inlined blobs unsupported\");\n+\t\telse if (!prefixcmp(command_buf.buf, \"ls \"))\n+\t\t\tgoto error; /* TODO */\n+\t\telse if (!prefixcmp(command_buf.buf, \"cat-blob \"))\n+\t\t\tgoto error; /* TODO */\n+\t\telse if (!prefixcmp(command_buf.buf, \"commit \"))\n+\t\t\tparse_commit(command_buf.buf + 7);\n+\t\telse if (!prefixcmp(command_buf.buf, \"tag \"))\n+\t\t\t/* TODO: No-op */\n+\t\t\tparse_tag(command_buf.buf + 4);\n+\t\telse if (!prefixcmp(command_buf.buf, \"reset \"))\n+\t\t\t/* TODO: No-op */\n+\t\t\tparse_reset_branch(command_buf.buf + 6);\n+\t\telse if (!strcmp(command_buf.buf, \"checkpoint\")\n+\t\t\t|| !prefixcmp(command_buf.buf, \"progress \")) {\n+\t\t\t/* Ignored */\n+\t\t\tread_next_command();\n+\t\t\tskip_optional_lf();\n+\t\t}\n+\t\telse if (!prefixcmp(command_buf.buf, \"feature \")\n+\t\t\t|| !prefixcmp(command_buf.buf, \"option \"))\n+\t\t\t/* Ignored */\n+\t\t\tread_next_command();\n+\t\telse\n+\t\t\tgoto error;\n+\t};\n+\treturn;\n+error:\n+\tdie(\"Unsupported command: %s\", command_buf.buf);\n+}\n+\n+int svnload_init(const char *filename)\n+{\n+\tinfile = filename ? fopen(filename, \"r\") : stdin;\n+\tif (!infile)\n+\t\tdie(\"Cannot open %s: %s\", filename, strerror(errno));\n+\tdump_export_init();\n+\treturn 0;\n+}\n+\n+void svnload_deinit(void)\n+{\n+\tstrbuf_release(&command_buf);\n+\tstrbuf_release(&path_d);\n+}\ndiff --git a/vcs-svn/svnload.h b/vcs-svn/svnload.h\nnew file mode 100644\nindex 0000000..12bb559\n--- /dev/null\n+++ b/vcs-svn/svnload.h\n@@ -0,0 +1,19 @@\n+#ifndef SVNLOAD_H_\n+#define SVNLOAD_H_\n+\n+#include \"strbuf.h\"\n+\n+#define SVN_DATE_FORMAT \"%Y-%m-%dT%H:%M:%S.000000Z\"\n+#define SVN_DATE_LEN 28\n+#define COPY_BUFFER_LEN 4096\n+\n+struct ident {\n+\tstruct strbuf name, email;\n+\tchar date[SVN_DATE_LEN];\n+};\n+\n+int svnload_init(const char *filename);\n+void svnload_deinit(void);\n+void svnload_read(void);\n+\n+#endif\n-- \n1.7.4.rc1.7.g2cf08.dirty\n"},{"id":"164599","messageId":"1301422392-21177-6-git-send-email-artagnon@gmail.com","threadId":"26913","inReplyTo":"1301422392-21177-1-git-send-email-artagnon@gmail.com","subject":"[PATCH 5/5] t9012-svn-fi: Add tests for svn-fi","fromName":"Ramkumar Ramachandra","fromEmail":"artagnon@gmail.com","sentAt":"2011-03-29T18:13:12Z","receivedAt":"2011-03-29T18:13:12Z","isPatch":true,"sender":{"key":"r@artagnon.com","avatar":"https://avatars.githubusercontent.com/u/37226?v=4"},"body":"Create a test-svn-fi in toplevel directory, add rules to build it, and\nadd some basic tests.\n\nSigned-off-by: Ramkumar Ramachandra <artagnon@gmail.com>\n---\nVerifying svn:date is very non-trivial, because of timezone conversion\nissues. I tried fuzzing those out and checking only year and month,\nbut a timezone shift can cause a change in both of them as\nwell. Example: 31 Dec 2011 11:45 PM in one timezone can very well be 1\nJan 2012 in another timezone.\n\nMore tests? Suggestions for reduced testcases of complex revision\nhistory tasks? I'll probably import some tests from svnrdump in the\nnext iteration.\n\n .gitignore        |    1 +\n Makefile          |    3 +\n t/t9012-svn-fi.sh |  705 +++++++++++++++++++++++++++++++++++++++++++++++++++++\n test-svn-fi.c     |   17 ++\n 4 files changed, 726 insertions(+), 0 deletions(-)\n create mode 100755 t/t9012-svn-fi.sh\n create mode 100644 test-svn-fi.c\n\ndiff --git a/.gitignore b/.gitignore\nindex c460c66..1cf145d 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -181,6 +181,7 @@\n /test-subprocess\n /test-svn-fe\n /test-treap\n+/test-svn-fi\n /common-cmds.h\n *.tar.gz\n *.dsc\ndiff --git a/Makefile b/Makefile\nindex 7206d35..7fc5a2c 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -435,6 +435,7 @@ TEST_PROGRAMS_NEED_X += test-string-pool\n TEST_PROGRAMS_NEED_X += test-subprocess\n TEST_PROGRAMS_NEED_X += test-svn-fe\n TEST_PROGRAMS_NEED_X += test-treap\n+TEST_PROGRAMS_NEED_X += test-svn-fi\n TEST_PROGRAMS_NEED_X += test-index-version\n TEST_PROGRAMS_NEED_X += test-mktemp\n \n@@ -2152,6 +2153,8 @@ test-string-pool$X: vcs-svn/lib.a\n \n test-svn-fe$X: vcs-svn/lib.a\n \n+test-svn-fi$X: vcs-svn/lib.a\n+\n .PRECIOUS: $(TEST_OBJS)\n \n test-%$X: test-%.o $(GITLIBS)\ndiff --git a/t/t9012-svn-fi.sh b/t/t9012-svn-fi.sh\nnew file mode 100755\nindex 0000000..8bc2075\n--- /dev/null\n+++ b/t/t9012-svn-fi.sh\n@@ -0,0 +1,705 @@\n+#!/bin/sh\n+\n+test_description='check svn dumpfile exporter'\n+\n+. ./test-lib.sh\n+\n+svnrepo=\"testsvn\"\n+\n+if svnadmin --help >/dev/null 2>&1\n+then\n+    load_dump () {\n+\tsvnadmin load \"$1\"\n+    }\n+    test_set_prereq SVN\n+fi\n+\n+reinit_svn () {\n+\trm -rf \"$svnrepo\" &&\n+\trm -f stream &&\n+\tsvnadmin create \"$svnrepo\" &&\n+\tprintf \"#!/bin/sh\" > \"$svnrepo\"/hooks/pre-revprop-change &&\n+\tchmod +x \"$svnrepo\"/hooks/pre-revprop-change &&\n+\tmkfifo stream\n+}\n+\n+svn_look () {\n+\tsubcommand=$1 &&\n+\tshift &&\n+\tsvnlook \"$subcommand\" \"$svnrepo\" \"$@\"\n+}\n+\n+try_load () {\n+\tinput=$1 &&\n+\tmaybe_fail=${2:+test_$2} &&\n+\n+\t{\n+\t\t$maybe_fail test-svn-fi \"$input\" >stream &\n+\t} &&\n+\tload_dump \"$svnrepo\" <stream &&\n+\twait $!\n+}\n+\n+test_expect_success SVN 'M: regular files' '\n+\treinit_svn &&\n+\ttest_tick &&\n+\tcat >expect.tree <<-\\EOF &&\n+\t/\n+\t foo\n+\t bar\n+\tEOF\n+\tcat >expect.cat.foo <<-\\EOF &&\n+\tnothing\n+\tEOF\n+\tcat >expect.cat.bar <<-\\EOF &&\n+\tnothing again\n+\tEOF\n+\tcat >input <<-EOF &&\n+\treset refs/heads/master\n+\tcommit refs/heads/master\n+\tmark :1\n+\tauthor $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE\n+\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\tdata 9\n+\tsomething\n+\tM 100644 inline foo\n+\tdata 8\n+\tnothing\n+\tM 100644 inline bar\n+\tdata 14\n+\tnothing again\n+\tEOF\n+\ttry_load input &&\n+\tsvn_look tree >actual.tree &&\n+\tsvn_look cat foo >actual.cat.foo &&\n+\tsvn_look cat bar >actual.cat.bar &&\n+\ttest_cmp expect.tree actual.tree &&\n+\ttest_cmp expect.cat.foo actual.cat.foo &&\n+\ttest_cmp expect.cat.bar actual.cat.bar\n+'\n+\n+test_expect_success SVN 'D: regular files' '\n+\treinit_svn &&\n+\ttest_tick &&\n+\tcat >expect.tree <<-\\EOF &&\n+\t/\n+\tEOF\n+\tcat >input <<-EOF &&\n+\treset refs/heads/master\n+\tcommit refs/heads/master\n+\tmark :1\n+\tauthor $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE\n+\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\tdata 9\n+\tsomething\n+\tM 100644 inline foo\n+\tdata 7\n+\tnothing\n+\tcommit refs/heads/master\n+\tauthor $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE\n+\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\tdata 0\n+\tD foo\n+\tEOF\n+\ttry_load input &&\n+\tsvn_look tree >actual.tree &&\n+\ttest_cmp expect.tree actual.tree\n+'\n+\n+test_expect_success SVN 'D: directories' '\n+\treinit_svn &&\n+\ttest_tick &&\n+\tcat >expect.tree <<-\\EOF &&\n+\t/\n+\tEOF\n+\tcat >input <<-EOF &&\n+\treset refs/heads/master\n+\tcommit refs/heads/master\n+\tmark :1\n+\tauthor $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE\n+\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\tdata 9\n+\tsomething\n+\tM 100644 inline subdir/foo\n+\tdata 7\n+\tnothing\n+\tcommit refs/heads/master\n+\tauthor $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE\n+\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\tdata 0\n+\tD subdir\n+\tEOF\n+\ttry_load input &&\n+\tsvn_look tree >actual.tree &&\n+\ttest_cmp expect.tree actual.tree\n+'\n+\n+test_expect_success SVN 'R: regular files' '\n+\treinit_svn &&\n+\ttest_tick &&\n+\tcat >expect.tree <<-\\EOF &&\n+\t/\n+\t bar\n+\tEOF\n+\tcat >expect.cat <<-\\EOF &&\n+\tnothing\n+\tEOF\n+\tcat >input <<-EOF &&\n+\treset refs/heads/master\n+\tcommit refs/heads/master\n+\tmark :1\n+\tauthor $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE\n+\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\tdata 9\n+\tsomething\n+\tM 100644 inline foo\n+\tdata 8\n+\tnothing\n+\tcommit refs/heads/master\n+\tauthor $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE\n+\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\tdata 6\n+\trename\n+\tR foo bar\n+\tEOF\n+\ttry_load input &&\n+\tsvn_look tree >actual.tree &&\n+\tsvn_look cat bar >actual.cat &&\n+\ttest_cmp expect.tree actual.tree &&\n+\ttest_cmp expect.cat actual.cat\n+'\n+\n+test_expect_success SVN 'R: directories' '\n+\treinit_svn &&\n+\ttest_tick &&\n+\tcat >expect.tree <<-\\EOF &&\n+\t/\n+\t subdir2/\n+\t  foo\n+\t  bar\n+\tEOF\n+\tcat >expect.cat.foo <<-\\EOF &&\n+\tnothing\n+\tEOF\n+\tcat >expect.cat.bar <<-\\EOF &&\n+\tnothing again\n+\tEOF\n+\tcat >input <<-EOF &&\n+\treset refs/heads/master\n+\tcommit refs/heads/master\n+\tmark :1\n+\tauthor $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE\n+\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\tdata 9\n+\tsomething\n+\tM 100644 inline subdir/foo\n+\tdata 8\n+\tnothing\n+\tM 100644 inline subdir/bar\n+\tdata 14\n+\tnothing again\n+\tcommit refs/heads/master\n+\tauthor $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE\n+\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\tdata 12\n+\treplace root\n+\tR subdir subdir2\n+\tEOF\n+\ttry_load input &&\n+\tsvn_look tree >actual.tree &&\n+\tsvn_look cat subdir2/foo >actual.cat.foo &&\n+\tsvn_look cat subdir2/bar >actual.cat.bar &&\n+\ttest_cmp expect.tree actual.tree &&\n+\ttest_cmp expect.cat.foo actual.cat.foo &&\n+\ttest_cmp expect.cat.bar actual.cat.bar\n+'\n+\n+test_expect_success SVN 'C: regular files' '\n+\treinit_svn &&\n+\ttest_tick &&\n+\tcat >expect.tree <<-\\EOF &&\n+\t/\n+\t foo\n+\t bar\n+\tEOF\n+\tcat >expect.cat <<-\\EOF &&\n+\tnothing\n+\tEOF\n+\tcat >input <<-EOF &&\n+\treset refs/heads/master\n+\tcommit refs/heads/master\n+\tmark :1\n+\tauthor $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE\n+\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\tdata 9\n+\tsomething\n+\tM 100644 inline foo\n+\tdata 8\n+\tnothing\n+\tcommit refs/heads/master\n+\tauthor $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE\n+\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\tdata 6\n+\trename\n+\tC foo bar\n+\tEOF\n+\ttry_load input &&\n+\tsvn_look tree >actual.tree &&\n+\tsvn_look cat foo >actual.cat.foo &&\n+\tsvn_look cat bar >actual.cat.bar &&\n+\ttest_cmp expect.tree actual.tree &&\n+\ttest_cmp expect.cat actual.cat.foo &&\n+\ttest_cmp expect.cat actual.cat.bar\n+'\n+\n+test_expect_success SVN 'C: directories' '\n+\treinit_svn &&\n+\ttest_tick &&\n+\tcat >expect.tree <<-\\EOF &&\n+\t/\n+\t subdir/\n+\t  foo\n+\t  bar\n+\t subdir2/\n+\t  foo\n+\t  bar\n+\tEOF\n+\tcat >expect.cat.foo <<-\\EOF &&\n+\tnothing\n+\tEOF\n+\tcat >expect.cat.bar <<-\\EOF &&\n+\tnothing again\n+\tEOF\n+\tcat >input <<-EOF &&\n+\treset refs/heads/master\n+\tcommit refs/heads/master\n+\tmark :1\n+\tauthor $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE\n+\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\tdata 9\n+\tsomething\n+\tM 100644 inline subdir/foo\n+\tdata 8\n+\tnothing\n+\tM 100644 inline subdir/bar\n+\tdata 14\n+\tnothing again\n+\tcommit refs/heads/master\n+\tauthor $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE\n+\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\tdata 12\n+\treplace root\n+\tC subdir subdir2\n+\tEOF\n+\ttry_load input &&\n+\tsvn_look tree >actual.tree &&\n+\tsvn_look cat subdir2/foo >actual.cat.foo &&\n+\tsvn_look cat subdir2/bar >actual.cat.bar &&\n+\ttest_cmp expect.tree actual.tree &&\n+\ttest_cmp expect.cat.foo actual.cat.foo &&\n+\ttest_cmp expect.cat.bar actual.cat.bar\n+'\n+\n+test_expect_success SVN 'ignore checkpoint, progress, feature, option' '\n+\treinit_svn &&\n+\ttest_tick &&\n+\tcat >expect.tree <<-\\EOF &&\n+\t/\n+\tEOF\n+\tcat >input <<-EOF &&\n+\tcheckpoint\n+\tprogress 3\n+\tfeature foo\n+\toption bar\n+\tEOF\n+\ttry_load input &&\n+\tsvn_look tree >actual.tree &&\n+\ttest_cmp expect.tree actual.tree\n+'\n+\n+test_expect_success SVN 'ignore tag' '\n+\treinit_svn &&\n+\ttest_tick &&\n+\tcat >expect.tree <<-\\EOF &&\n+\t/\n+\t foo\n+\tEOF\n+\tcat >input <<-EOF &&\n+\treset refs/heads/master\n+\tcommit refs/heads/master\n+\tmark :1\n+\tauthor $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE\n+\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\tdata 9\n+\tsomething\n+\tM 644 inline foo\n+\tdata 0\n+\ttag moo\n+\tfrom master\n+\ttagger <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE\n+\tdata 8\n+\tnothing\n+\ttag bar\n+\tfrom master\n+\ttagger $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE\n+\tdata 10\n+\tsomething\n+\tEOF\n+\ttry_load input &&\n+\tsvn_look tree >actual.tree &&\n+\ttest_cmp expect.tree actual.tree\n+'\n+\n+test_expect_success SVN 'ignore notemodify' '\n+\treinit_svn &&\n+\ttest_tick &&\n+\tcat >expect.tree <<-\\EOF &&\n+\t/\n+\tEOF\n+\tcat >input <<-EOF &&\n+\treset refs/heads/master\n+\tcommit refs/heads/master\n+\tmark :1\n+\tauthor $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE\n+\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\tdata 9\n+\tsomething\n+\tN moo :1\n+\tdata 8\n+\tnothing\n+\tEOF\n+\ttry_load input &&\n+\tsvn_look tree >actual.tree &&\n+\ttest_cmp expect.tree actual.tree\n+'\n+\n+test_expect_success SVN 'svn:special and svn:executable' '\n+\treinit_svn &&\n+\ttest_tick &&\n+\tcat >input <<-EOF &&\n+\treset refs/heads/master\n+\tcommit refs/heads/master\n+\tmark :1\n+\tauthor $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE\n+\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\tdata 7\n+\tnothing\n+\tM 644 inline baz\n+\tdata 0\n+\tM 100755 inline foo\n+\tdata 0\n+\tM 755 inline moo\n+\tdata 0\n+\tM 120000 inline bar\n+\tdata 0\n+\tEOF\n+\ttry_load input &&\n+\tsvn_look propget svn:executable foo &&\n+\tsvn_look propget svn:executable moo &&\n+\tsvn_look propget svn:special bar\n+'\n+\n+test_expect_success SVN 'invalid toplevel command' '\n+\treinit_svn &&\n+\ttest_tick &&\n+\tcat >input <<-EOF &&\n+\treset refs/heads/master\n+\tcommit refs/heads/master\n+\tmarks :1\n+\tauthor $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE\n+\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\tdata 0\n+\tM 100644 inline foo\n+\tdata 0\n+\tEOF\n+\ttest_must_fail try_load input\n+'\n+\n+test_expect_success SVN 'invalid command after commit' '\n+\treinit_svn &&\n+\ttest_tick &&\n+\tcat >input <<-EOF &&\n+\treset refs/heads/master\n+\tcommit refs/heads/master\n+\tmark :1\n+\tauthor $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE\n+\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\tdatum 0\n+\tM 100644 inline foo\n+\tdata 0\n+\tEOF\n+\ttest_must_fail try_load input\n+'\n+\n+test_expect_success SVN 'empty log, empty file' '\n+\treinit_svn &&\n+\ttest_tick &&\n+\tcat >expect.tree <<-\\EOF &&\n+\t/\n+\t foo\n+\tEOF\n+\tcat >input <<-EOF &&\n+\treset refs/heads/master\n+\tcommit refs/heads/master\n+\tmark :1\n+\tauthor $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE\n+\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\tdata 0\n+\tM 100644 inline foo\n+\tdata 0\n+\tEOF\n+\ttry_load input &&\n+\tsvn_look tree >actual.tree &&\n+\ttest_cmp expect.tree actual.tree\n+'\n+\n+test_expect_success SVN 'missing lf after data' '\n+\treinit_svn &&\n+\ttest_tick &&\n+\tcat >expect.log <<-\\EOF &&\n+\tsomething\n+\tEOF\n+\tcat >input <<-EOF &&\n+\treset refs/heads/master\n+\tcommit refs/heads/master\n+\tmark :1\n+\tauthor $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE\n+\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\tdata 9\n+\tsomethingM 100644 inline foo\n+\tdata 0\n+\tEOF\n+\ttry_load input &&\n+\tsvn_look log >actual.log &&\n+\ttest_cmp expect.log actual.log\n+'\n+\n+test_expect_success SVN 'revprops: svn:author, svn:log' '\n+\treinit_svn &&\n+\ttest_tick &&\n+\techo \"nothing\" >expect.log &&\n+\techo \"author\" >expect.author &&\n+\tcat >input <<-EOF &&\n+\treset refs/heads/master\n+\tcommit refs/heads/master\n+\tmark :1\n+\tauthor $GIT_AUTHOR_NAME <author@example.com> $GIT_AUTHOR_DATE\n+\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\tdata 7\n+\tnothing\n+\tM 100644 inline foo\n+\tdata 0\n+\tEOF\n+\ttry_load input &&\n+\tsvn_look log >actual.log &&\n+\tsvn_look author >actual.author &&\n+\ttest_cmp expect.log actual.log &&\n+\ttest_cmp expect.author actual.author\n+'\n+\n+test_expect_success SVN 'missing author line' '\n+\treinit_svn &&\n+\ttest_tick &&\n+\tcat >expect.tree <<-\\EOF &&\n+\t/\n+\t foo\n+\tEOF\n+\tcat >input <<-EOF &&\n+\treset refs/heads/master\n+\tcommit refs/heads/master\n+\tmark :1\n+\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\tdata 0\n+\tM 100644 inline foo\n+\tdata 0\n+\tEOF\n+\ttry_load input &&\n+\tsvn_look tree >actual.tree &&\n+\ttest_cmp expect.tree actual.tree\n+'\n+\n+test_expect_success SVN 'author email without @' '\n+\treinit_svn &&\n+\ttest_tick &&\n+\tcat >input <<-EOF &&\n+\treset refs/heads/master\n+\tcommit refs/heads/master\n+\tmark :1\n+\tauthor $GIT_AUTHOR_NAME <example> $GIT_AUTHOR_DATE\n+\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\tdata 0\n+\tM 100644 inline foo\n+\tdata 0\n+\tEOF\n+\ttry_load input\n+'\n+\n+test_expect_success SVN 'blob marks unsupported' '\n+\treinit_svn &&\n+\ttest_tick &&\n+\tcat >input <<-EOF &&\n+\tblob\n+\tmark :1\n+\tdata 0\n+\treset refs/heads/master\n+\tcommit refs/heads/master\n+\tmark :2\n+\tauthor $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE\n+\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\tdata 0\n+\tM 100644 :1 foo\n+\tdata 0\n+\tEOF\n+\ttest_must_fail try_load input\n+'\n+\n+test_expect_success SVN 'malformed filemodify line' '\n+\treinit_svn &&\n+\ttest_tick &&\n+\tcat >input <<-EOF &&\n+\treset refs/heads/master\n+\tcommit refs/heads/master\n+\tmark :1\n+\tauthor $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE\n+\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\tdata 0\n+\tM 100644 inline\n+\tdata 0\n+\tEOF\n+\ttest_must_fail try_load input\n+'\n+\n+test_expect_success SVN 'malformed author line' '\n+\treinit_svn &&\n+\ttest_tick &&\n+\tcat >input <<-EOF &&\n+\treset refs/heads/master\n+\tcommit refs/heads/master\n+\tmark :1\n+\tauthor 2d3%*s&f#k|\n+\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\tdata 0\n+\tM 100644 inline foo\n+\tdata 0\n+\tEOF\n+\ttest_must_fail try_load input\n+'\n+\n+test_expect_success SVN 'missing committer line' '\n+\treinit_svn &&\n+\ttest_tick &&\n+\tcat >input <<-EOF &&\n+\treset refs/heads/master\n+\tcommit refs/heads/master\n+\tmark :1\n+\tauthor $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE\n+\tdata 0\n+\tM 100644 inline foo\n+\tdata 0\n+\tEOF\n+\ttest_must_fail try_load input\n+'\n+\n+test_expect_success SVN 'malformed data length' '\n+\treinit_svn &&\n+\ttest_tick &&\n+\tcat >input <<-EOF &&\n+\treset refs/heads/master\n+\tcommit refs/heads/master\n+\tmark :1\n+\tauthor $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE\n+\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_AUTHOR_DATE\n+\tdata 0\n+\tM 100644 inline foo\n+\tdata 14238\n+\tEOF\n+\ttest_must_fail try_load input\n+'\n+\n+test_expect_success SVN 'recursive directory creation' '\n+\treinit_svn &&\n+\ttest_tick &&\n+\tcat >expect.tree <<-\\EOF &&\n+\t/\n+\t alpha/\n+\t  beta/\n+\t   gamma\n+\tEOF\n+\tcat >input <<-EOF &&\n+\treset refs/heads/master\n+\tcommit refs/heads/master\n+\tmark :1\n+\tauthor $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE\n+\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\tdata 7\n+\tnothing\n+\tM 100644 inline alpha/beta/gamma\n+\tdata 12\n+\tsome content\n+\tEOF\n+\ttry_load input &&\n+\tsvn_look tree >actual.tree &&\n+\ttest_cmp expect.tree actual.tree\n+'\n+\n+test_expect_success SVN 'replace symlink with normal file' '\n+\treinit_svn &&\n+\ttest_tick &&\n+\tcat >expect.tree <<-\\EOF &&\n+\t/\n+\t alpha/\n+\t  beta/\n+\t   gamma\n+\tEOF\n+\tcat >input <<-EOF &&\n+\treset refs/heads/master\n+\tcommit refs/heads/master\n+\tmark :1\n+\tauthor $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE\n+\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\tdata 7\n+\tnothing\n+\tM 120000 inline alpha/beta/gamma\n+\tdata 0\n+\tcommit refs/heads/master\n+\tmark :1\n+\tauthor $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE\n+\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\tdata 7\n+\tnothing\n+\tM 100644 inline alpha/beta/gamma\n+\tdata 0\n+\tEOF\n+\ttry_load input &&\n+\tsvn_look tree -r1 >actual.tree1 &&\n+\tsvn_look tree -r2 >actual.tree2 &&\n+\ttest_cmp expect.tree actual.tree1 &&\n+\ttest_cmp expect.tree actual.tree2\n+'\n+\n+test_expect_success SVN 'path includes symlink' '\n+\treinit_svn &&\n+\ttest_tick &&\n+\tcat >input <<-EOF &&\n+\treset refs/heads/master\n+\tcommit refs/heads/master\n+\tmark :1\n+\tauthor $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE\n+\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\tdata 7\n+\tnothing\n+\tM 120000 inline alpha/beta/gamma\n+\tdata 0\n+\tcommit refs/heads/master\n+\tmark :1\n+\tauthor $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE\n+\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\tdata 7\n+\tnothing\n+\tM 100644 inline alpha/beta/gamma/bar\n+\tdata 0\n+\tEOF\n+\ttest_must_fail try_load input\n+'\n+\n+test_done\ndiff --git a/test-svn-fi.c b/test-svn-fi.c\nnew file mode 100644\nindex 0000000..cc6b312\n--- /dev/null\n+++ b/test-svn-fi.c\n@@ -0,0 +1,17 @@\n+/*\n+ * test-svn-fe: Code to exercise the svn import lib\n+ */\n+\n+#include \"git-compat-util.h\"\n+#include \"vcs-svn/svnload.h\"\n+\n+int main(int argc, char *argv[])\n+{\n+\tif (argc != 2)\n+\t\tusage(\"test-svn-fe <file>\");\n+\tif (svnload_init(argv[1]))\n+\t\treturn 1;\n+\tsvnload_read();\n+\tsvnload_deinit();\n+\treturn 0;\n+}\n-- \n1.7.4.rc1.7.g2cf08.dirty\n"},{"id":"164622","messageId":"20110329204412.GA13812@elie","threadId":"26913","inReplyTo":"1301422392-21177-3-git-send-email-artagnon@gmail.com","subject":"Re: [PATCH 2/5] fast-export: Introduce --inline-blobs","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2011-03-29T20:44:12Z","receivedAt":"2011-03-29T20:44:12Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Hi,\n\nRamkumar Ramachandra wrote:\n\n> Introduce a new command-line option --inline-blobs that always inlines\n> blobs instead of referring to them via marks or their original SHA-1\n> hash.\n\nCould you expand on this?  What does it mean to inline a blob (is it the\nsame thing as the reference manual describes as using the inline data\nformat with the filemodify command)?  Why do we want to always do\nthat?  How would a person or script choose whether to use this option?\nAre there any downsides?\n\nI ask because I would be happy to use something like this. ;-)  Thanks\nfor working on it.\n\n> --- a/Documentation/git-fast-export.txt\n> +++ b/Documentation/git-fast-export.txt\n> @@ -90,6 +90,11 @@ marks the same across runs.\n>  \tresulting stream can only be used by a repository which\n>  \talready contains the necessary objects.\n>  \n> +--inline-blobs::\n> +\tInline all blobs, instead of referring to them via marks or\n> +\ttheir original SHA-1 hash.  This is useful to parsers, as they\n> +\tdon't need to persist blobs.\n\nThis explanation leaves something out, I think.  If it is useful to\nparsers, that means it simplifies the syntax, so a natural conclusion\nwould be that parsers do not need to learn about the non-inline\nsyntax.  But I think the last time[1] this came up, we decided that\none should not encourage that, because it moves away from a world in\nwhich \"git fast-export\", \"hg fast-export\", and svn-fe use one standard\nformat and can be used interchangeably.\n\n[1] http://thread.gmane.org/gmane.comp.version-control.git/165237/focus=165289\n\nPerhaps it would be better to say something to the effect that \"This\ncan decrease the memory footprint and complexity of the work some\nfast-import backends have to do\"?  In other words, it's just an\noptimization.\n\nTo that end, if the same blob keeps on coming up again and again, I'd\nbe tempted to allow making a mark for it in the future, even when\n--inline-blobs is specified.  In other words, I'd prefer (unless\nreal-world considerations prevent it) for --inline-blobs to be a hint\nor a permission instead of something binding.\n\n> --- a/builtin/fast-export.c\n> +++ b/builtin/fast-export.c\n[...]\n> @@ -118,7 +119,7 @@ static void handle_object(const unsigned char *sha1)\n>  \tchar *buf;\n>  \tstruct object *object;\n>  \n> -\tif (no_data)\n> +\tif (no_data || inline_blobs)\n>  \t\treturn;\n\nMaybe inline_blobs should imply no_data internally?\n\n>  \n>  \tif (is_null_sha1(sha1))\n> @@ -218,7 +219,23 @@ static void show_filemodify(struct diff_queue_struct *q,\n>  \t\t\tif (no_data || S_ISGITLINK(spec->mode))\n>  \t\t\t\tprintf(\"M %06o %s %s\\n\", spec->mode,\n>  \t\t\t\t       sha1_to_hex(spec->sha1), spec->path);\n> -\t\t\telse {\n> +\t\t\telse if (inline_blobs) {\n\nIf so, this could be something like\n\n\tint inline_me = inline_blobs && !S_ISGITLINK(spec->mode);\n\t...\n\n\tif (no_data || S_ISGITLINK(spec->mode)) {\n\t\tconst char *dataref =\n\t\t\tinline_me ? \"inline\" : sha1_to_hex(spec->sha1);\n\t\tprintf(\"M %06o %s %s\\n\", spec->mode, dataref, spec->path);\n\t} else {\n\t\tstruct object *object = lookup_object(spec->sha1);\n\t\tprintf(\"M %06o :%d %s\\n\", spec->mode,\n\t\t       get_object_mark(object), spec->path);\n\t}\n\n\tif (inline_blob && export_data(spec->data, spec->size))\n\t\tdie_errno(\"Could not write blob '%s'\",\n\t\t\t\tsha1_to_hex(spec->sha1));\n\n> --- a/contrib/svn-fe/.gitignore\n> +++ b/contrib/svn-fe/.gitignore\n[...]\n> --- a/contrib/svn-fe/Makefile\n> +++ b/contrib/svn-fe/Makefile\n[...]\n> --- /dev/null\n> +++ b/contrib/svn-fe/svn-fi.c\n[...]\n> --- /dev/null\n> +++ b/contrib/svn-fe/svn-fi.txt\n\nSnuck in from another patch?\n\nHope that helps,\nJonathan\n"},{"id":"164751","messageId":"20110331021451.GA31090@elie","threadId":"26913","inReplyTo":"1301422392-21177-4-git-send-email-artagnon@gmail.com","subject":"Re: [PATCH 3/5] strbuf: Introduce strbuf_fwrite corresponding to strbuf_fread","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2011-03-31T02:15:14Z","receivedAt":"2011-03-31T02:15:14Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Ramkumar Ramachandra wrote:\n\n> [Subject: strbuf: Introduce strbuf_fwrite corresponding to strbuf_fread]\n>\n> Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>\n\nAnd why?  FWIW strbuf_fread takes care of (1) setting the size of sb\nto accomodate the input, (2) starting reading at the end of the\ncurrent strbuf, and (3) freeing the buffer if nothing could be read,\nso symmetry doesn't seem like a strong justification.\n\nWhich is not to say this is a bad change.  Just that it's hard to see\nhow it lives up to its goals without knowing what those goals are.\n"},{"id":"164845","messageId":"20110331232307.GA21608@elie","threadId":"26913","inReplyTo":"1301422392-21177-6-git-send-email-artagnon@gmail.com","subject":"Re: [PATCH 5/5] t9012-svn-fi: Add tests for svn-fi","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2011-03-31T23:23:07Z","receivedAt":"2011-03-31T23:23:07Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Ramkumar Ramachandra wrote:\n\n> Verifying svn:date is very non-trivial, because of timezone conversion\n> issues.\n\nMaybe the TZ variable can help.\n"}]}