From: Jonathan Nieder Date: Thu, 26 May 2011 06:51:38 +0000 (-0500) Subject: Merge branch 'db/vcs-svn-incremental' into svn-fe X-Git-Tag: v1.7.12-rc0~41^2~13^2~5 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/9ecfa8ae4c6701cae85c4f22fdfacffe22d8a75e?ds=inline;hp=-c Merge branch 'db/vcs-svn-incremental' into svn-fe This teaches svn-fe to incrementally import into an existing repository (at last!) at the expense of less convenient UI. Think of it as growing pains. This opens the door to many excellent things, and it would be a bad idea to discourage people from building on it for much longer. * db/vcs-svn-incremental: vcs-svn: avoid using ls command twice vcs-svn: use mark from previous import for parent commit vcs-svn: handle filenames with dq correctly vcs-svn: quote paths correctly for ls command vcs-svn: eliminate repo_tree structure vcs-svn: add a comment before each commit vcs-svn: save marks for imported commits vcs-svn: use higher mark numbers for blobs vcs-svn: set up channel to read fast-import cat-blob response Conflicts: t/t9010-svn-fe.sh vcs-svn/fast_export.c vcs-svn/fast_export.h vcs-svn/repo_tree.c vcs-svn/svndump.c --- 9ecfa8ae4c6701cae85c4f22fdfacffe22d8a75e diff --combined t/t9010-svn-fe.sh index 6f6175a8f7,720fd6b5a3..003395c5f6 --- a/t/t9010-svn-fe.sh +++ b/t/t9010-svn-fe.sh @@@ -5,8 -5,26 +5,26 @@@ test_description='check svn dumpfile im . ./test-lib.sh reinit_git () { + if ! test_declared_prereq PIPE + then + echo >&4 "reinit_git: need to declare PIPE prerequisite" + return 127 + fi rm -fr .git && - git init + rm -f stream backflow && + git init && + mkfifo stream backflow + } + + try_dump () { + input=$1 && + maybe_fail=${2:+test_$2} && + + { + $maybe_fail test-svn-fe "$input" >stream 3backflow && + wait $! } properties () { @@@ -35,21 -53,27 +53,27 @@@ text_no_props () >empty - test_expect_success 'empty dump' ' + test_expect_success 'setup: have pipes?' ' + rm -f frob && + if mkfifo frob + then + test_set_prereq PIPE + fi + ' + + test_expect_success PIPE 'empty dump' ' reinit_git && echo "SVN-fs-dump-format-version: 2" >input && - test-svn-fe input >stream && - git fast-import v4.dump && - test_must_fail test-svn-fe v4.dump >stream && - test_cmp empty stream + try_dump v4.dump must_fail ' - test_expect_failure 'empty revision' ' + test_expect_failure PIPE 'empty revision' ' reinit_git && printf "rev : %s\n" "" "" >expect && cat >emptyrev.dump <<-\EOF && @@@ -64,13 -88,12 +88,12 @@@ Content-length: 0 EOF - test-svn-fe emptyrev.dump >stream && - git fast-import actual && test_cmp expect actual ' - test_expect_success 'empty properties' ' + test_expect_success PIPE 'empty properties' ' reinit_git && printf "rev : %s\n" "" "" >expect && cat >emptyprop.dump <<-\EOF && @@@ -88,13 -111,12 +111,12 @@@ PROPS-END EOF - test-svn-fe emptyprop.dump >stream && - git fast-import actual && test_cmp expect actual ' - test_expect_success 'author name and commit message' ' + test_expect_success PIPE 'author name and commit message' ' reinit_git && echo "" >expect.author && cat >message <<-\EOF && @@@ -121,15 -143,14 +143,14 @@@ echo && cat props } >log.dump && - test-svn-fe log.dump >stream && - git fast-import actual.log && git log --format="<%an, %ae>" >actual.author && test_cmp message actual.log && test_cmp expect.author actual.author ' - test_expect_success 'unsupported properties are ignored' ' + test_expect_success PIPE 'unsupported properties are ignored' ' reinit_git && echo author >expect && cat >extraprop.dump <<-\EOF && @@@ -149,13 -170,12 +170,12 @@@ author PROPS-END EOF - test-svn-fe extraprop.dump >stream && - git fast-import actual && test_cmp expect actual ' - test_expect_failure 'timestamp and empty file' ' + test_expect_failure PIPE 'timestamp and empty file' ' echo author@example.com >expect.author && echo 1999-01-01 >expect.date && echo file >expect.files && @@@ -186,8 -206,7 +206,7 @@@ EOF } >emptyfile.dump && - test-svn-fe emptyfile.dump >stream && - git fast-import actual.author && git log --date=short --format=%ad HEAD >actual.date && git ls-tree -r --name-only HEAD >actual.files && @@@ -198,7 -217,7 +217,7 @@@ test_cmp empty file ' - test_expect_success 'directory with files' ' + test_expect_success PIPE 'directory with files' ' reinit_git && printf "%s\n" directory/file1 directory/file2 >expect.files && echo hi >hi && @@@ -242,8 -261,7 +261,7 @@@ EOF text_no_props hi } >directory.dump && - test-svn-fe directory.dump >stream && - git fast-import actual.files && git checkout HEAD directory && @@@ -252,7 -270,107 +270,107 @@@ test_cmp hi directory/file2 ' - test_expect_success 'node without action' ' + test_expect_success PIPE 'branch name with backslash' ' + reinit_git && + sort <<-\EOF >expect.branch-files && + trunk/file1 + trunk/file2 + "branches/UpdateFOPto094\\/file1" + "branches/UpdateFOPto094\\/file2" + EOF + + echo hi >hi && + echo hello >hello && + { + properties \ + svn:author author@example.com \ + svn:date "1999-02-02T00:01:02.000000Z" \ + svn:log "add directory with some files in it" && + echo PROPS-END + } >props.setup && + { + properties \ + svn:author brancher@example.com \ + svn:date "2007-12-06T21:38:34.000000Z" \ + svn:log "Updating fop to .94 and adjust fo-stylesheets" && + echo PROPS-END + } >props.branch && + { + cat <<-EOF && + SVN-fs-dump-format-version: 3 + + Revision-number: 1 + EOF + echo Prop-content-length: $(wc -c branch.dump && + try_dump branch.dump && + + git ls-tree -r --name-only HEAD | + sort >actual.branch-files && + test_cmp expect.branch-files actual.branch-files + ' + + test_expect_success PIPE 'node without action' ' + reinit_git && cat >inaction.dump <<-\EOF && SVN-fs-dump-format-version: 3 @@@ -269,10 -387,11 +387,11 @@@ PROPS-END EOF - test_must_fail test-svn-fe inaction.dump + try_dump inaction.dump must_fail ' - test_expect_success 'action: add node without text' ' + test_expect_success PIPE 'action: add node without text' ' + reinit_git && cat >textless.dump <<-\EOF && SVN-fs-dump-format-version: 3 @@@ -290,10 -409,10 +409,10 @@@ PROPS-END EOF - test_must_fail test-svn-fe textless.dump + try_dump textless.dump must_fail ' - test_expect_failure 'change file mode but keep old content' ' + test_expect_failure PIPE 'change file mode but keep old content' ' reinit_git && cat >expect <<-\EOF && OBJID @@@ -356,8 -475,7 +475,7 @@@ PROPS-END EOF - test-svn-fe filemode.dump >stream && - git fast-import expect.message && + { + properties \ + unimportant "something with a NUL (Q)" \ + svn:log "commit message"&& + echo PROPS-END + } | + q_to_nul >props && + { + cat <<-\EOF && + SVN-fs-dump-format-version: 3 + + Revision-number: 1 + EOF + echo Prop-content-length: $(wc -c nulprop.dump && - test-svn-fe nulprop.dump >stream && - git fast-import actual.message && + test_cmp expect.message actual.message +' + - test_expect_success 'NUL in log message, file content, and property name' ' ++test_expect_success PIPE 'NUL in log message, file content, and property name' ' + # Caveat: svnadmin 1.6.16 (r1073529) truncates at \0 in the + # svn:specialQnotreally example. + reinit_git && + cat >expect <<-\EOF && + OBJID + :100644 100644 OBJID OBJID M greeting + OBJID + :000000 100644 OBJID OBJID A greeting + EOF + printf "\n%s\n" "something with an ASCII NUL (Q)" >expect.message && + printf "%s\n" "helQo" >expect.hello1 && + printf "%s\n" "link hello" >expect.hello2 && + { + properties svn:log "something with an ASCII NUL (Q)" && + echo PROPS-END + } | + q_to_nul >props && + { + q_to_nul <<-\EOF && + SVN-fs-dump-format-version: 3 + + Revision-number: 1 + Prop-content-length: 10 + Content-length: 10 + + PROPS-END + + Node-path: greeting + Node-kind: file + Node-action: add + Prop-content-length: 10 + Text-content-length: 6 + Content-length: 16 + + PROPS-END + helQo + + Revision-number: 2 + EOF + echo Prop-content-length: $(wc -c 8bitclean.dump && - test-svn-fe 8bitclean.dump >stream && - git fast-import actual && + { + git cat-file commit HEAD | nul_to_q && + echo + } | + sed -ne "/^\$/,\$ p" >actual.message && + git cat-file blob HEAD^:greeting | nul_to_q >actual.hello1 && + git cat-file blob HEAD:greeting | nul_to_q >actual.hello2 && + test_cmp expect actual && + test_cmp expect.message actual.message && + test_cmp expect.hello1 actual.hello1 && + test_cmp expect.hello2 actual.hello2 +' + - test_expect_success 'change file mode and reiterate content' ' + test_expect_success PIPE 'change file mode and reiterate content' ' reinit_git && cat >expect <<-\EOF && OBJID @@@ -490,7 -500,7 +606,7 @@@ EOF echo "link hello" >expect.blob && echo hello >hello && - cat >filemode.dump <<-\EOF && + cat >filemode2.dump <<-\EOF && SVN-fs-dump-format-version: 3 Revision-number: 1 @@@ -545,8 -555,7 +661,7 @@@ PROPS-END link hello EOF - test-svn-fe filemode.dump >stream && - git fast-import delta.dump && - test_must_fail test-svn-fe delta.dump + test_must_fail try_dump delta.dump ' - test_expect_success 'property deltas supported' ' + test_expect_success PIPE 'property deltas supported' ' reinit_git && cat >expect <<-\EOF && OBJID @@@ -678,8 -688,7 +794,7 @@@ PROPS-END EOF } >propdelta.dump && - test-svn-fe propdelta.dump >stream && - git fast-import expect && OBJID @@@ -733,8 -742,7 +848,7 @@@ PROPS-END EOF - test-svn-fe changeroot.dump >stream && - git fast-import expect <<-\EOF && OBJID @@@ -819,8 -827,7 +933,7 @@@ PROPS-END link testing 321 EOF - test-svn-fe deleteprop.dump >stream && - git fast-import simple.fe && + test_expect_success SVNREPO,PIPE 't9135/svn.dump' ' + mkdir -p simple-git && ( cd simple-git && - git fast-import <../simple.fe + reinit_git && + try_dump "$TEST_DIRECTORY/t9135/svn.dump" ) && ( cd simple-svnco && diff --combined vcs-svn/fast_export.c index 99ed70b88a,f19db9ae82..ff980b3a2a --- a/vcs-svn/fast_export.c +++ b/vcs-svn/fast_export.c @@@ -8,81 -8,182 +8,194 @@@ #include "line_buffer.h" #include "repo_tree.h" #include "string_pool.h" + #include "strbuf.h" #define MAX_GITSVN_LINE_LEN 4096 static uint32_t first_commit_done; + static struct line_buffer report_buffer = LINE_BUFFER_INIT; - void fast_export_delete(uint32_t depth, uint32_t *path) + void fast_export_init(int fd) { - putchar('D'); - putchar(' '); - pool_print_seq(depth, path, '/', stdout); - putchar('\n'); + if (buffer_fdinit(&report_buffer, fd)) + die_errno("cannot read from file descriptor %d", fd); } - void fast_export_modify(uint32_t depth, uint32_t *path, uint32_t mode, - uint32_t mark) + void fast_export_deinit(void) + { + if (buffer_deinit(&report_buffer)) + die_errno("error closing fast-import feedback stream"); + } + + void fast_export_reset(void) + { + buffer_reset(&report_buffer); + } + + void fast_export_delete(uint32_t depth, const uint32_t *path) + { + printf("D \""); + pool_print_seq_q(depth, path, '/', stdout); + printf("\"\n"); + } + + static void fast_export_truncate(uint32_t depth, const uint32_t *path, uint32_t mode) + { + fast_export_modify(depth, path, mode, "inline"); + printf("data 0\n\n"); + } + + void fast_export_modify(uint32_t depth, const uint32_t *path, uint32_t mode, + const char *dataref) { /* Mode must be 100644, 100755, 120000, or 160000. */ - printf("M %06"PRIo32" :%"PRIu32" ", mode, mark); - pool_print_seq(depth, path, '/', stdout); - putchar('\n'); + if (!dataref) { + fast_export_truncate(depth, path, mode); + return; + } + printf("M %06"PRIo32" %s \"", mode, dataref); + pool_print_seq_q(depth, path, '/', stdout); + printf("\"\n"); } static char gitsvnline[MAX_GITSVN_LINE_LEN]; - void fast_export_commit(uint32_t revision, const char *author, -void fast_export_begin_commit(uint32_t revision, uint32_t author, char *log, - uint32_t uuid, uint32_t url, ++void fast_export_begin_commit(uint32_t revision, const char *author, + const struct strbuf *log, + const char *uuid, const char *url, unsigned long timestamp) { + static const struct strbuf empty = STRBUF_INIT; if (!log) - log = ""; - if (~uuid && ~url) { + log = ∅ + if (*uuid && *url) { snprintf(gitsvnline, MAX_GITSVN_LINE_LEN, "\n\ngit-svn-id: %s@%"PRIu32" %s\n", - pool_fetch(url), revision, pool_fetch(uuid)); + url, revision, uuid); } else { *gitsvnline = '\0'; } printf("commit refs/heads/master\n"); + printf("mark :%"PRIu32"\n", revision); printf("committer %s <%s@%s> %ld +0000\n", - ~author ? pool_fetch(author) : "nobody", - ~author ? pool_fetch(author) : "nobody", - ~uuid ? pool_fetch(uuid) : "local", timestamp); - printf("data %"PRIu32"\n%s%s\n", - (uint32_t) (strlen(log) + strlen(gitsvnline)), - log, gitsvnline); + *author ? author : "nobody", + *author ? author : "nobody", + *uuid ? uuid : "local", timestamp); + printf("data %"PRIuMAX"\n", + (uintmax_t) (log->len + strlen(gitsvnline))); + fwrite(log->buf, log->len, 1, stdout); + printf("%s\n", gitsvnline); if (!first_commit_done) { if (revision > 1) - printf("from refs/heads/master^0\n"); + printf("from :%"PRIu32"\n", revision - 1); first_commit_done = 1; } - repo_diff(revision - 1, revision); - fputc('\n', stdout); + } + void fast_export_end_commit(uint32_t revision) + { printf("progress Imported commit %"PRIu32".\n\n", revision); } + static void ls_from_rev(uint32_t rev, uint32_t depth, const uint32_t *path) + { + /* ls :5 path/to/old/file */ + printf("ls :%"PRIu32" \"", rev); + pool_print_seq_q(depth, path, '/', stdout); + printf("\"\n"); + fflush(stdout); + } + + static void ls_from_active_commit(uint32_t depth, const uint32_t *path) + { + /* ls "path/to/file" */ + printf("ls \""); + pool_print_seq_q(depth, path, '/', stdout); + printf("\"\n"); + fflush(stdout); + } + + static const char *get_response_line(void) + { + const char *line = buffer_read_line(&report_buffer); + if (line) + return line; + if (buffer_ferror(&report_buffer)) + die_errno("error reading from fast-import"); + die("unexpected end of fast-import feedback"); + } + +static void die_short_read(struct line_buffer *input) +{ + if (buffer_ferror(input)) + die_errno("error reading dump file"); + die("invalid dump: unexpected end of file"); +} + - void fast_export_blob(uint32_t mode, uint32_t mark, uint32_t len, struct line_buffer *input) + void fast_export_data(uint32_t mode, uint32_t len, struct line_buffer *input) { if (mode == REPO_MODE_LNK) { /* svn symlink blobs start with "link " */ - buffer_skip_bytes(input, 5); len -= 5; + if (buffer_skip_bytes(input, 5) != 5) + die_short_read(input); } - printf("blob\nmark :%"PRIu32"\ndata %"PRIu32"\n", mark, len); + printf("data %"PRIu32"\n", len); - buffer_copy_bytes(input, len); + if (buffer_copy_bytes(input, len) != len) + die_short_read(input); fputc('\n', stdout); } + + static int parse_ls_response(const char *response, uint32_t *mode, + struct strbuf *dataref) + { + const char *tab; + const char *response_end; + + assert(response); + response_end = response + strlen(response); + + if (*response == 'm') { /* Missing. */ + errno = ENOENT; + return -1; + } + + /* Mode. */ + if (response_end - response < strlen("100644") || + response[strlen("100644")] != ' ') + die("invalid ls response: missing mode: %s", response); + *mode = 0; + for (; *response != ' '; response++) { + char ch = *response; + if (ch < '0' || ch > '7') + die("invalid ls response: mode is not octal: %s", response); + *mode *= 8; + *mode += ch - '0'; + } + + /* ' blob ' or ' tree ' */ + if (response_end - response < strlen(" blob ") || + (response[1] != 'b' && response[1] != 't')) + die("unexpected ls response: not a tree or blob: %s", response); + response += strlen(" blob "); + + /* Dataref. */ + tab = memchr(response, '\t', response_end - response); + if (!tab) + die("invalid ls response: missing tab: %s", response); + strbuf_add(dataref, response, tab - response); + return 0; + } + + int fast_export_ls_rev(uint32_t rev, uint32_t depth, const uint32_t *path, + uint32_t *mode, struct strbuf *dataref) + { + ls_from_rev(rev, depth, path); + return parse_ls_response(get_response_line(), mode, dataref); + } + + int fast_export_ls(uint32_t depth, const uint32_t *path, + uint32_t *mode, struct strbuf *dataref) + { + ls_from_active_commit(depth, path); + return parse_ls_response(get_response_line(), mode, dataref); + } diff --combined vcs-svn/fast_export.h index 33a8fe996f,633d21944e..9c522d177d --- a/vcs-svn/fast_export.h +++ b/vcs-svn/fast_export.h @@@ -1,16 -1,25 +1,26 @@@ #ifndef FAST_EXPORT_H_ #define FAST_EXPORT_H_ - #include "line_buffer.h" struct strbuf; + struct line_buffer; - void fast_export_delete(uint32_t depth, uint32_t *path); - void fast_export_modify(uint32_t depth, uint32_t *path, uint32_t mode, - uint32_t mark); - void fast_export_commit(uint32_t revision, const char *author, + void fast_export_init(int fd); + void fast_export_deinit(void); + void fast_export_reset(void); + + void fast_export_delete(uint32_t depth, const uint32_t *path); + void fast_export_modify(uint32_t depth, const uint32_t *path, + uint32_t mode, const char *dataref); -void fast_export_begin_commit(uint32_t revision, uint32_t author, char *log, - uint32_t uuid, uint32_t url, unsigned long timestamp); ++void fast_export_begin_commit(uint32_t revision, const char *author, + const struct strbuf *log, const char *uuid, + const char *url, unsigned long timestamp); - void fast_export_blob(uint32_t mode, uint32_t mark, uint32_t len, - struct line_buffer *input); + void fast_export_end_commit(uint32_t revision); + void fast_export_data(uint32_t mode, uint32_t len, struct line_buffer *input); + + /* If there is no such file at that rev, returns -1, errno == ENOENT. */ + int fast_export_ls_rev(uint32_t rev, uint32_t depth, const uint32_t *path, + uint32_t *mode_out, struct strbuf *dataref_out); + int fast_export_ls(uint32_t depth, const uint32_t *path, + uint32_t *mode_out, struct strbuf *dataref_out); #endif diff --combined vcs-svn/repo_tree.h index 37bde2e374,f506352dc2..ce69fa7e58 --- a/vcs-svn/repo_tree.h +++ b/vcs-svn/repo_tree.h @@@ -1,7 -1,7 +1,7 @@@ #ifndef REPO_TREE_H_ #define REPO_TREE_H_ -#include "git-compat-util.h" +struct strbuf; #define REPO_MODE_DIR 0040000 #define REPO_MODE_BLB 0100644 @@@ -14,12 -14,10 +14,11 @@@ uint32_t next_blob_mark(void); void repo_copy(uint32_t revision, const uint32_t *src, const uint32_t *dst); void repo_add(uint32_t *path, uint32_t mode, uint32_t blob_mark); - uint32_t repo_read_path(const uint32_t *path); - uint32_t repo_read_mode(const uint32_t *path); + const char *repo_read_path(const uint32_t *path, uint32_t *mode_out); void repo_delete(uint32_t *path); -void repo_commit(uint32_t revision, uint32_t author, char *log, uint32_t uuid, - uint32_t url, long unsigned timestamp); +void repo_commit(uint32_t revision, const char *author, + const struct strbuf *log, const char *uuid, const char *url, + long unsigned timestamp); void repo_diff(uint32_t r1, uint32_t r2); void repo_init(void); void repo_reset(void); diff --combined vcs-svn/string_pool.c index 8af8d54d6e,be43598d5b..23a9d46522 --- a/vcs-svn/string_pool.c +++ b/vcs-svn/string_pool.c @@@ -4,6 -4,7 +4,7 @@@ */ #include "git-compat-util.h" + #include "quote.h" #include "trp.h" #include "obj_pool.h" #include "string_pool.h" @@@ -30,7 -31,7 +31,7 @@@ static int node_cmp(struct node *a, str } /* Build a Treap from the node structure (a trp_node w/ offset) */ -trp_gen(static, tree_, struct node, children, node, node_cmp); +trp_gen(static, tree_, struct node, children, node, node_cmp) const char *pool_fetch(uint32_t entry) { @@@ -65,7 -66,7 +66,7 @@@ uint32_t pool_tok_r(char *str, const ch return token ? pool_intern(token) : ~0; } - void pool_print_seq(uint32_t len, uint32_t *seq, char delim, FILE *stream) + void pool_print_seq(uint32_t len, const uint32_t *seq, char delim, FILE *stream) { uint32_t i; for (i = 0; i < len && ~seq[i]; i++) { @@@ -75,6 -76,16 +76,16 @@@ } } + void pool_print_seq_q(uint32_t len, const uint32_t *seq, char delim, FILE *stream) + { + uint32_t i; + for (i = 0; i < len && ~seq[i]; i++) { + quote_c_style(pool_fetch(seq[i]), NULL, stream, 1); + if (i < len - 1 && ~seq[i + 1]) + fputc(delim, stream); + } + } + uint32_t pool_tok_seq(uint32_t sz, uint32_t *seq, const char *delim, char *str) { char *context = NULL; diff --combined vcs-svn/svndump.c index 572a995966,99a5ba0d10..35a8af3c9f --- a/vcs-svn/svndump.c +++ b/vcs-svn/svndump.c @@@ -11,30 -11,42 +11,34 @@@ #include "repo_tree.h" #include "fast_export.h" #include "line_buffer.h" -#include "obj_pool.h" #include "string_pool.h" +#include "strbuf.h" + +/* + * Compare start of string to literal of equal length; + * must be guarded by length test. + */ +#define constcmp(s, ref) memcmp(s, ref, sizeof(ref) - 1) + #define REPORT_FILENO 3 + #define NODEACT_REPLACE 4 #define NODEACT_DELETE 3 #define NODEACT_ADD 2 #define NODEACT_CHANGE 1 #define NODEACT_UNKNOWN 0 - #define DUMP_CTX 0 - #define REV_CTX 1 - #define NODE_CTX 2 + /* States: */ + #define DUMP_CTX 0 /* dump metadata */ + #define REV_CTX 1 /* revision metadata */ + #define NODE_CTX 2 /* node metadata */ + #define INTERNODE_CTX 3 /* between nodes */ #define LENGTH_UNKNOWN (~0) #define DATE_RFC2822_LEN 31 -/* Create memory pool for log messages */ -obj_pool_gen(log, char, 4096) - static struct line_buffer input = LINE_BUFFER_INIT; -#define REPORT_FILENO 3 - -static char *log_copy(uint32_t length, const char *log) -{ - char *buffer; - log_free(log_pool.size); - buffer = log_pointer(log_alloc(length)); - strncpy(buffer, log, length); - return buffer; -} - static struct { uint32_t action, propLength, textLength, srcRev, type; uint32_t src[REPO_MAX_PATH_DEPTH], dst[REPO_MAX_PATH_DEPTH]; @@@ -42,16 -54,24 +46,16 @@@ } node_ctx; static struct { - uint32_t revision, author; + uint32_t revision; unsigned long timestamp; - char *log; + struct strbuf log, author; } rev_ctx; static struct { - uint32_t version, uuid, url; + uint32_t version; + struct strbuf uuid, url; } dump_ctx; -static struct { - uint32_t svn_log, svn_author, svn_date, svn_executable, svn_special, uuid, - revision_number, node_path, node_kind, node_action, - node_copyfrom_path, node_copyfrom_rev, text_content_length, - prop_content_length, content_length, svn_fs_dump_format_version, - /* version 3 format */ - text_delta, prop_delta; -} keys; - static void reset_node_ctx(char *fname) { node_ctx.type = 0; @@@ -69,58 -89,56 +73,58 @@@ static void reset_rev_ctx(uint32_t revi { rev_ctx.revision = revision; rev_ctx.timestamp = 0; - rev_ctx.log = NULL; - rev_ctx.author = ~0; + strbuf_reset(&rev_ctx.log); + strbuf_reset(&rev_ctx.author); } -static void reset_dump_ctx(uint32_t url) +static void reset_dump_ctx(const char *url) { - dump_ctx.url = url; + strbuf_reset(&dump_ctx.url); + if (url) + strbuf_addstr(&dump_ctx.url, url); dump_ctx.version = 1; - dump_ctx.uuid = ~0; -} - -static void init_keys(void) -{ - keys.svn_log = pool_intern("svn:log"); - keys.svn_author = pool_intern("svn:author"); - keys.svn_date = pool_intern("svn:date"); - keys.svn_executable = pool_intern("svn:executable"); - keys.svn_special = pool_intern("svn:special"); - keys.uuid = pool_intern("UUID"); - keys.revision_number = pool_intern("Revision-number"); - keys.node_path = pool_intern("Node-path"); - keys.node_kind = pool_intern("Node-kind"); - keys.node_action = pool_intern("Node-action"); - keys.node_copyfrom_path = pool_intern("Node-copyfrom-path"); - keys.node_copyfrom_rev = pool_intern("Node-copyfrom-rev"); - keys.text_content_length = pool_intern("Text-content-length"); - keys.prop_content_length = pool_intern("Prop-content-length"); - keys.content_length = pool_intern("Content-length"); - keys.svn_fs_dump_format_version = pool_intern("SVN-fs-dump-format-version"); - /* version 3 format (Subversion 1.1.0) */ - keys.text_delta = pool_intern("Text-delta"); - keys.prop_delta = pool_intern("Prop-delta"); + strbuf_reset(&dump_ctx.uuid); } -static void handle_property(uint32_t key, const char *val, uint32_t len, +static void handle_property(const struct strbuf *key_buf, + struct strbuf *val, uint32_t *type_set) { - if (key == keys.svn_log) { + const char *key = key_buf->buf; + size_t keylen = key_buf->len; + + switch (keylen + 1) { + case sizeof("svn:log"): + if (constcmp(key, "svn:log")) + break; if (!val) die("invalid dump: unsets svn:log"); - /* Value length excludes terminating nul. */ - rev_ctx.log = log_copy(len + 1, val); - } else if (key == keys.svn_author) { - rev_ctx.author = pool_intern(val); - } else if (key == keys.svn_date) { + strbuf_swap(&rev_ctx.log, val); + break; + case sizeof("svn:author"): + if (constcmp(key, "svn:author")) + break; + if (!val) + strbuf_reset(&rev_ctx.author); + else + strbuf_swap(&rev_ctx.author, val); + break; + case sizeof("svn:date"): + if (constcmp(key, "svn:date")) + break; if (!val) die("invalid dump: unsets svn:date"); - if (parse_date_basic(val, &rev_ctx.timestamp, NULL)) - warning("invalid timestamp: %s", val); - } else if (key == keys.svn_executable || key == keys.svn_special) { + if (parse_date_basic(val->buf, &rev_ctx.timestamp, NULL)) + warning("invalid timestamp: %s", val->buf); + break; + case sizeof("svn:executable"): + case sizeof("svn:special"): + if (keylen == strlen("svn:executable") && + constcmp(key, "svn:executable")) + break; + if (keylen == strlen("svn:special") && + constcmp(key, "svn:special")) + break; if (*type_set) { if (!val) return; @@@ -131,23 -149,15 +135,23 @@@ return; } *type_set = 1; - node_ctx.type = key == keys.svn_executable ? + node_ctx.type = keylen == strlen("svn:executable") ? REPO_MODE_EXE : REPO_MODE_LNK; } } +static void die_short_read(void) +{ + if (buffer_ferror(&input)) + die_errno("error reading dump file"); + die("invalid dump: unexpected end of file"); +} + static void read_props(void) { - uint32_t key = ~0; + static struct strbuf key = STRBUF_INIT; + static struct strbuf val = STRBUF_INIT; const char *t; /* * NEEDSWORK: to support simple mode changes like @@@ -164,34 -174,27 +168,34 @@@ uint32_t type_set = 0; while ((t = buffer_read_line(&input)) && strcmp(t, "PROPS-END")) { uint32_t len; - const char *val; const char type = t[0]; + int ch; if (!type || t[1] != ' ') die("invalid property line: %s\n", t); len = atoi(&t[2]); - val = buffer_read_string(&input, len); - buffer_skip_bytes(&input, 1); /* Discard trailing newline. */ + strbuf_reset(&val); + buffer_read_binary(&input, &val, len); + if (val.len < len) + die_short_read(); + + /* Discard trailing newline. */ + ch = buffer_read_char(&input); + if (ch == EOF) + die_short_read(); + if (ch != '\n') + die("invalid dump: expected newline after %s", val.buf); switch (type) { case 'K': - key = pool_intern(val); + strbuf_swap(&key, &val); continue; case 'D': - key = pool_intern(val); - val = NULL; - len = 0; - /* fall through */ + handle_property(&val, NULL, &type_set); + continue; case 'V': - handle_property(key, val, len, &type_set); - key = ~0; + handle_property(&key, &val, &type_set); + strbuf_reset(&key); continue; default: die("invalid property line: %s\n", t); @@@ -201,21 -204,26 +205,27 @@@ static void handle_node(void) { - uint32_t mark = 0; const uint32_t type = node_ctx.type; const int have_props = node_ctx.propLength != LENGTH_UNKNOWN; const int have_text = node_ctx.textLength != LENGTH_UNKNOWN; + /* + * Old text for this node: + * NULL - directory or bug + * empty_blob - empty + * "" - data retrievable from fast-import + */ + static const char *const empty_blob = "::empty::"; + const char *old_data = NULL; if (node_ctx.text_delta) die("text deltas not supported"); - if (have_text) - mark = next_blob_mark(); + if (node_ctx.action == NODEACT_DELETE) { if (have_text || have_props || node_ctx.srcRev) die("invalid dump: deletion node has " "copyfrom info, text, or properties"); - return repo_delete(node_ctx.dst); + repo_delete(node_ctx.dst); + return; } if (node_ctx.action == NODEACT_REPLACE) { repo_delete(node_ctx.dst); @@@ -230,23 -238,26 +240,26 @@@ die("invalid dump: directories cannot have text attached"); /* - * Decide on the new content (mark) and mode (node_ctx.type). + * Find old content (old_data) and decide on the new mode. */ if (node_ctx.action == NODEACT_CHANGE && !~*node_ctx.dst) { if (type != REPO_MODE_DIR) die("invalid dump: root of tree is not a regular file"); + old_data = NULL; } else if (node_ctx.action == NODEACT_CHANGE) { uint32_t mode; - if (!have_text) - mark = repo_read_path(node_ctx.dst); - mode = repo_read_mode(node_ctx.dst); + old_data = repo_read_path(node_ctx.dst, &mode); if (mode == REPO_MODE_DIR && type != REPO_MODE_DIR) die("invalid dump: cannot modify a directory into a file"); if (mode != REPO_MODE_DIR && type == REPO_MODE_DIR) die("invalid dump: cannot modify a file into a directory"); node_ctx.type = mode; } else if (node_ctx.action == NODEACT_ADD) { - if (!have_text && type != REPO_MODE_DIR) + if (type == REPO_MODE_DIR) + old_data = NULL; + else if (have_text) + old_data = empty_blob; + else die("invalid dump: adds node without text"); } else { die("invalid dump: Node-path block lacks Node-action"); @@@ -265,18 -276,34 +278,35 @@@ /* * Save the result. */ - repo_add(node_ctx.dst, node_ctx.type, mark); - if (have_text) - fast_export_blob(node_ctx.type, mark, - node_ctx.textLength, &input); + if (type == REPO_MODE_DIR) /* directories are not tracked. */ + return; + assert(old_data); + if (old_data == empty_blob) + /* For the fast_export_* functions, NULL means empty. */ + old_data = NULL; + if (!have_text) { + fast_export_modify(REPO_MAX_PATH_DEPTH, node_ctx.dst, + node_ctx.type, old_data); + return; + } + fast_export_modify(REPO_MAX_PATH_DEPTH, node_ctx.dst, + node_ctx.type, "inline"); + fast_export_data(node_ctx.type, node_ctx.textLength, &input); + } + + static void begin_revision(void) + { + if (!rev_ctx.revision) /* revision 0 gets no git commit. */ + return; - fast_export_begin_commit(rev_ctx.revision, rev_ctx.author, rev_ctx.log, - dump_ctx.uuid, dump_ctx.url, rev_ctx.timestamp); ++ fast_export_begin_commit(rev_ctx.revision, rev_ctx.author.buf, ++ &rev_ctx.log, dump_ctx.uuid.buf, dump_ctx.url.buf, ++ rev_ctx.timestamp); } - static void handle_revision(void) + static void end_revision(void) { if (rev_ctx.revision) - repo_commit(rev_ctx.revision, rev_ctx.author.buf, - &rev_ctx.log, dump_ctx.uuid.buf, dump_ctx.url.buf, - rev_ctx.timestamp); + fast_export_end_commit(rev_ctx.revision); } void svndump_read(const char *url) @@@ -285,65 -312,48 +315,69 @@@ char *t; uint32_t active_ctx = DUMP_CTX; uint32_t len; - uint32_t key; - reset_dump_ctx(pool_intern(url)); + reset_dump_ctx(url); while ((t = buffer_read_line(&input))) { - val = strstr(t, ": "); + val = strchr(t, ':'); if (!val) continue; - *val++ = '\0'; - *val++ = '\0'; - key = pool_intern(t); + val++; + if (*val != ' ') + continue; + val++; - if (key == keys.svn_fs_dump_format_version) { + /* strlen(key) + 1 */ + switch (val - t - 1) { + case sizeof("SVN-fs-dump-format-version"): + if (constcmp(t, "SVN-fs-dump-format-version")) + continue; dump_ctx.version = atoi(val); if (dump_ctx.version > 3) die("expected svn dump format version <= 3, found %"PRIu32, dump_ctx.version); - } else if (key == keys.uuid) { - dump_ctx.uuid = pool_intern(val); - } else if (key == keys.revision_number) { + break; + case sizeof("UUID"): + if (constcmp(t, "UUID")) + continue; + strbuf_reset(&dump_ctx.uuid); + strbuf_addstr(&dump_ctx.uuid, val); + break; + case sizeof("Revision-number"): + if (constcmp(t, "Revision-number")) + continue; if (active_ctx == NODE_CTX) handle_node(); + if (active_ctx == REV_CTX) + begin_revision(); if (active_ctx != DUMP_CTX) - handle_revision(); + end_revision(); active_ctx = REV_CTX; reset_rev_ctx(atoi(val)); - } else if (key == keys.node_path) { - if (active_ctx == NODE_CTX) - handle_node(); - if (active_ctx == REV_CTX) - begin_revision(); - active_ctx = NODE_CTX; - reset_node_ctx(val); - } else if (key == keys.node_kind) { + break; + case sizeof("Node-path"): + if (prefixcmp(t, "Node-")) + continue; + if (!constcmp(t + strlen("Node-"), "path")) { + if (active_ctx == NODE_CTX) + handle_node(); ++ if (active_ctx == REV_CTX) ++ begin_revision(); + active_ctx = NODE_CTX; + reset_node_ctx(val); + break; + } + if (constcmp(t + strlen("Node-"), "kind")) + continue; if (!strcmp(val, "dir")) node_ctx.type = REPO_MODE_DIR; else if (!strcmp(val, "file")) node_ctx.type = REPO_MODE_BLB; else fprintf(stderr, "Unknown node-kind: %s\n", val); - } else if (key == keys.node_action) { + break; + case sizeof("Node-action"): + if (constcmp(t, "Node-action")) + continue; if (!strcmp(val, "delete")) { node_ctx.action = NODEACT_DELETE; } else if (!strcmp(val, "add")) { @@@ -356,86 -366,59 +390,88 @@@ fprintf(stderr, "Unknown node-action: %s\n", val); node_ctx.action = NODEACT_UNKNOWN; } - } else if (key == keys.node_copyfrom_path) { + break; + case sizeof("Node-copyfrom-path"): + if (constcmp(t, "Node-copyfrom-path")) + continue; pool_tok_seq(REPO_MAX_PATH_DEPTH, node_ctx.src, "/", val); - } else if (key == keys.node_copyfrom_rev) { + break; + case sizeof("Node-copyfrom-rev"): + if (constcmp(t, "Node-copyfrom-rev")) + continue; node_ctx.srcRev = atoi(val); - } else if (key == keys.text_content_length) { - node_ctx.textLength = atoi(val); - } else if (key == keys.prop_content_length) { + break; + case sizeof("Text-content-length"): + if (!constcmp(t, "Text-content-length")) { + node_ctx.textLength = atoi(val); + break; + } + if (constcmp(t, "Prop-content-length")) + continue; node_ctx.propLength = atoi(val); - } else if (key == keys.text_delta) { - node_ctx.text_delta = !strcmp(val, "true"); - } else if (key == keys.prop_delta) { + break; + case sizeof("Text-delta"): + if (!constcmp(t, "Text-delta")) { + node_ctx.text_delta = !strcmp(val, "true"); + break; + } + if (constcmp(t, "Prop-delta")) + continue; node_ctx.prop_delta = !strcmp(val, "true"); - } else if (key == keys.content_length) { + break; + case sizeof("Content-length"): + if (constcmp(t, "Content-length")) + continue; len = atoi(val); - buffer_read_line(&input); + t = buffer_read_line(&input); + if (!t) + die_short_read(); + if (*t) + die("invalid dump: expected blank line after content length header"); if (active_ctx == REV_CTX) { read_props(); } else if (active_ctx == NODE_CTX) { handle_node(); - active_ctx = REV_CTX; + active_ctx = INTERNODE_CTX; } else { fprintf(stderr, "Unexpected content length header: %"PRIu32"\n", len); - buffer_skip_bytes(&input, len); + if (buffer_skip_bytes(&input, len) != len) + die_short_read(); } } } + if (buffer_ferror(&input)) + die_short_read(); if (active_ctx == NODE_CTX) handle_node(); + if (active_ctx == REV_CTX) + begin_revision(); if (active_ctx != DUMP_CTX) - handle_revision(); + end_revision(); } int svndump_init(const char *filename) { if (buffer_init(&input, filename)) return error("cannot open %s: %s", filename, strerror(errno)); - repo_init(); + fast_export_init(REPORT_FILENO); - reset_dump_ctx(~0); + strbuf_init(&dump_ctx.uuid, 4096); + strbuf_init(&dump_ctx.url, 4096); + strbuf_init(&rev_ctx.log, 4096); + strbuf_init(&rev_ctx.author, 4096); + reset_dump_ctx(NULL); reset_rev_ctx(0); reset_node_ctx(NULL); - init_keys(); return 0; } void svndump_deinit(void) { - repo_reset(); - log_reset(); + fast_export_deinit(); - reset_dump_ctx(~0); + reset_dump_ctx(NULL); reset_rev_ctx(0); reset_node_ctx(NULL); + strbuf_release(&rev_ctx.log); if (buffer_deinit(&input)) fprintf(stderr, "Input error\n"); if (ferror(stdout)) @@@ -444,10 -427,10 +480,10 @@@ void svndump_reset(void) { - log_reset(); + fast_export_reset(); buffer_reset(&input); - repo_reset(); - reset_dump_ctx(~0); - reset_rev_ctx(0); - reset_node_ctx(NULL); + strbuf_release(&dump_ctx.uuid); + strbuf_release(&dump_ctx.url); + strbuf_release(&rev_ctx.log); + strbuf_release(&rev_ctx.author); }