Merge branch 'db/vcs-svn-incremental' into svn-fe
authorJonathan Nieder <jrnieder@gmail.com>
Thu, 26 May 2011 06:51:38 +0000 (01:51 -0500)
committerJonathan Nieder <jrnieder@gmail.com>
Thu, 26 May 2011 07:02:44 +0000 (02:02 -0500)
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

1  2 
t/t9010-svn-fe.sh
vcs-svn/fast_export.c
vcs-svn/fast_export.h
vcs-svn/repo_tree.h
vcs-svn/string_pool.c
vcs-svn/svndump.c
diff --combined t/t9010-svn-fe.sh
index 6f6175a8f7d9b0c6e6334f89ff21b74f067d6532,720fd6b5a32e88f0b58614e0bea4cc3e7a1e0e07..003395c5f6f05bf7afe2aef35146518a3323af02
@@@ -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 3<backflow &
+       } &&
+       git fast-import --cat-blob-fd=3 <stream 3>backflow &&
+       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 <stream
+       try_dump input
  '
  
- test_expect_success 'v4 dumps not supported' '
+ test_expect_success PIPE 'v4 dumps not supported' '
        reinit_git &&
        echo "SVN-fs-dump-format-version: 4" >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 <nobody, nobody@local>: %s\n" "" "" >expect &&
        cat >emptyrev.dump <<-\EOF &&
        Content-length: 0
  
        EOF
-       test-svn-fe emptyrev.dump >stream &&
-       git fast-import <stream &&
+       try_dump emptyrev.dump &&
        git log -p --format="rev <%an, %ae>: %s" HEAD >actual &&
        test_cmp expect actual
  '
  
- test_expect_success 'empty properties' '
+ test_expect_success PIPE 'empty properties' '
        reinit_git &&
        printf "rev <nobody, nobody@local>: %s\n" "" "" >expect &&
        cat >emptyprop.dump <<-\EOF &&
  
        PROPS-END
        EOF
-       test-svn-fe emptyprop.dump >stream &&
-       git fast-import <stream &&
+       try_dump emptyprop.dump &&
        git log -p --format="rev <%an, %ae>: %s" HEAD >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 "<author@example.com, author@example.com@local>" >expect.author &&
        cat >message <<-\EOF &&
                echo &&
                cat props
        } >log.dump &&
-       test-svn-fe log.dump >stream &&
-       git fast-import <stream &&
+       try_dump log.dump &&
        git log -p --format="%B" HEAD >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 &&
        author
        PROPS-END
        EOF
-       test-svn-fe extraprop.dump >stream &&
-       git fast-import <stream &&
+       try_dump extraprop.dump &&
        git log -p --format=%an HEAD >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 &&
  
                EOF
        } >emptyfile.dump &&
-       test-svn-fe emptyfile.dump >stream &&
-       git fast-import <stream &&
+       try_dump emptyfile.dump &&
        git log --format=%an HEAD >actual.author &&
        git log --date=short --format=%ad HEAD >actual.date &&
        git ls-tree -r --name-only HEAD >actual.files &&
        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 &&
                EOF
                text_no_props hi
        } >directory.dump &&
-       test-svn-fe directory.dump >stream &&
-       git fast-import <stream &&
+       try_dump directory.dump &&
  
        git ls-tree -r --name-only HEAD >actual.files &&
        git checkout HEAD directory &&
        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 <props.setup) &&
+               echo Content-length: $(wc -c <props.setup) &&
+               echo &&
+               cat props.setup &&
+               cat <<-\EOF &&
+               Node-path: trunk
+               Node-kind: dir
+               Node-action: add
+               Prop-content-length: 10
+               Content-length: 10
+               PROPS-END
+               Node-path: branches
+               Node-kind: dir
+               Node-action: add
+               Prop-content-length: 10
+               Content-length: 10
+               PROPS-END
+               Node-path: trunk/file1
+               Node-kind: file
+               Node-action: add
+               EOF
+               text_no_props hello &&
+               cat <<-\EOF &&
+               Node-path: trunk/file2
+               Node-kind: file
+               Node-action: add
+               EOF
+               text_no_props hi &&
+               cat <<-\EOF &&
+               Revision-number: 2
+               EOF
+               echo Prop-content-length: $(wc -c <props.branch) &&
+               echo Content-length: $(wc -c <props.branch) &&
+               echo &&
+               cat props.branch &&
+               cat <<-\EOF
+               Node-path: branches/UpdateFOPto094\
+               Node-kind: dir
+               Node-action: add
+               Node-copyfrom-rev: 1
+               Node-copyfrom-path: trunk
+               Node-kind: dir
+               Node-action: add
+               Prop-content-length: 34
+               Content-length: 34
+               K 13
+               svn:mergeinfo
+               V 0
+               PROPS-END
+               EOF
+       } >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
  
  
        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
  
  
        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
  
        PROPS-END
        EOF
-       test-svn-fe filemode.dump >stream &&
-       git fast-import <stream &&
+       try_dump filemode.dump &&
        {
                git rev-list HEAD |
                git diff-tree --root --stdin |
        test_cmp hello actual.target
  '
  
- test_expect_success 'NUL in property value' '
++test_expect_success PIPE 'NUL in property value' '
 +      reinit_git &&
 +      echo "commit message" >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 <props) &&
 +              echo Content-length: $(wc -c <props) &&
 +              echo &&
 +              cat props
 +      } >nulprop.dump &&
-       test-svn-fe nulprop.dump >stream &&
-       git fast-import <stream &&
++      try_dump nulprop.dump &&
 +      git diff-tree --always -s --format=%s HEAD >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 <props) &&
 +              echo Content-length: $(wc -c <props) &&
 +              echo &&
 +              cat props &&
 +              q_to_nul <<-\EOF
 +
 +              Node-path: greeting
 +              Node-kind: file
 +              Node-action: change
 +              Prop-content-length: 43
 +              Text-content-length: 11
 +              Content-length: 54
 +
 +              K 21
 +              svn:specialQnotreally
 +              V 1
 +              *
 +              PROPS-END
 +              link hello
 +              EOF
 +      } >8bitclean.dump &&
-       test-svn-fe 8bitclean.dump >stream &&
-       git fast-import <stream &&
++      try_dump 8bitclean.dump &&
 +      {
 +              git rev-list HEAD |
 +              git diff-tree --root --stdin |
 +              sed "s/$_x40/OBJID/g"
 +      } >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
        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
        PROPS-END
        link hello
        EOF
-       test-svn-fe filemode.dump >stream &&
-       git fast-import <stream &&
+       try_dump filemode2.dump &&
        {
                git rev-list HEAD |
                git diff-tree --root --stdin |
        test_cmp hello actual.target
  '
  
- test_expect_success 'deltas not supported' '
+ test_expect_success PIPE 'deltas not supported' '
+       reinit_git &&
        {
                # (old) h + (inline) ello + (old) \n
                printf "SVNQ%b%b%s" "Q\003\006\005\004" "\001Q\0204\001\002" "ello" |
                echo PROPS-END &&
                cat delta
        } >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
                PROPS-END
                EOF
        } >propdelta.dump &&
-       test-svn-fe propdelta.dump >stream &&
-       git fast-import <stream &&
+       try_dump propdelta.dump &&
        {
                git rev-list HEAD |
                git diff-tree --stdin |
        test_cmp expect actual
  '
  
- test_expect_success 'properties on /' '
+ test_expect_success PIPE 'properties on /' '
        reinit_git &&
        cat <<-\EOF >expect &&
        OBJID
  
        PROPS-END
        EOF
-       test-svn-fe changeroot.dump >stream &&
-       git fast-import <stream &&
+       try_dump changeroot.dump &&
        {
                git rev-list HEAD |
                git diff-tree --root --always --stdin |
        test_cmp expect actual
  '
  
- test_expect_success 'deltas for typechange' '
+ test_expect_success PIPE 'deltas for typechange' '
        reinit_git &&
        cat >expect <<-\EOF &&
        OBJID
        PROPS-END
        link testing 321
        EOF
-       test-svn-fe deleteprop.dump >stream &&
-       git fast-import <stream &&
+       try_dump deleteprop.dump &&
        {
                git rev-list HEAD |
                git diff-tree --root --stdin |
@@@ -844,12 -851,12 +957,12 @@@ test_expect_success 'set up svn repo' 
        fi
  '
  
- test_expect_success SVNREPO 't9135/svn.dump' '
-       git init simple-git &&
-       test-svn-fe "$TEST_DIRECTORY/t9135/svn.dump" >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 99ed70b88a5aaacbb48a9463f19ec198d7bedf84,f19db9ae8274e530250189d138616e18fbb071ba..ff980b3a2aad56c13d129bdd8b24091de8981865
  #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 = &empty;
 +      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);
  }
  
- void fast_export_blob(uint32_t mode, uint32_t mark, uint32_t len, struct line_buffer *input)
+ 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_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 33a8fe996f5fc025f587c1d09ae3f81e1786dbbb,633d21944e83b795a170021be9290114e6e28f10..9c522d177d4959266ae6756546a0b30789dbc681
@@@ -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 37bde2e37484071998edbe790d38d7b19eb24fb5,f506352dc2d560442f7c45c7df35516fe3fef69e..ce69fa7e58536d28fd7e262562bb7de587602cdc
@@@ -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
  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 8af8d54d6ef41a2e84e500bd0d1a5523e59f8a32,be43598d5be357e7640eeec9fff3a0b1c082b4cb..23a9d46522038af0e1e6420a77245b1eb9aa8b3b
@@@ -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++) {
        }
  }
  
+ 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 572a99596657b85cbda372a9107a43c6c2fb44b2,99a5ba0d1008083a2a0c868d1984f44c03ba4232..35a8af3c9f7faee7692bbebbd39308a37335ad5e
  #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];
  } 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;
                        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
        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);
  
  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
+        *  "<dataref>" - 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);
                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");
        /*
         * 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)
        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")) {
                                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))
  
  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);
  }