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
This commit is contained in:
Jonathan Nieder 2011-05-26 01:51:38 -05:00
commit 9ecfa8ae4c
9 changed files with 422 additions and 413 deletions

View file

@ -7,7 +7,11 @@ svn-fe - convert an SVN "dumpfile" to a fast-import stream
SYNOPSIS
--------
svnadmin dump --incremental REPO | svn-fe [url] | git fast-import
[verse]
mkfifo backchannel &&
svnadmin dump --incremental REPO |
svn-fe [url] 3<backchannel |
git fast-import --cat-blob-fd=3 3>backchannel
DESCRIPTION
-----------

View file

@ -5,8 +5,26 @@ test_description='check svn dumpfile importer'
. ./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 @@ 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 &&
@ -64,13 +88,12 @@ test_expect_failure 'empty revision' '
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 &&
@ -88,13 +111,12 @@ test_expect_success 'empty properties' '
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 &&
@ -121,15 +143,14 @@ test_expect_success 'author name and commit message' '
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 &&
@ -149,13 +170,12 @@ test_expect_success 'unsupported properties are ignored' '
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 &&
@ -186,8 +206,7 @@ test_expect_failure 'timestamp and empty file' '
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 &&
@ -198,7 +217,7 @@ test_expect_failure 'timestamp and empty file' '
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 @@ test_expect_success 'directory with files' '
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 &&
@ -252,7 +270,107 @@ test_expect_success 'directory with files' '
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
@ -269,10 +387,11 @@ test_expect_success 'node without action' '
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 @@ test_expect_success 'action: add node without text' '
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 @@ test_expect_failure 'change file mode but keep old content' '
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 |
@ -370,7 +488,7 @@ test_expect_failure 'change file mode but keep old content' '
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 &&
{
@ -391,13 +509,12 @@ test_expect_success 'NUL in property value' '
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 &&
@ -458,8 +575,7 @@ test_expect_success 'NUL in log message, file content, and property name' '
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 |
@ -478,7 +594,7 @@ test_expect_success 'NUL in log message, file content, and property name' '
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 +606,7 @@ test_expect_success 'change file mode and reiterate content' '
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 +661,7 @@ test_expect_success 'change file mode and reiterate content' '
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 |
@ -559,7 +674,8 @@ test_expect_success 'change file mode and reiterate content' '
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" |
@ -619,10 +735,10 @@ test_expect_success 'deltas not supported' '
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
@ -678,8 +794,7 @@ test_expect_success 'property deltas supported' '
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 |
@ -688,7 +803,7 @@ test_expect_success 'property deltas supported' '
test_cmp expect actual
'
test_expect_success 'properties on /' '
test_expect_success PIPE 'properties on /' '
reinit_git &&
cat <<-\EOF >expect &&
OBJID
@ -733,8 +848,7 @@ test_expect_success 'properties on /' '
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 |
@ -743,7 +857,7 @@ test_expect_success 'properties on /' '
test_cmp expect actual
'
test_expect_success 'deltas for typechange' '
test_expect_success PIPE 'deltas for typechange' '
reinit_git &&
cat >expect <<-\EOF &&
OBJID
@ -819,8 +933,7 @@ test_expect_success 'deltas for typechange' '
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 +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 &&

View file

@ -8,30 +8,58 @@
#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, const char *author,
const struct strbuf *log,
const char *uuid, const char *url,
unsigned long timestamp)
@ -47,6 +75,7 @@ void fast_export_commit(uint32_t revision, const char *author,
*gitsvnline = '\0';
}
printf("commit refs/heads/master\n");
printf("mark :%"PRIu32"\n", revision);
printf("committer %s <%s@%s> %ld +0000\n",
*author ? author : "nobody",
*author ? author : "nobody",
@ -57,15 +86,44 @@ void fast_export_commit(uint32_t revision, const char *author,
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))
@ -73,7 +131,7 @@ static void die_short_read(struct line_buffer *input)
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 " */
@ -81,8 +139,63 @@ void fast_export_blob(uint32_t mode, uint32_t mark, uint32_t len, struct line_bu
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);
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);
}

View file

@ -1,16 +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, 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

View file

@ -4,323 +4,45 @@
*/
#include "git-compat-util.h"
#include "string_pool.h"
#include "strbuf.h"
#include "repo_tree.h"
#include "obj_pool.h"
#include "fast_export.h"
#include "trp.h"
struct repo_dirent {
uint32_t name_offset;
struct trp_node children;
uint32_t mode;
uint32_t content_offset;
};
struct repo_dir {
struct trp_root entries;
};
struct repo_commit {
uint32_t root_dir_offset;
};
/* Memory pools for commit, dir and dirent */
obj_pool_gen(commit, struct repo_commit, 4096)
obj_pool_gen(dir, struct repo_dir, 4096)
obj_pool_gen(dent, struct repo_dirent, 4096)
static uint32_t active_commit;
static uint32_t mark;
static int repo_dirent_name_cmp(const void *a, const void *b);
/* Treap for directory entries */
trp_gen(static, dent_, struct repo_dirent, children, dent, repo_dirent_name_cmp)
uint32_t next_blob_mark(void)
const char *repo_read_path(const uint32_t *path, uint32_t *mode_out)
{
return mark++;
}
int err;
static struct strbuf buf = STRBUF_INIT;
static struct repo_dir *repo_commit_root_dir(struct repo_commit *commit)
{
return dir_pointer(commit->root_dir_offset);
}
static struct repo_dirent *repo_first_dirent(struct repo_dir *dir)
{
return dent_first(&dir->entries);
}
static int repo_dirent_name_cmp(const void *a, const void *b)
{
const struct repo_dirent *dent1 = a, *dent2 = b;
uint32_t a_offset = dent1->name_offset;
uint32_t b_offset = dent2->name_offset;
return (a_offset > b_offset) - (a_offset < b_offset);
}
static int repo_dirent_is_dir(struct repo_dirent *dent)
{
return dent != NULL && dent->mode == REPO_MODE_DIR;
}
static struct repo_dir *repo_dir_from_dirent(struct repo_dirent *dent)
{
if (!repo_dirent_is_dir(dent))
strbuf_reset(&buf);
err = fast_export_ls(REPO_MAX_PATH_DEPTH, path, mode_out, &buf);
if (err) {
if (errno != ENOENT)
die_errno("BUG: unexpected fast_export_ls error");
/* Treat missing paths as directories. */
*mode_out = REPO_MODE_DIR;
return NULL;
return dir_pointer(dent->content_offset);
}
static struct repo_dir *repo_clone_dir(struct repo_dir *orig_dir)
{
uint32_t orig_o, new_o;
orig_o = dir_offset(orig_dir);
if (orig_o >= dir_pool.committed)
return orig_dir;
new_o = dir_alloc(1);
orig_dir = dir_pointer(orig_o);
*dir_pointer(new_o) = *orig_dir;
return dir_pointer(new_o);
}
static struct repo_dirent *repo_read_dirent(uint32_t revision,
const uint32_t *path)
{
uint32_t name = 0;
struct repo_dirent *key = dent_pointer(dent_alloc(1));
struct repo_dir *dir = NULL;
struct repo_dirent *dent = NULL;
dir = repo_commit_root_dir(commit_pointer(revision));
while (~(name = *path++)) {
key->name_offset = name;
dent = dent_search(&dir->entries, key);
if (dent == NULL || !repo_dirent_is_dir(dent))
break;
dir = repo_dir_from_dirent(dent);
}
dent_free(1);
return dent;
}
static void repo_write_dirent(const uint32_t *path, uint32_t mode,
uint32_t content_offset, uint32_t del)
{
uint32_t name, revision, dir_o = ~0, parent_dir_o = ~0;
struct repo_dir *dir;
struct repo_dirent *key;
struct repo_dirent *dent = NULL;
revision = active_commit;
dir = repo_commit_root_dir(commit_pointer(revision));
dir = repo_clone_dir(dir);
commit_pointer(revision)->root_dir_offset = dir_offset(dir);
while (~(name = *path++)) {
parent_dir_o = dir_offset(dir);
key = dent_pointer(dent_alloc(1));
key->name_offset = name;
dent = dent_search(&dir->entries, key);
if (dent == NULL)
dent = key;
else
dent_free(1);
if (dent == key) {
dent->mode = REPO_MODE_DIR;
dent->content_offset = 0;
dent = dent_insert(&dir->entries, dent);
}
if (dent_offset(dent) < dent_pool.committed) {
dir_o = repo_dirent_is_dir(dent) ?
dent->content_offset : ~0;
dent_remove(&dir->entries, dent);
dent = dent_pointer(dent_alloc(1));
dent->name_offset = name;
dent->mode = REPO_MODE_DIR;
dent->content_offset = dir_o;
dent = dent_insert(&dir->entries, dent);
}
dir = repo_dir_from_dirent(dent);
dir = repo_clone_dir(dir);
dent->content_offset = dir_offset(dir);
}
if (dent == NULL)
return;
dent->mode = mode;
dent->content_offset = content_offset;
if (del && ~parent_dir_o)
dent_remove(&dir_pointer(parent_dir_o)->entries, dent);
}
uint32_t repo_read_path(const uint32_t *path)
{
uint32_t content_offset = 0;
struct repo_dirent *dent = repo_read_dirent(active_commit, path);
if (dent != NULL)
content_offset = dent->content_offset;
return content_offset;
}
uint32_t repo_read_mode(const uint32_t *path)
{
struct repo_dirent *dent = repo_read_dirent(active_commit, path);
if (dent == NULL)
die("invalid dump: path to be modified is missing");
return dent->mode;
return buf.buf;
}
void repo_copy(uint32_t revision, const uint32_t *src, const uint32_t *dst)
{
uint32_t mode = 0, content_offset = 0;
struct repo_dirent *src_dent;
src_dent = repo_read_dirent(revision, src);
if (src_dent != NULL) {
mode = src_dent->mode;
content_offset = src_dent->content_offset;
repo_write_dirent(dst, mode, content_offset, 0);
}
}
int err;
uint32_t mode;
static struct strbuf data = STRBUF_INIT;
void repo_add(uint32_t *path, uint32_t mode, uint32_t blob_mark)
{
repo_write_dirent(path, mode, blob_mark, 0);
strbuf_reset(&data);
err = fast_export_ls_rev(revision, REPO_MAX_PATH_DEPTH, src, &mode, &data);
if (err) {
if (errno != ENOENT)
die_errno("BUG: unexpected fast_export_ls_rev error");
fast_export_delete(REPO_MAX_PATH_DEPTH, dst);
return;
}
fast_export_modify(REPO_MAX_PATH_DEPTH, dst, mode, data.buf);
}
void repo_delete(uint32_t *path)
{
repo_write_dirent(path, 0, 0, 1);
}
static void repo_git_add_r(uint32_t depth, uint32_t *path, struct repo_dir *dir);
static void repo_git_add(uint32_t depth, uint32_t *path, struct repo_dirent *dent)
{
if (repo_dirent_is_dir(dent))
repo_git_add_r(depth, path, repo_dir_from_dirent(dent));
else
fast_export_modify(depth, path,
dent->mode, dent->content_offset);
}
static void repo_git_add_r(uint32_t depth, uint32_t *path, struct repo_dir *dir)
{
struct repo_dirent *de = repo_first_dirent(dir);
while (de) {
path[depth] = de->name_offset;
repo_git_add(depth + 1, path, de);
de = dent_next(&dir->entries, de);
}
}
static void repo_diff_r(uint32_t depth, uint32_t *path, struct repo_dir *dir1,
struct repo_dir *dir2)
{
struct repo_dirent *de1, *de2;
de1 = repo_first_dirent(dir1);
de2 = repo_first_dirent(dir2);
while (de1 && de2) {
if (de1->name_offset < de2->name_offset) {
path[depth] = de1->name_offset;
fast_export_delete(depth + 1, path);
de1 = dent_next(&dir1->entries, de1);
continue;
}
if (de1->name_offset > de2->name_offset) {
path[depth] = de2->name_offset;
repo_git_add(depth + 1, path, de2);
de2 = dent_next(&dir2->entries, de2);
continue;
}
path[depth] = de1->name_offset;
if (de1->mode == de2->mode &&
de1->content_offset == de2->content_offset) {
; /* No change. */
} else if (repo_dirent_is_dir(de1) && repo_dirent_is_dir(de2)) {
repo_diff_r(depth + 1, path,
repo_dir_from_dirent(de1),
repo_dir_from_dirent(de2));
} else if (!repo_dirent_is_dir(de1) && !repo_dirent_is_dir(de2)) {
repo_git_add(depth + 1, path, de2);
} else {
fast_export_delete(depth + 1, path);
repo_git_add(depth + 1, path, de2);
}
de1 = dent_next(&dir1->entries, de1);
de2 = dent_next(&dir2->entries, de2);
}
while (de1) {
path[depth] = de1->name_offset;
fast_export_delete(depth + 1, path);
de1 = dent_next(&dir1->entries, de1);
}
while (de2) {
path[depth] = de2->name_offset;
repo_git_add(depth + 1, path, de2);
de2 = dent_next(&dir2->entries, de2);
}
}
static uint32_t path_stack[REPO_MAX_PATH_DEPTH];
void repo_diff(uint32_t r1, uint32_t r2)
{
repo_diff_r(0,
path_stack,
repo_commit_root_dir(commit_pointer(r1)),
repo_commit_root_dir(commit_pointer(r2)));
}
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();
dir_commit();
active_commit = commit_alloc(1);
commit_pointer(active_commit)->root_dir_offset =
commit_pointer(active_commit - 1)->root_dir_offset;
}
static void mark_init(void)
{
uint32_t i;
mark = 0;
for (i = 0; i < dent_pool.size; i++)
if (!repo_dirent_is_dir(dent_pointer(i)) &&
dent_pointer(i)->content_offset > mark)
mark = dent_pointer(i)->content_offset;
mark++;
}
void repo_init(void)
{
mark_init();
if (commit_pool.size == 0) {
/* Create empty tree for commit 0. */
commit_alloc(1);
commit_pointer(0)->root_dir_offset = dir_alloc(1);
dir_pointer(0)->entries.trp_root = ~0;
dir_commit();
}
/* Preallocate next commit, ready for changes. */
active_commit = commit_alloc(1);
commit_pointer(active_commit)->root_dir_offset =
commit_pointer(active_commit - 1)->root_dir_offset;
}
void repo_reset(void)
{
pool_reset();
commit_reset();
dir_reset();
dent_reset();
fast_export_delete(REPO_MAX_PATH_DEPTH, path);
}

View file

@ -14,8 +14,7 @@ struct strbuf;
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, const char *author,
const struct strbuf *log, const char *uuid, const char *url,

View file

@ -4,6 +4,7 @@
*/
#include "git-compat-util.h"
#include "quote.h"
#include "trp.h"
#include "obj_pool.h"
#include "string_pool.h"
@ -65,7 +66,7 @@ uint32_t pool_tok_r(char *str, const char *delim, char **saveptr)
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 @@ void pool_print_seq(uint32_t len, uint32_t *seq, char delim, FILE *stream)
}
}
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;

View file

@ -4,7 +4,8 @@
uint32_t pool_intern(const char *key);
const char *pool_fetch(uint32_t entry);
uint32_t pool_tok_r(char *str, const char *delim, char **saveptr);
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);
void pool_print_seq_q(uint32_t len, const uint32_t *seq, char delim, FILE *stream);
uint32_t pool_tok_seq(uint32_t sz, uint32_t *seq, const char *delim, char *str);
void pool_reset(void);

View file

@ -20,15 +20,19 @@
*/
#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
@ -201,15 +205,21 @@ static void read_props(void)
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 "
@ -230,23 +240,26 @@ static void handle_node(void)
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 +278,35 @@ static void handle_node(void)
/*
* 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 handle_revision(void)
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.buf,
&rev_ctx.log, dump_ctx.uuid.buf, dump_ctx.url.buf,
rev_ctx.timestamp);
}
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)
@ -317,8 +347,10 @@ void svndump_read(const char *url)
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));
break;
@ -328,6 +360,8 @@ void svndump_read(const char *url)
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;
@ -398,7 +432,7 @@ void svndump_read(const char *url)
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);
if (buffer_skip_bytes(&input, len) != len)
@ -410,15 +444,17 @@ void svndump_read(const char *url)
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);
strbuf_init(&dump_ctx.uuid, 4096);
strbuf_init(&dump_ctx.url, 4096);
strbuf_init(&rev_ctx.log, 4096);
@ -431,7 +467,7 @@ int svndump_init(const char *filename)
void svndump_deinit(void)
{
repo_reset();
fast_export_deinit();
reset_dump_ctx(NULL);
reset_rev_ctx(0);
reset_node_ctx(NULL);
@ -444,8 +480,8 @@ void svndump_deinit(void)
void svndump_reset(void)
{
fast_export_reset();
buffer_reset(&input);
repo_reset();
strbuf_release(&dump_ctx.uuid);
strbuf_release(&dump_ctx.url);
strbuf_release(&rev_ctx.log);