Merge branch 'svn-fe' of git://repo.or.cz/git/jrn
authorJunio C Hamano <gitster@pobox.com>
Sat, 26 Mar 2011 18:35:41 +0000 (11:35 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sat, 26 Mar 2011 18:35:41 +0000 (11:35 -0700)
* 'svn-fe' of git://repo.or.cz/git/jrn:
vcs-svn: handle log message with embedded NUL
vcs-svn: avoid unnecessary copying of log message and author
vcs-svn: remove buffer_read_string
vcs-svn: make reading of properties binary-safe

t/t0081-line-buffer.sh
t/t9010-svn-fe.sh
test-line-buffer.c
vcs-svn/fast_export.c
vcs-svn/fast_export.h
vcs-svn/line_buffer.c
vcs-svn/line_buffer.h
vcs-svn/line_buffer.txt
vcs-svn/repo_tree.c
vcs-svn/repo_tree.h
vcs-svn/svndump.c
index 550fad0823a8d27cf1d49b1c55154ea7ce69de02..1dbe1c9b08c1d5140bf237bfee2a02cf4831d261 100755 (executable)
@@ -53,7 +53,7 @@ long_read_test () {
                } >input &
        } &&
        test-line-buffer input <<-EOF >output &&
-       read $readsize
+       binary $readsize
        copy $copysize
        EOF
        kill $! &&
@@ -71,23 +71,23 @@ test_expect_success 'setup: have pipes?' '
 '
 
 test_expect_success 'hello world' '
-       echo HELLO >expect &&
+       echo ">HELLO" >expect &&
        test-line-buffer <<-\EOF >actual &&
-       read 6
+       binary 6
        HELLO
        EOF
        test_cmp expect actual
 '
 
 test_expect_success PIPE '0-length read, no input available' '
-       >expect &&
+       printf ">" >expect &&
        rm -f input &&
        mkfifo input &&
        {
                sleep 100 >input &
        } &&
        test-line-buffer input <<-\EOF >actual &&
-       read 0
+       binary 0
        copy 0
        EOF
        kill $! &&
@@ -95,9 +95,9 @@ test_expect_success PIPE '0-length read, no input available' '
 '
 
 test_expect_success '0-length read, send along greeting' '
-       echo HELLO >expect &&
+       echo ">HELLO" >expect &&
        test-line-buffer <<-\EOF >actual &&
-       read 0
+       binary 0
        copy 6
        HELLO
        EOF
@@ -105,7 +105,7 @@ test_expect_success '0-length read, send along greeting' '
 '
 
 test_expect_success PIPE '1-byte read, no input available' '
-       printf "%s" ab >expect &&
+       printf ">%s" ab >expect &&
        rm -f input &&
        mkfifo input &&
        {
@@ -116,7 +116,7 @@ test_expect_success PIPE '1-byte read, no input available' '
                } >input &
        } &&
        test-line-buffer input <<-\EOF >actual &&
-       read 1
+       binary 1
        copy 1
        EOF
        kill $! &&
@@ -140,15 +140,6 @@ test_expect_success 'read from file descriptor' '
        test_cmp expect actual
 '
 
-test_expect_success 'buffer_read_string copes with null byte' '
-       >expect &&
-       q_to_nul <<-\EOF | test-line-buffer >actual &&
-       read 2
-       Q
-       EOF
-       test_cmp expect actual
-'
-
 test_expect_success 'skip, copy null byte' '
        echo Q | q_to_nul >expect &&
        q_to_nul <<-\EOF | test-line-buffer >actual &&
@@ -170,18 +161,18 @@ test_expect_success 'read null byte' '
 '
 
 test_expect_success 'long reads are truncated' '
-       echo foo >expect &&
+       echo ">foo" >expect &&
        test-line-buffer <<-\EOF >actual &&
-       read 5
+       binary 5
        foo
        EOF
        test_cmp expect actual
 '
 
 test_expect_success 'long copies are truncated' '
-       printf "%s\n" "" foo >expect &&
+       printf "%s\n" ">" foo >expect &&
        test-line-buffer <<-\EOF >actual &&
-       read 1
+       binary 1
 
        copy 5
        foo
index 5a6a4b9b7a6e6abbb9570ddceaac68baf5c2690b..478c860647b78518820cd244042584131c485914 100755 (executable)
@@ -370,6 +370,110 @@ test_expect_failure 'change file mode but keep old content' '
        test_cmp hello actual.target
 '
 
+test_expect_success '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 &&
+       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' '
+       # 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" "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 &&
+       {
+               git rev-list HEAD |
+               git diff-tree --root --stdin |
+               sed "s/$_x40/OBJID/g"
+       } >actual &&
+       git cat-file commit HEAD | nul_to_q | 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' '
        reinit_git &&
        cat >expect <<-\EOF &&
index 25b20b93fd4a1113629c99bd5a53dfee965119c1..7ec9b13c9b2ceb84fb6dd0dd229e9f6cb8c1a635 100644 (file)
@@ -32,12 +32,6 @@ static void handle_command(const char *command, const char *arg, struct line_buf
                        buffer_copy_bytes(buf, strtouint32(arg));
                        return;
                }
-       case 'r':
-               if (!prefixcmp(command, "read ")) {
-                       const char *s = buffer_read_string(buf, strtouint32(arg));
-                       fputs(s, stdout);
-                       return;
-               }
        case 's':
                if (!prefixcmp(command, "skip ")) {
                        buffer_skip_bytes(buf, strtouint32(arg));
index a4d4d9993dac74f0e751009f30588aef62d3a573..2e5bb6725568c06125d9e856f9bddd0b50b407c2 100644 (file)
@@ -31,12 +31,14 @@ void fast_export_modify(uint32_t depth, uint32_t *path, uint32_t mode,
 }
 
 static char gitsvnline[MAX_GITSVN_LINE_LEN];
-void fast_export_commit(uint32_t revision, const char *author, char *log,
+void fast_export_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 = "";
+               log = &empty;
        if (*uuid && *url) {
                snprintf(gitsvnline, MAX_GITSVN_LINE_LEN,
                                "\n\ngit-svn-id: %s@%"PRIu32" %s\n",
@@ -49,9 +51,9 @@ void fast_export_commit(uint32_t revision, const char *author, char *log,
                   *author ? author : "nobody",
                   *author ? author : "nobody",
                   *uuid ? uuid : "local", timestamp);
-       printf("data %"PRIu32"\n%s%s\n",
-                  (uint32_t) (strlen(log) + strlen(gitsvnline)),
-                  log, gitsvnline);
+       printf("data %"PRIuMAX"\n", 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");
index 05cf97f3a7854e3ddf7060b361278ca82adc844b..33a8fe996f5fc025f587c1d09ae3f81e1786dbbb 100644 (file)
@@ -2,13 +2,14 @@
 #define FAST_EXPORT_H_
 
 #include "line_buffer.h"
+struct strbuf;
 
 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, char *log,
-                       const char *uuid, const char *url,
-                       unsigned long timestamp);
+void fast_export_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);
 
index 33e733a04c2b694a7dce80c8404edd464fe1a6b7..c39038723ed4a90f99f70a694eae58ba488556b6 100644 (file)
@@ -91,13 +91,6 @@ char *buffer_read_line(struct line_buffer *buf)
        return buf->line_buffer;
 }
 
-char *buffer_read_string(struct line_buffer *buf, uint32_t len)
-{
-       strbuf_reset(&buf->blob_buffer);
-       strbuf_fread(&buf->blob_buffer, len, buf->infile);
-       return ferror(buf->infile) ? NULL : buf->blob_buffer.buf;
-}
-
 void buffer_read_binary(struct line_buffer *buf,
                                struct strbuf *sb, uint32_t size)
 {
@@ -134,5 +127,4 @@ off_t buffer_skip_bytes(struct line_buffer *buf, off_t nbytes)
 
 void buffer_reset(struct line_buffer *buf)
 {
-       strbuf_release(&buf->blob_buffer);
 }
index f5c468afa45f1f567dc5cc2b3e2e748b79fe5cb0..d0b22dda76e8a193f15fa5483abd217cdde101da 100644 (file)
@@ -7,10 +7,9 @@
 
 struct line_buffer {
        char line_buffer[LINE_BUFFER_LEN];
-       struct strbuf blob_buffer;
        FILE *infile;
 };
-#define LINE_BUFFER_INIT {"", STRBUF_INIT, NULL}
+#define LINE_BUFFER_INIT { "", NULL }
 
 int buffer_init(struct line_buffer *buf, const char *filename);
 int buffer_fdinit(struct line_buffer *buf, int fd);
@@ -23,7 +22,6 @@ long buffer_tmpfile_prepare_to_read(struct line_buffer *buf);
 
 int buffer_ferror(struct line_buffer *buf);
 char *buffer_read_line(struct line_buffer *buf);
-char *buffer_read_string(struct line_buffer *buf, uint32_t len);
 int buffer_read_char(struct line_buffer *buf);
 void buffer_read_binary(struct line_buffer *buf, struct strbuf *sb, uint32_t len);
 /* Returns number of bytes read (not necessarily written). */
index 4ef0755cf53e4ee220f1906be7c0cd54ff3265ab..8e139eb22df0f44ba0b805b374943dd5d12e8437 100644 (file)
@@ -16,8 +16,8 @@ The calling program:
 
  - initializes a `struct line_buffer` to LINE_BUFFER_INIT
  - specifies a file to read with `buffer_init`
- - processes input with `buffer_read_line`, `buffer_read_string`,
-   `buffer_skip_bytes`, and `buffer_copy_bytes`
+ - processes input with `buffer_read_line`, `buffer_skip_bytes`,
+   and `buffer_copy_bytes`
  - closes the file with `buffer_deinit`, perhaps to start over and
    read another file.
 
@@ -37,7 +37,7 @@ the calling program.  A program
    the temporary file
  - declares writing is over with `buffer_tmpfile_prepare_to_read`
  - can re-read what was written with `buffer_read_line`,
-   `buffer_read_string`, and so on
+   `buffer_copy_bytes`, and so on
  - can reuse the temporary file by calling `buffer_tmpfile_rewind`
    again
  - removes the temporary file with `buffer_deinit`, perhaps to
@@ -64,12 +64,6 @@ Functions
        Read a line and strip off the trailing newline.
        On failure or end of file, returns NULL.
 
-`buffer_read_string`::
-       Read `len` characters of input or up to the end of the
-       file, whichever comes first.  Returns NULL on error.
-       Returns whatever characters were read (possibly "")
-       for end of file.
-
 `buffer_copy_bytes`::
        Read `len` bytes of input and dump them to the standard output
        stream.  Returns early for error or end of file.
index 632dbd87360b0a4a0c2fa2d401a775e9939bab35..a21d89de97404479ecf2ca46824f61acb7af56e6 100644 (file)
@@ -278,8 +278,9 @@ void repo_diff(uint32_t r1, uint32_t r2)
                    repo_commit_root_dir(commit_pointer(r2)));
 }
 
-void repo_commit(uint32_t revision, const char *author, char *log,
-                const char *uuid, const char *url, unsigned long timestamp)
+void repo_commit(uint32_t revision, const char *author,
+               const struct strbuf *log, const char *uuid, const char *url,
+               unsigned long timestamp)
 {
        fast_export_commit(revision, author, log, uuid, url, timestamp);
        dent_commit();
index a1b0e87651b7576cd4e1842261d745664331793a..37bde2e37484071998edbe790d38d7b19eb24fb5 100644 (file)
@@ -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
@@ -18,7 +18,7 @@ uint32_t repo_read_path(const uint32_t *path);
 uint32_t repo_read_mode(const uint32_t *path);
 void repo_delete(uint32_t *path);
 void repo_commit(uint32_t revision, const char *author,
-               char *log, const char *uuid, const char *url,
+               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);
index ea5b128e4f205cd95694839ca9eb9b072333fa99..eef49ca1928d21f7bb0d038693fd92c2a6e5ac85 100644 (file)
@@ -83,7 +83,7 @@ static void reset_dump_ctx(const char *url)
 }
 
 static void handle_property(const struct strbuf *key_buf,
-                               const char *val, uint32_t len,
+                               struct strbuf *val,
                                uint32_t *type_set)
 {
        const char *key = key_buf->buf;
@@ -95,23 +95,23 @@ static void handle_property(const struct strbuf *key_buf,
                        break;
                if (!val)
                        die("invalid dump: unsets svn:log");
-               strbuf_reset(&rev_ctx.log);
-               strbuf_add(&rev_ctx.log, val, len);
+               strbuf_swap(&rev_ctx.log, val);
                break;
        case sizeof("svn:author"):
                if (constcmp(key, "svn:author"))
                        break;
-               strbuf_reset(&rev_ctx.author);
-               if (val)
-                       strbuf_add(&rev_ctx.author, val, len);
+               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);
+               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"):
@@ -147,6 +147,7 @@ static void die_short_read(void)
 static void read_props(void)
 {
        static struct strbuf key = STRBUF_INIT;
+       static struct strbuf val = STRBUF_INIT;
        const char *t;
        /*
         * NEEDSWORK: to support simple mode changes like
@@ -163,15 +164,15 @@ static void read_props(void)
        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);
-               if (!val || strlen(val) != len)
+               strbuf_reset(&val);
+               buffer_read_binary(&input, &val, len);
+               if (val.len < len)
                        die_short_read();
 
                /* Discard trailing newline. */
@@ -179,22 +180,17 @@ static void read_props(void)
                if (ch == EOF)
                        die_short_read();
                if (ch != '\n')
-                       die("invalid dump: expected newline after %s", val);
+                       die("invalid dump: expected newline after %s", val.buf);
 
                switch (type) {
                case 'K':
+                       strbuf_swap(&key, &val);
+                       continue;
                case 'D':
-                       strbuf_reset(&key);
-                       if (val)
-                               strbuf_add(&key, val, len);
-                       if (type == 'K')
-                               continue;
-                       assert(type == 'D');
-                       val = NULL;
-                       len = 0;
-                       /* fall through */
+                       handle_property(&val, NULL, &type_set);
+                       continue;
                case 'V':
-                       handle_property(&key, val, len, &type_set);
+                       handle_property(&key, &val, &type_set);
                        strbuf_reset(&key);
                        continue;
                default:
@@ -278,7 +274,7 @@ static void handle_revision(void)
 {
        if (rev_ctx.revision)
                repo_commit(rev_ctx.revision, rev_ctx.author.buf,
-                       rev_ctx.log.buf, dump_ctx.uuid.buf, dump_ctx.url.buf,
+                       &rev_ctx.log, dump_ctx.uuid.buf, dump_ctx.url.buf,
                        rev_ctx.timestamp);
 }