From 634ede32ae7d4c76e96e88f9cd5c1b3a70ea08ac Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 14 Feb 2007 14:54:00 -0800 Subject: t0020: add test for auto-crlf This tests lowlevel of update/checkout codepaths and some patch application. Currently, variants of "git apply" that look at the working tree files does not work, so it does not test the patch application without parameter and with --index parameter when autocrlf is set to produce CRLF files. We should add test for diff generation too. Signed-off-by: Junio C Hamano --- t/t0020-crlf.sh | 206 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100755 t/t0020-crlf.sh (limited to 't') diff --git a/t/t0020-crlf.sh b/t/t0020-crlf.sh new file mode 100755 index 0000000000..58a4d86df3 --- /dev/null +++ b/t/t0020-crlf.sh @@ -0,0 +1,206 @@ +#!/bin/sh + +test_description='CRLF conversion' + +. ./test-lib.sh + +append_cr () { + sed -e 's/$/Q/' | tr Q '\015' +} + +remove_cr () { + tr '\015' Q <"$1" | grep Q >/dev/null && + tr '\015' Q <"$1" | sed -ne 's/Q$//p' +} + +test_expect_success setup ' + + git repo-config core.autocrlf false && + + for w in Hello world how are you; do echo $w; done >one && + mkdir dir && + for w in I am very very fine thank you; do echo $w; done >dir/two && + git add . && + + git commit -m initial && + + one=`git rev-parse HEAD:one` && + dir=`git rev-parse HEAD:dir` && + two=`git rev-parse HEAD:dir/two` && + + for w in Some extra lines here; do echo $w; done >>one && + git diff >patch.file && + patched=`git hash-object --stdin tmp && mv -f tmp $f && + git update-index -- $f || { + echo Oops + false + break + } + done && + + differs=`git diff-index --cached HEAD` && + test -z "$differs" || { + echo Oops "$differs" + false + } + +' + +test_expect_success 'update with autocrlf=true' ' + + rm -f tmp one dir/two && + git read-tree --reset -u HEAD && + git repo-config core.autocrlf true && + + for f in one dir/two + do + append_cr <$f >tmp && mv -f tmp $f && + git update-index -- $f || { + echo "Oops $f" + false + break + } + done && + + differs=`git diff-index --cached HEAD` && + test -z "$differs" || { + echo Oops "$differs" + false + } + +' + +test_expect_success 'checkout with autocrlf=true' ' + + rm -f tmp one dir/two && + git repo-config core.autocrlf true && + git read-tree --reset -u HEAD && + + for f in one dir/two + do + remove_cr "$f" >tmp && mv -f tmp $f && + git update-index -- $f || { + echo "Eh? $f" + false + break + } + done && + test "$one" = `git hash-object --stdin /dev/null + then + echo "Eh? $f" + false + break + else + git update-index -- $f + fi + done && + test "$one" = `git hash-object --stdin tmp && mv -f tmp one && + + git apply patch.file && + test "$patched" = "`git hash-object --stdin Date: Sat, 17 Feb 2007 13:12:52 -0800 Subject: Teach 'git apply' to look at $GIT_DIR/config When neither --index nor --cached was used, git-apply did not try calling setup_git_directory(), which means it did not look at configuration files at all. This fixes it to call the setup function but still allow the command to be run in a directory not controlled by git. The bug probably meant that 'git apply', not moving up to the toplevel, did not apply properly formatted diffs from the toplevel when you are inside a subdirectory, even though 'git apply --index' would. As a side effect, this patch fixes it as well. Signed-off-by: Junio C Hamano --- builtin-apply.c | 21 +++++++----- t/t4119-apply-config.sh | 90 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 8 deletions(-) create mode 100755 t/t4119-apply-config.sh (limited to 't') diff --git a/builtin-apply.c b/builtin-apply.c index 3fefdacd94..fc1d6730d9 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -2595,9 +2595,18 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix) int read_stdin = 1; int inaccurate_eof = 0; int errs = 0; + int is_not_gitdir = 0; const char *whitespace_option = NULL; + prefix = setup_git_directory_gently(&is_not_gitdir); + prefix_length = prefix ? strlen(prefix) : 0; + if (!is_not_gitdir) { + git_config(git_apply_config); + if (apply_default_whitespace) + parse_whitespace_option(apply_default_whitespace); + } + for (i = 1; i < argc; i++) { const char *arg = argv[i]; char *end; @@ -2648,10 +2657,14 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix) continue; } if (!strcmp(arg, "--index")) { + if (is_not_gitdir) + die("--index outside a repository"); check_index = 1; continue; } if (!strcmp(arg, "--cached")) { + if (is_not_gitdir) + die("--cached outside a repository"); check_index = 1; cached = 1; continue; @@ -2700,14 +2713,6 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix) inaccurate_eof = 1; continue; } - - if (check_index && prefix_length < 0) { - prefix = setup_git_directory(); - prefix_length = prefix ? strlen(prefix) : 0; - git_config(git_apply_config); - if (!whitespace_option && apply_default_whitespace) - parse_whitespace_option(apply_default_whitespace); - } if (0 < prefix_length) arg = prefix_filename(prefix, prefix_length, arg); diff --git a/t/t4119-apply-config.sh b/t/t4119-apply-config.sh new file mode 100755 index 0000000000..0e8ea7e2b8 --- /dev/null +++ b/t/t4119-apply-config.sh @@ -0,0 +1,90 @@ +#!/bin/sh +# +# Copyright (c) 2007 Junio C Hamano +# + +test_description='git-apply --whitespace=strip and configuration file. + +' + +. ./test-lib.sh + +test_expect_success setup ' + echo A >file1 && + cp file1 saved && + git add file1 && + echo "B " >file1 && + git diff >patch.file +' + +test_expect_success 'apply --whitespace=strip' ' + + cp saved file1 && + git update-index --refresh && + + git apply --whitespace=strip patch.file && + if grep " " file1 + then + echo "Eh?" + false + else + echo Happy + fi +' + +test_expect_success 'apply --whitespace=strip from config' ' + + cp saved file1 && + git update-index --refresh && + + git config apply.whitespace strip && + git apply patch.file && + if grep " " file1 + then + echo "Eh?" + false + else + echo Happy + fi +' + +mkdir sub +D=`pwd` + +test_expect_success 'apply --whitespace=strip in subdir' ' + + cd "$D" && + git config --unset-all apply.whitespace + cp saved file1 && + git update-index --refresh && + + cd sub && + git apply --whitespace=strip ../patch.file && + if grep " " ../file1 + then + echo "Eh?" + false + else + echo Happy + fi +' + +test_expect_success 'apply --whitespace=strip from config in subdir' ' + + cd "$D" && + git config apply.whitespace strip && + cp saved file1 && + git update-index --refresh && + + cd sub && + git apply ../patch.file && + if grep " " file1 + then + echo "Eh?" + false + else + echo Happy + fi +' + +test_done -- cgit v1.3 From 6716027108f426c83038b05baf3f20ceefe6fbd1 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 17 Feb 2007 12:37:25 -0800 Subject: Teach core.autocrlf to 'git apply' This teaches git-apply that the data read from and written to the filesystem might need to get converted to adjust for local line-ending convention. Signed-off-by: Junio C Hamano --- builtin-apply.c | 34 ++++++++++++++++++++++++++++------ t/t0020-crlf.sh | 19 +++++++++++++++---- 2 files changed, 43 insertions(+), 10 deletions(-) (limited to 't') diff --git a/builtin-apply.c b/builtin-apply.c index 3fefdacd94..45c4acbd20 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -1393,28 +1393,39 @@ static void show_stats(struct patch *patch) free(qname); } -static int read_old_data(struct stat *st, const char *path, void *buf, unsigned long size) +static int read_old_data(struct stat *st, const char *path, char **buf_p, unsigned long *alloc_p, unsigned long *size_p) { int fd; unsigned long got; + unsigned long nsize; + char *nbuf; + unsigned long size = *size_p; + char *buf = *buf_p; switch (st->st_mode & S_IFMT) { case S_IFLNK: - return readlink(path, buf, size); + return readlink(path, buf, size) != size; case S_IFREG: fd = open(path, O_RDONLY); if (fd < 0) return error("unable to open %s", path); got = 0; for (;;) { - int ret = xread(fd, (char *) buf + got, size - got); + int ret = xread(fd, buf + got, size - got); if (ret <= 0) break; got += ret; } close(fd); - return got; - + nsize = got; + nbuf = buf; + if (convert_to_git(path, &nbuf, &nsize)) { + free(buf); + *buf_p = nbuf; + *alloc_p = nsize; + *size_p = nsize; + } + return got != size; default: return -1; } @@ -1910,7 +1921,7 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry * size = st->st_size; alloc = size + 8192; buf = xmalloc(alloc); - if (read_old_data(st, patch->old_name, buf, alloc) != size) + if (read_old_data(st, patch->old_name, &buf, &alloc, &size)) return error("read of %s failed", patch->old_name); } @@ -2282,12 +2293,22 @@ static void add_index_file(const char *path, unsigned mode, void *buf, unsigned static int try_create_file(const char *path, unsigned int mode, const char *buf, unsigned long size) { int fd; + char *nbuf; + unsigned long nsize; if (S_ISLNK(mode)) /* Although buf:size is counted string, it also is NUL * terminated. */ return symlink(buf, path); + nsize = size; + nbuf = (char *) buf; + if (convert_to_working_tree(path, &nbuf, &nsize)) { + free((char *) buf); + buf = nbuf; + size = nsize; + } + fd = open(path, O_CREAT | O_EXCL | O_WRONLY, (mode & 0100) ? 0777 : 0666); if (fd < 0) return -1; @@ -2598,6 +2619,7 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix) const char *whitespace_option = NULL; + for (i = 1; i < argc; i++) { const char *arg = argv[i]; char *end; diff --git a/t/t0020-crlf.sh b/t/t0020-crlf.sh index 58a4d86df3..723b29ad17 100755 --- a/t/t0020-crlf.sh +++ b/t/t0020-crlf.sh @@ -180,11 +180,8 @@ test_expect_success 'apply patch (autocrlf=true)' ' git repo-config core.autocrlf true && git read-tree --reset -u HEAD && - # Sore thumb - remove_cr one >tmp && mv -f tmp one && - git apply patch.file && - test "$patched" = "`git hash-object --stdin Date: Mon, 19 Feb 2007 17:57:29 -0800 Subject: git-apply: require -p when working in a subdirectory. git-apply running inside a subdirectory, with or without --index, used to always assume that the patch is formatted in such a way to apply with -p1 from the toplevel, but it is more useful and consistent with the use of "GNU patch -p1" if it defaulted to assume that its input is meant to apply at the level it is invoked in. This changes the behaviour. It used to be that the patch generated this way would apply without any trick: edit Documentation/Makefile git diff >patch.file cd Documentation git apply ../patch.file You need to give an explicit -p2 to git-apply now. On the other hand, if you got a patch from somebody else who did not follow "patch is to apply from the top with -p1" convention, the input patch would start with: diff -u Makefile.old Makefile --- Makefile.old +++ Makefile and in such a case, you can apply it with: git apply -p0 patch.file Signed-off-by: Junio C Hamano --- builtin-apply.c | 42 ++++++++++++++++++++++++++++-------------- t/t4119-apply-config.sh | 32 ++++++++++++++++++-------------- 2 files changed, 46 insertions(+), 28 deletions(-) (limited to 't') diff --git a/builtin-apply.c b/builtin-apply.c index 3f829fb661..053511e2ee 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -238,7 +238,7 @@ static int name_terminate(const char *name, int namelen, int c, int terminate) return 1; } -static char * find_name(const char *line, char *def, int p_value, int terminate) +static char *find_name(const char *line, char *def, int p_value, int terminate) { int len; const char *start = line; @@ -362,7 +362,7 @@ static int gitdiff_hdrend(const char *line, struct patch *patch) static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, const char *oldnew) { if (!orig_name && !isnull) - return find_name(line, NULL, 1, TERM_TAB); + return find_name(line, NULL, p_value, TERM_TAB); if (orig_name) { int len; @@ -372,7 +372,7 @@ static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, len = strlen(name); if (isnull) die("git-apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr); - another = find_name(line, NULL, 1, TERM_TAB); + another = find_name(line, NULL, p_value, TERM_TAB); if (!another || memcmp(another, name, len)) die("git-apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr); free(another); @@ -427,28 +427,28 @@ static int gitdiff_newfile(const char *line, struct patch *patch) static int gitdiff_copysrc(const char *line, struct patch *patch) { patch->is_copy = 1; - patch->old_name = find_name(line, NULL, 0, 0); + patch->old_name = find_name(line, NULL, p_value-1, 0); return 0; } static int gitdiff_copydst(const char *line, struct patch *patch) { patch->is_copy = 1; - patch->new_name = find_name(line, NULL, 0, 0); + patch->new_name = find_name(line, NULL, p_value-1, 0); return 0; } static int gitdiff_renamesrc(const char *line, struct patch *patch) { patch->is_rename = 1; - patch->old_name = find_name(line, NULL, 0, 0); + patch->old_name = find_name(line, NULL, p_value-1, 0); return 0; } static int gitdiff_renamedst(const char *line, struct patch *patch) { patch->is_rename = 1; - patch->new_name = find_name(line, NULL, 0, 0); + patch->new_name = find_name(line, NULL, p_value-1, 0); return 0; } @@ -2499,15 +2499,26 @@ static int use_patch(struct patch *p) return 0; x = x->next; } - if (0 < prefix_length) { - int pathlen = strlen(pathname); - if (pathlen <= prefix_length || - memcmp(prefix, pathname, prefix_length)) - return 0; - } return 1; } +static char *prefix_one(char *name) +{ + if (!name) + return name; + return xstrdup(prefix_filename(prefix, prefix_length, name)); +} + +static void prefix_patches(struct patch *p) +{ + if (!prefix) + return; + for ( ; p; p = p->next) { + p->new_name = prefix_one(p->new_name); + p->old_name = prefix_one(p->old_name); + } +} + static int apply_patch(int fd, const char *filename, int inaccurate_eof) { unsigned long offset, size; @@ -2530,11 +2541,14 @@ static int apply_patch(int fd, const char *filename, int inaccurate_eof) break; if (apply_in_reverse) reverse_patches(patch); + if (prefix) + prefix_patches(patch); if (use_patch(patch)) { patch_stats(patch); *listp = patch; listp = &patch->next; - } else { + } + else { /* perhaps free it a bit better? */ free(patch); skipped_patch++; diff --git a/t/t4119-apply-config.sh b/t/t4119-apply-config.sh index 0e8ea7e2b8..816b5b8fb2 100755 --- a/t/t4119-apply-config.sh +++ b/t/t4119-apply-config.sh @@ -10,20 +10,22 @@ test_description='git-apply --whitespace=strip and configuration file. . ./test-lib.sh test_expect_success setup ' - echo A >file1 && - cp file1 saved && - git add file1 && - echo "B " >file1 && + mkdir sub && + echo A >sub/file1 && + cp sub/file1 saved && + git add sub/file1 && + echo "B " >sub/file1 && git diff >patch.file ' test_expect_success 'apply --whitespace=strip' ' - cp saved file1 && + rm -f sub/file1 && + cp saved sub/file1 && git update-index --refresh && git apply --whitespace=strip patch.file && - if grep " " file1 + if grep " " sub/file1 then echo "Eh?" false @@ -34,12 +36,13 @@ test_expect_success 'apply --whitespace=strip' ' test_expect_success 'apply --whitespace=strip from config' ' - cp saved file1 && + rm -f sub/file1 && + cp saved sub/file1 && git update-index --refresh && git config apply.whitespace strip && git apply patch.file && - if grep " " file1 + if grep " " sub/file1 then echo "Eh?" false @@ -48,19 +51,19 @@ test_expect_success 'apply --whitespace=strip from config' ' fi ' -mkdir sub D=`pwd` test_expect_success 'apply --whitespace=strip in subdir' ' cd "$D" && git config --unset-all apply.whitespace - cp saved file1 && + rm -f sub/file1 && + cp saved sub/file1 && git update-index --refresh && cd sub && - git apply --whitespace=strip ../patch.file && - if grep " " ../file1 + git apply --whitespace=strip -p2 ../patch.file && + if grep " " file1 then echo "Eh?" false @@ -73,11 +76,12 @@ test_expect_success 'apply --whitespace=strip from config in subdir' ' cd "$D" && git config apply.whitespace strip && - cp saved file1 && + rm -f sub/file1 && + cp saved sub/file1 && git update-index --refresh && cd sub && - git apply ../patch.file && + git apply -p2 ../patch.file && if grep " " file1 then echo "Eh?" -- cgit v1.3 From d2cd696322011eb13125930e747f0d2a7778b992 Mon Sep 17 00:00:00 2001 From: Alex Riesen Date: Tue, 20 Feb 2007 10:04:32 +0100 Subject: disable t4016-diff-quote.sh on some filesystems ... because the filesystems (most typically FAT and NTFS) do not support HT nor LF in filenames. Signed-off-by: Alex Riesen --- t/t4016-diff-quote.sh | 4 ++++ 1 file changed, 4 insertions(+) (limited to 't') diff --git a/t/t4016-diff-quote.sh b/t/t4016-diff-quote.sh index edde8f5568..2e7cd5f255 100755 --- a/t/t4016-diff-quote.sh +++ b/t/t4016-diff-quote.sh @@ -13,6 +13,10 @@ P1='pathname with HT' P2='pathname with SP' P3='pathname with LF' +: >"$P1" 2>&1 && test -f "$P1" && rm -f "$P1" || { + echo >&2 'Filesystem does not support tabs in names' + test_done +} test_expect_success setup ' echo P0.0 >"$P0.0" && -- cgit v1.3 From c24e9757e9f608ad3985ee9093d28ca5cd37f052 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 21 Feb 2007 01:14:22 -0800 Subject: t4119: add test for traditional patch and different p_value Signed-off-by: Junio C Hamano --- t/t4119-apply-config.sh | 51 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) (limited to 't') diff --git a/t/t4119-apply-config.sh b/t/t4119-apply-config.sh index 816b5b8fb2..f9b9425153 100755 --- a/t/t4119-apply-config.sh +++ b/t/t4119-apply-config.sh @@ -18,6 +18,15 @@ test_expect_success setup ' git diff >patch.file ' +# Also handcraft GNU diff output; note this has trailing whitespace. +cat >gpatch.file <<\EOF +--- file1 2007-02-21 01:04:24.000000000 -0800 ++++ file1+ 2007-02-21 01:07:44.000000000 -0800 +@@ -1 +1 @@ +-A ++B +EOF + test_expect_success 'apply --whitespace=strip' ' rm -f sub/file1 && @@ -29,8 +38,12 @@ test_expect_success 'apply --whitespace=strip' ' then echo "Eh?" false - else + elif grep B sub/file1 + then echo Happy + else + echo "Huh?" + false fi ' @@ -46,6 +59,9 @@ test_expect_success 'apply --whitespace=strip from config' ' then echo "Eh?" false + elif grep B sub/file1 + then + echo Happy else echo Happy fi @@ -67,8 +83,12 @@ test_expect_success 'apply --whitespace=strip in subdir' ' then echo "Eh?" false - else + elif grep B file1 + then echo Happy + else + echo "Huh?" + false fi ' @@ -86,8 +106,35 @@ test_expect_success 'apply --whitespace=strip from config in subdir' ' then echo "Eh?" false + elif grep B file1 + then + echo Happy else + echo "Huh?" + false + fi +' + +test_expect_success 'same in subdir but with traditional patch input' ' + + cd "$D" && + git config apply.whitespace strip && + rm -f sub/file1 && + cp saved sub/file1 && + git update-index --refresh && + + cd sub && + git apply -p0 ../gpatch.file && + if grep " " file1 + then + echo "Eh?" + false + elif grep B file1 + then echo Happy + else + echo "Huh?" + false fi ' -- cgit v1.3 From 9987d7c58a847ab1605ae3216ff1ca95b19f0ad1 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 21 Feb 2007 14:31:10 -0800 Subject: git-apply: notice "diff --git" patch again Earlier one that tried to be too consistent with GNU patch by not stripping the leading path when we _know_ we are in a subdirectory and the patch is relative to the toplevel was a mistake. This fixes it. - No change to behaviour when it is run from the toplevel of the repository. - When run from a subdirectory to apply a git-generated patch, it uses the right -p value automatically, with or without --index nor --cached option. - When run from a subdirectory to apply a randomly generated patch, it wants the right -p value to be given by the user. The second one is a pure improvement to correct inconsistency between --index and non --index case, compared with 1.5.0. The third point could be further improved to guess what the right value for -p should be by looking at the patch, but should be a topic of a separate patch. Signed-off-by: Junio C Hamano --- builtin-apply.c | 23 ++++++++++++++++------- t/t4119-apply-config.sh | 4 ++-- 2 files changed, 18 insertions(+), 9 deletions(-) (limited to 't') diff --git a/builtin-apply.c b/builtin-apply.c index 1beebe5ff1..12f00e38db 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -144,6 +144,7 @@ struct patch { unsigned long deflate_origlen; int lines_added, lines_deleted; int score; + unsigned int is_toplevel_relative:1; unsigned int inaccurate_eof:1; unsigned int is_binary:1; unsigned int is_copy:1; @@ -362,7 +363,7 @@ static int gitdiff_hdrend(const char *line, struct patch *patch) static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, const char *oldnew) { if (!orig_name && !isnull) - return find_name(line, NULL, p_value, TERM_TAB); + return find_name(line, NULL, 1, TERM_TAB); if (orig_name) { int len; @@ -372,7 +373,7 @@ static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, len = strlen(name); if (isnull) die("git-apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr); - another = find_name(line, NULL, p_value, TERM_TAB); + another = find_name(line, NULL, 1, TERM_TAB); if (!another || memcmp(another, name, len)) die("git-apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr); free(another); @@ -427,28 +428,28 @@ static int gitdiff_newfile(const char *line, struct patch *patch) static int gitdiff_copysrc(const char *line, struct patch *patch) { patch->is_copy = 1; - patch->old_name = find_name(line, NULL, p_value-1, 0); + patch->old_name = find_name(line, NULL, 0, 0); return 0; } static int gitdiff_copydst(const char *line, struct patch *patch) { patch->is_copy = 1; - patch->new_name = find_name(line, NULL, p_value-1, 0); + patch->new_name = find_name(line, NULL, 0, 0); return 0; } static int gitdiff_renamesrc(const char *line, struct patch *patch) { patch->is_rename = 1; - patch->old_name = find_name(line, NULL, p_value-1, 0); + patch->old_name = find_name(line, NULL, 0, 0); return 0; } static int gitdiff_renamedst(const char *line, struct patch *patch) { patch->is_rename = 1; - patch->new_name = find_name(line, NULL, p_value-1, 0); + patch->new_name = find_name(line, NULL, 0, 0); return 0; } @@ -787,6 +788,7 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc { unsigned long offset, len; + patch->is_toplevel_relative = 0; patch->is_rename = patch->is_copy = 0; patch->is_new = patch->is_delete = -1; patch->old_mode = patch->new_mode = 0; @@ -831,6 +833,7 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc die("git diff header lacks filename information (line %d)", linenr); patch->old_name = patch->new_name = patch->def_name; } + patch->is_toplevel_relative = 1; *hdrsize = git_hdr_len; return offset; } @@ -2499,6 +2502,12 @@ static int use_patch(struct patch *p) return 0; x = x->next; } + if (0 < prefix_length) { + int pathlen = strlen(pathname); + if (pathlen <= prefix_length || + memcmp(prefix, pathname, prefix_length)) + return 0; + } return 1; } @@ -2513,7 +2522,7 @@ static void prefix_one(char **name) static void prefix_patches(struct patch *p) { - if (!prefix) + if (!prefix || p->is_toplevel_relative) return; for ( ; p; p = p->next) { if (p->new_name == p->old_name) { diff --git a/t/t4119-apply-config.sh b/t/t4119-apply-config.sh index f9b9425153..32e0d7172e 100755 --- a/t/t4119-apply-config.sh +++ b/t/t4119-apply-config.sh @@ -78,7 +78,7 @@ test_expect_success 'apply --whitespace=strip in subdir' ' git update-index --refresh && cd sub && - git apply --whitespace=strip -p2 ../patch.file && + git apply --whitespace=strip ../patch.file && if grep " " file1 then echo "Eh?" @@ -101,7 +101,7 @@ test_expect_success 'apply --whitespace=strip from config in subdir' ' git update-index --refresh && cd sub && - git apply -p2 ../patch.file && + git apply ../patch.file && if grep " " file1 then echo "Eh?" -- cgit v1.3 From 3e8a5db966c26a0a986161103d59683b909a6c78 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 21 Feb 2007 16:05:56 -0800 Subject: git-apply: guess correct -p value for non-git patches. This enhances the third point in the previous commit. When applying a non-git patch that begins like this: --- 2.6.orig/mm/slab.c +++ 2.6/mm/slab.c @@ -N,M +L,K @@@ ... and if you are in 'mm' subdirectory, we notice that -p2 is the right option to use to apply the patch in file slab.c in the current directory (i.e. mm/slab.c) The guess function also knows about this pattern, where you would need to use -p0 if applying from the top-level: --- mm/slab.c +++ mm/slab.c @@ -N,M +L,K @@@ ... Signed-off-by: Junio C Hamano --- builtin-apply.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++-- t/t4119-apply-config.sh | 56 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 111 insertions(+), 4 deletions(-) (limited to 't') diff --git a/builtin-apply.c b/builtin-apply.c index 12f00e38db..c7d4bdd474 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -28,6 +28,7 @@ static int newfd = -1; static int unidiff_zero; static int p_value = 1; +static int p_value_known; static int check_index; static int write_index; static int cached; @@ -312,11 +313,54 @@ static char *find_name(const char *line, char *def, int p_value, int terminate) return name; } +static int count_slashes(const char *cp) +{ + int cnt = 0; + char ch; + + while ((ch = *cp++)) + if (ch == '/') + cnt++; + return cnt; +} + +/* + * Given the string after "--- " or "+++ ", guess the appropriate + * p_value for the given patch. + */ +static int guess_p_value(const char *nameline) +{ + char *name, *cp; + int val = -1; + + if (is_dev_null(nameline)) + return -1; + name = find_name(nameline, NULL, 0, TERM_SPACE | TERM_TAB); + if (!name) + return -1; + cp = strchr(name, '/'); + if (!cp) + val = 0; + else if (prefix) { + /* + * Does it begin with "a/$our-prefix" and such? Then this is + * very likely to apply to our directory. + */ + if (!strncmp(name, prefix, prefix_length)) + val = count_slashes(prefix); + else { + cp++; + if (!strncmp(cp, prefix, prefix_length)) + val = count_slashes(prefix) + 1; + } + } + free(name); + return val; +} + /* * Get the name etc info from the --/+++ lines of a traditional patch header * - * NOTE! This hardcodes "-p1" behaviour in filename detection. - * * FIXME! The end-of-filename heuristics are kind of screwy. For existing * files, we can happily check the index for a match, but for creating a * new file we should try to match whatever "patch" does. I have no idea. @@ -327,6 +371,16 @@ static void parse_traditional_patch(const char *first, const char *second, struc first += 4; /* skip "--- " */ second += 4; /* skip "+++ " */ + if (!p_value_known) { + int p, q; + p = guess_p_value(first); + q = guess_p_value(second); + if (p < 0) p = q; + if (0 <= p && p == q) { + p_value = p; + p_value_known = 1; + } + } if (is_dev_null(first)) { patch->is_new = 1; patch->is_delete = 0; @@ -2656,6 +2710,7 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix) } if (!strncmp(arg, "-p", 2)) { p_value = atoi(arg + 2); + p_value_known = 1; continue; } if (!strcmp(arg, "--no-add")) { diff --git a/t/t4119-apply-config.sh b/t/t4119-apply-config.sh index 32e0d7172e..55f46737c6 100755 --- a/t/t4119-apply-config.sh +++ b/t/t4119-apply-config.sh @@ -19,7 +19,7 @@ test_expect_success setup ' ' # Also handcraft GNU diff output; note this has trailing whitespace. -cat >gpatch.file <<\EOF +cat >gpatch.file <<\EOF && --- file1 2007-02-21 01:04:24.000000000 -0800 +++ file1+ 2007-02-21 01:07:44.000000000 -0800 @@ -1 +1 @@ @@ -27,6 +27,12 @@ cat >gpatch.file <<\EOF +B EOF +sed -e 's|file1|sub/&|' gpatch.file >gpatch-sub.file && +sed -e ' + /^--- /s|file1|a/sub/&| + /^+++ /s|file1|b/sub/&| +' gpatch.file >gpatch-ab-sub.file && + test_expect_success 'apply --whitespace=strip' ' rm -f sub/file1 && @@ -124,7 +130,53 @@ test_expect_success 'same in subdir but with traditional patch input' ' git update-index --refresh && cd sub && - git apply -p0 ../gpatch.file && + git apply ../gpatch.file && + if grep " " file1 + then + echo "Eh?" + false + elif grep B file1 + then + echo Happy + else + echo "Huh?" + false + fi +' + +test_expect_success 'same but with traditional patch input of depth 1' ' + + cd "$D" && + git config apply.whitespace strip && + rm -f sub/file1 && + cp saved sub/file1 && + git update-index --refresh && + + cd sub && + git apply ../gpatch-sub.file && + if grep " " file1 + then + echo "Eh?" + false + elif grep B file1 + then + echo Happy + else + echo "Huh?" + false + fi +' + +test_expect_success 'same but with traditional patch input of depth 2' ' + + cd "$D" && + git config apply.whitespace strip && + rm -f sub/file1 && + cp saved sub/file1 && + git update-index --refresh && + + cd sub && + git apply ../gpatch-ab-sub.file && if grep " " file1 then echo "Eh?" -- cgit v1.3 From fe6e0eecb03379e6acb742f77b0b5f589a7b7422 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 21 Feb 2007 16:18:45 -0800 Subject: t4119: test autocomputing -p for traditional diff input. Signed-off-by: Junio C Hamano --- t/t4119-apply-config.sh | 117 ++++++++++++++++++------------------------------ 1 file changed, 43 insertions(+), 74 deletions(-) (limited to 't') diff --git a/t/t4119-apply-config.sh b/t/t4119-apply-config.sh index 55f46737c6..620a9207bf 100755 --- a/t/t4119-apply-config.sh +++ b/t/t4119-apply-config.sh @@ -33,24 +33,28 @@ sed -e ' /^+++ /s|file1|b/sub/&| ' gpatch.file >gpatch-ab-sub.file && -test_expect_success 'apply --whitespace=strip' ' - - rm -f sub/file1 && - cp saved sub/file1 && - git update-index --refresh && - - git apply --whitespace=strip patch.file && - if grep " " sub/file1 +check_result () { + if grep " " "$1" then echo "Eh?" false - elif grep B sub/file1 + elif grep B "$1" then echo Happy else echo "Huh?" false fi +} + +test_expect_success 'apply --whitespace=strip' ' + + rm -f sub/file1 && + cp saved sub/file1 && + git update-index --refresh && + + git apply --whitespace=strip patch.file && + check_result sub/file1 ' test_expect_success 'apply --whitespace=strip from config' ' @@ -61,16 +65,7 @@ test_expect_success 'apply --whitespace=strip from config' ' git config apply.whitespace strip && git apply patch.file && - if grep " " sub/file1 - then - echo "Eh?" - false - elif grep B sub/file1 - then - echo Happy - else - echo Happy - fi + check_result sub/file1 ' D=`pwd` @@ -85,17 +80,7 @@ test_expect_success 'apply --whitespace=strip in subdir' ' cd sub && git apply --whitespace=strip ../patch.file && - if grep " " file1 - then - echo "Eh?" - false - elif grep B file1 - then - echo Happy - else - echo "Huh?" - false - fi + check_result file1 ' test_expect_success 'apply --whitespace=strip from config in subdir' ' @@ -108,17 +93,7 @@ test_expect_success 'apply --whitespace=strip from config in subdir' ' cd sub && git apply ../patch.file && - if grep " " file1 - then - echo "Eh?" - false - elif grep B file1 - then - echo Happy - else - echo "Huh?" - false - fi + check_result file1 ' test_expect_success 'same in subdir but with traditional patch input' ' @@ -131,17 +106,7 @@ test_expect_success 'same in subdir but with traditional patch input' ' cd sub && git apply ../gpatch.file && - if grep " " file1 - then - echo "Eh?" - false - elif grep B file1 - then - echo Happy - else - echo "Huh?" - false - fi + check_result file1 ' test_expect_success 'same but with traditional patch input of depth 1' ' @@ -154,17 +119,7 @@ test_expect_success 'same but with traditional patch input of depth 1' ' cd sub && git apply ../gpatch-sub.file && - if grep " " file1 - then - echo "Eh?" - false - elif grep B file1 - then - echo Happy - else - echo "Huh?" - false - fi + check_result file1 ' test_expect_success 'same but with traditional patch input of depth 2' ' @@ -177,17 +132,31 @@ test_expect_success 'same but with traditional patch input of depth 2' ' cd sub && git apply ../gpatch-ab-sub.file && - if grep " " file1 - then - echo "Eh?" - false - elif grep B file1 - then - echo Happy - else - echo "Huh?" - false - fi + check_result file1 +' + +test_expect_success 'same but with traditional patch input of depth 1' ' + + cd "$D" && + git config apply.whitespace strip && + rm -f sub/file1 && + cp saved sub/file1 && + git update-index --refresh && + + git apply -p0 gpatch-sub.file && + check_result sub/file1 +' + +test_expect_success 'same but with traditional patch input of depth 2' ' + + cd "$D" && + git config apply.whitespace strip && + rm -f sub/file1 && + cp saved sub/file1 && + git update-index --refresh && + + git apply gpatch-ab-sub.file && + check_result sub/file1 ' test_done -- cgit v1.3 From 8565d2d853d85f246faa9bcde91aba3415a24d54 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 15 Feb 2007 11:43:56 +0100 Subject: Make tests independent of global config files This was done by setting $HOME to somewhere bogus. A better method is to reuse $GIT_CONFIG, which was invented for ignoring the global config file explicitely. Technically, setting GIT_CONFIG=.git/config could be wrong, but it passes all the tests, and we can keep the tests that way. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- t/test-lib.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 't') diff --git a/t/test-lib.sh b/t/test-lib.sh index 37822fc13d..a403fe042b 100755 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -255,8 +255,8 @@ test_done () { PATH=$(pwd)/..:$PATH GIT_EXEC_PATH=$(pwd)/.. GIT_TEMPLATE_DIR=$(pwd)/../templates/blt -HOME=$(pwd)/trash -export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR HOME +GIT_CONFIG=.git/config +export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git export GITPERLLIB -- cgit v1.3 From e7db67e6f18495332c4d688d3291b05851526a6e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 11 Jan 2007 17:09:26 -0800 Subject: git-svn: make multi-init capable of reusing the Ra connection If a user specified a seperate URL and --tags/--branches as a sepearte URL, allow the Ra object (and therefore the connection) to be reused. We'll get rid of libsvn_ls_fullurl() since it was only used in one place. Signed-off-by: Eric Wong --- git-svn.perl | 50 ++++++++++++++++++--------------------- t/t9103-git-svn-graft-branches.sh | 6 +++++ 2 files changed, 29 insertions(+), 27 deletions(-) (limited to 't') diff --git a/git-svn.perl b/git-svn.perl index 72f73ea623..02786f1a6a 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -595,8 +595,9 @@ sub cmd_multi_init { command_noisy('config', 'svn.trunk', $trunk_url); } } - complete_url_ls_init($url, $_branches, '--branches/-b', $_prefix); - complete_url_ls_init($url, $_tags, '--tags/-t', $_prefix . 'tags/'); + my $ra = $url ? Git::SVN::Ra->new($url) : undef; + complete_url_ls_init($ra, $_branches, '--branches/-b', $_prefix); + complete_url_ls_init($ra, $_tags, '--tags/-t', $_prefix . 'tags/'); } sub multi_fetch { @@ -890,29 +891,38 @@ sub complete_svn_url { } sub complete_url_ls_init { - my ($url, $path, $switch, $pfx) = @_; + my ($ra, $path, $switch, $pfx) = @_; unless ($path) { print STDERR "W: $switch not specified\n"; return; } - my $full_url = complete_svn_url($url, $path); - my @ls = libsvn_ls_fullurl($full_url); - foreach my $u (map { "$full_url/$_" } (grep m!/$!, @ls)) { - $u =~ s#/+$##; - if ($u !~ m!\Q$full_url\E/(.+)$!) { - print STDERR "W: Unrecognized URL: $u\n"; - die "This should never happen\n"; + $path =~ s#/+$##; + if ($path =~ m#^[a-z\+]+://#) { + $ra = Git::SVN::Ra->new($path); + $path = ''; + } else { + $path =~ s#^/+##; + unless ($ra) { + fatal("E: '$path' is not a complete URL ", + "and a separate URL is not specified\n"); } - # don't try to init already existing refs - my $id = $pfx.$1; + } + my $r = defined $_revision ? $_revision : $ra->get_latest_revnum; + my ($dirent, undef, undef) = $ra->get_dir($path, $r); + my $url = $ra->{url} . (length $path ? "/$path" : ''); + foreach my $d (sort keys %$dirent) { + next if ($dirent->{$d}->kind != $SVN::Node::dir); + my $u = "$url/$d"; + my $id = "$pfx$d"; my $gs = eval { Git::SVN->new($id) }; + # don't try to init already existing refs unless ($gs) { print "init $u => $id\n"; Git::SVN->init($id, $u); } } my ($n) = ($switch =~ /^--(\w+)/); - command_noisy('config', "svn.$n", $full_url); + command_noisy('config', "svn.$n", $url); } sub common_prefix { @@ -2851,20 +2861,6 @@ sub libsvn_commit_cb { } } -sub libsvn_ls_fullurl { - my $fullurl = shift; - my $ra = Git::SVN::Ra->new($fullurl); - my @ret; - my $r = defined $_revision ? $_revision : $ra->get_latest_revnum; - my ($dirent, undef, undef) = $ra->get_dir('', $r); - foreach my $d (sort keys %$dirent) { - if ($dirent->{$d}->kind == $SVN::Node::dir) { - push @ret, "$d/"; # add '/' for compat with cli svn - } - } - return @ret; -} - sub libsvn_skip_unknown_revs { my $err = shift; my $errno = $err->apr_err(); diff --git a/t/t9103-git-svn-graft-branches.sh b/t/t9103-git-svn-graft-branches.sh index 183ae3b1c2..8d946d2aa5 100755 --- a/t/t9103-git-svn-graft-branches.sh +++ b/t/t9103-git-svn-graft-branches.sh @@ -26,6 +26,12 @@ test_expect_success 'initialize repo' " git-svn multi-fetch " +test_expect_success 'multi-init set .git/config correctly' " + test '$svnrepo/trunk' = '`git repo-config --get svn.trunk`' && + test '$svnrepo/branches' = '`git repo-config --get svn.branches`' && + test '$svnrepo/tags' = '`git repo-config --get svn.tags`' + " + r1=`git-rev-list remotes/trunk | tail -n1` r2=`git-rev-list remotes/tags/a | tail -n1` r3=`git-rev-list remotes/a | tail -n1` -- cgit v1.3 From ad2f90851e03294b5b30f735252001bfc1bd9de3 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 11 Jan 2007 17:55:50 -0800 Subject: git-svn: add a test for show-ignore Signed-off-by: Eric Wong --- t/t9101-git-svn-props.sh | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 't') diff --git a/t/t9101-git-svn-props.sh b/t/t9101-git-svn-props.sh index e8133d81cb..622ea1c0df 100755 --- a/t/t9101-git-svn-props.sh +++ b/t/t9101-git-svn-props.sh @@ -121,4 +121,30 @@ b_ne_cr="`git-hash-object ne_cr`" test_expect_success 'CRLF + $Id$' "test '$a_cr' = '$b_cr'" test_expect_success 'CRLF + $Id$ (no newline)' "test '$a_ne_cr' = '$b_ne_cr'" +cat > show-ignore.expect <<\EOF + +# / +/no-such-file* + +# deeply +/deeply/no-such-file* + +# deeply/nested +/deeply/nested/no-such-file* + +# deeply/nested/directory +/deeply/nested/directory/no-such-file* +EOF + +test_expect_success 'test show-ignore' " + cd test_wc && + mkdir -p deeply/nested/directory && + svn add deeply && + svn propset -R svn:ignore 'no-such-file*' . + svn commit -m 'propset svn:ignore' + cd .. && + git-svn show-ignore > show-ignore.got && + cmp show-ignore.expect show-ignore.got + " + test_done -- cgit v1.3 From d05d72e07e49869fe988d4d99e6ac60711570db5 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 15 Jan 2007 22:59:26 -0800 Subject: git-svn: remove graft-branches command It's becoming a maintenance burden. I've never found it particularly useful myself, nor have I heard much feedback about it; so I'm assuming it's just as useless to everyone else. Signed-off-by: Eric Wong --- git-svn.perl | 529 +------------------------------------- t/t9103-git-svn-graft-branches.sh | 67 ----- 2 files changed, 3 insertions(+), 593 deletions(-) delete mode 100755 t/t9103-git-svn-graft-branches.sh (limited to 't') diff --git a/git-svn.perl b/git-svn.perl index 261e33d023..e75021bce2 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -60,16 +60,13 @@ $sha1_short = qr/[a-f\d]{4,40}/; my ($_stdin, $_help, $_edit, $_repack, $_repack_nr, $_repack_flags, $_message, $_file, $_no_metadata, - $_template, $_shared, $_no_default_regex, $_no_graft_copy, - $_version, $_upgrade, $_branch_all_refs, @_opt_m, + $_template, $_shared, + $_version, $_upgrade, $_merge, $_strategy, $_dry_run, $_prefix); -my (@_branch_from, %tree_map); my @repo_path_split_cache; -my %fc_opts = ( 'branch|b=s' => \@_branch_from, - 'follow-parent|follow' => \$_follow_parent, - 'branch-all-refs|B' => \$_branch_all_refs, +my %fc_opts = ( 'follow-parent|follow' => \$_follow_parent, 'authors-file|A=s' => \$_authors, 'repack:i' => \$_repack, 'no-metadata' => \$_no_metadata, @@ -111,13 +108,6 @@ my %cmd = ( rebuild => [ \&cmd_rebuild, "Rebuild git-svn metadata (after git clone)", { 'copy-remote|remote=s' => \$_cp_remote, 'upgrade' => \$_upgrade } ], - 'graft-branches' => [ \&graft_branches, - 'Detect merges/branches from already imported history', - { 'merge-rx|m' => \@_opt_m, - 'branch|b=s' => \@_branch_from, - 'branch-all-refs|B' => \$_branch_all_refs, - 'no-default-regex' => \$_no_default_regex, - 'no-graft-copy' => \$_no_graft_copy } ], 'multi-init' => [ \&cmd_multi_init, 'Initialize multiple trees (like git-svnimport)', { %multi_opts, %init_opts, @@ -167,13 +157,11 @@ my $rv = GetOptions(%opts, 'help|H|h' => \$_help, 'id|i=s' => \$GIT_SVN); exit 1 if (!$rv && $cmd ne 'log'); -set_default_vals(); usage(0) if $_help; version() if $_version; usage(1) unless defined $cmd; init_vars(); load_authors() if $_authors; -load_all_refs() if $_branch_all_refs; migration_check() unless $cmd =~ /^(?:init|rebuild|multi-init|commit-diff)$/; $cmd{$cmd}->[0]->(@ARGV); exit 0; @@ -394,40 +382,6 @@ sub cmd_show_ignore { $gs->traverse_ignore(\*STDOUT, '', $r); } -sub graft_branches { - my $gr_file = "$GIT_DIR/info/grafts"; - my ($grafts, $comments) = read_grafts($gr_file); - my $gr_sha1; - - if (%$grafts) { - # temporarily disable our grafts file to make this idempotent - chomp($gr_sha1 = command(qw/hash-object -w/,$gr_file)); - rename $gr_file, "$gr_file~$gr_sha1" or croak $!; - } - - my $l_map = read_url_paths(); - my @re = map { qr/$_/is } @_opt_m if @_opt_m; - unless ($_no_default_regex) { - push @re, (qr/\b(?:merge|merging|merged)\s+with\s+([\w\.\-]+)/i, - qr/\b(?:merge|merging|merged)\s+([\w\.\-]+)/i, - qr/\b(?:from|of)\s+([\w\.\-]+)/i ); - } - foreach my $u (keys %$l_map) { - if (@re) { - foreach my $p (keys %{$l_map->{$u}}) { - graft_merge_msg($grafts,$l_map,$u,$p,@re); - } - } - unless ($_no_graft_copy) { - graft_file_copy_lib($grafts,$l_map,$u); - } - } - graft_tree_joins($grafts); - - write_grafts($grafts, $comments, $gr_file); - unlink "$gr_file~$gr_sha1" if $gr_sha1; -} - sub cmd_multi_init { my $url = shift; unless (defined $_trunk || defined $_branches || defined $_tags) { @@ -601,157 +555,6 @@ sub common_prefix { return ''; } -# grafts set here are 'stronger' in that they're based on actual tree -# matches, and won't be deleted from merge-base checking in write_grafts() -sub graft_tree_joins { - my $grafts = shift; - map_tree_joins() if (@_branch_from && !%tree_map); - return unless %tree_map; - - git_svn_each(sub { - my $i = shift; - my @args = (qw/rev-list --pretty=raw/, "refs/remotes/$i"); - my ($fh, $ctx) = command_output_pipe(@args); - while (<$fh>) { - next unless /^commit ($sha1)$/o; - my $c = $1; - my ($t) = (<$fh> =~ /^tree ($sha1)$/o); - next unless $tree_map{$t}; - - my $l; - do { - $l = readline $fh; - } until ($l =~ /^committer (?:.+) (\d+) ([\-\+]?\d+)$/); - - my ($s, $tz) = ($1, $2); - if ($tz =~ s/^\+//) { - $s += tz_to_s_offset($tz); - } elsif ($tz =~ s/^\-//) { - $s -= tz_to_s_offset($tz); - } - - my ($url_a, $r_a, $uuid_a) = cmt_metadata($c); - - foreach my $p (@{$tree_map{$t}}) { - next if $p eq $c; - my $mb = eval { command('merge-base', $c, $p) }; - next unless ($@ || $?); - if (defined $r_a) { - # see if SVN says it's a relative - my ($url_b, $r_b, $uuid_b) = - cmt_metadata($p); - next if (defined $url_b && - defined $url_a && - ($url_a eq $url_b) && - ($uuid_a eq $uuid_b)); - if ($uuid_a eq $uuid_b) { - if ($r_b < $r_a) { - $grafts->{$c}->{$p} = 2; - next; - } elsif ($r_b > $r_a) { - $grafts->{$p}->{$c} = 2; - next; - } - } - } - my $ct = get_commit_time($p); - if ($ct < $s) { - $grafts->{$c}->{$p} = 2; - } elsif ($ct > $s) { - $grafts->{$p}->{$c} = 2; - } - # what should we do when $ct == $s ? - } - } - command_close_pipe($fh, $ctx); - }); -} - -sub graft_file_copy_lib { - my ($grafts, $l_map, $u) = @_; - my $tree_paths = $l_map->{$u}; - my $pfx = common_prefix([keys %$tree_paths]); - my ($repo, $path) = repo_path_split($u.$pfx); - $SVN = Git::SVN::Ra->new($repo); - - my ($base, $head) = libsvn_parse_revision(); - my $inc = 1000; - my ($min, $max) = ($base, $head < $base+$inc ? $head : $base+$inc); - my $eh = $SVN::Error::handler; - $SVN::Error::handler = \&libsvn_skip_unknown_revs; - while (1) { - $SVN->dup->get_log([$path], $min, $max, 0, 2, 1, - sub { - libsvn_graft_file_copies($grafts, $tree_paths, - $path, @_); - }); - last if ($max >= $head); - $min = $max + 1; - $max += $inc; - $max = $head if ($max > $head); - } - $SVN::Error::handler = $eh; -} - -sub process_merge_msg_matches { - my ($grafts, $l_map, $u, $p, $c, @matches) = @_; - my (@strong, @weak); - foreach (@matches) { - # merging with ourselves is not interesting - next if $_ eq $p; - if ($l_map->{$u}->{$_}) { - push @strong, $_; - } else { - push @weak, $_; - } - } - foreach my $w (@weak) { - last if @strong; - # no exact match, use branch name as regexp. - my $re = qr/\Q$w\E/i; - foreach (keys %{$l_map->{$u}}) { - if (/$re/) { - push @strong, $l_map->{$u}->{$_}; - last; - } - } - last if @strong; - $w = basename($w); - $re = qr/\Q$w\E/i; - foreach (keys %{$l_map->{$u}}) { - if (/$re/) { - push @strong, $l_map->{$u}->{$_}; - last; - } - } - } - my ($rev) = ($c->{m} =~ /^git-svn-id:\s(?:\S+?)\@(\d+) - \s(?:[a-f\d\-]+)$/xsm); - unless (defined $rev) { - ($rev) = ($c->{m} =~/^git-svn-id:\s(\d+) - \@(?:[a-f\d\-]+)/xsm); - return unless defined $rev; - } - foreach my $m (@strong) { - my ($r0, $s0) = find_rev_before($rev, $m, 1); - $grafts->{$c->{c}}->{$s0} = 1 if defined $s0; - } -} - -sub graft_merge_msg { - my ($grafts, $l_map, $u, $p, @re) = @_; - - my $x = $l_map->{$u}->{$p}; - my $rl = rev_list_raw("refs/remotes/$x"); - while (my $c = next_rev_list_entry($rl)) { - foreach my $re (@re) { - my (@br) = ($c->{m} =~ /$re/g); - next unless @br; - process_merge_msg_matches($grafts,$l_map,$u,$p,$c,@br); - } - } -} - sub verify_ref { my ($ref) = @_; eval { command_oneline([ 'rev-parse', '--verify', $ref ], @@ -807,58 +610,6 @@ sub get_tree_from_treeish { return $expected; } -sub get_diff { - my ($from, $treeish) = @_; - print "diff-tree $from $treeish\n"; - my @diff_tree = qw(diff-tree -z -r); - if ($_cp_similarity) { - push @diff_tree, "-C$_cp_similarity"; - } else { - push @diff_tree, '-C'; - } - push @diff_tree, '--find-copies-harder' if $_find_copies_harder; - push @diff_tree, "-l$_l" if defined $_l; - push @diff_tree, $from, $treeish; - my ($diff_fh, $ctx) = command_output_pipe(@diff_tree); - local $/ = "\0"; - my $state = 'meta'; - my @mods; - while (<$diff_fh>) { - chomp $_; # this gets rid of the trailing "\0" - if ($state eq 'meta' && /^:(\d{6})\s(\d{6})\s - $sha1\s($sha1)\s([MTCRAD])\d*$/xo) { - push @mods, { mode_a => $1, mode_b => $2, - sha1_b => $3, chg => $4 }; - if ($4 =~ /^(?:C|R)$/) { - $state = 'file_a'; - } else { - $state = 'file_b'; - } - } elsif ($state eq 'file_a') { - my $x = $mods[$#mods] or croak "Empty array\n"; - if ($x->{chg} !~ /^(?:C|R)$/) { - croak "Error parsing $_, $x->{chg}\n"; - } - $x->{file_a} = $_; - $state = 'file_b'; - } elsif ($state eq 'file_b') { - my $x = $mods[$#mods] or croak "Empty array\n"; - if (exists $x->{file_a} && $x->{chg} !~ /^(?:C|R)$/) { - croak "Error parsing $_, $x->{chg}\n"; - } - if (!exists $x->{file_a} && $x->{chg} =~ /^(?:C|R)$/) { - croak "Error parsing $_, $x->{chg}\n"; - } - $x->{file_b} = $_; - $state = 'meta'; - } else { - croak "Error parsing $_\n"; - } - } - command_close_pipe($diff_fh, $ctx); - return \@mods; -} - sub get_commit_entry { my ($treeish) = shift; my %log_entry = ( log => '', tree => get_tree_from_treeish($treeish) ); @@ -899,34 +650,6 @@ sub get_commit_entry { \%log_entry; } -sub rev_list_raw { - my ($fh, $c) = command_output_pipe(qw/rev-list --pretty=raw/, @_); - return { fh => $fh, ctx => $c, t => { } }; -} - -sub next_rev_list_entry { - my $rl = shift; - my $fh = $rl->{fh}; - my $x = $rl->{t}; - while (<$fh>) { - if (/^commit ($sha1)$/o) { - if ($x->{c}) { - $rl->{t} = { c => $1 }; - return $x; - } else { - $x->{c} = $1; - } - } elsif (/^parent ($sha1)$/o) { - $x->{p}->{$1} = 1; - } elsif (s/^ //) { - $x->{m} ||= ''; - $x->{m} .= $_; - } - } - command_close_pipe($fh, $rl->{ctx}); - return ($x != $rl->{t}) ? $x : undef; -} - sub s_to_file { my ($str, $file, $mode) = @_; open my $fd,'>',$file or croak $!; @@ -962,43 +685,6 @@ sub check_upgrade_needed { } } -# fills %tree_map with a reverse mapping of trees to commits. Useful -# for finding parents to commit on. -sub map_tree_joins { - my %seen; - foreach my $br (@_branch_from) { - my $pipe = command_output_pipe(qw/rev-list - --topo-order --pretty=raw/, $br); - while (<$pipe>) { - if (/^commit ($sha1)$/o) { - my $commit = $1; - - # if we've seen a commit, - # we've seen its parents - last if $seen{$commit}; - my ($tree) = (<$pipe> =~ /^tree ($sha1)$/o); - unless (defined $tree) { - die "Failed to parse commit $commit\n"; - } - push @{$tree_map{$tree}}, $commit; - $seen{$commit} = 1; - } - } - close $pipe; - } -} - -sub load_all_refs { - if (@_branch_from) { - print STDERR '--branch|-b parameters are ignored when ', - "--branch-all-refs|-B is passed\n"; - } - - # don't worry about rev-list on non-commit objects/tags, - # it shouldn't blow up if a ref is a blob or tree... - @_branch_from = command(qw/rev-parse --symbolic --all/); -} - # ' = real-name ' mapping based on git-svnimport: sub load_authors { open my $authors, '<', $_authors or die "Can't open $_authors $!\n"; @@ -1073,20 +759,6 @@ sub migration_check { print "Done upgrading.\n"; } -sub find_rev_before { - my ($r, $id, $eq_ok) = @_; - my $f = "$GIT_DIR/svn/$id/.rev_db"; - return (undef,undef) unless -r $f; - --$r unless $eq_ok; - while ($r > 0) { - if (my $c = revdb_get($f, $r)) { - return ($r, $c); - } - --$r; - } - return (undef, undef); -} - sub init_vars { $GIT_SVN ||= $ENV{GIT_SVN_ID} || 'git-svn'; $Git::SVN::default = $GIT_SVN; @@ -1094,7 +766,6 @@ sub init_vars { $REVDB = "$GIT_SVN_DIR/.rev_db"; $GIT_SVN_INDEX = "$GIT_SVN_DIR/index"; $SVN_URL = undef; - %tree_map = (); } # convert GetOpt::Long specs for use by git-config @@ -1120,95 +791,6 @@ sub read_repo_config { } } -sub set_default_vals { - if (defined $_repack) { - $_repack = 1000 if ($_repack <= 0); - $_repack_nr = $_repack; - $_repack_flags ||= '-d'; - } -} - -sub read_grafts { - my $gr_file = shift; - my ($grafts, $comments) = ({}, {}); - if (open my $fh, '<', $gr_file) { - my @tmp; - while (<$fh>) { - if (/^($sha1)\s+/) { - my $c = $1; - if (@tmp) { - @{$comments->{$c}} = @tmp; - @tmp = (); - } - foreach my $p (split /\s+/, $_) { - $grafts->{$c}->{$p} = 1; - } - } else { - push @tmp, $_; - } - } - close $fh or croak $!; - @{$comments->{'END'}} = @tmp if @tmp; - } - return ($grafts, $comments); -} - -sub write_grafts { - my ($grafts, $comments, $gr_file) = @_; - - open my $fh, '>', $gr_file or croak $!; - foreach my $c (sort keys %$grafts) { - if ($comments->{$c}) { - print $fh $_ foreach @{$comments->{$c}}; - } - my $p = $grafts->{$c}; - my %x; # real parents - delete $p->{$c}; # commits are not self-reproducing... - my $ch = command_output_pipe(qw/cat-file commit/, $c); - while (<$ch>) { - if (/^parent ($sha1)/) { - $x{$1} = $p->{$1} = 1; - } else { - last unless /^\S/; - } - } - close $ch; # breaking the pipe - - # if real parents are the only ones in the grafts, drop it - next if join(' ',sort keys %$p) eq join(' ',sort keys %x); - - my (@ip, @jp, $mb); - my %del = %x; - @ip = @jp = keys %$p; - foreach my $i (@ip) { - next if $del{$i} || $p->{$i} == 2; - foreach my $j (@jp) { - next if $i eq $j || $del{$j} || $p->{$j} == 2; - $mb = eval { command('merge-base', $i, $j) }; - next unless $mb; - chomp $mb; - next if $x{$mb}; - if ($mb eq $j) { - delete $p->{$i}; - $del{$i} = 1; - } elsif ($mb eq $i) { - delete $p->{$j}; - $del{$j} = 1; - } - } - } - - # if real parents are the only ones in the grafts, drop it - next if join(' ',sort keys %$p) eq join(' ',sort keys %x); - - print $fh $c, ' ', join(' ', sort keys %$p),"\n"; - } - if ($comments->{'END'}) { - print $fh $_ foreach @{$comments->{'END'}}; - } - close $fh or croak $!; -} - sub read_url_paths_all { my ($l_map, $pfx, $p) = @_; my @dir; @@ -1936,48 +1518,6 @@ sub uri_decode { $f } -sub libsvn_parse_revision { - my $base = shift; - my $head = $SVN->get_latest_revnum(); - if (!defined $_revision || $_revision eq 'BASE:HEAD') { - return ($base + 1, $head) if (defined $base); - return (0, $head); - } - return ($1, $2) if ($_revision =~ /^(\d+):(\d+)$/); - return ($_revision, $_revision) if ($_revision =~ /^\d+$/); - if ($_revision =~ /^BASE:(\d+)$/) { - return ($base + 1, $1) if (defined $base); - return (0, $head); - } - return ($1, $head) if ($_revision =~ /^(\d+):HEAD$/); - die "revision argument: $_revision not understood by git-svn\n", - "Try using the command-line svn client instead\n"; -} - -sub libsvn_traverse_ignore { - my ($fh, $path, $r) = @_; - $path =~ s#^/+##g; - my ($dirent, undef, $props) = $SVN->get_dir($path, $r); - my $p = $path; - $p =~ s#^\Q$SVN->{svn_path}\E/##; - print $fh length $p ? "\n# $p\n" : "\n# /\n"; - if (my $s = $props->{'svn:ignore'}) { - $s =~ s/[\r\n]+/\n/g; - chomp $s; - if (length $p == 0) { - $s =~ s#\n#\n/$p#g; - print $fh "/$s\n"; - } else { - $s =~ s#\n#\n/$p/#g; - print $fh "/$p/$s\n"; - } - } - foreach (sort keys %$dirent) { - next if $dirent->{$_}->kind != $SVN::Node::dir; - libsvn_traverse_ignore($fh, "$path/$_", $r); - } -} - sub revisions_eq { my ($path, $r0, $r1) = @_; return 1 if $r0 == $r1; @@ -2065,69 +1605,6 @@ sub _libsvn_new_tree { libsvn_log_entry($rev, $author, $date, $log, $parents, $ed); } -sub find_graft_path_commit { - my ($tree_paths, $p1, $r1) = @_; - foreach my $x (keys %$tree_paths) { - next unless ($p1 =~ /^\Q$x\E/); - my $i = $tree_paths->{$x}; - my ($r0, $parent) = find_rev_before($r1,$i,1); - return $parent if (defined $r0 && $r0 == $r1); - print STDERR "r$r1 of $i not imported\n"; - next; - } - return undef; -} - -sub find_graft_path_parents { - my ($grafts, $tree_paths, $c, $p0, $r0) = @_; - foreach my $x (keys %$tree_paths) { - next unless ($p0 =~ /^\Q$x\E/); - my $i = $tree_paths->{$x}; - my ($r, $parent) = find_rev_before($r0, $i, 1); - if (defined $r && defined $parent && revisions_eq($x,$r,$r0)) { - my ($url_b, undef, $uuid_b) = cmt_metadata($c); - my ($url_a, undef, $uuid_a) = cmt_metadata($parent); - next if ($url_a && $url_b && $url_a eq $url_b && - $uuid_b eq $uuid_a); - $grafts->{$c}->{$parent} = 1; - } - } -} - -sub libsvn_graft_file_copies { - my ($grafts, $tree_paths, $path, $paths, $rev) = @_; - foreach (keys %$paths) { - my $i = $paths->{$_}; - my ($m, $p0, $r0) = ($i->action, $i->copyfrom_path, - $i->copyfrom_rev); - next unless (defined $p0 && defined $r0); - - my $p1 = $_; - $p1 =~ s#^/##; - $p0 =~ s#^/##; - my $c = find_graft_path_commit($tree_paths, $p1, $rev); - next unless $c; - find_graft_path_parents($grafts, $tree_paths, $c, $p0, $r0); - } -} - -sub revdb_get { - my ($file, $rev) = @_; - my $ret; - my $offset = $rev * 41; - open my $fh, '<', $file or croak $!; - seek $fh, $offset, 0; - if (tell $fh == $offset) { - $ret = readline $fh; - if (defined $ret) { - chomp $ret; - $ret = undef if ($ret =~ /^0{40}$/); - } - } - close $fh or croak $!; - return $ret; -} - { my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file. $SVN::Node::dir.$SVN::Node::unknown. diff --git a/t/t9103-git-svn-graft-branches.sh b/t/t9103-git-svn-graft-branches.sh deleted file mode 100755 index 8d946d2aa5..0000000000 --- a/t/t9103-git-svn-graft-branches.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/bin/sh -test_description='git-svn graft-branches' -. ./lib-git-svn.sh - -svnrepo="$svnrepo/test-git-svn" - -test_expect_success 'initialize repo' " - mkdir import && - cd import && - mkdir -p trunk branches tags && - echo hello > trunk/readme && - svn import -m 'import for git-svn' . $svnrepo && - cd .. && - svn cp -m 'tag a' $svnrepo/trunk $svnrepo/tags/a && - svn cp -m 'branch a' $svnrepo/trunk $svnrepo/branches/a && - svn co $svnrepo wc && - cd wc && - echo feedme >> branches/a/readme && - poke branches/a/readme && - svn commit -m hungry && - cd trunk && - svn merge -r3:4 $svnrepo/branches/a && - svn commit -m 'merge with a' && - cd ../.. && - git-svn multi-init $svnrepo -T trunk -b branches -t tags && - git-svn multi-fetch - " - -test_expect_success 'multi-init set .git/config correctly' " - test '$svnrepo/trunk' = '`git repo-config --get svn.trunk`' && - test '$svnrepo/branches' = '`git repo-config --get svn.branches`' && - test '$svnrepo/tags' = '`git repo-config --get svn.tags`' - " - -r1=`git-rev-list remotes/trunk | tail -n1` -r2=`git-rev-list remotes/tags/a | tail -n1` -r3=`git-rev-list remotes/a | tail -n1` -r4=`git-rev-parse remotes/a` -r5=`git-rev-parse remotes/trunk` - -test_expect_success 'test graft-branches regexes and copies' " - test -n "$r1" && - test -n "$r2" && - test -n "$r3" && - test -n "$r4" && - test -n "$r5" && - git-svn graft-branches && - grep '^$r2 $r1' $GIT_DIR/info/grafts && - grep '^$r3 $r1' $GIT_DIR/info/grafts && - grep '^$r5 ' $GIT_DIR/info/grafts | grep '$r4' | grep '$r1' - " - -test_debug 'gitk --all & sleep 1' - -test_expect_success 'test graft-branches with tree-joins' " - rm $GIT_DIR/info/grafts && - git-svn graft-branches --no-default-regex --no-graft-copy -B && - grep '^$r3 ' $GIT_DIR/info/grafts | grep '$r1' | grep '$r2' && - grep '^$r2 $r1' $GIT_DIR/info/grafts && - grep '^$r5 ' $GIT_DIR/info/grafts | grep '$r1' | grep '$r4' - " - -# the result of this is kinda funky, we have a strange history and -# this is just a test :) -test_debug 'gitk --all &' - -test_done -- cgit v1.3 From 706587fc6d56db1ba6c7207d4c0c456bac6f77c2 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 18 Jan 2007 17:50:01 -0800 Subject: git-svn: add support for metadata in .git/config Of course, we handle metadata migrations from previous versions and we have added unit tests. The new .git/config remotes resemble non-SVN remotes. Below is an example with comments: [svn-remote "git-svn"] ; like non-svn remotes, we have one URL per-remote url = http://foo.bar.org/svn ; 'fetch' keys are done in the same way as non-svn ; remotes, too. With the left-hand-side of the ':' ; being the remote (SVN) repository path relative to the ; above 'url' key; and the right-hand-side being a ; remote ref in git (refs/remotes/*). ; An empty left-hand-side means that it will fetch ; the entire contents of the 'url' key. ; old-style (migrated from previous versions of git-svn) ; are like this: fetch = :refs/remotes/git-svn ; this is created by a current version of git-svn ; using the multi-init command with an explicit ; url (specified above). This allows multi-init ; to reuse SVN::Ra connections. fetch = trunk:refs/remotes/trunk fetch = branches/a:refs/remotes/a fetch = branches/b:refs/remotes/b fetch = tags/0.1:refs/remotes/tags/0.1 fetch = tags/0.2:refs/remotes/tags/0.2 fetch = tags/0.3:refs/remotes/tags/0.3 [svn-remote "alt"] ; this is another old-style remote migrated over ; to the new config format url = http://foo.bar.org/alt fetch = :refs/remotes/alt Signed-off-by: Eric Wong --- git-svn.perl | 583 +++++++++++++++++++++++---------------------- t/t9107-git-svn-migrate.sh | 63 +++++ 2 files changed, 367 insertions(+), 279 deletions(-) create mode 100755 t/t9107-git-svn-migrate.sh (limited to 't') diff --git a/git-svn.perl b/git-svn.perl index e75021bce2..9d50d305c1 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -13,9 +13,8 @@ use vars qw/ $AUTHOR $VERSION $AUTHOR = 'Eric Wong '; $VERSION = '@@GIT_VERSION@@'; -use Cwd qw/abs_path/; -$GIT_DIR = abs_path($ENV{GIT_DIR} || '.git'); -$ENV{GIT_DIR} = $GIT_DIR; +$ENV{GIT_DIR} ||= '.git'; +$Git::SVN::default_repo_id = $ENV{GIT_SVN_ID} || 'git-svn'; my $LC_ALL = $ENV{LC_ALL}; $Git::SVN::Log::TZ = $ENV{TZ}; @@ -47,6 +46,7 @@ BEGIN { foreach (qw/command command_oneline command_noisy command_output_pipe command_input_pipe command_close_pipe/) { $s .= "*SVN::Git::Editor::$_ = *SVN::Git::Fetcher::$_ = ". + "*Git::SVN::Migration::$_ = ". "*Git::SVN::Log::$_ = *Git::SVN::$_ = *$_ = *Git::$_; "; } eval $s; @@ -64,17 +64,17 @@ my ($_stdin, $_help, $_edit, $_version, $_upgrade, $_merge, $_strategy, $_dry_run, $_prefix); -my @repo_path_split_cache; +my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username, + 'config-dir=s' => \$Git::SVN::Ra::config_dir, + 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache ); my %fc_opts = ( 'follow-parent|follow' => \$_follow_parent, 'authors-file|A=s' => \$_authors, 'repack:i' => \$_repack, 'no-metadata' => \$_no_metadata, 'quiet|q' => \$_q, - 'username=s' => \$Git::SVN::Prompt::_username, - 'config-dir=s' => \$Git::SVN::Ra::config_dir, - 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache, - 'repack-flags|repack-args|repack-opts=s' => \$_repack_flags); + 'repack-flags|repack-args|repack-opts=s' => \$_repack_flags, + %remote_opts ); my ($_trunk, $_tags, $_branches); my %multi_opts = ( 'trunk|T=s' => \$_trunk, @@ -110,16 +110,20 @@ my %cmd = ( 'upgrade' => \$_upgrade } ], 'multi-init' => [ \&cmd_multi_init, 'Initialize multiple trees (like git-svnimport)', - { %multi_opts, %init_opts, + { %multi_opts, %init_opts, %remote_opts, 'revision|r=i' => \$_revision, - 'username=s' => \$Git::SVN::Prompt::_username, - 'config-dir=s' => \$Git::SVN::Ra::config_dir, - 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache, 'prefix=s' => \$_prefix, } ], 'multi-fetch' => [ \&cmd_multi_fetch, 'Fetch multiple trees (like git-svnimport)', \%fc_opts ], + 'migrate' => [ sub { }, + # no-op, we automatically run this anyways, + # we may add a flag to automatically optimize the + # configuration to avoid reconnects in the future + 'Migrate configuration/metadata/layout from + previous versions of git-svn', + \%remote_opts ], 'log' => [ \&Git::SVN::Log::cmd_show_log, 'Show commit logs', { 'limit=i' => \$Git::SVN::Log::limit, 'revision|r=s' => \$_revision, @@ -154,15 +158,16 @@ my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd); read_repo_config(\%opts); my $rv = GetOptions(%opts, 'help|H|h' => \$_help, 'version|V' => \$_version, - 'id|i=s' => \$GIT_SVN); + 'id|i=s' => \$Git::SVN::default_repo_id); exit 1 if (!$rv && $cmd ne 'log'); usage(0) if $_help; version() if $_version; usage(1) unless defined $cmd; -init_vars(); load_authors() if $_authors; -migration_check() unless $cmd =~ /^(?:init|rebuild|multi-init|commit-diff)$/; +unless ($cmd =~ /^(?:init|rebuild|multi-init|commit-diff)$/) { + Git::SVN::Migration::migration_check(); +} $cmd{$cmd}->[0]->(@ARGV); exit 0; @@ -203,17 +208,12 @@ sub version { sub cmd_rebuild { my $url = shift; - my $gs = $url ? Git::SVN->init(undef, $url) + my $gs = $url ? Git::SVN->init($url) : eval { Git::SVN->new }; $gs ||= Git::SVN->_new; if (!verify_ref($gs->refname.'^0')) { $gs->copy_remote_ref; } - if ($_upgrade) { - command_noisy('update-ref',$gs->refname, $gs->{id}.'-HEAD'); - } else { - $gs->check_upgrade_needed; - } my ($rev_list, $ctx) = command_output_pipe("rev-list", $gs->refname); my $latest; @@ -238,7 +238,7 @@ sub cmd_rebuild { if (!$gs->{url} && !$url) { fatal "SVN repository location required\n"; } - $gs = Git::SVN->init(undef, $url); + $gs = Git::SVN->init($url); $latest = $rev; } $gs->rev_db_set($rev, $c); @@ -268,7 +268,7 @@ sub cmd_init { } do_git_init_db(); - Git::SVN->init(undef, $url); + Git::SVN->init($url); } sub cmd_fetch { @@ -389,28 +389,35 @@ sub cmd_multi_init { } do_git_init_db(); $_prefix = '' unless defined $_prefix; + $url =~ s#/+$## if defined $url; if (defined $_trunk) { - my $gs_trunk = eval { Git::SVN->new($_prefix . 'trunk') }; + my $trunk_ref = $_prefix . 'trunk'; + # try both old-style and new-style lookups: + my $gs_trunk = eval { Git::SVN->new($trunk_ref) }; unless ($gs_trunk) { - my $trunk_url = complete_svn_url($url, $_trunk); - $gs_trunk = Git::SVN->init($_prefix . 'trunk', - $trunk_url); - command_noisy('config', 'svn.trunk', $trunk_url); + my ($trunk_url, $trunk_path) = + complete_svn_url($url, $_trunk); + $gs_trunk = Git::SVN->init($trunk_url, $trunk_path, + undef, $trunk_ref); } } + return unless defined $_branches || defined $_tags; my $ra = $url ? Git::SVN::Ra->new($url) : undef; complete_url_ls_init($ra, $_branches, '--branches/-b', $_prefix); complete_url_ls_init($ra, $_tags, '--tags/-t', $_prefix . 'tags/'); } sub cmd_multi_fetch { - # try to do trunk first, since branches/tags - # may be descended from it. - if (-e "$ENV{GIT_DIR}/svn/trunk/info/url") { - my $gs = Git::SVN->new('trunk'); - $gs->fetch(@_); + my @gs; + foreach (command(qw/config -l/)) { + next unless m!^svn-remote\.(.+)\.fetch= + \s*(.*)\s*:\s*refs/remotes/(.+)\s*$!x; + my ($repo_id, $path, $ref_id) = ($1, $2, $3); + push @gs, Git::SVN->new($ref_id, $repo_id, $path); + } + foreach (@gs) { + $_->fetch; } - rec_fetch('', "$ENV{GIT_DIR}/svn", @_); } # this command is special because it requires no metadata @@ -464,95 +471,50 @@ sub cmd_commit_diff { ########################### utility functions ######################### -sub rec_fetch { - my ($pfx, $p, @args) = @_; - my @dir; - foreach (sort <$p/*>) { - if (-r "$_/info/url") { - $pfx .= '/' if $pfx && $pfx !~ m!/$!; - my $id = $pfx . basename $_; - next if $id eq 'trunk'; - my $gs = Git::SVN->new($id); - $gs->fetch(@args); - } elsif (-d $_) { - push @dir, $_; - } - } - foreach (@dir) { - my $x = $_; - $x =~ s!^\Q$ENV{GIT_DIR}\E/svn/!!o; - rec_fetch($x, $_, @args); - } -} - sub complete_svn_url { my ($url, $path) = @_; $path =~ s#/+$##; - $url =~ s#/+$## if $url; if ($path !~ m#^[a-z\+]+://#) { - $path = '/' . $path if ($path !~ m#^/#); if (!defined $url || $url !~ m#^[a-z\+]+://#) { fatal("E: '$path' is not a complete URL ", "and a separate URL is not specified\n"); } - $path = $url . $path; + return ($url, $path); } - return $path; + return ($path, ''); } sub complete_url_ls_init { - my ($ra, $path, $switch, $pfx) = @_; - unless ($path) { + my ($ra, $repo_path, $switch, $pfx) = @_; + unless ($repo_path) { print STDERR "W: $switch not specified\n"; return; } - $path =~ s#/+$##; - if ($path =~ m#^[a-z\+]+://#) { - $ra = Git::SVN::Ra->new($path); - $path = ''; + $repo_path =~ s#/+$##; + if ($repo_path =~ m#^[a-z\+]+://#) { + $ra = Git::SVN::Ra->new($repo_path); + $repo_path = ''; } else { - $path =~ s#^/+##; + $repo_path =~ s#^/+##; unless ($ra) { - fatal("E: '$path' is not a complete URL ", + fatal("E: '$repo_path' is not a complete URL ", "and a separate URL is not specified\n"); } } my $r = defined $_revision ? $_revision : $ra->get_latest_revnum; - my ($dirent, undef, undef) = $ra->get_dir($path, $r); - my $url = $ra->{url} . (length $path ? "/$path" : ''); + my ($dirent, undef, undef) = $ra->get_dir($repo_path, $r); + my $url = $ra->{url}; foreach my $d (sort keys %$dirent) { next if ($dirent->{$d}->kind != $SVN::Node::dir); - my $u = "$url/$d"; - my $id = "$pfx$d"; - my $gs = eval { Git::SVN->new($id) }; + my $path = "$repo_path/$d"; + my $ref = "$pfx$d"; + my $gs = eval { Git::SVN->new($ref) }; # don't try to init already existing refs unless ($gs) { - print "init $u => $id\n"; - Git::SVN->init($id, $u); + print "init $url/$path => $ref\n"; + Git::SVN->init($url, $path, undef, $ref); } } - my ($n) = ($switch =~ /^--(\w+)/); - command_noisy('config', "svn.$n", $url); -} - -sub common_prefix { - my $paths = shift; - my %common; - foreach (@$paths) { - my @tmp = split m#/#, $_; - my $p = ''; - while (my $x = shift @tmp) { - $p .= "/$x"; - $common{$p} ||= 0; - $common{$p}++; - } - } - foreach (sort {length $b <=> length $a} keys %common) { - if ($common{$_} == @$paths) { - return $_; - } - } - return ''; } sub verify_ref { @@ -561,34 +523,6 @@ sub verify_ref { { STDERR => 0 }); }; } -sub repo_path_split { - my $full_url = shift; - $full_url =~ s#/+$##; - - foreach (@repo_path_split_cache) { - if ($full_url =~ s#$_##) { - my $u = $1; - $full_url =~ s#^/+##; - return ($u, $full_url); - } - } - my $tmp = Git::SVN::Ra->new($full_url); - return ($tmp->{repos_root}, $tmp->{svn_path}); -} - -sub setup_git_svn { - defined $SVN_URL or croak "SVN repository location required\n"; - unless (-d $GIT_DIR) { - croak "GIT_DIR=$GIT_DIR does not exist!\n"; - } - mkpath([$GIT_SVN_DIR]); - mkpath(["$GIT_SVN_DIR/info"]); - open my $fh, '>>',$REVDB or croak $!; - close $fh; - s_to_file($SVN_URL,"$GIT_SVN_DIR/info/url"); - -} - sub get_tree_from_treeish { my ($treeish) = @_; # $treeish can be a symbolic ref, too: @@ -668,23 +602,6 @@ sub file_to_s { return $ret; } -sub check_upgrade_needed { - if (!-r $REVDB) { - -d $GIT_SVN_DIR or mkpath([$GIT_SVN_DIR]); - open my $fh, '>>',$REVDB or croak $!; - close $fh; - } - return unless eval { - command([qw/rev-parse --verify/,"$GIT_SVN-HEAD^0"], - {STDERR => 0}); - }; - my $head = eval { command('rev-parse',"refs/remotes/$GIT_SVN") }; - if ($@ || !$head) { - print STDERR "Please run: $0 rebuild --upgrade\n"; - exit 1; - } -} - # ' = real-name ' mapping based on git-svnimport: sub load_authors { open my $authors, '<', $_authors or die "Can't open $_authors $!\n"; @@ -702,75 +619,9 @@ sub load_authors { close $authors or croak $!; } -sub git_svn_each { - my $sub = shift; - foreach (command(qw/rev-parse --symbolic --all/)) { - next unless s#^refs/remotes/##; - chomp $_; - next unless -f "$GIT_DIR/svn/$_/info/url"; - &$sub($_); - } -} - -sub migrate_revdb { - git_svn_each(sub { - my $id = shift; - defined(my $pid = fork) or croak $!; - if (!$pid) { - $GIT_SVN = $ENV{GIT_SVN_ID} = $id; - init_vars(); - exit 0 if -r $REVDB; - print "Upgrading svn => git mapping...\n"; - -d $GIT_SVN_DIR or mkpath([$GIT_SVN_DIR]); - open my $fh, '>>',$REVDB or croak $!; - close $fh; - rebuild(); - print "Done upgrading. You may now delete the ", - "deprecated $GIT_SVN_DIR/revs directory\n"; - exit 0; - } - waitpid $pid, 0; - croak $? if $?; - }); -} - -sub migration_check { - migrate_revdb() unless (-e $REVDB); - return if (-d "$GIT_DIR/svn" || !-d $GIT_DIR); - print "Upgrading repository...\n"; - unless (-d "$GIT_DIR/svn") { - mkdir "$GIT_DIR/svn" or croak $!; - } - print "Data from a previous version of git-svn exists, but\n\t", - "$GIT_SVN_DIR\n\t(required for this version ", - "($VERSION) of git-svn) does not.\n"; - - foreach my $x (command(qw/rev-parse --symbolic --all/)) { - next unless $x =~ s#^refs/remotes/##; - chomp $x; - next unless -f "$GIT_DIR/$x/info/url"; - my $u = eval { file_to_s("$GIT_DIR/$x/info/url") }; - next unless $u; - my $dn = dirname("$GIT_DIR/svn/$x"); - mkpath([$dn]) unless -d $dn; - rename "$GIT_DIR/$x", "$GIT_DIR/svn/$x" or croak "$!: $x"; - } - migrate_revdb() if (-d $GIT_SVN_DIR && !-w $REVDB); - print "Done upgrading.\n"; -} - -sub init_vars { - $GIT_SVN ||= $ENV{GIT_SVN_ID} || 'git-svn'; - $Git::SVN::default = $GIT_SVN; - $GIT_SVN_DIR = "$GIT_DIR/svn/$GIT_SVN"; - $REVDB = "$GIT_SVN_DIR/.rev_db"; - $GIT_SVN_INDEX = "$GIT_SVN_DIR/index"; - $SVN_URL = undef; -} - # convert GetOpt::Long specs for use by git-config sub read_repo_config { - return unless -d $GIT_DIR; + return unless -d $ENV{GIT_DIR}; my $opts = shift; foreach my $o (keys %$opts) { my $v = $opts->{$o}; @@ -791,38 +642,6 @@ sub read_repo_config { } } -sub read_url_paths_all { - my ($l_map, $pfx, $p) = @_; - my @dir; - foreach (<$p/*>) { - if (-r "$_/info/url") { - $pfx .= '/' if $pfx && $pfx !~ m!/$!; - my $id = $pfx . basename $_; - my $url = file_to_s("$_/info/url"); - my ($u, $p) = repo_path_split($url); - $l_map->{$u}->{$p} = $id; - } elsif (-d $_) { - push @dir, $_; - } - } - foreach (@dir) { - my $x = $_; - $x =~ s!^\Q$GIT_DIR\E/svn/!!o; - read_url_paths_all($l_map, $x, $_); - } -} - -# this one only gets ids that have been imported, not new ones -sub read_url_paths { - my $l_map = {}; - git_svn_each(sub { my $x = shift; - my $url = file_to_s("$GIT_DIR/svn/$x/info/url"); - my ($u, $p) = repo_path_split($url); - $l_map->{$u}->{$p} = $x; - }); - return $l_map; -} - sub extract_metadata { my $id = shift or return (undef, undef, undef); my ($url, $rev, $uuid) = ($id =~ /^git-svn-id:\s(\S+?)\@(\d+) @@ -866,7 +685,7 @@ sub tz_to_s_offset { package Git::SVN; use strict; use warnings; -use vars qw/$default/; +use vars qw/$default_repo_id/; use Carp qw/croak/; use File::Path qw/mkpath/; use IPC::Open3; @@ -882,28 +701,76 @@ BEGIN { svn:entry:committed-date/; } +# we allow dashes, unlike remotes2config.sh +sub sanitize_remote_name { + my ($name) = @_; + $name =~ tr/A-Za-z0-9-/./c; + $name; +} + sub init { - my ($class, $id, $url) = @_; - my $self = _new($class, $id); - mkpath(["$self->{dir}/info"]); + my ($class, $url, $path, $repo_id, $ref_id) = @_; + my $self = _new($class, $repo_id, $ref_id, $path); + mkpath([$self->{dir}]); if (defined $url) { $url =~ s!/+$!!; # strip trailing slash - ::s_to_file($url, "$self->{dir}/info/url"); + my $orig_url = eval { + command_oneline('config', '--get', + "svn-remote.$repo_id.url") + }; + if ($orig_url) { + if ($orig_url ne $url) { + die "svn-remote.$repo_id.url already set: ", + "$orig_url\nwanted to set to: $url\n"; + } + } else { + command_noisy('config', + "svn-remote.$repo_id.url", $url); + } + command_noisy('config', '--add', + "svn-remote.$repo_id.fetch", + "$path:".$self->refname); } $self->{url} = $url; - open my $fh, '>>', $self->{db_path} or croak $!; - close $fh or croak $!; + unless (-f $self->{db_path}) { + open my $fh, '>>', $self->{db_path} or croak $!; + close $fh or croak $!; + } $self; } +sub find_ref { + my ($ref_id) = @_; + foreach (command(qw/config -l/)) { + next unless m!^svn-remote\.(.+)\.fetch= + \s*(.*)\s*:\s*refs/remotes/(.+)\s*$!x; + my ($repo_id, $path, $ref) = ($1, $2, $3); + if ($ref eq $ref_id) { + $path = '' if ($path =~ m#^\./?#); + return ($repo_id, $path); + } + } + (undef, undef, undef); +} + sub new { - my ($class, $id) = @_; - my $self = _new($class, $id); - $self->{url} = ::file_to_s("$self->{dir}/info/url"); + my ($class, $ref_id, $repo_id, $path) = @_; + if (defined $ref_id && !defined $repo_id && !defined $path) { + ($repo_id, $path) = find_ref($ref_id); + if (!defined $repo_id) { + die "Could not find a \"svn-remote.*.fetch\" key ", + "in the repository configuration matching: ", + "refs/remotes/$ref_id\n"; + } + } + my $self = _new($class, $repo_id, $ref_id, $path); + $self->{url} = command_oneline('config', '--get', + "svn-remote.$repo_id.url") or + die "Failed to read \"svn-remote.$repo_id.url\" in config\n"; $self; } -sub refname { "refs/remotes/$_[0]->{id}" } +sub refname { "refs/remotes/$_[0]->{ref_id}" } sub ra { my ($self) = shift; @@ -952,7 +819,7 @@ sub last_rev_commit { return ($self->{last_rev}, $self->{last_commit}); } my $c = ::verify_ref($self->refname.'^0'); - if (defined $c && length $c) { + if ($c) { my $rev = (::cmt_metadata($c))[1]; if (defined $rev) { ($self->{last_rev}, $self->{last_commit}) = ($rev, $c); @@ -1064,18 +931,9 @@ sub get_commit_parents { @ret; } -sub check_upgrade_needed { +sub full_url { my ($self) = @_; - if (!-r $self->{db_path}) { - -d $self->{dir} or mkpath([$self->{dir}]); - open my $fh, '>>', $self->{db_path} or croak $!; - close $fh; - } - return unless ::verify_ref($self->{id}.'-HEAD^0'); - my $head = ::verify_ref($self->refname.'^0'); - if ($@ || !$head) { - ::fatal("Please run: $0 rebuild --upgrade\n"); - } + $self->ra->{url} . (length $self->{path} ? '/' . $self->{path} : ''); } sub do_git_commit { @@ -1105,7 +963,7 @@ sub do_git_commit { defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec)) or croak $!; print $msg_fh $log_entry->{log} or croak $!; - print $msg_fh "\ngit-svn-id: ", $self->ra->{url}, '@', + print $msg_fh "\ngit-svn-id: ", $self->full_url, '@', $log_entry->{revision}, ' ', $self->ra->uuid, "\n" or croak $!; $msg_fh->flush == 0 or croak $!; @@ -1128,7 +986,7 @@ sub do_git_commit { } sub do_fetch { - my ($self, $paths, $rev) = @_; #, $author, $date, $log) = @_; + my ($self, $paths, $rev) = @_; my $ed = SVN::Git::Fetcher->new($self); my ($last_rev, @parents); if ($self->{last_commit}) { @@ -1138,7 +996,8 @@ sub do_fetch { } else { $last_rev = $rev; } - unless ($self->ra->gs_do_update($last_rev, $rev, '', 1, $ed)) { + unless ($self->ra->gs_do_update($last_rev, $rev, + $self->{path}, 1, $ed)) { die "SVN connection failed somewhere...\n"; } $self->make_log_entry($rev, \@parents, $ed); @@ -1361,11 +1220,19 @@ sub rev_db_get { } sub _new { - my ($class, $id) = @_; - $id ||= $Git::SVN::default; - my $dir = "$ENV{GIT_DIR}/svn/$id"; - bless { id => $id, dir => $dir, index => "$dir/index", - db_path => "$dir/.rev_db" }, $class; + my ($class, $repo_id, $ref_id, $path) = @_; + unless (defined $repo_id && length $repo_id) { + $repo_id = $Git::SVN::default_repo_id; + } + unless (defined $ref_id && length $ref_id) { + $_[2] = $ref_id = $repo_id; + } + $_[1] = $repo_id = sanitize_remote_name($repo_id); + my $dir = "$ENV{GIT_DIR}/svn/$ref_id"; + $_[3] = $path = '' unless (defined $path); + bless { ref_id => $ref_id, dir => $dir, index => "$dir/index", + path => $path, + db_path => "$dir/.rev_db", repo_id => $repo_id }, $class; } sub uri_encode { @@ -1630,6 +1497,9 @@ sub new { my $self = SVN::Delta::Editor->new; bless $self, $class; $self->{c} = $git_svn->{last_commit} if exists $git_svn->{last_commit}; + if (length $git_svn->{path}) { + $self->{path_strip} = qr/\Q$git_svn->{path}\E\/?/; + } $self->{empty} = {}; $self->{dir_prop} = {}; $self->{file_prop} = {}; @@ -1650,33 +1520,41 @@ sub open_directory { { path => $path }; } +sub git_path { + my ($self, $path) = @_; + $path =~ s!$self->{path_strip}!! if $self->{path_strip}; + $path; +} + sub delete_entry { my ($self, $path, $rev, $pb) = @_; my $gui = $self->{gui}; + my $gpath = $self->git_path($path); # remove entire directories. - if (command('ls-tree', $self->{c}, '--', $path) =~ /^040000 tree/) { + if (command('ls-tree', $self->{c}, '--', $gpath) =~ /^040000 tree/) { my ($ls, $ctx) = command_output_pipe(qw/ls-tree -r --name-only -z/, - $self->{c}, '--', $path); + $self->{c}, '--', $gpath); local $/ = "\0"; while (<$ls>) { print $gui '0 ',0 x 40,"\t",$_ or croak $!; print "\tD\t$_\n" unless $self->{q}; } - print "\tD\t$path/\n" unless $self->{q}; + print "\tD\t$gpath/\n" unless $self->{q}; command_close_pipe($ls, $ctx); $self->{empty}->{$path} = 0 } else { - print $gui '0 ',0 x 40,"\t",$path,"\0" or croak $!; - print "\tD\t$path\n" unless $self->{q}; + print $gui '0 ',0 x 40,"\t",$gpath,"\0" or croak $!; + print "\tD\t$gpath\n" unless $self->{q}; } undef; } sub open_file { my ($self, $path, $pb, $rev) = @_; - my ($mode, $blob) = (command('ls-tree', $self->{c}, '--',$path) + my $gpath = $self->git_path($path); + my ($mode, $blob) = (command('ls-tree', $self->{c}, '--', $gpath) =~ /^(\d{6}) blob ([a-f\d]{40})\t/); unless (defined $mode && defined $blob) { die "$path was not found in commit $self->{c} (r$rev)\n"; @@ -1775,7 +1653,7 @@ sub apply_textdelta { sub close_file { my ($self, $fb, $exp) = @_; my $hash; - my $path = $fb->{path}; + my $path = $self->git_path($fb->{path}); if (my $fh = $fb->{fh}) { seek($fh, 0, 0) or croak $!; my $md5 = Digest::MD5->new; @@ -2223,7 +2101,7 @@ sub gs_do_update { $editor, $pool); my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); my $new = ($rev_a == $rev_b); - $reporter->set_path($path, $rev_a, $new, @lock, $pool); + $reporter->set_path('', $rev_a, $new, @lock, $pool); $reporter->finish_report($pool); $pool->clear; $editor->{git_commit_ok}; @@ -2561,6 +2439,153 @@ out: print '-' x72,"\n" unless $incremental || $oneline; } +package Git::SVN::Migration; +# these version numbers do NOT correspond to actual version numbers +# of git nor git-svn. They are just relative. +# +# v0 layout: .git/$id/info/url, refs/heads/$id-HEAD +# +# v1 layout: .git/$id/info/url, refs/remotes/$id +# +# v2 layout: .git/svn/$id/info/url, refs/remotes/$id +# +# v3 layout: .git/svn/$id, refs/remotes/$id +# - info/url may remain for backwards compatibility +# - this is what we migrate up to this layout automatically, +# - this will be used by git svn init on single branches +# +# v4 layout: .git/svn/$repo_id/$id, refs/remotes/$repo_id/$id +# - this is only created for newly multi-init-ed +# repositories. Similar in spirit to the +# --use-separate-remotes option in git-clone (now default) +# - we do not automatically migrate to this (following +# the example set by core git) +use strict; +use warnings; +use Carp qw/croak/; +use File::Path qw/mkpath/; +use File::Basename qw/dirname/; + +sub migrate_from_v0 { + my $git_dir = $ENV{GIT_DIR}; + return undef unless -d $git_dir; + my ($fh, $ctx) = command_output_pipe(qw/rev-parse --symbolic --all/); + my $migrated = 0; + while (<$fh>) { + chomp; + my ($id, $orig_ref) = ($_, $_); + next unless $id =~ s#^refs/heads/(.+)-HEAD$#$1#; + next unless -f "$git_dir/$id/info/url"; + my $new_ref = "refs/remotes/$id"; + if (::verify_ref("$new_ref^0")) { + print STDERR "W: $orig_ref is probably an old ", + "branch used by an ancient version of ", + "git-svn.\n", + "However, $new_ref also exists.\n", + "We will not be able ", + "to use this branch until this ", + "ambiguity is resolved.\n"; + next; + } + print STDERR "Migrating from v0 layout...\n" if !$migrated; + print STDERR "Renaming ref: $orig_ref => $new_ref\n"; + command_noisy('update-ref', $new_ref, $orig_ref); + command_noisy('update-ref', '-d', $orig_ref, $orig_ref); + $migrated++; + } + command_close_pipe($fh, $ctx); + print STDERR "Done migrating from v0 layout...\n" if $migrated; + $migrated; +} + +sub migrate_from_v1 { + my $git_dir = $ENV{GIT_DIR}; + my $migrated = 0; + return $migrated unless -d $git_dir; + my $svn_dir = "$git_dir/svn"; + + # just in case somebody used 'svn' as their $id at some point... + return $migrated if -d $svn_dir && ! -f "$svn_dir/info/url"; + + print STDERR "Migrating from a git-svn v1 layout...\n"; + mkpath([$svn_dir]); + print STDERR "Data from a previous version of git-svn exists, but\n\t", + "$svn_dir\n\t(required for this version ", + "($::VERSION) of git-svn) does not. exist\n"; + my ($fh, $ctx) = command_output_pipe(qw/rev-parse --symbolic --all/); + while (<$fh>) { + my $x = $_; + next unless $x =~ s#^refs/remotes/##; + chomp $x; + next unless -f "$git_dir/$x/info/url"; + my $u = eval { ::file_to_s("$git_dir/$x/info/url") }; + next unless $u; + my $dn = dirname("$git_dir/svn/$x"); + mkpath([$dn]) unless -d $dn; + if ($x eq 'svn') { # they used 'svn' as GIT_SVN_ID: + mkpath(["$git_dir/svn/svn"]); + print STDERR " - $git_dir/$x/info => ", + "$git_dir/svn/$x/info\n"; + rename "$git_dir/$x/info", "$git_dir/svn/$x/info" or + croak "$!: $x"; + # don't worry too much about these, they probably + # don't exist with repos this old (save for index, + # and we can easily regenerate that) + foreach my $f (qw/unhandled.log index .rev_db/) { + rename "$git_dir/$x/$f", "$git_dir/svn/$x/$f"; + } + } else { + print STDERR " - $git_dir/$x => $git_dir/svn/$x\n"; + rename "$git_dir/$x", "$git_dir/svn/$x" or + croak "$!: $x"; + } + $migrated++; + } + command_close_pipe($fh, $ctx); + print STDERR "Done migrating from a git-svn v1 layout\n"; + $migrated; +} + +sub read_old_urls { + my ($l_map, $pfx, $path) = @_; + my @dir; + foreach (<$path/*>) { + if (-r "$_/info/url") { + $pfx .= '/' if $pfx && $pfx !~ m!/$!; + my $ref_id = $pfx . basename $_; + my $url = ::file_to_s("$_/info/url"); + $l_map->{$ref_id} = $url; + } elsif (-d $_) { + push @dir, $_; + } + } + foreach (@dir) { + my $x = $_; + $x =~ s!^\Q$ENV{GIT_DIR}\E/svn/!!o; + read_old_urls($l_map, $x, $_); + } +} + +sub migrate_from_v2 { + my @cfg = command(qw/config -l/); + return if grep /^svn-remote\..+\.url=/, @cfg; + my %l_map; + read_old_urls(\%l_map, '', "$ENV{GIT_DIR}/svn"); + my $migrated = 0; + + foreach my $ref_id (sort keys %l_map) { + Git::SVN->init($l_map{$ref_id}, $ref_id); + $migrated++; + } + $migrated; +} + +sub migration_check { + migrate_from_v0(); + migrate_from_v1(); + migrate_from_v2(); +} + __END__ Data structures: diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh new file mode 100755 index 0000000000..53318f1b1a --- /dev/null +++ b/t/t9107-git-svn-migrate.sh @@ -0,0 +1,63 @@ +#!/bin/sh +# Copyright (c) 2006 Eric Wong +test_description='git-svn metadata migrations from previous versions' +. ./lib-git-svn.sh + +test_expect_success 'setup old-looking metadata' " + cp $GIT_DIR/config $GIT_DIR/config-old-git-svn && + git-svn init $svnrepo && + git-svn fetch && + for i in trunk branches/a branches/b tags/0.1 tags/0.2 tags/0.3; do + mkdir -p \$i && echo hello >> \$i/README || exit 1; done && + git ls-files -o trunk branches tags | git update-index --add --stdin && + git commit -m 'test' && + git-svn dcommit && + mv $GIT_DIR/svn/* $GIT_DIR/ && + rmdir $GIT_DIR/svn && + git-update-ref refs/heads/git-svn-HEAD refs/remotes/git-svn && + git-update-ref refs/heads/svn-HEAD refs/remotes/git-svn && + git-update-ref -d refs/remotes/git-svn refs/remotes/git-svn + " + +head=`git rev-parse --verify refs/heads/git-svn-HEAD^0` +test_expect_success 'git-svn-HEAD is a real HEAD' "test -n '$head'" + +test_expect_success 'initialize old-style (v0) git-svn layout' " + mkdir -p $GIT_DIR/git-svn/info $GIT_DIR/svn/info && + echo $svnrepo > $GIT_DIR/git-svn/info/url && + echo $svnrepo > $GIT_DIR/svn/info/url && + git-svn migrate && + ! test -d $GIT_DIR/git-svn && + git-rev-parse --verify refs/remotes/git-svn^0 && + git-rev-parse --verify refs/remotes/svn^0 && + test \`git repo-config --get svn-remote.git-svn.url\` = '$svnrepo' && + test \`git repo-config --get svn-remote.git-svn.fetch\` = \ + ':refs/remotes/git-svn' + " + +test_expect_success 'initialize a multi-repository repo' " + git-svn multi-init $svnrepo -T trunk -t tags -b branches && + git-repo-config --get-all svn-remote.git-svn.fetch > fetch.out && + grep '^trunk:refs/remotes/trunk$' fetch.out && + grep '^branches/a:refs/remotes/a$' fetch.out && + grep '^branches/b:refs/remotes/b$' fetch.out && + grep '^tags/0\.1:refs/remotes/tags/0\.1$' fetch.out && + grep '^tags/0\.2:refs/remotes/tags/0\.2$' fetch.out && + grep '^tags/0\.3:refs/remotes/tags/0\.3$' fetch.out + " + +test_expect_success 'multi-fetch works on partial urls + paths' " + git-svn multi-fetch && + for i in trunk a b tags/0.1 tags/0.2 tags/0.3; do + git rev-parse --verify refs/remotes/\$i^0 >> refs.out || exit 1; + done && + test -z \"\`sort < refs.out | uniq -d\`\" && + for i in trunk a b tags/0.1 tags/0.2 tags/0.3; do + for j in trunk a b tags/0.1 tags/0.2 tags/0.3; do + if test \$j != \$i; then continue; fi + test -z \"\`git diff refs/remotes/\$i \ + refs/remotes/\$j\`\" ||exit 1; done; done + " + +test_done + -- cgit v1.3 From 47e39c55c91993b94824b7a317ebeb965aaeb45a Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 21 Jan 2007 04:27:09 -0800 Subject: git-svn: enable --minimize to simplify the config and connections --minimize will update the git-svn configuration to attempt to connect to the repository root (instead of directly to the path(s) we are tracking) in order to allow more efficient reuse of connections (for multi-fetch and follow-parent). Signed-off-by: Eric Wong --- git-svn.perl | 110 ++++++++++++++++++++++++++++++++++++++++++--- t/t9107-git-svn-migrate.sh | 25 +++++++++++ 2 files changed, 129 insertions(+), 6 deletions(-) (limited to 't') diff --git a/git-svn.perl b/git-svn.perl index 4084e0657b..15d65e21e1 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -119,8 +119,6 @@ my %cmd = ( \%fc_opts ], 'migrate' => [ sub { }, # no-op, we automatically run this anyways, - # we may add a flag to automatically optimize the - # configuration to avoid reconnects in the future 'Migrate configuration/metadata/layout from previous versions of git-svn', \%remote_opts ], @@ -158,6 +156,8 @@ my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd); read_repo_config(\%opts); my $rv = GetOptions(%opts, 'help|H|h' => \$_help, 'version|V' => \$_version, + 'minimize-connections' => + \$Git::SVN::Migration::_minimize, 'id|i=s' => \$Git::SVN::default_repo_id); exit 1 if (!$rv && $cmd ne 'log'); @@ -702,10 +702,22 @@ BEGIN { svn:entry:committed-date/; } -# we allow dashes, unlike remotes2config.sh +sub read_all_remotes { + my $r = {}; + foreach (grep { s/^svn-remote\.// } command(qw/repo-config -l/)) { + if (m!^(.+)\.fetch=\s*(.*)\s*:\s*refs/remotes/(.+)\s*$!) { + $r->{$1}->{fetch}->{$2} = $3; + } elsif (m!^(.+)\.url=\s*(.*)\s*$!) { + $r->{$1}->{url} = $2; + } + } + $r; +} + +# we allow more chars than remotes2config.sh... sub sanitize_remote_name { my ($name) = @_; - $name =~ tr/A-Za-z0-9-/./c; + $name =~ tr{A-Za-z0-9:,/+-}{.}c; $name; } @@ -2467,7 +2479,8 @@ use strict; use warnings; use Carp qw/croak/; use File::Path qw/mkpath/; -use File::Basename qw/dirname/; +use File::Basename qw/dirname basename/; +use vars qw/$_minimize/; sub migrate_from_v0 { my $git_dir = $ENV{GIT_DIR}; @@ -2577,16 +2590,101 @@ sub migrate_from_v2 { my $migrated = 0; foreach my $ref_id (sort keys %l_map) { - Git::SVN->init($l_map{$ref_id}, $ref_id); + Git::SVN->init($l_map{$ref_id}, '', $ref_id, $ref_id); $migrated++; } $migrated; } +sub minimize_connections { + my $r = Git::SVN::read_all_remotes(); + my $new_urls = {}; + my $root_repos = {}; + foreach my $repo_id (keys %$r) { + my $url = $r->{$repo_id}->{url} or next; + my $fetch = $r->{$repo_id}->{fetch} or next; + my $ra = Git::SVN::Ra->new($url); + + # skip existing cases where we already connect to the root + if (($ra->{url} eq $ra->{repos_root}) || + (Git::SVN::sanitize_remote_name($ra->{repos_root}) eq + $repo_id)) { + $root_repos->{$ra->{url}} = $repo_id; + next; + } + + my $root_ra = Git::SVN::Ra->new($ra->{repos_root}); + my $root_path = $ra->{url}; + $root_path =~ s#^\Q$ra->{repos_root}\E/*##; + foreach my $path (keys %$fetch) { + my $ref_id = $fetch->{$path}; + my $gs = Git::SVN->new($ref_id, $repo_id, $path); + + # make sure we can read when connecting to + # a higher level of a repository + my ($last_rev, undef) = $gs->last_rev_commit; + if (!defined $last_rev) { + $last_rev = eval { + $root_ra->get_latest_revnum; + }; + next if $@; + } + my $new = $root_path; + $new .= length $path ? "/$path" : ''; + eval { + $root_ra->get_log([$new], $last_rev, $last_rev, + 0, 0, 1, sub { }); + }; + next if $@; + $new_urls->{$ra->{repos_root}}->{$new} = + { ref_id => $ref_id, + old_repo_id => $repo_id, + old_path => $path }; + } + } + + my @emptied; + foreach my $url (keys %$new_urls) { + # see if we can re-use an existing [svn-remote "repo_id"] + # instead of creating a(n ugly) new section: + my $repo_id = $root_repos->{$url} || + Git::SVN::sanitize_remote_name($url); + + my $fetch = $new_urls->{$url}; + foreach my $path (keys %$fetch) { + my $x = $fetch->{$path}; + Git::SVN->init($url, $path, $repo_id, $x->{ref_id}); + my $pfx = "svn-remote.$x->{old_repo_id}"; + + my $old_fetch = quotemeta("$x->{old_path}:". + "refs/remotes/$x->{ref_id}"); + command_noisy(qw/repo-config --unset/, + "$pfx.fetch", '^'. $old_fetch . '$'); + delete $r->{$x->{old_repo_id}}-> + {fetch}->{$x->{old_path}}; + if (!keys %{$r->{$x->{old_repo_id}}->{fetch}}) { + command_noisy(qw/repo-config --unset/, + "$pfx.url"); + push @emptied, $x->{old_repo_id} + } + } + } + if (@emptied) { + my $file = $ENV{GIT_CONFIG} || $ENV{GIT_CONFIG_LOCAL} || + "$ENV{GIT_DIR}/config"; + print STDERR < $GIT_DIR/svn/\$ref/info/url ) || exit 1; + done && + git-svn migrate --minimize && + test -z \"\`git-repo-config -l |grep -v '^svn-remote\.git-svn\.'\`\" && + git-repo-config --get-all svn-remote.git-svn.fetch > fetch.out && + grep '^trunk:refs/remotes/trunk$' fetch.out && + grep '^branches/a:refs/remotes/a$' fetch.out && + grep '^branches/b:refs/remotes/b$' fetch.out && + grep '^tags/0\.1:refs/remotes/tags/0\.1$' fetch.out && + grep '^tags/0\.2:refs/remotes/tags/0\.2$' fetch.out && + grep '^tags/0\.3:refs/remotes/tags/0\.3$' fetch.out + grep '^:refs/remotes/git-svn' fetch.out + " + test_done -- cgit v1.3 From 15710b6f34da26d30079dbc83c797a8335040b75 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 22 Jan 2007 02:20:33 -0800 Subject: git-svn: fix --follow-parent to work with Git::SVN While we're at it, beef up the test because I was getting false-passes during development. Signed-off-by: Eric Wong --- git-svn.perl | 196 +++++++++++++++++++++------------------ t/t9104-git-svn-follow-parent.sh | 6 +- 2 files changed, 110 insertions(+), 92 deletions(-) (limited to 't') diff --git a/git-svn.perl b/git-svn.perl index 15d65e21e1..8d49959f37 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -790,6 +790,16 @@ sub ra { $self->{ra} ||= Git::SVN::Ra->new($self->{url}); } +sub rel_path { + my ($self) = @_; + my $repos_root = $self->ra->{repos_root}; + return $self->{path} if ($self->{url} eq $repos_root); + my $url = $self->{url} . + (length $self->{path} ? "/$self->{path}" : $self->{path}); + $url =~ s!^\Q$repos_root\E/*!!g; + $url; +} + sub copy_remote_ref { my ($self) = @_; my $origin = $::_cp_remote ? $::_cp_remote : 'origin'; @@ -998,16 +1008,97 @@ sub do_git_commit { return $commit; } +sub revisions_eq { + my ($self, $r0, $r1) = @_; + return 1 if $r0 == $r1; + my $nr = 0; + $self->ra->get_log([$self->{path}], $r0, $r1, + 0, 0, 1, sub { $nr++ }); + return 0 if ($nr > 1); + return 1; +} + +sub find_parent_branch { + my ($self, $paths, $rev) = @_; + + # look for a parent from another branch: + my $i = $paths->{'/'.$self->rel_path} or return; + my $branch_from = $i->copyfrom_path or return; + my $r = $i->copyfrom_rev; + my $repos_root = $self->ra->{repos_root}; + my $url = $self->ra->{url}; + my $new_url = $repos_root . $branch_from; + print STDERR "Found possible branch point: ", + "$new_url => ", $self->full_url, ", $r\n"; + $branch_from =~ s#^/##; + my $remotes = read_all_remotes(); + my $gs; + foreach my $repo_id (keys %$remotes) { + my $u = $remotes->{$repo_id}->{url} or next; + next if $url ne $u; + my $fetch = $remotes->{$repo_id}->{fetch}; + foreach my $f (keys %$fetch) { + next if $f ne $branch_from; + $gs = Git::SVN->new($fetch->{$f}, $repo_id, $f); + last; + } + last if $gs; + } + unless ($gs) { + my $ref_id = $branch_from; + $ref_id .= "\@$r" if find_ref($ref_id); + # just grow a tail if we're not unique enough :x + $ref_id .= '-' while find_ref($ref_id); + $gs = Git::SVN->init($new_url, '', $ref_id, $ref_id); + } + my ($r0, $parent) = $gs->find_rev_before($r, 1); + if ($::_follow_parent && (!defined $r0 || !defined $parent)) { + foreach (0 .. $r) { + my $log_entry = eval { $gs->do_fetch(undef, $_) }; + $gs->do_git_commit($log_entry) if $log_entry; + } + ($r0, $parent) = $gs->last_rev_commit; + } + if (defined $r0 && defined $parent && $gs->revisions_eq($r0, $r)) { + print STDERR "Found branch parent: ($self->{ref_id}) $parent\n"; + command_noisy('read-tree', $parent); + my $ed; + if ($self->ra->can_do_switch) { + # do_switch works with svn/trunk >= r22312, but that + # is not included with SVN 1.4.2 (the latest version + # at the moment), so we can't rely on it + $self->{last_commit} = $parent; + $ed = SVN::Git::Fetcher->new($self); + $gs->ra->gs_do_switch($r0, $rev, $gs->{path}, 1, + $self->full_url, $ed) + or die "SVN connection failed somewhere...\n"; + } else { + $ed = SVN::Git::Fetcher->new($self); + $self->ra->gs_do_update($rev, $rev, $self->{path}, + 1, $ed) + or die "SVN connection failed somewhere...\n"; + } + return $self->make_log_entry($rev, [$parent], $ed); + } + print STDERR "Branch parent not found...\n"; + return undef; +} + sub do_fetch { my ($self, $paths, $rev) = @_; - my $ed = SVN::Git::Fetcher->new($self); + my $ed; my ($last_rev, @parents); if ($self->{last_commit}) { + $ed = SVN::Git::Fetcher->new($self); $last_rev = $self->{last_rev}; $ed->{c} = $self->{last_commit}; @parents = ($self->{last_commit}); } else { $last_rev = $rev; + if (my $log_entry = $self->find_parent_branch($paths, $rev)) { + return $log_entry; + } + $ed = SVN::Git::Fetcher->new($self); } unless ($self->ra->gs_do_update($last_rev, $rev, $self->{path}, 1, $ed)) { @@ -1120,9 +1211,9 @@ sub fetch { my @revs; $self->ra->get_log([''], $min, $max, 0, 1, 1, sub { my ($paths, $rev, $author, $date, $log) = @_; - push @revs, $rev }); + push @revs, [ $paths, $rev ] }); foreach (@revs) { - my $log_entry = $self->do_fetch(undef, $_); + my $log_entry = $self->do_fetch(@$_); $self->do_git_commit($log_entry, @parents); } last if $max >= $head; @@ -1232,6 +1323,18 @@ sub rev_db_get { $ret; } +sub find_rev_before { + my ($self, $rev, $eq_ok) = @_; + --$rev unless $eq_ok; + while ($rev > 0) { + if (my $c = $self->rev_db_get($rev)) { + return ($rev, $c); + } + --$rev; + } + return (undef, undef); +} + sub _new { my ($class, $repo_id, $ref_id, $path) = @_; unless (defined $repo_id && length $repo_id) { @@ -1398,93 +1501,6 @@ sub uri_decode { $f } -sub revisions_eq { - my ($path, $r0, $r1) = @_; - return 1 if $r0 == $r1; - my $nr = 0; - # should be OK to use Pool here (r1 - r0) should be small - $SVN->get_log([$path], $r0, $r1, 0, 0, 1, sub {$nr++}); - return 0 if ($nr > 1); - return 1; -} - -sub libsvn_find_parent_branch { - my ($paths, $rev, $author, $date, $log) = @_; - my $svn_path = '/'.$SVN->{svn_path}; - - # look for a parent from another branch: - my $i = $paths->{$svn_path} or return; - my $branch_from = $i->copyfrom_path or return; - my $r = $i->copyfrom_rev; - print STDERR "Found possible branch point: ", - "$branch_from => $svn_path, $r\n"; - $branch_from =~ s#^/##; - my $l_map = {}; - read_url_paths_all($l_map, '', "$GIT_DIR/svn"); - my $url = $SVN->{repos_root}; - defined $l_map->{$url} or return; - my $id = $l_map->{$url}->{$branch_from}; - if (!defined $id && $_follow_parent) { - print STDERR "Following parent: $branch_from\@$r\n"; - # auto create a new branch and follow it - $id = basename($branch_from); - $id .= '@'.$r if -r "$GIT_DIR/svn/$id"; - while (-r "$GIT_DIR/svn/$id") { - # just grow a tail if we're not unique enough :x - $id .= '-'; - } - } - return unless defined $id; - - my ($r0, $parent) = find_rev_before($r,$id,1); - if ($_follow_parent && (!defined $r0 || !defined $parent)) { - defined(my $pid = fork) or croak $!; - if (!$pid) { - $GIT_SVN = $ENV{GIT_SVN_ID} = $id; - init_vars(); - $SVN_URL = "$url/$branch_from"; - $SVN = undef; - setup_git_svn(); - # we can't assume SVN_URL exists at r+1: - $_revision = "0:$r"; - fetch_lib(); - exit 0; - } - waitpid $pid, 0; - croak $? if $?; - ($r0, $parent) = find_rev_before($r,$id,1); - } - return unless (defined $r0 && defined $parent); - if (revisions_eq($branch_from, $r0, $r)) { - unlink $GIT_SVN_INDEX; - print STDERR "Found branch parent: ($GIT_SVN) $parent\n"; - command_noisy('read-tree', $parent); - unless ($SVN->can_do_switch) { - return _libsvn_new_tree($paths, $rev, $author, $date, - $log, [$parent]); - } - # do_switch works with svn/trunk >= r22312, but that is not - # included with SVN 1.4.2 (the latest version at the moment), - # so we can't rely on it. - my $ra = Git::SVN::Ra->new("$url/$branch_from"); - my $ed = SVN::Git::Fetcher->new({c => $parent, q => $_q }); - $ra->gs_do_switch($r0, $rev, '', 1, $SVN->{url}, $ed) or - die "SVN connection failed somewhere...\n"; - return libsvn_log_entry($rev, $author, $date, $log, [$parent]); - } - print STDERR "Nope, branch point not imported or unknown\n"; - return undef; -} - -sub _libsvn_new_tree { - my ($paths, $rev, $author, $date, $log, $parents) = @_; - my $ed = SVN::Git::Fetcher->new({q => $_q}); - unless ($SVN->gs_do_update($rev, $rev, '', 1, $ed)) { - die "SVN connection failed somewhere...\n"; - } - libsvn_log_entry($rev, $author, $date, $log, $parents, $ed); -} - { my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file. $SVN::Node::dir.$SVN::Node::unknown. diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 405b555368..91fdfe964e 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -30,8 +30,10 @@ test_expect_success 'initialize repo' " test_expect_success 'init and fetch --follow-parent a moved directory' " git-svn init -i thunk $svnrepo/thunk && git-svn fetch --follow-parent -i thunk && - git-rev-parse --verify refs/remotes/trunk && - test '$?' -eq '0' + test \"\`git-rev-parse --verify refs/remotes/trunk\`\" \ + = \"\`git-rev-parse --verify refs/remotes/thunk~1\`\" && + test \"\`git-cat-file blob refs/remotes/thunk:readme |\ + sed -n -e '3p'\`\" = goodbye " test_debug 'gitk --all &' -- cgit v1.3 From 8b8fc06824cde2b314807e5e3a20e0adfd948cda Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 22 Jan 2007 11:44:57 -0800 Subject: git-svn: --follow-parent works with svn-remotes multiple branches Bugs fixed: * We didn't allow manually (not using git-svn) init-ed remotes/fetch refspecs to be used before. It works now because that's what I did in this test. git-svn init should offer more control in the future. * correctly strip paths in the delta editor when using do_switch(). * Make the -i / GIT_SVN_ID option work correctly when doing fetch on a multi-ref svn-remote Signed-off-by: Eric Wong --- git-svn.perl | 49 ++++++++++++++++++++++++++-------------- t/t9104-git-svn-follow-parent.sh | 13 +++++++++++ 2 files changed, 45 insertions(+), 17 deletions(-) (limited to 't') diff --git a/git-svn.perl b/git-svn.perl index 8d49959f37..84f4679570 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -14,7 +14,8 @@ $AUTHOR = 'Eric Wong '; $VERSION = '@@GIT_VERSION@@'; $ENV{GIT_DIR} ||= '.git'; -$Git::SVN::default_repo_id = $ENV{GIT_SVN_ID} || 'git-svn'; +$Git::SVN::default_repo_id = 'git-svn'; +$Git::SVN::default_ref_id = $ENV{GIT_SVN_ID} || 'git-svn'; my $LC_ALL = $ENV{LC_ALL}; $Git::SVN::Log::TZ = $ENV{TZ}; @@ -158,7 +159,7 @@ my $rv = GetOptions(%opts, 'help|H|h' => \$_help, 'version|V' => \$_version, 'minimize-connections' => \$Git::SVN::Migration::_minimize, - 'id|i=s' => \$Git::SVN::default_repo_id); + 'id|i=s' => \$Git::SVN::default_ref_id); exit 1 if (!$rv && $cmd ne 'log'); usage(0) if $_help; @@ -686,7 +687,7 @@ sub tz_to_s_offset { package Git::SVN; use strict; use warnings; -use vars qw/$default_repo_id/; +use vars qw/$default_repo_id $default_ref_id/; use Carp qw/croak/; use File::Path qw/mkpath/; use IPC::Open3; @@ -704,7 +705,7 @@ BEGIN { sub read_all_remotes { my $r = {}; - foreach (grep { s/^svn-remote\.// } command(qw/repo-config -l/)) { + foreach (grep { s/^svn-remote\.// } command(qw/config -l/)) { if (m!^(.+)\.fetch=\s*(.*)\s*:\s*refs/remotes/(.+)\s*$!) { $r->{$1}->{fetch}->{$2} = $3; } elsif (m!^(.+)\.url=\s*(.*)\s*$!) { @@ -724,7 +725,6 @@ sub sanitize_remote_name { sub init { my ($class, $url, $path, $repo_id, $ref_id) = @_; my $self = _new($class, $repo_id, $ref_id, $path); - mkpath([$self->{dir}]); if (defined $url) { $url =~ s!/+$!!; # strip trailing slash my $orig_url = eval { @@ -745,10 +745,6 @@ sub init { "$path:".$self->refname); } $self->{url} = $url; - unless (-f $self->{db_path}) { - open my $fh, '>>', $self->{db_path} or croak $!; - close $fh or croak $!; - } $self; } @@ -777,6 +773,14 @@ sub new { } } my $self = _new($class, $repo_id, $ref_id, $path); + if (!defined $self->{path} || !length $self->{path}) { + my $fetch = command_oneline('config', '--get', + "svn-remote.$repo_id.fetch", + ":refs/remotes/$ref_id\$") or + die "Failed to read \"svn-remote.$repo_id.fetch\" ", + "\":refs/remotes/$ref_id\$\" in config\n"; + ($self->{path}, undef) = split(/\s*:\s*/, $fetch); + } $self->{url} = command_oneline('config', '--get', "svn-remote.$repo_id.url") or die "Failed to read \"svn-remote.$repo_id.url\" in config\n"; @@ -1064,6 +1068,7 @@ sub find_parent_branch { command_noisy('read-tree', $parent); my $ed; if ($self->ra->can_do_switch) { + print STDERR "Following parent with do_switch\n"; # do_switch works with svn/trunk >= r22312, but that # is not included with SVN 1.4.2 (the latest version # at the moment), so we can't rely on it @@ -1073,6 +1078,7 @@ sub find_parent_branch { $self->full_url, $ed) or die "SVN connection failed somewhere...\n"; } else { + print STDERR "Following parent with do_update\n"; $ed = SVN::Git::Fetcher->new($self); $self->ra->gs_do_update($rev, $rev, $self->{path}, 1, $ed) @@ -1209,7 +1215,7 @@ sub fetch { $SVN::Error::handler = \&skip_unknown_revs; while (1) { my @revs; - $self->ra->get_log([''], $min, $max, 0, 1, 1, sub { + $self->ra->get_log([$self->{path}], $min, $max, 0, 1, 1, sub { my ($paths, $rev, $author, $date, $log) = @_; push @revs, [ $paths, $rev ] }); foreach (@revs) { @@ -1341,11 +1347,16 @@ sub _new { $repo_id = $Git::SVN::default_repo_id; } unless (defined $ref_id && length $ref_id) { - $_[2] = $ref_id = $repo_id; + $_[2] = $ref_id = $Git::SVN::default_ref_id; } $_[1] = $repo_id = sanitize_remote_name($repo_id); my $dir = "$ENV{GIT_DIR}/svn/$ref_id"; $_[3] = $path = '' unless (defined $path); + mkpath([$dir]); + unless (-f "$dir/.rev_db") { + open my $fh, '>>', "$dir/.rev_db" or croak $!; + close $fh or croak $!; + } bless { ref_id => $ref_id, dir => $dir, index => "$dir/index", path => $path, db_path => "$dir/.rev_db", repo_id => $repo_id }, $class; @@ -1526,9 +1537,6 @@ sub new { my $self = SVN::Delta::Editor->new; bless $self, $class; $self->{c} = $git_svn->{last_commit} if exists $git_svn->{last_commit}; - if (length $git_svn->{path}) { - $self->{path_strip} = qr/\Q$git_svn->{path}\E\/?/; - } $self->{empty} = {}; $self->{dir_prop} = {}; $self->{file_prop} = {}; @@ -1540,6 +1548,11 @@ sub new { $self; } +sub set_path_strip { + my ($self, $path) = @_; + $self->{path_strip} = qr/^\Q$path\E\/?/; +} + sub open_root { { path => '' }; } @@ -2128,6 +2141,7 @@ sub uuid { sub gs_do_update { my ($self, $rev_a, $rev_b, $path, $recurse, $editor) = @_; my $pool = SVN::Pool->new; + $editor->set_path_strip($path); my $reporter = $self->do_update($rev_b, $path, $recurse, $editor, $pool); my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); @@ -2141,10 +2155,11 @@ sub gs_do_update { sub gs_do_switch { my ($self, $rev_a, $rev_b, $path, $recurse, $url_b, $editor) = @_; my $pool = SVN::Pool->new; + $editor->set_path_strip($path); my $reporter = $self->do_switch($rev_b, $path, $recurse, $url_b, $editor, $pool); my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); - $reporter->set_path($path, $rev_a, 0, @lock, $pool); + $reporter->set_path('', $rev_a, 0, @lock, $pool); $reporter->finish_report($pool); $pool->clear; $editor->{git_commit_ok}; @@ -2674,12 +2689,12 @@ sub minimize_connections { my $old_fetch = quotemeta("$x->{old_path}:". "refs/remotes/$x->{ref_id}"); - command_noisy(qw/repo-config --unset/, + command_noisy(qw/config --unset/, "$pfx.fetch", '^'. $old_fetch . '$'); delete $r->{$x->{old_repo_id}}-> {fetch}->{$x->{old_path}}; if (!keys %{$r->{$x->{old_repo_id}}->{fetch}}) { - command_noisy(qw/repo-config --unset/, + command_noisy(qw/config --unset/, "$pfx.url"); push @emptied, $x->{old_repo_id} } diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 91fdfe964e..3afec978d6 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -36,6 +36,19 @@ test_expect_success 'init and fetch --follow-parent a moved directory' " sed -n -e '3p'\`\" = goodbye " +test_expect_success 'init and fetch from one svn-remote' " + git-repo-config svn-remote.git-svn.url $svnrepo && + git-repo-config --add svn-remote.git-svn.fetch \ + trunk:refs/remotes/svn/trunk && + git-repo-config --add svn-remote.git-svn.fetch \ + thunk:refs/remotes/svn/thunk && + git-svn fetch --follow-parent -i svn/thunk && + test \"\`git-rev-parse --verify refs/remotes/svn/trunk\`\" \ + = \"\`git-rev-parse --verify refs/remotes/svn/thunk~1\`\" && + test \"\`git-cat-file blob refs/remotes/svn/thunk:readme |\ + sed -n -e '3p'\`\" = goodbye + " + test_debug 'gitk --all &' test_done -- cgit v1.3 From b805b44a923e32251af1abd4e8d7bf5f7d4d8ef6 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 22 Jan 2007 13:52:04 -0800 Subject: git-svn: disallow ambigious local refspecs Having multiple fetch refspecs pointing to the same local ref would be a very bad thing. Start avoiding the use of fatal() or exit() inside the modules so we can libify more easily. Signed-off-by: Eric Wong --- git-svn.perl | 40 +++++++++++++++++++++++++++++++++------- t/t9100-git-svn-basic.sh | 11 +++++++++++ 2 files changed, 44 insertions(+), 7 deletions(-) (limited to 't') diff --git a/git-svn.perl b/git-svn.perl index 84f4679570..f01fb9a35d 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -169,7 +169,11 @@ load_authors() if $_authors; unless ($cmd =~ /^(?:init|rebuild|multi-init|commit-diff)$/) { Git::SVN::Migration::migration_check(); } -$cmd{$cmd}->[0]->(@ARGV); +eval { + Git::SVN::verify_remotes_sanity(); + $cmd{$cmd}->[0]->(@ARGV); +}; +fatal $@ if $@; exit 0; ####################### primary functions ###################### @@ -715,6 +719,22 @@ sub read_all_remotes { $r; } +sub verify_remotes_sanity { + my %seen; + foreach (command(qw/config -l/)) { + if (m!^svn-remote\.(?:.+)\.fetch=.*:refs/remotes/(\S+)\s*$!) { + if ($seen{$1}) { + die "Remote ref refs/remote/$1 is tracked by", + "\n \"$_\"\nand\n \"$seen{$1}\"\n", + "Please resolve this ambiguity in ", + "your git configuration file before ", + "continuing\n"; + } + $seen{$1} = $_; + } + } +} + # we allow more chars than remotes2config.sh... sub sanitize_remote_name { my ($name) = @_; @@ -727,16 +747,22 @@ sub init { my $self = _new($class, $repo_id, $ref_id, $path); if (defined $url) { $url =~ s!/+$!!; # strip trailing slash + + # verify that we aren't overwriting anything: my $orig_url = eval { command_oneline('config', '--get', "svn-remote.$repo_id.url") }; - if ($orig_url) { - if ($orig_url ne $url) { - die "svn-remote.$repo_id.url already set: ", - "$orig_url\nwanted to set to: $url\n"; - } - } else { + if ($orig_url && ($orig_url ne $url)) { + die "svn-remote.$repo_id.url already set: ", + "$orig_url\nwanted to set to: $url\n"; + } + my ($xrepo_id, $xpath) = find_ref($self->refname); + if (defined $xpath) { + die "svn-remote.$xrepo_id.fetch already set to track ", + "$xpath:refs/remotes/", $self->refname, "\n"; + } + if (!$orig_url) { command_noisy('config', "svn-remote.$repo_id.url", $url); } diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index 040da92756..af617486dd 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh @@ -215,4 +215,15 @@ echo tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904 >> expected test_expect_success "$name" "diff -u a expected" +test_expect_failure 'exit if remote refs are ambigious' " + git-repo-config --add svn-remote.git-svn.fetch \ + bar:refs/remotes/git-svn && + git-svn migrate + " +test_expect_failure 'exit if init-ing a would clobber a URL' " + git-repo-config --unset svn-remote.git-svn.fetch \ + '^bar:refs/remotes/git-svn$' && + git-svn init $svnrepo/bar + " + test_done -- cgit v1.3 From a2003abc23a5961534e8a0cc70b881eb78d54328 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 22 Jan 2007 15:22:50 -0800 Subject: git-svn: allow --follow-parent on deleted directories Any operations on the index in Git::SVN that is not wrapped by tmp_index_do() is wrong. Signed-off-by: Eric Wong --- git-svn.perl | 2 +- t/t9104-git-svn-follow-parent.sh | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) (limited to 't') diff --git a/git-svn.perl b/git-svn.perl index f01fb9a35d..88c022701d 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1091,7 +1091,7 @@ sub find_parent_branch { } if (defined $r0 && defined $parent && $gs->revisions_eq($r0, $r)) { print STDERR "Found branch parent: ($self->{ref_id}) $parent\n"; - command_noisy('read-tree', $parent); + $self->assert_index_clean($parent); my $ed; if ($self->ra->can_do_switch) { print STDERR "Following parent with do_switch\n"; diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 3afec978d6..402b614c76 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -49,6 +49,18 @@ test_expect_success 'init and fetch from one svn-remote' " sed -n -e '3p'\`\" = goodbye " +test_expect_success 'follow deleted parent' " + svn cp -m 'resurrecting trunk as junk' \ + -r2 $svnrepo/trunk $svnrepo/junk && + git-repo-config --add svn-remote.git-svn.fetch \ + junk:refs/remotes/svn/junk && + git-svn fetch --follow-parent -i svn/thunk && + git-svn fetch -i svn/junk --follow-parent && + test -z \"\`git diff svn/junk svn/trunk\`\" && + test \"\`git merge-base svn/junk svn/trunk\`\" \ + = \"\`git rev-parse svn/trunk\`\" + " + test_debug 'gitk --all &' test_done -- cgit v1.3 From e6434f876097f196acbd9a806637d0f6076752fd Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 23 Jan 2007 16:29:23 -0800 Subject: git-svn: 'init' attempts to connect to the repository root if possible This allows connections to be used more efficiently and not require users to run 'git-svn migrate --minimize' for new repositories. Signed-off-by: Eric Wong --- git-svn.perl | 96 ++++++++++++++++++++++++++++++++++++++---------- t/t9100-git-svn-basic.sh | 14 ++++++- 2 files changed, 89 insertions(+), 21 deletions(-) (limited to 't') diff --git a/git-svn.perl b/git-svn.perl index de026b4e4c..d290a0d8ee 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -748,35 +748,78 @@ sub sanitize_remote_name { $name; } -sub init { - my ($class, $url, $path, $repo_id, $ref_id) = @_; - my $self = _new($class, $repo_id, $ref_id, $path); - if (defined $url) { - $url =~ s!/+$!!; # strip trailing slash +sub find_existing_remote { + my ($url, $remotes) = @_; + my $existing; + foreach my $repo_id (keys %$remotes) { + my $u = $remotes->{$repo_id}->{url} or next; + next if $u ne $url; + $existing = $repo_id; + last; + } + $existing; +} +sub init_remote_config { + my ($self, $url) = @_; + $url =~ s!/+$!!; # strip trailing slash + my $r = read_all_remotes(); + my $existing = find_existing_remote($url, $r); + if ($existing) { + print STDERR "Using existing ", + "[svn-remote \"$existing\"]\n"; + $self->{repo_id} = $existing; + } else { + my $min_url = Git::SVN::Ra->new($url)->minimize_url; + $existing = find_existing_remote($min_url, $r); + if ($existing) { + print STDERR "Using existing ", + "[svn-remote \"$existing\"]\n"; + $self->{repo_id} = $existing; + } + if ($min_url ne $url) { + print STDERR "Using higher level of URL: ", + "$url => $min_url\n"; + my $old_path = $self->{path}; + $self->{path} = $url; + $self->{path} =~ s!^\Q$min_url\E/*!!; + if (length $old_path) { + $self->{path} .= "/$old_path"; + } + $url = $min_url; + } + } + my $orig_url; + if (!$existing) { # verify that we aren't overwriting anything: - my $orig_url = eval { + $orig_url = eval { command_oneline('config', '--get', - "svn-remote.$repo_id.url") + "svn-remote.$self->{repo_id}.url") }; if ($orig_url && ($orig_url ne $url)) { - die "svn-remote.$repo_id.url already set: ", + die "svn-remote.$self->{repo_id}.url already set: ", "$orig_url\nwanted to set to: $url\n"; } - my ($xrepo_id, $xpath) = find_ref($self->refname); - if (defined $xpath) { - die "svn-remote.$xrepo_id.fetch already set to track ", - "$xpath:refs/remotes/", $self->refname, "\n"; - } - if (!$orig_url) { - command_noisy('config', - "svn-remote.$repo_id.url", $url); - } - command_noisy('config', '--add', - "svn-remote.$repo_id.fetch", - "$path:".$self->refname); } + my ($xrepo_id, $xpath) = find_ref($self->refname); + if (defined $xpath) { + die "svn-remote.$xrepo_id.fetch already set to track ", + "$xpath:refs/remotes/", $self->refname, "\n"; + } + command_noisy('config', + "svn-remote.$self->{repo_id}.url", $url); + command_noisy('config', '--add', + "svn-remote.$self->{repo_id}.fetch", + "$self->{path}:".$self->refname); $self->{url} = $url; +} + +sub init { + my ($class, $url, $path, $repo_id, $ref_id) = @_; + my $self = _new($class, $repo_id, $ref_id, $path); + if (defined $url) { + $self->init_remote_config($url); + } $self; } @@ -2208,6 +2251,19 @@ sub gs_do_switch { $editor->{git_commit_ok}; } +sub minimize_url { + my ($self) = @_; + return $self->{url} if ($self->{url} eq $self->{repos_root}); + my $url = $self->{repos_root}; + my @components = split(m!/!, $self->{svn_path}); + my $c = ''; + do { + $url .= "/$c" if length $c; + eval { (ref $self)->new($url)->get_latest_revnum }; + } while ($@ && ($c = shift @components)); + $url; +} + sub can_do_switch { my $self = shift; unless (defined $can_do_switch) { diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index af617486dd..97798c4d07 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh @@ -220,10 +220,22 @@ test_expect_failure 'exit if remote refs are ambigious' " bar:refs/remotes/git-svn && git-svn migrate " + test_expect_failure 'exit if init-ing a would clobber a URL' " + svnadmin create ${PWD}/svnrepo2 && + svn mkdir -m 'mkdir bar' ${svnrepo}2/bar && git-repo-config --unset svn-remote.git-svn.fetch \ '^bar:refs/remotes/git-svn$' && - git-svn init $svnrepo/bar + git-svn init ${svnrepo}2/bar + " + +test_expect_success \ + 'init allows us to connect to another directory in the same repo' " + git-svn init -i bar $svnrepo/bar && + git repo-config --get svn-remote.git-svn.fetch \ + '^bar:refs/remotes/bar$' && + git repo-config --get svn-remote.git-svn.fetch \ + '^:refs/remotes/git-svn$' " test_done -- cgit v1.3 From 7f578c55af80e9346135004bd47099cbb451f859 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 24 Jan 2007 02:16:25 -0800 Subject: git-svn: --follow-parent now works on sub-directories of larger branches This means that tracking the path of: /another-larger/trunk/thunk/bump/thud inside a repository would follow: /larger-parent/trunk/thunk/bump/thud even if the svn log output looks like this: -------------------------------------------- Changed paths: A /another-larger (from /larger-parent:5) -------------------------------------------- Note: the usage of get_log() in git-svn still makes a an assumption that shouldn't be made with regard to revisions existing for a particular path. Signed-off-by: Eric Wong --- git-svn.perl | 31 +++++++++++++++++++++++-------- t/t9104-git-svn-follow-parent.sh | 17 +++++++++++++++++ 2 files changed, 40 insertions(+), 8 deletions(-) (limited to 't') diff --git a/git-svn.perl b/git-svn.perl index d290a0d8ee..123d4d63f4 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1102,9 +1102,21 @@ sub find_parent_branch { return undef unless $::_follow_parent; # look for a parent from another branch: - my $abs_path = '/'.$self->rel_path; - my $i = $paths->{$abs_path} or goto not_found; + my @b_path_components = split m#/#, $self->rel_path; + my @a_path_components; + my $i; + while (@b_path_components) { + $i = $paths->{'/'.join('/', @b_path_components)}; + last if $i; + unshift(@a_path_components, pop(@b_path_components)); + } + goto not_found unless defined $i; my $branch_from = $i->copyfrom_path or goto not_found; + if (@a_path_components) { + print STDERR "branch_from: $branch_from => "; + $branch_from .= '/'.join('/', @a_path_components); + print STDERR $branch_from, "\n"; + } my $r = $i->copyfrom_rev; my $repos_root = $self->ra->{repos_root}; my $url = $self->ra->{url}; @@ -1134,10 +1146,11 @@ sub find_parent_branch { } my ($r0, $parent) = $gs->find_rev_before($r, 1); if ($::_follow_parent && (!defined $r0 || !defined $parent)) { - foreach (0 .. $r) { - my $log_entry = eval { $gs->do_fetch(undef, $_) }; + $gs->ra->get_log([$gs->{path}], 0, $r, 0, 1, 1, sub { + my ($paths, $rev) = @_; + my $log_entry = eval { $gs->do_fetch($paths, $rev) }; $gs->do_git_commit($log_entry) if $log_entry; - } + }); ($r0, $parent) = $gs->last_rev_commit; } if (defined $r0 && defined $parent && $gs->revisions_eq($r0, $r)) { @@ -1164,10 +1177,12 @@ sub find_parent_branch { return $self->make_log_entry($rev, [$parent], $ed); } not_found: - print STDERR "Branch parent for path: '$abs_path' not found\n"; + print STDERR "Branch parent for path: '/", + $self->rel_path, "' not found\n"; return undef unless $paths; - foreach my $p (sort keys %$paths) { - print STDERR ' ', $p->action, ' ', $p; + foreach my $x (sort keys %$paths) { + my $p = $paths->{$x}; + print STDERR ' ', $p->action, ' ', $x; if (my $cp_from = $p->copyfrom_path) { print STDERR "(from $cp_from:", $p->copyfrom_rev, ')'; } diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 402b614c76..22b45a6602 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -61,6 +61,23 @@ test_expect_success 'follow deleted parent' " = \"\`git rev-parse svn/trunk\`\" " +test_expect_success 'follow larger parent' " + mkdir -p import/trunk/thunk/bump/thud && + echo hi > import/trunk/thunk/bump/thud/file && + svn import -m 'import a larger parent' import $svnrepo/larger-parent && + svn cp -m 'hi' $svnrepo/larger-parent $svnrepo/another-larger && + git-svn init -i larger $svnrepo/another-larger/trunk/thunk/bump/thud && + git-svn fetch -i larger --follow-parent && + git-rev-parse --verify refs/remotes/larger && + git-rev-parse --verify \ + refs/remotes/larger-parent/trunk/thunk/bump/thud && + test \"\`git-merge-base \ + refs/remotes/larger-parent/trunk/thunk/bump/thud \ + refs/remotes/larger\`\" = \ + \"\`git-rev-parse refs/remotes/larger\`\" + true + " + test_debug 'gitk --all &' test_done -- cgit v1.3 From 1492b4245ad735e74f226bd796dc85de4f843739 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 25 Jan 2007 10:52:36 -0800 Subject: git-svn: add an odd test case that seems to cause segfaults over HTTP Signed-off-by: Eric Wong --- t/t9104-git-svn-follow-parent.sh | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 't') diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 22b45a6602..615c863b94 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -78,6 +78,21 @@ test_expect_success 'follow larger parent' " true " +# This seems to cause segfaults over HTTP... +test_expect_success 'follow higher-level parent' " + svn mkdir -m 'follow higher-level parent' $svnrepo/blob && + svn co $svnrepo/blob blob && + cd blob && + echo hi > hi && + svn add hi && + svn commit -m 'hi' && + cd .. + svn mkdir -m 'new glob at top level' $svnrepo/glob && + svn mv -m 'move blob down a level' $svnrepo/blob $svnrepo/glob/blob && + git-svn init -i blob $svnrepo/glob/blob && + git-svn fetch -i blob --follow-parent + " + test_debug 'gitk --all &' test_done -- cgit v1.3 From 97f6987afaae239f7e3ae3944e0b29343b43a894 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 25 Jan 2007 11:53:13 -0800 Subject: git-svn: avoid tracking change-less revisions They simply aren't interesting to track, and this will allow us to avoid get_log(). Since r0 is covered by this, we need to update the tests to not rely on r0 (which is always empty). Signed-off-by: Eric Wong --- git-svn.perl | 61 +++++++++++++++++++++++++--------------------- t/t9100-git-svn-basic.sh | 2 -- t/t9107-git-svn-migrate.sh | 14 +++++++---- 3 files changed, 42 insertions(+), 35 deletions(-) (limited to 't') diff --git a/git-svn.perl b/git-svn.perl index a19afb83fb..6ff3a8c5c5 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -315,7 +315,7 @@ sub cmd_set_tree { my $gs = Git::SVN->new; my ($r_last, $cmt_last) = $gs->last_rev_commit; $gs->fetch; - if ($r_last != $gs->{last_rev}) { + if (defined $gs->{last_rev} && $r_last != $gs->{last_rev}) { fatal "There are new revisions that were fetched ", "and need to be merged (or acknowledged) ", "before committing.\nlast rev: $r_last\n", @@ -1214,50 +1214,46 @@ sub do_fetch { $self->make_log_entry($rev, \@parents, $ed); } -sub write_untracked { - my ($self, $rev, $fh, $untracked) = @_; - my $h; - print $fh "r$rev\n" or croak $!; - $h = $untracked->{empty}; +sub get_untracked { + my ($self, $ed) = @_; + my @out; + my $h = $ed->{empty}; foreach (sort keys %$h) { my $act = $h->{$_} ? '+empty_dir' : '-empty_dir'; - print $fh " $act: ", uri_encode($_), "\n" or croak $!; + push @out, " $act: " . uri_encode($_); warn "W: $act: $_\n"; } foreach my $t (qw/dir_prop file_prop/) { - $h = $untracked->{$t} or next; + $h = $ed->{$t} or next; foreach my $path (sort keys %$h) { my $ppath = $path eq '' ? '.' : $path; foreach my $prop (sort keys %{$h->{$path}}) { next if $SKIP_PROP{$prop}; my $v = $h->{$path}->{$prop}; + my $t_ppath_prop = "$t: " . + uri_encode($ppath) . ' ' . + uri_encode($prop); if (defined $v) { - print $fh " +$t: ", - uri_encode($ppath), ' ', - uri_encode($prop), ' ', - uri_encode($v), "\n" - or croak $!; + push @out, " +$t_ppath_prop " . + uri_encode($v); } else { - print $fh " -$t: ", - uri_encode($ppath), ' ', - uri_encode($prop), "\n" - or croak $!; + push @out, " -$t_ppath_prop"; } } } } foreach my $t (qw/absent_file absent_directory/) { - $h = $untracked->{$t} or next; + $h = $ed->{$t} or next; foreach my $parent (sort keys %$h) { foreach my $path (sort @{$h->{$parent}}) { - print $fh " $t: ", - uri_encode("$parent/$path"), "\n" - or croak $!; + push @out, " $t: " . + uri_encode("$parent/$path"); warn "W: $t: $parent/$path ", "Insufficient permissions?\n"; } } } + \@out; } sub parse_svn_date { @@ -1280,12 +1276,15 @@ sub check_author { } sub make_log_entry { - my ($self, $rev, $parents, $untracked) = @_; - my $rp = $self->ra->rev_proplist($rev); - my %log_entry = ( parents => $parents || [], revision => $rev, - revprops => $rp, log => ''); + my ($self, $rev, $parents, $ed) = @_; + my $untracked = $self->get_untracked($ed); + open my $un, '>>', "$self->{dir}/unhandled.log" or croak $!; - $self->write_untracked($rev, $un, $untracked); + print $un "r$rev\n" or croak $!; + print $un $_, "\n" foreach @$untracked; + my %log_entry = ( parents => $parents || [], revision => $rev, + log => ''); + my $rp = $self->ra->rev_proplist($rev); foreach (sort keys %$rp) { my $v = $rp->{$_}; if (/^svn:(author|date|log)$/) { @@ -1296,6 +1295,11 @@ sub make_log_entry { } } close $un or croak $!; + + delete $rp->{'svn:date'}; # this is the only revprop for r0 + return undef if ($ed->{nr} == 0 && scalar @$untracked == 0 && + scalar keys %$rp == 0); + $log_entry{date} = parse_svn_date($log_entry{date}); $log_entry{author} = check_author($log_entry{author}); $log_entry{log} .= "\n"; @@ -1320,8 +1324,9 @@ sub fetch { my ($paths, $rev, $author, $date, $log) = @_; push @revs, [ $paths, $rev ] }); foreach (@revs) { - my $log_entry = $self->do_fetch(@$_); - $self->do_git_commit($log_entry, @parents); + if (my $log_entry = $self->do_fetch(@$_)) { + $self->do_git_commit($log_entry, @parents); + } } last if $max >= $head; $min = $max + 1; diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index 97798c4d07..5355243b92 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh @@ -211,8 +211,6 @@ tree d667270a1f7b109f5eb3aaea21ede14b56bfdd6e tree 8f51f74cf0163afc9ad68a4b1537288c4558b5a4 EOF -echo tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904 >> expected - test_expect_success "$name" "diff -u a expected" test_expect_failure 'exit if remote refs are ambigious' " diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh index 74a45ec647..f6d84ba7a5 100755 --- a/t/t9107-git-svn-migrate.sh +++ b/t/t9107-git-svn-migrate.sh @@ -5,13 +5,17 @@ test_description='git-svn metadata migrations from previous versions' test_expect_success 'setup old-looking metadata' " cp $GIT_DIR/config $GIT_DIR/config-old-git-svn && + mkdir import && + cd import + for i in trunk branches/a branches/b \ + tags/0.1 tags/0.2 tags/0.3; do + mkdir -p \$i && \ + echo hello >> \$i/README || exit 1 + done && \ + svn import -m test . $svnrepo + cd .. && git-svn init $svnrepo && git-svn fetch && - for i in trunk branches/a branches/b tags/0.1 tags/0.2 tags/0.3; do - mkdir -p \$i && echo hello >> \$i/README || exit 1; done && - git ls-files -o trunk branches tags | git update-index --add --stdin && - git commit -m 'test' && - git-svn dcommit && mv $GIT_DIR/svn/* $GIT_DIR/ && rmdir $GIT_DIR/svn && git-update-ref refs/heads/git-svn-HEAD refs/remotes/git-svn && -- cgit v1.3 From e5a0b240fc237af6165b728ae9c79288ef624d3b Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 25 Jan 2007 15:44:54 -0800 Subject: git-svn: correctly track revisions made to deleted branches git-svn has never been able to handle deleted branches very well because svn_ra_get_log() is all-or-nothing, meaning that if the max revision passed to it does not contain the path we're tracking, we miss all the revisions in the repository. Branches fetched using --follow-parent still do this sub-optimally (will be fixed soon). --follow-parent will soon become the default, so we will assume that when using get_log(); We will also avoid tracking revprops for revisions with no path-related changes since otherwise we just end up pulling logs to paths we don't care about. Also added a test for this to t9104-git-svn-follow-parent.sh and correctly commit the log message in the preceeding test (which conflicted with a filename). Signed-off-by: Eric Wong --- git-svn.perl | 46 +++++++++++++++++++++------------------- t/t9104-git-svn-follow-parent.sh | 11 +++++++++- 2 files changed, 34 insertions(+), 23 deletions(-) (limited to 't') diff --git a/git-svn.perl b/git-svn.perl index 6ff3a8c5c5..0e2348af35 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1100,6 +1100,11 @@ sub revisions_eq { sub find_parent_branch { my ($self, $paths, $rev) = @_; return undef unless $::_follow_parent; + unless (defined $paths) { + $self->ra->get_log([''], $rev, $rev, 0, 1, 1, + sub { $paths = $_[0] }); + } + return undef unless defined $paths; # look for a parent from another branch: my @b_path_components = split m#/#, $self->rel_path; @@ -1146,11 +1151,11 @@ sub find_parent_branch { } my ($r0, $parent) = $gs->find_rev_before($r, 1); if ($::_follow_parent && (!defined $r0 || !defined $parent)) { - $gs->ra->get_log([$gs->{path}], 0, $r, 0, 1, 1, sub { - my ($paths, $rev) = @_; - my $log_entry = eval { $gs->do_fetch($paths, $rev) }; - $gs->do_git_commit($log_entry) if $log_entry; - }); + foreach (1 .. $r) { + if (my $log_entry = $gs->do_fetch(undef, $_)) { + $gs->do_git_commit($log_entry); + } + } ($r0, $parent) = $gs->last_rev_commit; } if (defined $r0 && defined $parent && $gs->revisions_eq($r0, $r)) { @@ -1178,16 +1183,8 @@ sub find_parent_branch { } not_found: print STDERR "Branch parent for path: '/", - $self->rel_path, "' not found\n"; - return undef unless $paths; - foreach my $x (sort keys %$paths) { - my $p = $paths->{$x}; - print STDERR ' ', $p->action, ' ', $x; - if (my $cp_from = $p->copyfrom_path) { - print STDERR "(from $cp_from:", $p->copyfrom_rev, ')'; - } - print STDERR "\n"; - } + $self->rel_path, "' @ $rev not found\n"; + print STDERR ' ', $_, "\n" foreach (sort keys %$paths); return undef; } @@ -1279,6 +1276,8 @@ sub make_log_entry { my ($self, $rev, $parents, $ed) = @_; my $untracked = $self->get_untracked($ed); + return undef if ($ed->{nr} == 0 && scalar @$untracked == 0); + open my $un, '>>', "$self->{dir}/unhandled.log" or croak $!; print $un "r$rev\n" or croak $!; print $un $_, "\n" foreach @$untracked; @@ -1296,10 +1295,6 @@ sub make_log_entry { } close $un or croak $!; - delete $rp->{'svn:date'}; # this is the only revprop for r0 - return undef if ($ed->{nr} == 0 && scalar @$untracked == 0 && - scalar keys %$rp == 0); - $log_entry{date} = parse_svn_date($log_entry{date}); $log_entry{author} = check_author($log_entry{author}); $log_entry{log} .= "\n"; @@ -1317,18 +1312,26 @@ sub fetch { my $inc = 1000; my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc); my $err_handler = $SVN::Error::handler; - $SVN::Error::handler = \&skip_unknown_revs; + my $err; + $SVN::Error::handler = sub { ($err) = @_; skip_unknown_revs($err); } ; while (1) { my @revs; $self->ra->get_log([$self->{path}], $min, $max, 0, 1, 1, sub { my ($paths, $rev, $author, $date, $log) = @_; push @revs, [ $paths, $rev ] }); + if (! @revs && $err) { + print STDERR "Branch probably deleted:\n ", + $err->expanded_message, + "\nWill attempt to follow revisions ", + "committed before the deletion\n"; + @revs = map { [ undef, $_ ] } ($min .. $max); + } foreach (@revs) { if (my $log_entry = $self->do_fetch(@$_)) { $self->do_git_commit($log_entry, @parents); } } - last if $max >= $head; + last if $max >= $head || $err; $min = $max + 1; $max += $inc; $max = $head if ($max > $head); @@ -2226,7 +2229,6 @@ sub dup { sub get_log { my ($self, @args) = @_; my $pool = SVN::Pool->new; - $args[4]-- if $args[4] && ! $::_follow_parent; splice(@args, 3, 1) if ($SVN::Core::VERSION le '1.2.0'); my $ret = $self->SUPER::get_log(@args, $pool); $pool->clear; diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 615c863b94..a6ba0faebd 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -85,7 +85,7 @@ test_expect_success 'follow higher-level parent' " cd blob && echo hi > hi && svn add hi && - svn commit -m 'hi' && + svn commit -m 'hihi' && cd .. svn mkdir -m 'new glob at top level' $svnrepo/glob && svn mv -m 'move blob down a level' $svnrepo/blob $svnrepo/glob/blob && @@ -93,6 +93,15 @@ test_expect_success 'follow higher-level parent' " git-svn fetch -i blob --follow-parent " +test_expect_success 'follow deleted directory' " + svn mv -m 'bye!' $svnrepo/glob/blob/hi $svnrepo/glob/blob/bye&& + svn rm -m 'remove glob' $svnrepo/glob && + git-svn init -i glob $svnrepo/glob && + git-svn fetch -i glob && + test \"\`git cat-file blob refs/remotes/glob~1:blob/bye\`\" = hi && + test -z \"\`git ls-tree -z refs/remotes/glob\`\" + " + test_debug 'gitk --all &' test_done -- cgit v1.3 From 3ebe8df7f690281c21e330eec156098c14f4e685 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 25 Jan 2007 17:35:40 -0800 Subject: git-svn: fix segfaults from accessing svn_log_changed_path_t svn_log_changed_path_t structs were being used out of scope outside of svn_ra_get_log (because I wanted to eventually be able to use git-svn with only a single connection to the repository). So now we dup them into a hash. This was fixed while making --follow-parent fetches more efficient. I've moved parsing of the command-line --revision argument outside of the Git::SVN module so Git::SVN::fetch() can be used in more places (such as find_parent_branch). Signed-off-by: Eric Wong --- git-svn.perl | 104 +++++++++++++++++++++++++-------------- t/t9104-git-svn-follow-parent.sh | 1 - 2 files changed, 68 insertions(+), 37 deletions(-) (limited to 't') diff --git a/git-svn.perl b/git-svn.perl index 0e2348af35..4c9ef7fe15 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -283,7 +283,7 @@ sub cmd_fetch { instead.\n"; } my $gs = Git::SVN->new; - $gs->fetch; + $gs->fetch(parse_revision_argument()); if ($gs->{last_commit} && !verify_ref('refs/heads/master^0')) { command_noisy(qw(update-ref refs/heads/master), $gs->{last_commit}); @@ -482,6 +482,18 @@ sub cmd_commit_diff { ########################### utility functions ######################### +sub parse_revision_argument { + if (!defined $_revision || $_revision eq 'BASE:HEAD') { + return (undef, undef); + } + return ($1, $2) if ($_revision =~ /^(\d+):(\d+)$/); + return ($_revision, $_revision) if ($_revision =~ /^\d+$/); + return (undef, $1) if ($_revision =~ /^BASE:(\d+)$/); + return ($1, undef) if ($_revision =~ /^(\d+):HEAD$/); + die "revision argument: $_revision not understood by git-svn\n", + "Try using the command-line svn client instead\n"; +} + sub complete_svn_url { my ($url, $path) = @_; $path =~ s#/+$##; @@ -914,6 +926,9 @@ sub traverse_ignore { } } +sub last_rev { ($_[0]->last_rev_commit)[0] } +sub last_commit { ($_[0]->last_rev_commit)[1] } + # returns the newest SVN revision number and newest commit SHA1 sub last_rev_commit { my ($self) = @_; @@ -951,22 +966,11 @@ sub last_rev_commit { return ($rev, $c); } -sub parse_revision { - my ($self, $base) = @_; - my $head = $self->ra->get_latest_revnum; - if (!defined $::_revision || $::_revision eq 'BASE:HEAD') { - return ($base + 1, $head) if (defined $base); - return (0, $head); - } - return ($1, $2) if ($::_revision =~ /^(\d+):(\d+)$/); - return ($::_revision, $::_revision) if ($::_revision =~ /^\d+$/); - if ($::_revision =~ /^BASE:(\d+)$/) { - return ($base + 1, $1) if (defined $base); - return (0, $head); - } - return ($1, $head) if ($::_revision =~ /^(\d+):HEAD$/); - die "revision argument: $::_revision not understood by git-svn\n", - "Try using the command-line svn client instead\n"; +sub get_fetch_range { + my ($self, $min, $max) = @_; + $max ||= $self->ra->get_latest_revnum; + $min ||= $self->last_rev || 0; + (++$min, $max); } sub tmp_index_do { @@ -1101,8 +1105,8 @@ sub find_parent_branch { my ($self, $paths, $rev) = @_; return undef unless $::_follow_parent; unless (defined $paths) { - $self->ra->get_log([''], $rev, $rev, 0, 1, 1, - sub { $paths = $_[0] }); + $self->ra->get_log([$self->{path}], $rev, $rev, 0, 1, 1, + sub { $paths = dup_changed_paths($_[0]) }); } return undef unless defined $paths; @@ -1116,13 +1120,13 @@ sub find_parent_branch { unshift(@a_path_components, pop(@b_path_components)); } goto not_found unless defined $i; - my $branch_from = $i->copyfrom_path or goto not_found; + my $branch_from = $i->{copyfrom_path} or goto not_found; if (@a_path_components) { print STDERR "branch_from: $branch_from => "; $branch_from .= '/'.join('/', @a_path_components); print STDERR $branch_from, "\n"; } - my $r = $i->copyfrom_rev; + my $r = $i->{copyfrom_rev}; my $repos_root = $self->ra->{repos_root}; my $url = $self->ra->{url}; my $new_url = $repos_root . $branch_from; @@ -1151,11 +1155,7 @@ sub find_parent_branch { } my ($r0, $parent) = $gs->find_rev_before($r, 1); if ($::_follow_parent && (!defined $r0 || !defined $parent)) { - foreach (1 .. $r) { - if (my $log_entry = $gs->do_fetch(undef, $_)) { - $gs->do_git_commit($log_entry); - } - } + $gs->fetch(0, $r); ($r0, $parent) = $gs->last_rev_commit; } if (defined $r0 && defined $parent && $gs->revisions_eq($r0, $r)) { @@ -1183,8 +1183,19 @@ sub find_parent_branch { } not_found: print STDERR "Branch parent for path: '/", - $self->rel_path, "' @ $rev not found\n"; - print STDERR ' ', $_, "\n" foreach (sort keys %$paths); + $self->rel_path, "' @ r$rev not found:\n"; + return undef unless $paths; + print STDERR "Changed paths:\n"; + foreach my $x (sort keys %$paths) { + my $p = $paths->{$x}; + print STDERR "\t$p->{action}\t$x"; + if ($p->{copyfrom_path}) { + print STDERR "(from $p->{copyfrom_path}: ", + "$p->{copyfrom_rev})"; + } + print STDERR "\n"; + } + print STDERR '-'x72, "\n"; return undef; } @@ -1302,9 +1313,9 @@ sub make_log_entry { } sub fetch { - my ($self, @parents) = @_; + my ($self, $min_rev, $max_rev, @parents) = @_; my ($last_rev, $last_commit) = $self->last_rev_commit; - my ($base, $head) = $self->parse_revision($last_rev); + my ($base, $head) = $self->get_fetch_range($min_rev, $max_rev); return if ($base > $head); if (defined $last_commit) { $self->assert_index_clean($last_commit); @@ -1316,13 +1327,16 @@ sub fetch { $SVN::Error::handler = sub { ($err) = @_; skip_unknown_revs($err); } ; while (1) { my @revs; - $self->ra->get_log([$self->{path}], $min, $max, 0, 1, 1, sub { - my ($paths, $rev, $author, $date, $log) = @_; - push @revs, [ $paths, $rev ] }); - if (! @revs && $err) { + $self->ra->get_log([$self->{path}], $min, $max, 0, 1, 1, + sub { + my ($paths, $rev) = @_; + push @revs, [ dup_changed_paths($paths), $rev ]; + }); + if (! @revs && $err && $max >= $head) { print STDERR "Branch probably deleted:\n ", $err->expanded_message, "\nWill attempt to follow revisions ", + "r$min .. r$max", "committed before the deletion\n"; @revs = map { [ undef, $_ ] } ($min .. $max); } @@ -1331,7 +1345,7 @@ sub fetch { $self->do_git_commit($log_entry, @parents); } } - last if $max >= $head || $err; + last if $max >= $head; $min = $max + 1; $max += $inc; $max = $head if ($max > $head); @@ -1347,7 +1361,7 @@ sub set_tree_cb { $log_entry->{author} = $author; $self->do_git_commit($log_entry, "$rev=$tree"); } else { - $self->fetch("$rev=$tree"); + $self->fetch(undef, undef, "$rev=$tree"); } } @@ -1393,6 +1407,24 @@ sub skip_unknown_revs { croak "Error from SVN, ($errno): ", $err->expanded_message,"\n"; } +# svn_log_changed_path_t objects passed to get_log are likely to be +# overwritten even if only the refs are copied to an external variable, +# so we should dup the structures in their entirety. Using an externally +# passed pool (instead of our temporary and quickly cleared pool in +# Git::SVN::Ra) does not help matters at all... +sub dup_changed_paths { + my ($paths) = @_; + return undef unless $paths; + my %ret; + foreach my $p (keys %$paths) { + my $i = $paths->{$p}; + my %s = map { $_ => $i->$_ } + qw/copyfrom_path copyfrom_rev action/; + $ret{$p} = \%s; + } + \%ret; +} + # rev_db: # Tie::File seems to be prone to offset errors if revisions get sparse, # it's not that fast, either. Tie::File is also not in Perl 5.6. So diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index a6ba0faebd..bfb718886f 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -78,7 +78,6 @@ test_expect_success 'follow larger parent' " true " -# This seems to cause segfaults over HTTP... test_expect_success 'follow higher-level parent' " svn mkdir -m 'follow higher-level parent' $svnrepo/blob && svn co $svnrepo/blob blob && -- cgit v1.3 From d3a840dc74d2098c31aac1b89093d847e1d33dd8 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 26 Jan 2007 01:32:45 -0800 Subject: git-svn: fix committing to subdirectories, add tests I broke this part with the URL minimization; since git-svn will now try to connect to the root of the repository and will end up writing files there if it can... Signed-off-by: Eric Wong --- git-svn.perl | 14 ++++++++++---- t/t9100-git-svn-basic.sh | 29 +++++++++++++++++++++++++++++ t/t9105-git-svn-commit-diff.sh | 9 +++++++++ 3 files changed, 48 insertions(+), 4 deletions(-) (limited to 't') diff --git a/git-svn.perl b/git-svn.perl index 4c9ef7fe15..1d448e75da 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -354,7 +354,7 @@ sub cmd_dcommit { my $pool = SVN::Pool->new; my %ed_opts = ( r => $last_rev, ra => $ra->dup, - svn_path => $ra->{svn_path} ); + svn_path => $gs->{path} ); my $ed = SVN::Git::Editor->new(\%ed_opts, $ra->get_commit_editor($log, sub { print "Committed r$_[0]\n"; @@ -437,6 +437,7 @@ sub cmd_commit_diff { my $usage = "Usage: $0 commit-diff -r ". " []\n"; fatal($usage) if (!defined $ta || !defined $tb); + my $svn_path; if (!defined $url) { my $gs = eval { Git::SVN->new }; if (!$gs) { @@ -444,6 +445,7 @@ sub cmd_commit_diff { "the command-line\n", $usage); } $url = $gs->{url}; + $svn_path = $gs->{path}; } unless (defined $_revision) { fatal("-r|--revision is a required argument\n", $usage); @@ -459,6 +461,7 @@ sub cmd_commit_diff { $_message ||= get_commit_entry($tb)->{log}; } my $ra ||= Git::SVN::Ra->new($url); + $svn_path ||= $ra->{svn_path}; my $r = $_revision; if ($r eq 'HEAD') { $r = $ra->get_latest_revnum; @@ -468,7 +471,7 @@ sub cmd_commit_diff { my $pool = SVN::Pool->new; my %ed_opts = ( r => $r, ra => $ra->dup, - svn_path => $ra->{svn_path} ); + svn_path => $svn_path ); my $ed = SVN::Git::Editor->new(\%ed_opts, $ra->get_commit_editor($_message, sub { print "Committed r$_[0]\n" }), @@ -1374,7 +1377,7 @@ sub set_tree { my $pool = SVN::Pool->new; my $ed = SVN::Git::Editor->new({ r => $self->{last_rev}, ra => $self->ra->dup, - svn_path => $self->ra->{svn_path} + svn_path => $self->{path} }, $self->ra->get_commit_editor( $log_entry->{log}, sub { @@ -1902,6 +1905,8 @@ sub new { $self->{pool} = SVN::Pool->new; $self->{bat} = { '' => $self->open_root($self->{r}, $self->{pool}) }; $self->{rm} = { }; + $self->{path_prefix} = length $self->{svn_path} ? + "$self->{svn_path}/" : ''; require Digest::MD5; return $self; } @@ -1911,7 +1916,8 @@ sub split_path { } sub repo_path { - (defined $_[1] && length $_[1]) ? $_[1] : '' + my ($self, $path) = @_; + $self->{path_prefix}.(defined $path ? $path : ''); } sub url_path { diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index 5355243b92..3dc4de2fad 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh @@ -236,4 +236,33 @@ test_expect_success \ '^:refs/remotes/git-svn$' " +test_expect_success 'able to dcommit to a subdirectory' " + git-svn fetch -i bar && + git checkout -b my-bar refs/remotes/bar && + echo abc > d && + git update-index --add d && + git commit -m '/bar/d should be in the log' && + git-svn dcommit -i bar && + test -z \"\`git diff refs/heads/my-bar refs/remotes/bar\`\" && + mkdir newdir && + echo new > newdir/dir && + git update-index --add newdir/dir && + git commit -m 'add a new directory' && + git-svn dcommit -i bar && + test -z \"\`git diff refs/heads/my-bar refs/remotes/bar\`\" && + echo foo >> newdir/dir && + git update-index newdir/dir && + git commit -m 'modify a file in new directory' && + git-svn dcommit -i bar && + test -z \"\`git diff refs/heads/my-bar refs/remotes/bar\`\" + " + +test_expect_success 'able to set-tree to a subdirectory' " + echo cba > d && + git update-index d && + git commit -m 'update /bar/d' && + git-svn set-tree -i bar HEAD && + test -z \"\`git diff refs/heads/my-bar refs/remotes/bar\`\" + " + test_done diff --git a/t/t9105-git-svn-commit-diff.sh b/t/t9105-git-svn-commit-diff.sh index 6323c7e3ac..c668dd1270 100755 --- a/t/t9105-git-svn-commit-diff.sh +++ b/t/t9105-git-svn-commit-diff.sh @@ -31,4 +31,13 @@ test_expect_success 'test the commit-diff command' " cmp readme wc/readme " +test_expect_success 'commit-diff to a sub-directory (with git-svn config)' " + svn import -m 'sub-directory' import $svnrepo/subdir && + git-svn init $svnrepo/subdir && + git-svn fetch && + git-svn commit-diff -r3 '$prev' '$head' && + svn cat $svnrepo/subdir/readme > readme.2 && + cmp readme readme.2 + " + test_done -- cgit v1.3 From 2b27f6c8847ebee631e7ad17ac9986e461d7674b Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 28 Jan 2007 04:59:05 -0800 Subject: git-svn: correctly handle do_{switch,update} in deep directories The do_update or do_switch functions in SVN only allow for a single path component; so 'path/to/deep/dir' would be interpreted as 'path'. SVN 1.4.x has a reparent function that can let us change the session to use a higher-level root of the repository, so we can use that for do_switch (which still doesn't seem to work in SVN 1.4.3 (a fix was attempted, but they missed the rest of the typemap changes needed in trunk...)). On the do_update side, we can use set_path on higher level directories and set them to a newer revision so they don't get updated. We can't do this with do_switch, either, because the relative path we're tracking can change (directory moving into a child of itself). Because of these changes, we need to double check that our Fetch editor is correctly performing stripping on any prefixed paths from update, otherwise we'll just die() because that would be a bug. Added a test case which helped me notice and fix problems with do_switch, too. Signed-off-by: Eric Wong --- git-svn.perl | 46 +++++++++++++++++++++++++++++++++------- t/t9104-git-svn-follow-parent.sh | 31 +++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 8 deletions(-) (limited to 't') diff --git a/git-svn.perl b/git-svn.perl index 36e5c57adf..dcc7fd4603 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1153,7 +1153,7 @@ sub find_parent_branch { if ($self->ra->can_do_switch) { print STDERR "Following parent with do_switch\n"; # do_switch works with svn/trunk >= r22312, but that - # is not included with SVN 1.4.2 (the latest version + # is not included with SVN 1.4.3 (the latest version # at the moment), so we can't rely on it $self->{last_commit} = $parent; $ed = SVN::Git::Fetcher->new($self); @@ -1621,7 +1621,10 @@ sub open_directory { sub git_path { my ($self, $path) = @_; - $path =~ s!$self->{path_strip}!! if $self->{path_strip}; + if ($self->{path_strip}) { + $path =~ s!$self->{path_strip}!! or + die "Failed to strip path '$path' ($self->{path_strip})\n"; + } $path; } @@ -2249,25 +2252,52 @@ sub gs_do_update { my ($self, $rev_a, $rev_b, $path, $recurse, $editor) = @_; my $pool = SVN::Pool->new; $editor->set_path_strip($path); - my $reporter = $self->do_update($rev_b, $path, $recurse, - $editor, $pool); + my (@pc) = split m#/#, $path; + my $reporter = $self->do_update($rev_b, (@pc ? shift @pc : ''), + $recurse, $editor, $pool); my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); + + # Since we can't rely on svn_ra_reparent being available, we'll + # just have to do some magic with set_path to make it so + # we only want a partial path. + my $sp = ''; + my $final = join('/', @pc); + while (@pc) { + $reporter->set_path($sp, $rev_b, 0, @lock, $pool); + $sp .= '/' if length $sp; + $sp .= shift @pc; + } + die "BUG: '$sp' != '$final'\n" if ($sp ne $final); + my $new = ($rev_a == $rev_b); - $reporter->set_path('', $rev_a, $new, @lock, $pool); + $reporter->set_path($sp, $rev_a, $new, @lock, $pool); + $reporter->finish_report($pool); $pool->clear; $editor->{git_commit_ok}; } +# this requires SVN 1.4.3 or later (do_switch didn't work before 1.4.3, and +# svn_ra_reparent didn't work before 1.4) sub gs_do_switch { my ($self, $rev_a, $rev_b, $path, $recurse, $url_b, $editor) = @_; my $pool = SVN::Pool->new; - $editor->set_path_strip($path); - my $reporter = $self->do_switch($rev_b, $path, $recurse, - $url_b, $editor, $pool); + + my $full_url = $self->{url}; + my $old_url = $full_url; + $full_url .= "/$path" if length $path; + SVN::_Ra::svn_ra_reparent($self->{session}, $full_url, $pool); + $self->{url} = $full_url; + + my $reporter = $self->do_switch($rev_b, '', + $recurse, $url_b, $editor, $pool); my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); $reporter->set_path('', $rev_a, 0, @lock, $pool); $reporter->finish_report($pool); + + SVN::_Ra::svn_ra_reparent($self->{session}, $old_url, $pool); + $self->{url} = $old_url; + $pool->clear; $editor->{git_commit_ok}; } diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index bfb718886f..6d243f8488 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -101,6 +101,37 @@ test_expect_success 'follow deleted directory' " test -z \"\`git ls-tree -z refs/remotes/glob\`\" " +# ref: r9270 of the Subversion repository: (http://svn.collab.net/repos/svn) +# in trunk/subversion/bindings/swig/perl +test_expect_success '' " + mkdir -p import/trunk/subversion/bindings/swig/perl/t && + for i in a b c ; do \ + echo \$i > import/trunk/subversion/bindings/swig/perl/\$i.pm && + echo _\$i > import/trunk/subversion/bindings/swig/perl/t/\$i.t; \ + done && + echo 'bad delete test' > \ + import/trunk/subversion/bindings/swig/perl/t/larger-parent && + echo 'bad delete test 2' > \ + import/trunk/subversion/bindings/swig/perl/another-larger && + cd import && + svn import -m 'r9270 test' . $svnrepo/r9270 && + cd .. && + svn co $svnrepo/r9270/trunk/subversion/bindings/swig/perl r9270 && + cd r9270 && + svn mkdir native && + svn mv t native/t && + for i in a b c; do svn mv \$i.pm native/\$i.pm; done && + echo z >> native/t/c.t && + svn commit -m 'reorg test' && + cd .. && + git-svn init -i r9270-t \ + $svnrepo/r9270/trunk/subversion/bindings/swig/perl/native/t && + git-svn fetch -i r9270-t --follow-parent && + test \`git rev-list r9270-t | wc -l\` -eq 2 && + test \"\`git ls-tree --name-only r9270-t~1\`\" = \ + \"\`git ls-tree --name-only r9270-t\`\" + " + test_debug 'gitk --all &' test_done -- cgit v1.3 From ce2a0f2f9d9fd8eee8eaa1f24a60bdafade80c24 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 28 Jan 2007 21:34:23 -0800 Subject: git-svn: stop using path names as refnames with --follow-parent Using path names as refnames breaks horribly if a user is tracking one large, toplevel directory, and a lower-level directory is followed from another project is a parent of another ref, as it will cause refnames such as: 'refs/remotes/trunk/path/to/stuff', which will conflict with a refname of 'refs/remotes/trunk'. Now we just append @$revno to the end of it the current refname. And if we have followed back to a grandparent, then we'll strip any existing '@$parent_revno' strings before appending our own '@$revno' string to it. Signed-off-by: Eric Wong --- git-svn.perl | 6 ++++-- t/t9104-git-svn-follow-parent.sh | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) (limited to 't') diff --git a/git-svn.perl b/git-svn.perl index dcc7fd4603..4f7938ab7e 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1135,10 +1135,12 @@ sub find_parent_branch { last if $gs; } unless ($gs) { - my $ref_id = $branch_from; - $ref_id .= "\@$r" if find_ref($ref_id); + my $ref_id = $self->{ref_id}; + $ref_id =~ s/\@\d+$//; + $ref_id .= "\@$r"; # just grow a tail if we're not unique enough :x $ref_id .= '-' while find_ref($ref_id); + print STDERR "Initializing parent: $ref_id\n"; $gs = Git::SVN->init($new_url, '', $ref_id, $ref_id); } my ($r0, $parent) = $gs->find_rev_before($r, 1); diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 6d243f8488..0f4e736271 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -30,10 +30,12 @@ test_expect_success 'initialize repo' " test_expect_success 'init and fetch --follow-parent a moved directory' " git-svn init -i thunk $svnrepo/thunk && git-svn fetch --follow-parent -i thunk && - test \"\`git-rev-parse --verify refs/remotes/trunk\`\" \ + test \"\`git-rev-parse --verify refs/remotes/thunk@2\`\" \ = \"\`git-rev-parse --verify refs/remotes/thunk~1\`\" && test \"\`git-cat-file blob refs/remotes/thunk:readme |\ - sed -n -e '3p'\`\" = goodbye + sed -n -e '3p'\`\" = goodbye && + test -n \"\`git-config --get svn-remote.git-svn.fetch \ + '^trunk:refs/remotes/thunk@2$'\`\" " test_expect_success 'init and fetch from one svn-remote' " -- cgit v1.3 From 289370578ca5833641fbb59813173ac6db1986d1 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 30 Jan 2007 00:35:18 -0800 Subject: git-svn: fetch tracks initial change with --follow-parent We were still skipping path information from get_log if we are tracking /r9270/drunk/subversion/bindings/..., but got something like this in the log: A /r9270/drunk (from /r9270/trunk:14) Signed-off-by: Eric Wong --- git-svn.perl | 17 ++++++++++++++--- t/t9104-git-svn-follow-parent.sh | 14 +++++++++++++- 2 files changed, 27 insertions(+), 4 deletions(-) (limited to 't') diff --git a/git-svn.perl b/git-svn.perl index 4e357dfcef..b0248c9487 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1090,6 +1090,19 @@ sub revisions_eq { return 1; } +sub match_paths { + my ($self, $paths) = @_; + return 1 if $paths->{'/'}; + $self->{path_regex} ||= qr/^\/\Q$self->{path}\E\/?/; + grep /$self->{path_regex}/, keys %$paths and return 1; + my $c = ''; + foreach (split m#/#, $self->rel_path) { + $c .= "/$_"; + return 1 if $paths->{$c}; + } + return 0; +} + sub find_parent_branch { my ($self, $paths, $rev) = @_; return undef unless $::_follow_parent; @@ -2313,7 +2326,6 @@ sub gs_fetch_loop_common { if (my $last_commit = $gs->last_commit) { $gs->assert_index_clean($last_commit); } - $gs->{path_regex} = qr/^\/\Q$gs->{path}\E\/?/; } while (1) { my @revs; @@ -2339,8 +2351,7 @@ sub gs_fetch_loop_common { my ($paths, $r) = @$_; foreach my $gs (@gs) { if ($paths) { - grep /$gs->{path_regex}/, keys %$paths - or next; + $gs->match_paths($paths) or next; } next if defined $gs->rev_db_get($r); if (my $log_entry = $gs->do_fetch($paths, $r)) { diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 0f4e736271..dcec16bda2 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -105,7 +105,7 @@ test_expect_success 'follow deleted directory' " # ref: r9270 of the Subversion repository: (http://svn.collab.net/repos/svn) # in trunk/subversion/bindings/swig/perl -test_expect_success '' " +test_expect_success 'follow-parent avoids deleting relevant info' " mkdir -p import/trunk/subversion/bindings/swig/perl/t && for i in a b c ; do \ echo \$i > import/trunk/subversion/bindings/swig/perl/\$i.pm && @@ -134,6 +134,18 @@ test_expect_success '' " \"\`git ls-tree --name-only r9270-t\`\" " +test_expect_success "track initial change if it was only made to parent" " + svn cp -m 'wheee!' $svnrepo/r9270/trunk $svnrepo/r9270/drunk && + git-svn init -i r9270-d \ + $svnrepo/r9270/drunk/subversion/bindings/swig/perl/native/t && + git-svn fetch -i r9270-d --follow-parent && + test \`git rev-list r9270-d | wc -l\` -eq 3 && + test \"\`git ls-tree --name-only r9270-t\`\" = \ + \"\`git ls-tree --name-only r9270-d\`\" && + test \"\`git rev-parse r9270-t\`\" = \ + \"\`git rev-parse r9270-d~1\`\" + " + test_debug 'gitk --all &' test_done -- cgit v1.3 From 8a603774def11e5a90ae2dbbc0c8d4abacdb2d99 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 31 Jan 2007 02:45:50 -0800 Subject: git-svn: fix several fetch bugs related to repeated invocations We no longer delete the top-level directory even if it got deleted from the upstream repository. In gs_do_update; we double-check that the path we're tracking exists at both endpoints before proceeding. We have also added additional protection against fetching revisions out-of-order. To simplify our internal interfaces, I've disabled passing the 'recursive' flag to the gs_do_{switch,update} wrapper functions since we always want it in git-svn. We also pass the entire Git::SVN object rather than just the path because it helped me debug. When printing progress, the refname is printed out to make it less confusing when multi-fetch is running. Signed-off-by: Eric Wong --- git-svn.perl | 42 ++++++++++++++++++++++++++++------------ t/t9104-git-svn-follow-parent.sh | 16 ++++++++++++--- 2 files changed, 43 insertions(+), 15 deletions(-) (limited to 't') diff --git a/git-svn.perl b/git-svn.perl index 845f22af59..2afa2537b9 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -981,6 +981,12 @@ sub full_url { sub do_git_commit { my ($self, $log_entry) = @_; + my $lr = $self->last_rev; + if (defined $lr && $lr >= $log_entry->{revision}) { + die "Last fetched revision of ", $self->refname, + " was r$lr, but we are about to fetch: ", + "r$log_entry->{revision}!\n"; + } if (my $c = $self->rev_db_get($log_entry->{revision})) { croak "$log_entry->{revision} = $c already exists! ", "Why are we refetching it?\n"; @@ -1024,7 +1030,7 @@ sub do_git_commit { $self->{last_rev} = $log_entry->{revision}; $self->{last_commit} = $commit; - print "r$log_entry->{revision} = $commit\n"; + print "r$log_entry->{revision} = $commit ($self->{ref_id})\n"; return $commit; } @@ -1121,14 +1127,13 @@ sub find_parent_branch { # at the moment), so we can't rely on it $self->{last_commit} = $parent; $ed = SVN::Git::Fetcher->new($self); - $gs->ra->gs_do_switch($r0, $rev, $gs->{path}, 1, + $gs->ra->gs_do_switch($r0, $rev, $gs, $self->full_url, $ed) or die "SVN connection failed somewhere...\n"; } else { print STDERR "Following parent with do_update\n"; $ed = SVN::Git::Fetcher->new($self); - $self->ra->gs_do_update($rev, $rev, $self->{path}, - 1, $ed) + $self->ra->gs_do_update($rev, $rev, $self, $ed) or die "SVN connection failed somewhere...\n"; } print STDERR "Successfully followed parent\n"; @@ -1170,8 +1175,7 @@ sub do_fetch { $ed = SVN::Git::Fetcher->new($self); $ed->{new_fetch} = 1; } - unless ($self->ra->gs_do_update($last_rev, $rev, - $self->{path}, 1, $ed)) { + unless ($self->ra->gs_do_update($last_rev, $rev, $self, $ed)) { die "SVN connection failed somewhere...\n"; } $self->make_log_entry($rev, \@parents, $ed); @@ -1616,6 +1620,8 @@ sub delete_entry { my ($self, $path, $rev, $pb) = @_; my $gpath = $self->git_path($path); + return undef if ($gpath eq ''); + # remove entire directories. if (command('ls-tree', $self->{c}, '--', $gpath) =~ /^040000 tree/) { my ($ls, $ctx) = command_output_pipe(qw/ls-tree @@ -2233,12 +2239,23 @@ sub uuid { } sub gs_do_update { - my ($self, $rev_a, $rev_b, $path, $recurse, $editor) = @_; + my ($self, $rev_a, $rev_b, $gs, $editor) = @_; + my $new = ($rev_a == $rev_b); + my $path = $gs->{path}; + + my $ta = $self->check_path($path, $rev_a); + my $tb = $new ? $ta : $self->check_path($path, $rev_b); + return 1 if ($tb != $SVN::Node::dir && $ta != $SVN::Node::dir); + if ($ta == $SVN::Node::none) { + $rev_a = $rev_b; + $new = 1; + } + my $pool = SVN::Pool->new; $editor->set_path_strip($path); my (@pc) = split m#/#, $path; my $reporter = $self->do_update($rev_b, (@pc ? shift @pc : ''), - $recurse, $editor, $pool); + 1, $editor, $pool); my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); # Since we can't rely on svn_ra_reparent being available, we'll @@ -2253,7 +2270,6 @@ sub gs_do_update { } die "BUG: '$sp' != '$final'\n" if ($sp ne $final); - my $new = ($rev_a == $rev_b); $reporter->set_path($sp, $rev_a, $new, @lock, $pool); $reporter->finish_report($pool); @@ -2264,7 +2280,8 @@ sub gs_do_update { # this requires SVN 1.4.3 or later (do_switch didn't work before 1.4.3, and # svn_ra_reparent didn't work before 1.4) sub gs_do_switch { - my ($self, $rev_a, $rev_b, $path, $recurse, $url_b, $editor) = @_; + my ($self, $rev_a, $rev_b, $gs, $url_b, $editor) = @_; + my $path = $gs->{path}; my $pool = SVN::Pool->new; my $full_url = $self->{url}; @@ -2282,8 +2299,7 @@ sub gs_do_switch { } } $ra ||= $self; - my $reporter = $ra->do_switch($rev_b, '', - $recurse, $url_b, $editor, $pool); + my $reporter = $ra->do_switch($rev_b, '', 1, $url_b, $editor, $pool); my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); $reporter->set_path('', $rev_a, 0, @lock, $pool); $reporter->finish_report($pool); @@ -2333,6 +2349,8 @@ sub gs_fetch_loop_common { if ($paths) { $gs->match_paths($paths) or next; } + my $lr = $gs->last_rev; + next if defined $lr && $lr >= $r; next if defined $gs->rev_db_get($r); if (my $log_entry = $gs->do_fetch($paths, $r)) { $gs->do_git_commit($log_entry); diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index dcec16bda2..41b9c19d45 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -95,12 +95,12 @@ test_expect_success 'follow higher-level parent' " " test_expect_success 'follow deleted directory' " - svn mv -m 'bye!' $svnrepo/glob/blob/hi $svnrepo/glob/blob/bye&& + svn mv -m 'bye!' $svnrepo/glob/blob/hi $svnrepo/glob/blob/bye && svn rm -m 'remove glob' $svnrepo/glob && git-svn init -i glob $svnrepo/glob && git-svn fetch -i glob && - test \"\`git cat-file blob refs/remotes/glob~1:blob/bye\`\" = hi && - test -z \"\`git ls-tree -z refs/remotes/glob\`\" + test \"\`git cat-file blob refs/remotes/glob:blob/bye\`\" = hi && + test \"\`git ls-tree refs/remotes/glob | wc -l \`\" -eq 1 " # ref: r9270 of the Subversion repository: (http://svn.collab.net/repos/svn) @@ -146,6 +146,16 @@ test_expect_success "track initial change if it was only made to parent" " \"\`git rev-parse r9270-d~1\`\" " +test_expect_success "multi-fetch continues to work" " + git-svn multi-fetch --follow-parent + " + +test_expect_success "multi-fetch works off a 'clean' repository" " + rm -r $GIT_DIR/svn $GIT_DIR/refs/remotes $GIT_DIR/logs && + mkdir $GIT_DIR/svn && + git-svn multi-fetch --follow-parent + " + test_debug 'gitk --all &' test_done -- cgit v1.3 From d8115c5104dbee29433a7f33a3e0d3e1738a581e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 1 Feb 2007 03:30:31 -0800 Subject: git-svn: don't write to the config file from --follow-parent Having 'fetch' entries in the config file created from --follow-parent is wasteful because it can cause *future* of invocations to follow revisions we were never interested in in the first place. Signed-off-by: Eric Wong --- git-svn.perl | 20 +++++++++++--------- t/t9104-git-svn-follow-parent.sh | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) (limited to 't') diff --git a/git-svn.perl b/git-svn.perl index 1e3a3c08f1..a2db73fe00 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -724,7 +724,7 @@ sub find_existing_remote { } sub init_remote_config { - my ($self, $url) = @_; + my ($self, $url, $no_write) = @_; $url =~ s!/+$!!; # strip trailing slash my $r = read_all_remotes(); my $existing = find_existing_remote($url, $r); @@ -769,19 +769,21 @@ sub init_remote_config { die "svn-remote.$xrepo_id.fetch already set to track ", "$xpath:refs/remotes/", $self->refname, "\n"; } - command_noisy('config', - "svn-remote.$self->{repo_id}.url", $url); - command_noisy('config', '--add', - "svn-remote.$self->{repo_id}.fetch", - "$self->{path}:".$self->refname); + unless ($no_write) { + command_noisy('config', + "svn-remote.$self->{repo_id}.url", $url); + command_noisy('config', '--add', + "svn-remote.$self->{repo_id}.fetch", + "$self->{path}:".$self->refname); + } $self->{url} = $url; } sub init { - my ($class, $url, $path, $repo_id, $ref_id) = @_; + my ($class, $url, $path, $repo_id, $ref_id, $no_write) = @_; my $self = _new($class, $repo_id, $ref_id, $path); if (defined $url) { - $self->init_remote_config($url); + $self->init_remote_config($url, $no_write); } $self; } @@ -1112,7 +1114,7 @@ sub find_parent_branch { # just grow a tail if we're not unique enough :x $ref_id .= '-' while find_ref($ref_id); print STDERR "Initializing parent: $ref_id\n"; - $gs = Git::SVN->init($new_url, '', $ref_id, $ref_id); + $gs = Git::SVN->init($new_url, '', $ref_id, $ref_id, 1); } my ($r0, $parent) = $gs->find_rev_before($r, 1); if ($_follow_parent && (!defined $r0 || !defined $parent)) { diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 41b9c19d45..7c852c1d7f 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -34,7 +34,7 @@ test_expect_success 'init and fetch --follow-parent a moved directory' " = \"\`git-rev-parse --verify refs/remotes/thunk~1\`\" && test \"\`git-cat-file blob refs/remotes/thunk:readme |\ sed -n -e '3p'\`\" = goodbye && - test -n \"\`git-config --get svn-remote.git-svn.fetch \ + test -z \"\`git-config --get svn-remote.git-svn.fetch \ '^trunk:refs/remotes/thunk@2$'\`\" " -- cgit v1.3 From 9fa00b655cfd67bf344668a0d913f90ec9a8141d Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 3 Feb 2007 12:49:48 -0800 Subject: git-svn: just name the default svn-remote "svn" instead of "git-svn" It can be confusing and redundant, since historically the default remote ref (not remote itself) has been "git-svn", too. Signed-off-by: Eric Wong --- git-svn.perl | 2 +- t/t9100-git-svn-basic.sh | 8 ++++---- t/t9104-git-svn-follow-parent.sh | 10 +++++----- t/t9107-git-svn-migrate.sh | 12 ++++++------ 4 files changed, 16 insertions(+), 16 deletions(-) (limited to 't') diff --git a/git-svn.perl b/git-svn.perl index 8c24012e7b..66b4c20fd9 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -10,7 +10,7 @@ $AUTHOR = 'Eric Wong '; $VERSION = '@@GIT_VERSION@@'; $ENV{GIT_DIR} ||= '.git'; -$Git::SVN::default_repo_id = 'git-svn'; +$Git::SVN::default_repo_id = 'svn'; $Git::SVN::default_ref_id = $ENV{GIT_SVN_ID} || 'git-svn'; $Git::SVN::Log::TZ = $ENV{TZ}; diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index 3dc4de2fad..8b6c8ffe10 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh @@ -214,7 +214,7 @@ EOF test_expect_success "$name" "diff -u a expected" test_expect_failure 'exit if remote refs are ambigious' " - git-repo-config --add svn-remote.git-svn.fetch \ + git-repo-config --add svn-remote.svn.fetch \ bar:refs/remotes/git-svn && git-svn migrate " @@ -222,7 +222,7 @@ test_expect_failure 'exit if remote refs are ambigious' " test_expect_failure 'exit if init-ing a would clobber a URL' " svnadmin create ${PWD}/svnrepo2 && svn mkdir -m 'mkdir bar' ${svnrepo}2/bar && - git-repo-config --unset svn-remote.git-svn.fetch \ + git-repo-config --unset svn-remote.svn.fetch \ '^bar:refs/remotes/git-svn$' && git-svn init ${svnrepo}2/bar " @@ -230,9 +230,9 @@ test_expect_failure 'exit if init-ing a would clobber a URL' " test_expect_success \ 'init allows us to connect to another directory in the same repo' " git-svn init -i bar $svnrepo/bar && - git repo-config --get svn-remote.git-svn.fetch \ + git repo-config --get svn-remote.svn.fetch \ '^bar:refs/remotes/bar$' && - git repo-config --get svn-remote.git-svn.fetch \ + git repo-config --get svn-remote.svn.fetch \ '^:refs/remotes/git-svn$' " diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 7c852c1d7f..eebb84974c 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -34,15 +34,15 @@ test_expect_success 'init and fetch --follow-parent a moved directory' " = \"\`git-rev-parse --verify refs/remotes/thunk~1\`\" && test \"\`git-cat-file blob refs/remotes/thunk:readme |\ sed -n -e '3p'\`\" = goodbye && - test -z \"\`git-config --get svn-remote.git-svn.fetch \ + test -z \"\`git-config --get svn-remote.svn.fetch \ '^trunk:refs/remotes/thunk@2$'\`\" " test_expect_success 'init and fetch from one svn-remote' " - git-repo-config svn-remote.git-svn.url $svnrepo && - git-repo-config --add svn-remote.git-svn.fetch \ + git-repo-config svn-remote.svn.url $svnrepo && + git-repo-config --add svn-remote.svn.fetch \ trunk:refs/remotes/svn/trunk && - git-repo-config --add svn-remote.git-svn.fetch \ + git-repo-config --add svn-remote.svn.fetch \ thunk:refs/remotes/svn/thunk && git-svn fetch --follow-parent -i svn/thunk && test \"\`git-rev-parse --verify refs/remotes/svn/trunk\`\" \ @@ -54,7 +54,7 @@ test_expect_success 'init and fetch from one svn-remote' " test_expect_success 'follow deleted parent' " svn cp -m 'resurrecting trunk as junk' \ -r2 $svnrepo/trunk $svnrepo/junk && - git-repo-config --add svn-remote.git-svn.fetch \ + git-repo-config --add svn-remote.svn.fetch \ junk:refs/remotes/svn/junk && git-svn fetch --follow-parent -i svn/thunk && git-svn fetch -i svn/junk --follow-parent && diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh index f6d84ba7a5..0fbfd264ec 100755 --- a/t/t9107-git-svn-migrate.sh +++ b/t/t9107-git-svn-migrate.sh @@ -34,14 +34,14 @@ test_expect_success 'initialize old-style (v0) git-svn layout' " ! test -d $GIT_DIR/git-svn && git-rev-parse --verify refs/remotes/git-svn^0 && git-rev-parse --verify refs/remotes/svn^0 && - test \`git repo-config --get svn-remote.git-svn.url\` = '$svnrepo' && - test \`git repo-config --get svn-remote.git-svn.fetch\` = \ + test \`git repo-config --get svn-remote.svn.url\` = '$svnrepo' && + test \`git repo-config --get svn-remote.svn.fetch\` = \ ':refs/remotes/git-svn' " test_expect_success 'initialize a multi-repository repo' " git-svn multi-init $svnrepo -T trunk -t tags -b branches && - git-repo-config --get-all svn-remote.git-svn.fetch > fetch.out && + git-repo-config --get-all svn-remote.svn.fetch > fetch.out && grep '^trunk:refs/remotes/trunk$' fetch.out && grep '^branches/a:refs/remotes/a$' fetch.out && grep '^branches/b:refs/remotes/b$' fetch.out && @@ -65,8 +65,8 @@ test_expect_success 'multi-fetch works on partial urls + paths' " " test_expect_success 'migrate --minimize on old multi-inited layout' " - git repo-config --unset-all svn-remote.git-svn.fetch && - git repo-config --unset-all svn-remote.git-svn.url && + git repo-config --unset-all svn-remote.svn.fetch && + git repo-config --unset-all svn-remote.svn.url && rm -rf $GIT_DIR/svn && for i in \`cat fetch.out\`; do path=\`expr \$i : '\\([^:]*\\):.*$'\` @@ -78,7 +78,7 @@ test_expect_success 'migrate --minimize on old multi-inited layout' " done && git-svn migrate --minimize && test -z \"\`git-repo-config -l |grep -v '^svn-remote\.git-svn\.'\`\" && - git-repo-config --get-all svn-remote.git-svn.fetch > fetch.out && + git-repo-config --get-all svn-remote.svn.fetch > fetch.out && grep '^trunk:refs/remotes/trunk$' fetch.out && grep '^branches/a:refs/remotes/a$' fetch.out && grep '^branches/b:refs/remotes/b$' fetch.out && -- cgit v1.3 From 4bb9ed0466e9ca6b72b3d9c454a743aea862b1f4 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 3 Feb 2007 13:29:17 -0800 Subject: git-svn: prepare multi-init for wildcard support Update the tests since we no longer write so many things to the config. Signed-off-by: Eric Wong --- git-svn.perl | 124 ++++++++++++++++++++++++++++++++++++++++++--- t/t9107-git-svn-migrate.sh | 20 +++++--- 2 files changed, 131 insertions(+), 13 deletions(-) (limited to 't') diff --git a/git-svn.perl b/git-svn.perl index 66b4c20fd9..6874eabffa 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -367,9 +367,10 @@ sub cmd_multi_init { sub cmd_multi_fetch { my $remotes = Git::SVN::read_all_remotes(); foreach my $repo_id (sort keys %$remotes) { - my $url = $remotes->{$repo_id}->{url} or next; - my $fetch = $remotes->{$repo_id}->{fetch} or next; - Git::SVN::fetch_all($repo_id, $url, $fetch); + if ($remotes->{$repo_id}->{url} && + $remotes->{$repo_id}->{fetch}) { + Git::SVN::fetch_all($repo_id, $remotes); + } } } @@ -479,9 +480,12 @@ sub complete_url_ls_init { # don't try to init already existing refs unless ($gs) { print "init $url/$path => $ref\n"; - $gs = Git::SVN->init($url, $path, undef, $ref); + $gs = Git::SVN->init($url, $path, undef, $ref, 1); + } + if ($gs) { + $remote_id = $gs->{repo_id}; + last; } - $remote_id ||= $gs->{repo_id} if $gs; } if (defined $remote_id) { $remote_path = "$ra->{svn_path}/$repo_path/*"; @@ -489,7 +493,7 @@ sub complete_url_ls_init { $remote_path =~ s#^/##g; my ($n) = ($switch =~ /^--(\w+)/); command_noisy('config', "svn-remote.$remote_id.$n", - $remote_path); + "$remote_path:refs/remotes/$pfx*"); } } @@ -660,9 +664,48 @@ BEGIN { my %LOCKFILES; END { unlink keys %LOCKFILES if %LOCKFILES } +sub resolve_local_globs { + my ($url, $fetch, $glob_spec) = @_; + return unless defined $glob_spec; + my $ref = $glob_spec->{ref}; + my $path = $glob_spec->{path}; + foreach (command(qw#for-each-ref --format=%(refname) refs/remotes#)) { + next unless m#^refs/remotes/$ref->{regex}$#; + my $p = $1; + my $pathname = $path->full_path($p); + my $refname = $ref->full_path($p); + if (my $existing = $fetch->{$pathname}) { + if ($existing ne $refname) { + die "Refspec conflict:\n", + "existing: refs/remotes/$existing\n", + " globbed: refs/remotes/$refname\n"; + } + my $u = (::cmt_metadata("refs/remotes/$refname"))[0]; + $u =~ s!^\Q$url\E/?!! or die + "refs/remotes/$refname: '$url' not found in '$u'\n"; + if ($pathname ne $u) { + warn "W: Refspec glob conflict ", + "(ref: refs/remotes/$refname):\n", + "expected path: $pathname\n", + " real path: $u\n", + "Continuing ahead with $u\n"; + next; + } + } else { + warn "Globbed ($path->{glob}:$ref->{glob}): ", + "$pathname == $refname\n"; + $fetch->{$pathname} = $refname; + } + } +} + sub fetch_all { - my ($repo_id, $url, $fetch) = @_; + my ($repo_id, $remotes) = @_; + my $fetch = $remotes->{$repo_id}->{fetch}; + my $url = $remotes->{$repo_id}->{url}; my @gs; + resolve_local_globs($url, $fetch, $remotes->{$repo_id}->{branches}); + resolve_local_globs($url, $fetch, $remotes->{$repo_id}->{tags}); my $ra = Git::SVN::Ra->new($url); my $head = $ra->get_latest_revnum; my $base = $head; @@ -685,6 +728,16 @@ sub read_all_remotes { $r->{$1}->{fetch}->{$2} = $3; } elsif (m!^(.+)\.url=\s*(.*)\s*$!) { $r->{$1}->{url} = $2; + } elsif (m!^(.+)\.(branches|tags)= + (.*):refs/remotes/(.+)\s*$/!x) { + my ($p, $g) = ($3, $4); + my $rs = $r->{$1}->{$2} = { + path => Git::SVN::GlobSpec->new($p), + ref => Git::SVN::GlobSpec->new($g) }; + if (length($rs->{ref}->{right}) != 0) { + die "The '*' glob character must be the last ", + "character of '$g'\n"; + } } } $r; @@ -3072,10 +3125,67 @@ sub DESTROY { command_close_pipe($self->{gui}, $self->{ctx}); } +package Git::SVN::GlobSpec; +use strict; +use warnings; + +sub new { + my ($class, $glob) = @_; + warn "glob: $glob\n"; + my $re = $glob; + $re =~ s!/+$!!g; # no need for trailing slashes + my $nr = ($re =~ s!^(.*/?)\*(/?.*)$!\(\[^/\]+\)!g); + my ($left, $right) = ($1, $2); + if ($nr > 1) { + warn "Only one '*' wildcard expansion ", + "is supported (got $nr): '$glob'\n"; + } elsif ($nr == 0) { + warn "One '*' is needed for glob: '$glob'\n"; + } + $re = quotemeta($left) . $re . quotemeta($right); + $left =~ s!/+$!!g; + $right =~ s!^/+!!g; + bless { left => $left, right => $right, + regex => qr/$re/, glob => $glob }, $class; +} + +sub full_path { + my ($self, $path) = @_; + return (length $self->{left} ? "$self->{left}/" : '') . + $path . (length $self->{right} ? "/$self->{right}" : ''); +} + __END__ Data structures: + +$remotes = { # returned by read_all_remotes() + 'svn' => { + # svn-remote.svn.url=https://svn.musicpd.org + url => 'https://svn.musicpd.org', + # svn-remote.svn.fetch=mpd/trunk:trunk + fetch => { + 'mpd/trunk' => 'trunk', + }, + # svn-remote.svn.tags=mpd/tags/*:tags/* + tags => { + path => { + left => 'mpd/tags', + right => '', + regex => qr!mpd/tags/([^/]+)$!, + glob => 'tags/*', + }, + ref => { + left => 'tags', + right => '', + regex => qr!tags/([^/]+)$!, + glob => 'tags/*', + }, + } + } +}; + $log_entry hashref as returned by libsvn_log_entry() { log => 'whitespace-formatted log entry diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh index 0fbfd264ec..8376429bcb 100755 --- a/t/t9107-git-svn-migrate.sh +++ b/t/t9107-git-svn-migrate.sh @@ -6,7 +6,7 @@ test_description='git-svn metadata migrations from previous versions' test_expect_success 'setup old-looking metadata' " cp $GIT_DIR/config $GIT_DIR/config-old-git-svn && mkdir import && - cd import + cd import && for i in trunk branches/a branches/b \ tags/0.1 tags/0.2 tags/0.3; do mkdir -p \$i && \ @@ -43,11 +43,19 @@ test_expect_success 'initialize a multi-repository repo' " git-svn multi-init $svnrepo -T trunk -t tags -b branches && git-repo-config --get-all svn-remote.svn.fetch > fetch.out && grep '^trunk:refs/remotes/trunk$' fetch.out && - grep '^branches/a:refs/remotes/a$' fetch.out && - grep '^branches/b:refs/remotes/b$' fetch.out && - grep '^tags/0\.1:refs/remotes/tags/0\.1$' fetch.out && - grep '^tags/0\.2:refs/remotes/tags/0\.2$' fetch.out && - grep '^tags/0\.3:refs/remotes/tags/0\.3$' fetch.out + test -n \"\`git-config --get svn-remote.svn.branches \ + '^branches/\*:refs/remotes/\*$'\`\" && + test -n \"\`git-config --get svn-remote.svn.tags \ + '^tags/\*:refs/remotes/tags/\*$'\`\" && + git config --unset svn-remote.svn.branches \ + '^branches/\*:refs/remotes/\*$' && + git config --unset svn-remote.svn.tags \ + '^tags/\*:refs/remotes/tags/\*$' && + git-config --add svn-remote.svn.fetch 'branches/a:refs/remotes/a' && + git-config --add svn-remote.svn.fetch 'branches/b:refs/remotes/b' && + for i in tags/0.1 tags/0.2 tags/0.3; do + git-config --add svn-remote.svn.fetch \ + \$i:refs/remotes/\$i || exit 1; done " # refs should all be different, but the trees should all be the same: -- cgit v1.3 From b9dffd8cad737a07d6a05503318c6746ac593f9c Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 9 Feb 2007 01:28:30 -0800 Subject: git-svn: --follow-parent tracks multi-parent paths We can have a branch that was deleted, then re-added under the same name but copied from another path, in which case we'll have multiple parents (we don't want to break the original ref, nor lose copypath info). Add a test for this, too, of course. Signed-off-by: Eric Wong --- git-svn.perl | 14 +++++++++++--- t/t9104-git-svn-follow-parent.sh | 7 +++++++ 2 files changed, 18 insertions(+), 3 deletions(-) (limited to 't') diff --git a/git-svn.perl b/git-svn.perl index 21d53054f6..cd35efec7e 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1266,11 +1266,19 @@ sub do_fetch { my ($self, $paths, $rev) = @_; my $ed; my ($last_rev, @parents); - if ($self->last_commit) { + if (my $lc = $self->last_commit) { + # we can have a branch that was deleted, then re-added + # under the same name but copied from another path, in + # which case we'll have multiple parents (we don't + # want to break the original ref, nor lose copypath info): + if (my $log_entry = $self->find_parent_branch($paths, $rev)) { + push @{$log_entry->{parents}}, $lc; + return $log_entry; + } $ed = SVN::Git::Fetcher->new($self); $last_rev = $self->{last_rev}; - $ed->{c} = $self->{last_commit}; - @parents = ($self->{last_commit}); + $ed->{c} = $lc; + @parents = ($lc); } else { $last_rev = $rev; if (my $log_entry = $self->find_parent_branch($paths, $rev)) { diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index eebb84974c..f5b7e5efe0 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -146,6 +146,13 @@ test_expect_success "track initial change if it was only made to parent" " \"\`git rev-parse r9270-d~1\`\" " +test_expect_success "track multi-parent paths" " + svn cp -m 'resurrect /glob' $svnrepo/r9270 $svnrepo/glob && + git-svn multi-fetch --follow-parent && + test \`git cat-file commit refs/remotes/glob | \ + grep '^parent ' | wc -l\` -eq 2 + " + test_expect_success "multi-fetch continues to work" " git-svn multi-fetch --follow-parent " -- cgit v1.3 From 0bed5eaa0edc3a2dfa0d5910ff1fb280539b242d Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 9 Feb 2007 02:45:03 -0800 Subject: git-svn: enable follow-parent functionality by default --no-follow-parent disables and reverts it back to the old default behavior of not following parents (if you don't care for full history). Signed-off-by: Eric Wong --- Documentation/git-svn.txt | 3 ++- git-svn.perl | 4 ++-- t/t9104-git-svn-follow-parent.sh | 26 +++++++++++++------------- 3 files changed, 17 insertions(+), 16 deletions(-) (limited to 't') diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 2914042587..50dc6ac818 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -311,7 +311,8 @@ for more information on using GIT_SVN_ID. This is especially helpful when we're tracking a directory that has been moved around within the repository, or if we started tracking a branch and never tracked the trunk it was - descended from. + descended from. This feature is enabled by default, use + --no-follow-parent to disable it. config key: svn.followparent diff --git a/git-svn.perl b/git-svn.perl index c8a1df3651..b9bacc384e 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -57,11 +57,11 @@ my ($_stdin, $_help, $_edit, $_version, $_merge, $_strategy, $_dry_run, $_prefix); - +$Git::SVN::_follow_parent = 1; my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username, 'config-dir=s' => \$Git::SVN::Ra::config_dir, 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache ); -my %fc_opts = ( 'follow-parent|follow' => \$Git::SVN::_follow_parent, +my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent, 'authors-file|A=s' => \$_authors, 'repack:i' => \$Git::SVN::_repack, 'no-metadata' => \$Git::SVN::_no_metadata, diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index f5b7e5efe0..53f5a925ac 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -3,7 +3,7 @@ # Copyright (c) 2006 Eric Wong # -test_description='git-svn --follow-parent fetching' +test_description='git-svn fetching' . ./lib-git-svn.sh test_expect_success 'initialize repo' " @@ -27,9 +27,9 @@ test_expect_success 'initialize repo' " cd .. " -test_expect_success 'init and fetch --follow-parent a moved directory' " +test_expect_success 'init and fetch a moved directory' " git-svn init -i thunk $svnrepo/thunk && - git-svn fetch --follow-parent -i thunk && + git-svn fetch -i thunk && test \"\`git-rev-parse --verify refs/remotes/thunk@2\`\" \ = \"\`git-rev-parse --verify refs/remotes/thunk~1\`\" && test \"\`git-cat-file blob refs/remotes/thunk:readme |\ @@ -44,7 +44,7 @@ test_expect_success 'init and fetch from one svn-remote' " trunk:refs/remotes/svn/trunk && git-repo-config --add svn-remote.svn.fetch \ thunk:refs/remotes/svn/thunk && - git-svn fetch --follow-parent -i svn/thunk && + git-svn fetch -i svn/thunk && test \"\`git-rev-parse --verify refs/remotes/svn/trunk\`\" \ = \"\`git-rev-parse --verify refs/remotes/svn/thunk~1\`\" && test \"\`git-cat-file blob refs/remotes/svn/thunk:readme |\ @@ -56,8 +56,8 @@ test_expect_success 'follow deleted parent' " -r2 $svnrepo/trunk $svnrepo/junk && git-repo-config --add svn-remote.svn.fetch \ junk:refs/remotes/svn/junk && - git-svn fetch --follow-parent -i svn/thunk && - git-svn fetch -i svn/junk --follow-parent && + git-svn fetch -i svn/thunk && + git-svn fetch -i svn/junk && test -z \"\`git diff svn/junk svn/trunk\`\" && test \"\`git merge-base svn/junk svn/trunk\`\" \ = \"\`git rev-parse svn/trunk\`\" @@ -69,7 +69,7 @@ test_expect_success 'follow larger parent' " svn import -m 'import a larger parent' import $svnrepo/larger-parent && svn cp -m 'hi' $svnrepo/larger-parent $svnrepo/another-larger && git-svn init -i larger $svnrepo/another-larger/trunk/thunk/bump/thud && - git-svn fetch -i larger --follow-parent && + git-svn fetch -i larger && git-rev-parse --verify refs/remotes/larger && git-rev-parse --verify \ refs/remotes/larger-parent/trunk/thunk/bump/thud && @@ -91,7 +91,7 @@ test_expect_success 'follow higher-level parent' " svn mkdir -m 'new glob at top level' $svnrepo/glob && svn mv -m 'move blob down a level' $svnrepo/blob $svnrepo/glob/blob && git-svn init -i blob $svnrepo/glob/blob && - git-svn fetch -i blob --follow-parent + git-svn fetch -i blob " test_expect_success 'follow deleted directory' " @@ -128,7 +128,7 @@ test_expect_success 'follow-parent avoids deleting relevant info' " cd .. && git-svn init -i r9270-t \ $svnrepo/r9270/trunk/subversion/bindings/swig/perl/native/t && - git-svn fetch -i r9270-t --follow-parent && + git-svn fetch -i r9270-t && test \`git rev-list r9270-t | wc -l\` -eq 2 && test \"\`git ls-tree --name-only r9270-t~1\`\" = \ \"\`git ls-tree --name-only r9270-t\`\" @@ -138,7 +138,7 @@ test_expect_success "track initial change if it was only made to parent" " svn cp -m 'wheee!' $svnrepo/r9270/trunk $svnrepo/r9270/drunk && git-svn init -i r9270-d \ $svnrepo/r9270/drunk/subversion/bindings/swig/perl/native/t && - git-svn fetch -i r9270-d --follow-parent && + git-svn fetch -i r9270-d && test \`git rev-list r9270-d | wc -l\` -eq 3 && test \"\`git ls-tree --name-only r9270-t\`\" = \ \"\`git ls-tree --name-only r9270-d\`\" && @@ -148,19 +148,19 @@ test_expect_success "track initial change if it was only made to parent" " test_expect_success "track multi-parent paths" " svn cp -m 'resurrect /glob' $svnrepo/r9270 $svnrepo/glob && - git-svn multi-fetch --follow-parent && + git-svn multi-fetch && test \`git cat-file commit refs/remotes/glob | \ grep '^parent ' | wc -l\` -eq 2 " test_expect_success "multi-fetch continues to work" " - git-svn multi-fetch --follow-parent + git-svn multi-fetch " test_expect_success "multi-fetch works off a 'clean' repository" " rm -r $GIT_DIR/svn $GIT_DIR/refs/remotes $GIT_DIR/logs && mkdir $GIT_DIR/svn && - git-svn multi-fetch --follow-parent + git-svn multi-fetch " test_debug 'gitk --all &' -- cgit v1.3 From 74a81227f95b52b1c3f7ac7ba84ac1a6e1708995 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 10 Feb 2007 13:28:50 -0800 Subject: git-svn: correctly handle globs with a right-hand-side path component Several bugs were found and fixed while getting this to work: * Remember the 'R'(eplace) case of actions and treat it like we would an 'A'(dd) case. * Fix a small case of follow-parent missing a parent if a subdirectory was modified in the revision where the parent was copied. * dirents returned by get_dir sometimes expire if the data structure is too big and the pool is destroyed, so we cache get_dir (along with check_path and get_revprops) temporarily along with its pool. Signed-off-by: Eric Wong --- git-svn.perl | 84 +++++++++++++++++++++++++++++++++++-------------- t/t9108-git-svn-glob.sh | 53 +++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 24 deletions(-) create mode 100755 t/t9108-git-svn-glob.sh (limited to 't') diff --git a/git-svn.perl b/git-svn.perl index ed363e972d..50b7dcf255 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1148,7 +1148,8 @@ sub match_paths { my $c = ''; foreach (split m#/#, $self->{path}) { $c .= "/$_"; - next unless ($paths->{$c} && ($paths->{$c}->{action} eq 'A')); + next unless ($paths->{$c} && + ($paths->{$c}->{action} =~ /^[AR]$/)); if ($self->ra->check_path($self->{path}, $r) == $SVN::Node::dir) { return 1; @@ -1176,11 +1177,11 @@ sub find_parent_branch { my $i; while (@b_path_components) { $i = $paths->{'/'.join('/', @b_path_components)}; - last if $i; + last if $i && defined $i->{copyfrom_path}; unshift(@a_path_components, pop(@b_path_components)); } - return undef unless defined $i; - my $branch_from = $i->{copyfrom_path} or return undef; + return undef unless defined $i && defined $i->{copyfrom_path}; + my $branch_from = $i->{copyfrom_path}; if (@a_path_components) { print STDERR "branch_from: $branch_from => "; $branch_from .= '/'.join('/', @a_path_components); @@ -2309,8 +2310,7 @@ my $RA; BEGIN { # enforce temporary pool usage for some simple functions my $e; - foreach (qw/get_latest_revnum rev_proplist get_file - check_path get_dir get_uuid get_repos_root/) { + foreach (qw/get_latest_revnum get_uuid get_repos_root/) { $e .= "sub $_ { my \$self = shift; my \$pool = SVN::Pool->new; @@ -2318,7 +2318,30 @@ BEGIN { \$pool->clear; wantarray ? \@ret : \$ret[0]; }\n"; } - eval $e; + + # get_dir needs $pool held in cache for dirents to work, + # check_path is cacheable and rev_proplist is close enough + # for our purposes. + foreach (qw/check_path get_dir rev_proplist/) { + $e .= "my \%${_}_cache; my \$${_}_rev = 0; sub $_ { + my \$self = shift; + my \$r = pop; + my \$k = join(\"\\0\", \@_); + if (my \$x = \$${_}_cache{\$r}->{\$k}) { + return wantarray ? \@\$x : \$x->[0]; + } + my \$pool = SVN::Pool->new; + my \@ret = \$self->SUPER::$_(\@_, \$r, \$pool); + if (\$r != \$${_}_rev) { + \%${_}_cache = ( pool => [] ); + \$${_}_rev = \$r; + } + \$${_}_cache{\$r}->{\$k} = \\\@ret; + push \@{\$${_}_cache{pool}}, \$pool; + wantarray ? \@ret : \$ret[0]; }\n"; + } + $e .= "\n1;"; + eval $e or die $@; } sub new { @@ -2564,8 +2587,34 @@ sub gs_fetch_loop_common { sub match_globs { my ($self, $exists, $paths, $globs, $r) = @_; + + sub get_dir_check { + my ($self, $exists, $g, $r) = @_; + my @x = eval { $self->get_dir($g->{path}->{left}, $r) }; + return unless scalar @x == 3; + my $dirents = $x[0]; + foreach my $de (keys %$dirents) { + next if $dirents->{$de}->kind != $SVN::Node::dir; + my $p = $g->{path}->full_path($de); + next if $exists->{$p}; + next if (length $g->{path}->{right} && + ($self->check_path($p, $r) != + $SVN::Node::dir)); + $exists->{$p} = Git::SVN->init($self->{url}, $p, undef, + $g->{ref}->full_path($de), 1); + } + } foreach my $g (@$globs) { + if (my $path = $paths->{"/$g->{path}->{left}"}) { + if ($path->{action} =~ /^[AR]$/) { + get_dir_check($self, $exists, $g, $r); + } + } foreach (keys %$paths) { + if (/$g->{path}->{left_regex}/) { + next if $paths->{$_}->{action} !~ /^[AR]$/; + get_dir_check($self, $exists, $g, $r); + } next unless /$g->{path}->{regex}/; my $p = $1; my $pathname = $g->{path}->full_path($p); @@ -2578,22 +2627,8 @@ sub match_globs { foreach (split m#/#, $g->{path}->{left}) { $c .= "/$_"; next unless ($paths->{$c} && - ($paths->{$c}->{action} eq 'A')); - my @x = eval { $self->get_dir($g->{path}->{left}, $r) }; - next unless scalar @x == 3; - my $dirents = $x[0]; - foreach my $de (keys %$dirents) { - next if $dirents->{$de}->kind != - $SVN::Node::dir; - my $p = $g->{path}->full_path($de); - next if $exists->{$p}; - next if (length $g->{path}->{right} && - ($self->check_path($p, $r) != - $SVN::Node::dir)); - $exists->{$p} = Git::SVN->init($self->{url}, - $p, undef, - $g->{ref}->full_path($de), 1); - } + ($paths->{$c}->{action} =~ /^[AR]$/)); + get_dir_check($self, $exists, $g, $r); } } values %$exists; @@ -3265,7 +3300,8 @@ sub new { if (length $right && !($right =~ s!^/+!!g)) { die "Missing leading '/' on right side of: '$glob' ($right)\n"; } - bless { left => $left, right => $right, + my $left_re = qr/^\/\Q$left\E(\/|$)/; + bless { left => $left, right => $right, left_regex => $left_re, regex => qr/$re/, glob => $glob }, $class; } diff --git a/t/t9108-git-svn-glob.sh b/t/t9108-git-svn-glob.sh new file mode 100755 index 0000000000..47cccdfd0e --- /dev/null +++ b/t/t9108-git-svn-glob.sh @@ -0,0 +1,53 @@ +#!/bin/sh +# Copyright (c) 2007 Eric Wong +test_description='git-svn globbing refspecs' +. ./lib-git-svn.sh + +cat > expect.end < trunk/src/a/readme && + echo 'goodbye world' > trunk/src/b/readme && + svn import -m 'initial' trunk $svnrepo/trunk && + svn co $svnrepo tmp && + cd tmp && + mkdir branches tags && + svn add branches tags && + svn cp trunk branches/start && + svn commit -m 'start a new branch' && + svn up && + echo 'hi' >> branches/start/src/b/readme && + echo 'hey' >> branches/start/src/a/readme && + svn commit -m 'hi' && + svn up && + svn cp branches/start tags/end && + echo 'bye' >> tags/end/src/b/readme && + echo 'aye' >> tags/end/src/a/readme && + svn commit -m 'the end' && + echo 'byebye' >> tags/end/src/b/readme && + svn commit -m 'nothing to see here' + cd .. && + git config --add svn-remote.svn.url $svnrepo && + git config --add svn-remote.svn.fetch \ + 'trunk/src/a:refs/remotes/trunk' && + git config --add svn-remote.svn.branches \ + 'branches/*/src/a:refs/remotes/branches/*' && + git config --add svn-remote.svn.tags\ + 'tags/*/src/a:refs/remotes/tags/*' && + git-svn multi-fetch && + git log --pretty=oneline refs/remotes/tags/end | \ + sed -e 's/^.\{41\}//' > output.end && + cmp expect.end output.end && + test \"\`git rev-parse refs/remotes/tags/end~1\`\" = \ + \"\`git rev-parse refs/remotes/branches/start\`\" && + test \"\`git rev-parse refs/remotes/branches/start~2\`\" = \ + \"\`git rev-parse refs/remotes/trunk\`\" + " + +test_done -- cgit v1.3 From 26a62d57a27407132d48e91b3c8f455a5fb22e4b Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 12 Feb 2007 13:25:25 -0800 Subject: git-svn: use separate, per-repository .rev_db files We need a separate .rev_db file for each repository we're tracking. This allows us to track the same logical path off multiple mirrors. We preserve a symlink to the old .rev_db (no-UUID) if we're (auto-)migrating from an old version to preserve backwards compatibility. Also, get rid of the uuid() wrapper since we cache UUID in our private config, and the SVN::Ra::get_uuid() function memoizes the return value per-connection. Signed-off-by: Eric Wong --- git-svn.perl | 173 ++++++++++++++++++++++++++++++++------------- t/t9107-git-svn-migrate.sh | 11 +++ 2 files changed, 134 insertions(+), 50 deletions(-) (limited to 't') diff --git a/git-svn.perl b/git-svn.perl index fdecffbbc7..beebe3d954 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -736,7 +736,7 @@ sub fetch_all { my $url = $remote->{url}; my (@gs, @globs); my $ra = Git::SVN::Ra->new($url); - my $uuid = $ra->uuid; + my $uuid = $ra->get_uuid; my $head = $ra->get_latest_revnum; my $base = $head; @@ -937,7 +937,8 @@ sub new { $self->{url} = command_oneline('config', '--get', "svn-remote.$repo_id.url") or die "Failed to read \"svn-remote.$repo_id.url\" in config\n"; - if (-z $self->{db_path} && ::verify_ref($self->refname.'^0')) { + if ((-z $self->db_path || ! -e $self->db_path) && + ::verify_ref($self->refname.'^0')) { $self->rebuild; } $self; @@ -945,18 +946,36 @@ sub new { sub refname { "refs/remotes/$_[0]->{ref_id}" } -sub set_svm_vars { - my ($self, $ra) = @_; - my $section = "svn-remote.$self->{repo_id}"; +sub svm_uuid { + my ($self) = @_; + return $self->{svm}->{uuid} if $self->svm; + $self->ra; + unless ($self->{svm}) { + die "SVM UUID not cached, and reading remotely failed\n"; + } + $self->{svm}->{uuid}; +} +sub svm { + my ($self) = @_; + return $self->{svm} if $self->{svm}; + my $svm; # see if we have it in our config, first: eval { - $self->{svm} = { + my $section = "svn-remote.$self->{repo_id}"; + $svm = { source => tmp_config('--get', "$section.svm-source"), uuid => tmp_config('--get', "$section.svm-uuid"), } }; - return $ra if ($self->{svm}->{source} && $self->{svm}->{uuid}); + $self->{svm} = $svm if ($svm && $svm->{source} && $svm->{uuid}); + $self->{svm}; +} + +sub _set_svm_vars { + my ($self, $ra) = @_; + + return $ra if ($self->svm); # nope, make sure we're connected to the repository root: if ($ra->{repos_root} ne $self->{url}) { @@ -965,6 +984,8 @@ sub set_svm_vars { my $r = $ra->get_latest_revnum; my ($props) = ($ra->get_dir('', $r))[2]; if (my $src = $props->{'svm:source'}) { + my $section = "svn-remote.$self->{repo_id}"; + # don't know what a '!' is there for, also the # username is of no interest $src =~ s{!$}{}; @@ -984,6 +1005,24 @@ sub set_svm_vars { $ra; } +# this allows us to memoize our SVN::Ra UUID locally and avoid a +# remote lookup (useful for 'git svn log'). +sub ra_uuid { + my ($self) = @_; + unless ($self->{ra_uuid}) { + my $key = "svn-remote.$self->{repo_id}.uuid"; + my $uuid = eval { tmp_config('--get', $key) }; + if (!$@ && $uuid && $uuid =~ /^([a-f\d\-]{30,})$/) { + $self->{ra_uuid} = $uuid; + } else { + die "ra_uuid called without URL\n" unless $self->{url}; + $self->{ra_uuid} = $self->ra->get_uuid; + tmp_config('--add', $key, $self->{ra_uuid}); + } + } + $self->{ra_uuid}; +} + sub ra { my ($self) = shift; my $ra = Git::SVN::Ra->new($self->{url}); @@ -992,7 +1031,7 @@ sub ra { die "Can't have both 'noMetadata' and ", "'useSvmProps' options set!\n"; } - $ra = $self->set_svm_vars($ra); + $ra = $self->_set_svm_vars($ra); $self->{-want_revprops} = 1; } $ra; @@ -1048,10 +1087,14 @@ sub last_rev_commit { return ($rev, $c); } } + my $db_path = $self->db_path; + unless (-e $db_path) { + ($self->{last_rev}, $self->{last_commit}) = (undef, undef); + return (undef, undef); + } my $offset = -41; # from tail my $rl; - open my $fh, '<', $self->{db_path} or - croak "$self->{db_path} not readable: $!\n"; + open my $fh, '<', $db_path or croak "$db_path not readable: $!\n"; sysseek($fh, $offset, 2); # don't care for errors sysread($fh, $rl, 41) == 41 or return (undef, undef); chomp $rl; @@ -1062,7 +1105,7 @@ sub last_rev_commit { chomp $rl; } if ($c && $c ne $rl) { - die "$self->{db_path} and ", $self->refname, + die "$db_path and ", $self->refname, " inconsistent!:\n$c != $rl\n"; } my $rev = sysseek($fh, 0, 1) or croak $!; @@ -1187,7 +1230,7 @@ sub do_git_commit { } my $author = $log_entry->{author}; my ($name, $email) = (defined $::users{$author} ? @{$::users{$author}} - : ($author, "$author\@".$self->ra->uuid)); + : ($author, "$author\@".$self->ra->get_uuid)); $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name; $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email; $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_entry->{date}; @@ -1227,6 +1270,8 @@ sub do_git_commit { print "r$log_entry->{revision}"; if (defined $log_entry->{svm_revision}) { print " (\@$log_entry->{svm_revision})"; + $self->rev_db_set($log_entry->{svm_revision}, $commit, + 0, $self->svm_uuid); } print " = $commit ($self->{ref_id})\n"; if (defined $_repack && (--$_repack_nr == 0)) { @@ -1492,7 +1537,7 @@ sub make_log_entry { $log_entry{svm_revision} = $r; } else { $log_entry{metadata} = $self->full_url . "\@$rev " . - $self->ra->uuid; + $self->ra->get_uuid; } \%log_entry; } @@ -1531,7 +1576,16 @@ sub set_tree { sub rebuild { my ($self) = @_; - print "Rebuilding $self->{db_path} ...\n"; + my $db_path = $self->db_path; + if (-f $self->{db_root}) { + rename $self->{db_root}, $db_path or die + "rename $self->{db_root} => $db_path failed: $!\n"; + my ($dir, $base) = ($db_path =~ m#^(.*?)/?([^/]+)$#); + symlink $base, $self->{db_root} or die + "symlink $base => $self->{db_root} failed: $!\n"; + return; + } + print "Rebuilding $db_path ...\n"; my ($rev_list, $ctx) = command_output_pipe("rev-list", $self->refname); my $latest; my $full_url = $self->full_url; @@ -1558,7 +1612,7 @@ sub rebuild { print "r$rev = $c\n"; } command_close_pipe($rev_list, $ctx); - print "Done rebuilding $self->{db_path}\n"; + print "Done rebuilding $db_path\n"; } # rev_db: @@ -1574,42 +1628,59 @@ sub rebuild { # And yes, it's still pretty fast (faster than Tie::File). # These files are disposable unless noMetadata or useSvmProps is set +sub _rev_db_set { + my ($fh, $rev, $commit) = @_; + my $offset = $rev * 41; + # assume that append is the common case: + seek $fh, 0, 2 or croak $!; + my $pos = tell $fh; + if ($pos < $offset) { + for (1 .. (($offset - $pos) / 41)) { + print $fh (('0' x 40),"\n") or croak $!; + } + } + seek $fh, $offset, 0 or croak $!; + print $fh $commit,"\n" or croak $!; +} + +sub mkfile { + my ($path) = @_; + unless (-e $path) { + my ($dir, $base) = ($path =~ m#^(.*?)/?([^/]+)$#); + mkpath([$dir]) unless -d $dir; + open my $fh, '>>', $path or die "Couldn't create $path: $!\n"; + close $fh or die "Couldn't close (create) $path: $!\n"; + } +} + sub rev_db_set { - my ($self, $rev, $commit, $update_ref) = @_; - length $commit == 40 or croak "arg3 must be a full SHA1 hexsum\n"; - my ($db, $db_lock) = ($self->{db_path}, "$self->{db_path}.lock"); + my ($self, $rev, $commit, $update_ref, $uuid) = @_; + length $commit == 40 or die "arg3 must be a full SHA1 hexsum\n"; + my $db = $self->db_path($uuid); + my $db_lock = "$db.lock"; my $sig; if ($update_ref) { $SIG{INT} = $SIG{HUP} = $SIG{TERM} = $SIG{ALRM} = $SIG{PIPE} = $SIG{USR1} = $SIG{USR2} = sub { $sig = $_[0] }; } + mkfile($db); + $LOCKFILES{$db_lock} = 1; my $sync; - # both of these options make our .rev_db file very, very important # and we can't afford to lose it because rebuild() won't work if ($self->use_svm_props || $self->no_metadata) { $sync = 1; copy($db, $db_lock) or die "rev_db_set(@_): ", - "Failed to copy: ", + "Failed to copy: ", "$db => $db_lock ($!)\n"; } else { rename $db, $db_lock or die "rev_db_set(@_): ", - "Failed to rename: ", + "Failed to rename: ", "$db => $db_lock ($!)\n"; } - open my $fh, '+<', $db_lock or croak $!; - my $offset = $rev * 41; - # assume that append is the common case: - seek $fh, 0, 2 or croak $!; - my $pos = tell $fh; - if ($pos < $offset) { - for (1 .. (($offset - $pos) / 41)) { - print $fh (('0' x 40),"\n") or croak $!; - } - } - seek $fh, $offset, 0 or croak $!; - print $fh $commit,"\n" or croak $!; + open my $fh, '+<', $db_lock or die "Couldn't open $db_lock: $!\n"; + _rev_db_set($fh, $rev, $commit); if ($sync) { $fh->flush or die "Couldn't flush $db_lock: $!\n"; $fh->sync or die "Couldn't sync $db_lock: $!\n"; @@ -1631,19 +1702,20 @@ sub rev_db_set { sub rev_db_max { my ($self) = @_; - my @stat = stat $self->{db_path} or - die "Couldn't stat $self->{db_path}: $!\n"; - ($stat[7] % 41) == 0 or - die "$self->{db_path} inconsistent size:$stat[7]\n"; + my $db_path = $self->db_path; + my @stat = stat $db_path or return 0; + ($stat[7] % 41) == 0 or die "$db_path inconsistent size: $stat[7]\n"; my $max = $stat[7] / 41; (($max > 0) ? $max - 1 : 0); } sub rev_db_get { - my ($self, $rev) = @_; + my ($self, $rev, $uuid) = @_; my $ret; my $offset = $rev * 41; - open my $fh, '<', $self->{db_path} or croak $!; + my $db_path = $self->db_path($uuid); + return undef unless -e $db_path; + open my $fh, '<', $db_path or croak $!; if (sysseek($fh, $offset, 0) == $offset) { my $read = sysread($fh, $ret, 40); $ret = undef if ($read != 40 || $ret eq ('0'x40)); @@ -1676,13 +1748,16 @@ sub _new { my $dir = "$ENV{GIT_DIR}/svn/$ref_id"; $_[3] = $path = '' unless (defined $path); mkpath([$dir]); - unless (-f "$dir/.rev_db") { - open my $fh, '>>', "$dir/.rev_db" or croak $!; - close $fh or croak $!; - } - bless { ref_id => $ref_id, dir => $dir, index => "$dir/index", + bless { + ref_id => $ref_id, dir => $dir, index => "$dir/index", path => $path, config => "$ENV{GIT_DIR}/svn/config", - db_path => "$dir/.rev_db", repo_id => $repo_id }, $class; + db_root => "$dir/.rev_db", repo_id => $repo_id }, $class; +} + +sub db_path { + my ($self, $uuid) = @_; + $uuid ||= $self->ra_uuid; + "$self->{db_root}.$uuid"; } sub uri_encode { @@ -2519,11 +2594,6 @@ sub get_commit_editor { $self->SUPER::get_commit_editor($log, $cb, @lock, $pool); } -sub uuid { - my ($self) = @_; - $self->{uuid} ||= $self->get_uuid; -} - sub gs_do_update { my ($self, $rev_a, $rev_b, $gs, $editor) = @_; my $new = ($rev_a == $rev_b); @@ -3140,6 +3210,9 @@ package Git::SVN::Migration; # - info/url may remain for backwards compatibility # - this is what we migrate up to this layout automatically, # - this will be used by git svn init on single branches +# v3.1 layout (auto migrated): +# - .rev_db => .rev_db.$UUID, .rev_db will remain as a symlink +# for backwards compatibility # # v4 layout: .git/svn/$repo_id/$id, refs/remotes/$repo_id/$id # - this is only created for newly multi-init-ed diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh index 8376429bcb..9f107ad7bf 100755 --- a/t/t9107-git-svn-migrate.sh +++ b/t/t9107-git-svn-migrate.sh @@ -96,5 +96,16 @@ test_expect_success 'migrate --minimize on old multi-inited layout' " grep '^:refs/remotes/git-svn' fetch.out " +test_expect_success ".rev_db auto-converted to .rev_db.UUID" " + git-svn fetch -i trunk && + expect=$GIT_DIR/svn/trunk/.rev_db.* && + test -n \"\$expect\" && + mv \$expect $GIT_DIR/svn/trunk/.rev_db && + git-svn fetch -i trunk && + test -L $GIT_DIR/svn/trunk/.rev_db && + test -f \$expect && + cmp \$expect $GIT_DIR/svn/trunk/.rev_db + " + test_done -- cgit v1.3 From 2edb9c5cf98e1a65c775ede6fc5b10a15bb94384 Mon Sep 17 00:00:00 2001 From: "sam@vilain.net" Date: Tue, 5 Dec 2006 16:17:38 +1100 Subject: git-svn: make test for SVK mirror path import A manual test that sets up a repository that looks like an SVK depot, and then imports it to check that it looks like we mirrored the 'original' source. There is also a minor modification to the git-svn test library shell file which sets a variable for the subversion repository's filesystem path. [ew: made some of the tests stricter and more thorough] Signed-off-by: Eric Wong --- t/lib-git-svn.sh | 2 +- t/t9109-git-svn-svk-mirrorpaths.sh | 104 +++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 1 deletion(-) create mode 100755 t/t9109-git-svn-svk-mirrorpaths.sh (limited to 't') diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh index 67d08cf740..27ad3b70f9 100644 --- a/t/lib-git-svn.sh +++ b/t/lib-git-svn.sh @@ -42,9 +42,9 @@ then exit fi +rawsvnrepo="$svnrepo" svnrepo="file://$svnrepo" - poke() { perl -e '@x = stat($ARGV[0]); utime($x[8], $x[9] + 1, $ARGV[0])' "$1" } diff --git a/t/t9109-git-svn-svk-mirrorpaths.sh b/t/t9109-git-svn-svk-mirrorpaths.sh new file mode 100755 index 0000000000..0e0ba3df39 --- /dev/null +++ b/t/t9109-git-svn-svk-mirrorpaths.sh @@ -0,0 +1,104 @@ +#!/bin/sh +# +# Copyright (c) 2006 Sam Vilian +# + +test_description='git-svn on SVK mirror paths' +. ./lib-git-svn.sh + +# ok, people who don't have SVK installed probably don't care about +# this test. + +# we set up the repository manually, because even if SVK is installed +# it is difficult to use it in a way that is idempotent. + +# we are not yet testing merge tickets.. + +uuid=b00bface-b1ff-c0ff-f0ff-b0bafe775e1e +url=https://really.slow.server.com/foobar + +test_expect_success 'initialize repo' " + git config svn-remote.svn.useSvmProps true && + + echo '#!/bin/sh' > $rawsvnrepo/hooks/pre-revprop-change && + echo 'exit 0' >> $rawsvnrepo/hooks/pre-revprop-change && + chmod +x $rawsvnrepo/hooks/pre-revprop-change && + + mkdir import && + cd import && + mkdir local && + echo hello > local/readme && + svn import -m 'random local work' . $svnrepo && + cd .. && + + svn co $svnrepo wc && + cd wc && + mkdir -p mirror/foobar && + svn add mirror && + svn ps svm:source $url mirror/foobar && + svn ps svm:uuid $uuid mirror/foobar && + svn ps svm:mirror / mirror/foobar && + svn commit -m 'setup mirror/foobar as mirror of upstream' && + svn ps -r 2 --revprop svm:headrev $uuid:0 $svnrepo && + + mkdir mirror/foobar/trunk + echo hello, world > mirror/foobar/trunk/readme && + svn add mirror/foobar/trunk && + svn commit -m 'first upstream revision' && + svn ps -r 3 --revprop svm:headrev $uuid:1 $svnrepo && + + svn up && + svn mkdir mirror/foobar/branches && + svn cp mirror/foobar/trunk mirror/foobar/branches/silly && + svn commit -m 'make branch for silliness' && + svn ps -r 4 --revprop svm:headrev $uuid:2 $svnrepo && + + svn up && + echo random untested feature >> mirror/foobar/trunk/readme && + svn commit -m 'add a c00l feature to trunk' && + svn ps -r 5 --revprop svm:headrev $uuid:3 $svnrepo && + + svn up && + echo bug fix >> mirror/foobar/branches/silly/readme && + svn commit -m 'fix a bug' && + svn ps -r 6 --revprop svm:headrev $uuid:4 $svnrepo && + + svn mkdir mirror/foobar/tags && + svn cp mirror/foobar/branches/silly mirror/foobar/tags/blah-1.0 && + svn commit -m 'make a release' && + svn ps -r 7 --revprop svm:headrev $uuid:5 $svnrepo && + + cd .. + " + +test_expect_success 'multi-init an SVK mirror path' " + git-svn multi-init -T trunk -t tags -b branches $svnrepo/mirror/foobar + " + +test_expect_success 'multi-fetch an SVK mirror path' "git-svn multi-fetch" + +test_expect_success 'got tag history OK' " + test \`git-log --pretty=oneline remotes/tags/blah-1.0 | wc -l\` -eq 3 + " + +test_expect_success 're-wrote git-svn-id URL, revision and UUID' " + git cat-file commit refs/remotes/trunk | \ + fgrep 'git-svn-id: $url/mirror/foobar/trunk@3 $uuid' && + git cat-file commit refs/remotes/tags/blah-1.0 | \ + fgrep 'git-svn-id: $url/mirror/foobar/tags/blah-1.0@5 $uuid' + git cat-file commit refs/remotes/silly | \ + fgrep 'git-svn-id: $url/mirror/foobar/branches/silly@4 $uuid' + " + +test_expect_success 're-wrote author e-mail domain UUID' " + test \`git log --pretty=fuller trunk | \ + grep '<.*@.*>' | fgrep '@$uuid>' | wc -l\` -eq 4 && + test \`git log --pretty=fuller remotes/silly | \ + grep '<.*@.*>' | fgrep '@$uuid>' | wc -l\` -eq 6 && + test \`git log --pretty=fuller remotes/tags/blah-1.0 | \ + grep '<.*@.*>' | fgrep '@$uuid>' | wc -l\` -eq 6 + " + +test_debug 'gitk --all &' + +test_done -- cgit v1.3 From ccb6b6f5b50bb32f90222a3e801a1901bf0b5657 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 13 Feb 2007 17:38:58 -0800 Subject: t910*: s/repo-config/config/g; poke around possible race conditions Some of the repo-config => config renaming missed the git-svn tests; so I'm just renaming them to be consisten with the rest of the modern git. Also, some of the newer tests didn't have 'poke' in them to workaround race conditions on fast machines. This adds places where they can _possibly_ occur; but I don't have fast enough hardware to trigger them. Signed-off-by: Eric Wong --- t/t9100-git-svn-basic.sh | 8 ++++---- t/t9104-git-svn-follow-parent.sh | 9 +++++---- t/t9107-git-svn-migrate.sh | 14 +++++++------- t/t9108-git-svn-glob.sh | 5 +++++ t/t9109-git-svn-svk-mirrorpaths.sh | 2 ++ 5 files changed, 23 insertions(+), 15 deletions(-) (limited to 't') diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index 8b6c8ffe10..7dcfc7e7db 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh @@ -214,7 +214,7 @@ EOF test_expect_success "$name" "diff -u a expected" test_expect_failure 'exit if remote refs are ambigious' " - git-repo-config --add svn-remote.svn.fetch \ + git-config --add svn-remote.svn.fetch \ bar:refs/remotes/git-svn && git-svn migrate " @@ -222,7 +222,7 @@ test_expect_failure 'exit if remote refs are ambigious' " test_expect_failure 'exit if init-ing a would clobber a URL' " svnadmin create ${PWD}/svnrepo2 && svn mkdir -m 'mkdir bar' ${svnrepo}2/bar && - git-repo-config --unset svn-remote.svn.fetch \ + git-config --unset svn-remote.svn.fetch \ '^bar:refs/remotes/git-svn$' && git-svn init ${svnrepo}2/bar " @@ -230,9 +230,9 @@ test_expect_failure 'exit if init-ing a would clobber a URL' " test_expect_success \ 'init allows us to connect to another directory in the same repo' " git-svn init -i bar $svnrepo/bar && - git repo-config --get svn-remote.svn.fetch \ + git config --get svn-remote.svn.fetch \ '^bar:refs/remotes/bar$' && - git repo-config --get svn-remote.svn.fetch \ + git config --get svn-remote.svn.fetch \ '^:refs/remotes/git-svn$' " diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 53f5a925ac..bd4f366e86 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -39,10 +39,10 @@ test_expect_success 'init and fetch a moved directory' " " test_expect_success 'init and fetch from one svn-remote' " - git-repo-config svn-remote.svn.url $svnrepo && - git-repo-config --add svn-remote.svn.fetch \ + git-config svn-remote.svn.url $svnrepo && + git-config --add svn-remote.svn.fetch \ trunk:refs/remotes/svn/trunk && - git-repo-config --add svn-remote.svn.fetch \ + git-config --add svn-remote.svn.fetch \ thunk:refs/remotes/svn/thunk && git-svn fetch -i svn/thunk && test \"\`git-rev-parse --verify refs/remotes/svn/trunk\`\" \ @@ -54,7 +54,7 @@ test_expect_success 'init and fetch from one svn-remote' " test_expect_success 'follow deleted parent' " svn cp -m 'resurrecting trunk as junk' \ -r2 $svnrepo/trunk $svnrepo/junk && - git-repo-config --add svn-remote.svn.fetch \ + git-config --add svn-remote.svn.fetch \ junk:refs/remotes/svn/junk && git-svn fetch -i svn/thunk && git-svn fetch -i svn/junk && @@ -124,6 +124,7 @@ test_expect_success 'follow-parent avoids deleting relevant info' " svn mv t native/t && for i in a b c; do svn mv \$i.pm native/\$i.pm; done && echo z >> native/t/c.t && + poke native/t/c.t && svn commit -m 'reorg test' && cd .. && git-svn init -i r9270-t \ diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh index 9f107ad7bf..d26c355f05 100755 --- a/t/t9107-git-svn-migrate.sh +++ b/t/t9107-git-svn-migrate.sh @@ -34,14 +34,14 @@ test_expect_success 'initialize old-style (v0) git-svn layout' " ! test -d $GIT_DIR/git-svn && git-rev-parse --verify refs/remotes/git-svn^0 && git-rev-parse --verify refs/remotes/svn^0 && - test \`git repo-config --get svn-remote.svn.url\` = '$svnrepo' && - test \`git repo-config --get svn-remote.svn.fetch\` = \ + test \`git config --get svn-remote.svn.url\` = '$svnrepo' && + test \`git config --get svn-remote.svn.fetch\` = \ ':refs/remotes/git-svn' " test_expect_success 'initialize a multi-repository repo' " git-svn multi-init $svnrepo -T trunk -t tags -b branches && - git-repo-config --get-all svn-remote.svn.fetch > fetch.out && + git-config --get-all svn-remote.svn.fetch > fetch.out && grep '^trunk:refs/remotes/trunk$' fetch.out && test -n \"\`git-config --get svn-remote.svn.branches \ '^branches/\*:refs/remotes/\*$'\`\" && @@ -73,8 +73,8 @@ test_expect_success 'multi-fetch works on partial urls + paths' " " test_expect_success 'migrate --minimize on old multi-inited layout' " - git repo-config --unset-all svn-remote.svn.fetch && - git repo-config --unset-all svn-remote.svn.url && + git config --unset-all svn-remote.svn.fetch && + git config --unset-all svn-remote.svn.url && rm -rf $GIT_DIR/svn && for i in \`cat fetch.out\`; do path=\`expr \$i : '\\([^:]*\\):.*$'\` @@ -85,8 +85,8 @@ test_expect_success 'migrate --minimize on old multi-inited layout' " echo $svnrepo\$path > $GIT_DIR/svn/\$ref/info/url ) || exit 1; done && git-svn migrate --minimize && - test -z \"\`git-repo-config -l |grep -v '^svn-remote\.git-svn\.'\`\" && - git-repo-config --get-all svn-remote.svn.fetch > fetch.out && + test -z \"\`git-config -l |grep -v '^svn-remote\.git-svn\.'\`\" && + git-config --get-all svn-remote.svn.fetch > fetch.out && grep '^trunk:refs/remotes/trunk$' fetch.out && grep '^branches/a:refs/remotes/a$' fetch.out && grep '^branches/b:refs/remotes/b$' fetch.out && diff --git a/t/t9108-git-svn-glob.sh b/t/t9108-git-svn-glob.sh index 47cccdfd0e..be21fc13b7 100755 --- a/t/t9108-git-svn-glob.sh +++ b/t/t9108-git-svn-glob.sh @@ -23,14 +23,19 @@ test_expect_success 'test refspec globbing' " svn commit -m 'start a new branch' && svn up && echo 'hi' >> branches/start/src/b/readme && + poke branches/start/src/b/readme && echo 'hey' >> branches/start/src/a/readme && + poke branches/start/src/a/readme && svn commit -m 'hi' && svn up && svn cp branches/start tags/end && echo 'bye' >> tags/end/src/b/readme && + poke tags/end/src/b/readme && echo 'aye' >> tags/end/src/a/readme && + poke tags/end/src/a/readme && svn commit -m 'the end' && echo 'byebye' >> tags/end/src/b/readme && + poke tags/end/src/b/readme && svn commit -m 'nothing to see here' cd .. && git config --add svn-remote.svn.url $svnrepo && diff --git a/t/t9109-git-svn-svk-mirrorpaths.sh b/t/t9109-git-svn-svk-mirrorpaths.sh index 0e0ba3df39..7e42151851 100755 --- a/t/t9109-git-svn-svk-mirrorpaths.sh +++ b/t/t9109-git-svn-svk-mirrorpaths.sh @@ -55,11 +55,13 @@ test_expect_success 'initialize repo' " svn up && echo random untested feature >> mirror/foobar/trunk/readme && + poke mirror/foobar/trunk/readme && svn commit -m 'add a c00l feature to trunk' && svn ps -r 5 --revprop svm:headrev $uuid:3 $svnrepo && svn up && echo bug fix >> mirror/foobar/branches/silly/readme && + poke mirror/foobar/branches/silly/readme && svn commit -m 'fix a bug' && svn ps -r 6 --revprop svm:headrev $uuid:4 $svnrepo && -- cgit v1.3 From dadc6d2a0904e55ac5a5a810dffac4d44fff0b66 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 14 Feb 2007 12:27:41 -0800 Subject: git-svn: allow 'init' to act as multi-init multi-init is now just an alias that requires -T/-t/-b; all options that 'init' can now accept. This will hopefully simplify usage and reduce typing. Also, allow the --shared option in 'init' to take an optional argument now that 'git-init --shared' supports an optional argument. Signed-off-by: Eric Wong --- git-svn.perl | 52 +++++++++++++++++++++++--------------- t/t9107-git-svn-migrate.sh | 4 +-- t/t9109-git-svn-svk-mirrorpaths.sh | 4 +-- 3 files changed, 36 insertions(+), 24 deletions(-) (limited to 't') diff --git a/git-svn.perl b/git-svn.perl index 3eed62fc0b..b2931cd5aa 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -71,10 +71,10 @@ my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent, %remote_opts ); my ($_trunk, $_tags, $_branches); -my %multi_opts = ( 'trunk|T=s' => \$_trunk, - 'tags|t=s' => \$_tags, - 'branches|b=s' => \$_branches ); -my %init_opts = ( 'template=s' => \$_template, 'shared' => \$_shared ); +my %init_opts = ( 'template=s' => \$_template, 'shared:s' => \$_shared, + 'trunk|T=s' => \$_trunk, 'tags|t=s' => \$_tags, + 'branches|b=s' => \$_branches, 'prefix=s' => \$_prefix, + %remote_opts ); my %cmt_opts = ( 'edit|e' => \$_edit, 'rmdir' => \$SVN::Git::Editor::_rmdir, 'find-copies-harder' => \$SVN::Git::Editor::_find_copies_harder, @@ -90,6 +90,10 @@ my %cmd = ( init => [ \&cmd_init, "Initialize a repo for tracking" . " (requires URL argument)", \%init_opts ], + 'multi-init' => [ \&cmd_multi_init, + "Deprecated alias for ". + "'$0 init -T -b -t'", + \%init_opts ], dcommit => [ \&cmd_dcommit, 'Commit several diffs to merge with upstream', { 'merge|m|M' => \$_merge, @@ -101,12 +105,6 @@ my %cmd = ( { 'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ], 'show-ignore' => [ \&cmd_show_ignore, "Show svn:ignore listings", { 'revision|r=i' => \$_revision } ], - 'multi-init' => [ \&cmd_multi_init, - 'Initialize multiple trees (like git-svnimport)', - { %multi_opts, %init_opts, %remote_opts, - 'revision|r=i' => \$_revision, - 'prefix=s' => \$_prefix, - } ], 'multi-fetch' => [ \&cmd_multi_fetch, "Deprecated alias for $0 fetch --all", { 'revision|r=s' => \$_revision, %fc_opts } ], @@ -182,6 +180,7 @@ Usage: $0 [options] [arguments]\n next if $cmd && $cmd ne $_; print $fd ' ',pack('A17',$_),$cmd{$_}->[1],"\n"; foreach (keys %{$cmd{$_}->[2]}) { + next if /^multi-/; # don't show deprecated commands # prints out arguments as they should be passed: my $x = s#[:=]s$## ? '' : s#[:=]i$## ? '' : ''; print $fd ' ' x 21, join(', ', map { length $_ > 1 ? @@ -207,21 +206,31 @@ sub do_git_init_db { unless (-d $ENV{GIT_DIR}) { my @init_db = ('init'); push @init_db, "--template=$_template" if defined $_template; - push @init_db, "--shared" if defined $_shared; + if (defined $_shared) { + if ($_shared =~ /[a-z]/) { + push @init_db, "--shared=$_shared"; + } else { + push @init_db, "--shared"; + } + } command_noisy(@init_db); } } +sub init_subdir { + my $repo_path = shift or return; + mkpath([$repo_path]) unless -d $repo_path; + chdir $repo_path or die "Couldn't chdir to $repo_path: $!\n"; + $ENV{GIT_DIR} = $repo_path . "/.git"; +} + sub cmd_init { - my $url = shift or die "SVN repository location required " . - "as a command-line argument\n"; - if (my $repo_path = shift) { - unless (-d $repo_path) { - mkpath([$repo_path]); - } - chdir $repo_path or croak $!; - $ENV{GIT_DIR} = $repo_path . "/.git"; + if (defined $_trunk || defined $_branches || defined $_tags) { + return cmd_multi_init(@_); } + my $url = shift or die "SVN repository location required ", + "as a command-line argument\n"; + init_subdir(@_); do_git_init_db(); Git::SVN->init($url); @@ -367,7 +376,10 @@ sub cmd_multi_init { } do_git_init_db(); $_prefix = '' unless defined $_prefix; - $url =~ s#/+$## if defined $url; + if (defined $url) { + $url =~ s#/+$##; + init_subdir(@_); + } if (defined $_trunk) { my $trunk_ref = $_prefix . 'trunk'; # try both old-style and new-style lookups: diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh index d26c355f05..a20038b670 100755 --- a/t/t9107-git-svn-migrate.sh +++ b/t/t9107-git-svn-migrate.sh @@ -40,7 +40,7 @@ test_expect_success 'initialize old-style (v0) git-svn layout' " " test_expect_success 'initialize a multi-repository repo' " - git-svn multi-init $svnrepo -T trunk -t tags -b branches && + git-svn init $svnrepo -T trunk -t tags -b branches && git-config --get-all svn-remote.svn.fetch > fetch.out && grep '^trunk:refs/remotes/trunk$' fetch.out && test -n \"\`git-config --get svn-remote.svn.branches \ @@ -72,7 +72,7 @@ test_expect_success 'multi-fetch works on partial urls + paths' " refs/remotes/\$j\`\" ||exit 1; done; done " -test_expect_success 'migrate --minimize on old multi-inited layout' " +test_expect_success 'migrate --minimize on old inited layout' " git config --unset-all svn-remote.svn.fetch && git config --unset-all svn-remote.svn.url && rm -rf $GIT_DIR/svn && diff --git a/t/t9109-git-svn-svk-mirrorpaths.sh b/t/t9109-git-svn-svk-mirrorpaths.sh index 7e42151851..1e1b97b5fc 100755 --- a/t/t9109-git-svn-svk-mirrorpaths.sh +++ b/t/t9109-git-svn-svk-mirrorpaths.sh @@ -73,8 +73,8 @@ test_expect_success 'initialize repo' " cd .. " -test_expect_success 'multi-init an SVK mirror path' " - git-svn multi-init -T trunk -t tags -b branches $svnrepo/mirror/foobar +test_expect_success 'init an SVK mirror path' " + git-svn init -T trunk -t tags -b branches $svnrepo/mirror/foobar " test_expect_success 'multi-fetch an SVK mirror path' "git-svn multi-fetch" -- cgit v1.3 From 28710f74ea1f1d8a46c867ddd471dae3d7c3a664 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 14 Feb 2007 13:32:21 -0800 Subject: git-svn: brown paper bag fixes * avoid skipping modification-only changes in fetch * correctly fetch when we only have branches and tags to glob from (no fetch keys defined) Signed-off-by: Eric Wong --- git-svn.perl | 5 +++-- t/t9108-git-svn-glob.sh | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) (limited to 't') diff --git a/git-svn.perl b/git-svn.perl index b2931cd5aa..24ca3087d6 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -782,7 +782,7 @@ sub fetch_all { my $ra = Git::SVN::Ra->new($url); my $uuid = $ra->get_uuid; my $head = $ra->get_latest_revnum; - my $base = $head; + my $base = defined $fetch ? $head : 0; # read the max revs for wildcard expansion (branches/*, tags/*) foreach my $t (qw/branches tags/) { @@ -2901,7 +2901,8 @@ sub match_globs { } } foreach (keys %$paths) { - if (/$g->{path}->{left_regex}/) { + if (/$g->{path}->{left_regex}/ && + !/$g->{path}->{regex}/) { next if $paths->{$_}->{action} !~ /^[AR]$/; get_dir_check($self, $exists, $g, $r); } diff --git a/t/t9108-git-svn-glob.sh b/t/t9108-git-svn-glob.sh index be21fc13b7..db4344cc84 100755 --- a/t/t9108-git-svn-glob.sh +++ b/t/t9108-git-svn-glob.sh @@ -55,4 +55,32 @@ test_expect_success 'test refspec globbing' " \"\`git rev-parse refs/remotes/trunk\`\" " +echo try to try > expect.two +echo nothing to see here >> expect.two +cat expect.end >> expect.two + +test_expect_success 'test left-hand-side only globbing' " + git config --add svn-remote.two.url $svnrepo && + git config --add svn-remote.two.fetch trunk:refs/remotes/two/trunk && + git config --add svn-remote.two.branches \ + 'branches/*:refs/remotes/two/branches/*' && + git config --add svn-remote.two.tags \ + 'tags/*:refs/remotes/two/tags/*' && + cd tmp && + echo 'try try' >> tags/end/src/b/readme && + poke tags/end/src/b/readme && + svn commit -m 'try to try' + cd .. && + git-svn fetch two && + test \`git rev-list refs/remotes/two/tags/end | wc -l\` -eq 6 && + test \`git rev-list refs/remotes/two/branches/start | wc -l\` -eq 3 && + test \`git rev-parse refs/remotes/two/branches/start~2\` = \ + \`git rev-parse refs/remotes/two/trunk\` && + test \`git rev-parse refs/remotes/two/tags/end~3\` = \ + \`git rev-parse refs/remotes/two/branches/start\` && + git log --pretty=oneline refs/remotes/two/tags/end | \ + sed -e 's/^.\{41\}//' > output.two && + cmp expect.two output.two + " + test_done -- cgit v1.3 From b7e5348c7f6369554813207a24cf841f48bd8b23 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 16 Feb 2007 04:09:28 -0800 Subject: git-svn: hide the private git-svn 'config' file as '.metadata' Having it named as 'config' prevents us from tracking a ref named 'config', which is a huge mistake. On the non-technical side, the word 'config' implies that a user can freely modify it; but that's not the case here. Signed-off-by: Eric Wong --- git-svn.perl | 7 ++++++- t/t9107-git-svn-migrate.sh | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) (limited to 't') diff --git a/git-svn.perl b/git-svn.perl index d7fc9aad52..571259fd09 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1286,7 +1286,12 @@ sub get_fetch_range { sub tmp_config { my (@args) = @_; - my $config = "$ENV{GIT_DIR}/svn/config"; + my $old_def_config = "$ENV{GIT_DIR}/svn/config"; + my $config = "$ENV{GIT_DIR}/svn/.metadata"; + if (-e $old_def_config && ! -e $config) { + rename $old_def_config, $config or + die "Failed rename $old_def_config => $config: $!\n"; + } my $old_config = $ENV{GIT_CONFIG}; $ENV{GIT_CONFIG} = $config; $@ = undef; diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh index a20038b670..dc2afdaa45 100755 --- a/t/t9107-git-svn-migrate.sh +++ b/t/t9107-git-svn-migrate.sh @@ -17,6 +17,7 @@ test_expect_success 'setup old-looking metadata' " git-svn init $svnrepo && git-svn fetch && mv $GIT_DIR/svn/* $GIT_DIR/ && + mv $GIT_DIR/svn/.metadata $GIT_DIR/ && rmdir $GIT_DIR/svn && git-update-ref refs/heads/git-svn-HEAD refs/remotes/git-svn && git-update-ref refs/heads/svn-HEAD refs/remotes/git-svn && -- cgit v1.3 From befc9adc0ced7d3e1c1316d6420007357d50b202 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 17 Feb 2007 02:53:07 -0800 Subject: git-svn: fix useSvmProps, hopefully for the last time svm:mirror is not useful at all for us. Parts of the old unit test were broken and based on my misunderstanding of the svm:mirror property. When we read svm:source; make sure we correctly handle the '!' in it: it is used to separate the path of the repository root from the virtual path within the repository. We don't need to make that distinction, honestly! We also ensure that subdirectories are also mirrored with the correct URL if we're using useSvmProps. We have a new test that uses dumped repo that was really created using SVN::Mirror to avoid ambiguities and mis-understandings about the svm: properties. Note: trailing whitespace in the svm.dump file is unfortunately a reality and required by SVN; so please ignore it when applying this patch. Also, ensure that the -R/--remote/--svn-remote flag is always in effect if explicitly passed via the command-line. This allows us to track logically different mirrors sharing the same URL (probably common with SVN::Mirror/SVK users). Signed-off-by: Eric Wong --- git-svn.perl | 91 ++++--- t/t9109-git-svn-svk-mirrorpaths.sh | 106 -------- t/t9110-git-svn-use-svm-props.sh | 51 ++++ t/t9110/svm.dump | 511 +++++++++++++++++++++++++++++++++++++ 4 files changed, 619 insertions(+), 140 deletions(-) delete mode 100755 t/t9109-git-svn-svk-mirrorpaths.sh create mode 100755 t/t9110-git-svn-use-svm-props.sh create mode 100644 t/t9110/svm.dump (limited to 't') diff --git a/git-svn.perl b/git-svn.perl index 7563eea352..1bcf058ef6 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -164,7 +164,9 @@ read_repo_config(\%opts); my $rv = GetOptions(%opts, 'help|H|h' => \$_help, 'version|V' => \$_version, 'minimize-connections' => \$Git::SVN::Migration::_minimize, 'id|i=s' => \$Git::SVN::default_ref_id, - 'svn-remote|remote|R=s' => \$Git::SVN::default_repo_id); + 'svn-remote|remote|R=s' => sub { + $Git::SVN::no_reuse_existing = 1; + $Git::SVN::default_repo_id = $_[1] }); exit 1 if (!$rv && $cmd ne 'log'); usage(0) if $_help; @@ -749,7 +751,7 @@ use strict; use warnings; use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent $_repack $_repack_flags $_use_svm_props $_head - $_use_svnsync_props/; + $_use_svnsync_props $no_reuse_existing/; use Carp qw/croak/; use File::Path qw/mkpath/; use File::Copy qw/copy/; @@ -944,6 +946,7 @@ sub sanitize_remote_name { sub find_existing_remote { my ($url, $remotes) = @_; + return undef if $no_reuse_existing; my $existing; foreach my $repo_id (keys %$remotes) { my $u = $remotes->{$repo_id}->{url} or next; @@ -1116,9 +1119,12 @@ sub svm { $svm = { source => tmp_config('--get', "$section.svm-source"), uuid => tmp_config('--get', "$section.svm-uuid"), + replace => tmp_config('--get', "$section.svm-replace"), } }; - $self->{svm} = $svm if ($svm && $svm->{source} && $svm->{uuid}); + if ($svm && $svm->{source} && $svm->{uuid} && $svm->{replace}) { + $self->{svm} = $svm; + } $self->{svm}; } @@ -1127,64 +1133,76 @@ sub _set_svm_vars { return $ra if $self->svm; my @err = ( "useSvmProps set, but failed to read SVM properties\n", - "(svm:source, svm:mirror, svm:mirror) ", + "(svm:source, svm:uuid) ", "from the following URLs:\n" ); sub read_svm_props { - my ($self, $props) = @_; + my ($self, $ra, $path, $r) = @_; + my $props = ($ra->get_dir($path, $r))[2]; my $src = $props->{'svm:source'}; - my $mirror = $props->{'svm:mirror'}; my $uuid = $props->{'svm:uuid'}; - return undef if (!$src || !$mirror || !$uuid); + return undef if (!$src || !$uuid); - chomp($src, $mirror, $uuid); + chomp($src, $uuid); $uuid =~ m{^[0-9a-f\-]{30,}$} or die "doesn't look right - svm:uuid is '$uuid'\n"; - # don't know what a '!' is there for, also the - # username is of no interest - $src =~ s{/?!$}{$mirror}; + + # the '!' is used to mark the repos_root!/relative/path + $src =~ s{/?!/?}{/}; $src =~ s{/+$}{}; # no trailing slashes please + # username is of no interest $src =~ s{(^[a-z\+]*://)[^/@]*@}{$1}; + my $replace = $ra->{url}; + $replace .= "/$path" if length $path; + my $section = "svn-remote.$self->{repo_id}"; - tmp_config('--add', "$section.svm-source", $src); - tmp_config('--add', "$section.svm-uuid", $uuid); - $self->{svm} = { source => $src , uuid => $uuid }; - return 1; + tmp_config("$section.svm-source", $src); + tmp_config("$section.svm-replace", $replace); + tmp_config("$section.svm-uuid", $uuid); + $self->{svm} = { + source => $src, + uuid => $uuid, + replace => $replace + }; } my $r = $ra->get_latest_revnum; my $path = $self->{path}; - my @tried_a = ($path); + my %tried; while (length $path) { - if ($self->read_svm_props(($ra->get_dir($path, $r))[2])) { - return $ra; + unless ($tried{"$self->{url}/$path"}) { + return $ra if $self->read_svm_props($ra, $path, $r); + $tried{"$self->{url}/$path"} = 1; } - $path =~ s#/?[^/]+$## && push @tried_a, $path; - } - if ($self->read_svm_props(($ra->get_dir('', $r))[2])) { - return $ra; + $path =~ s#/?[^/]+$##; } + die "Path: '$path' should be ''\n" if $path ne ''; + return $ra if $self->read_svm_props($ra, $path, $r); + $tried{"$self->{url}/$path"} = 1; if ($ra->{repos_root} eq $self->{url}) { - die @err, map { " $self->{url}/$_\n" } @tried_a, "\n"; + die @err, (map { " $_\n" } keys %tried), "\n"; } # nope, make sure we're connected to the repository root: my $ok; my @tried_b; $path = $ra->{svn_path}; - $path =~ s#/?[^/]+$##; # we already tried this one above $ra = Git::SVN::Ra->new($ra->{repos_root}); while (length $path) { - $ok = $self->read_svm_props(($ra->get_dir($path, $r))[2]); - last if $ok; - $path =~ s#/?[^/]+$## && push @tried_b, $path; + unless ($tried{"$ra->{url}/$path"}) { + $ok = $self->read_svm_props($ra, $path, $r); + last if $ok; + $tried{"$ra->{url}/$path"} = 1; + } + $path =~ s#/?[^/]+$##; } - $ok = $self->read_svm_props(($ra->get_dir('', $r))[2]) unless $ok; + die "Path: '$path' should be ''\n" if $path ne ''; + $ok ||= $self->read_svm_props($ra, $path, $r); + $tried{"$ra->{url}/$path"} = 1; if (!$ok) { - die @err, map { " $self->{url}/$_\n" } @tried_a, "\n", - map { " $ra->{url}/$_\n" } @tried_b, "\n" + die @err, (map { " $_\n" } keys %tried), "\n"; } Git::SVN::Ra->new($self->{url}); } @@ -1779,13 +1797,18 @@ sub make_log_entry { "options set!\n"; } my ($uuid, $r) = $headrev =~ m{^([a-f\d\-]{30,}):(\d+)$}; - if ($uuid ne $self->{svm}->{uuid}) { + # we don't want "SVM: initializing mirror for junk" ... + return undef if $r == 0; + my $svm = $self->svm; + if ($uuid ne $svm->{uuid}) { die "UUID mismatch on SVM path:\n", - "expected: $self->{svm}->{uuid}\n", + "expected: $svm->{uuid}\n", " got: $uuid\n"; } - my $full_url = $self->{svm}->{source}; - $full_url .= "/$self->{path}" if length $self->{path}; + my $full_url = $self->full_url; + $full_url =~ s#^\Q$svm->{replace}\E(/|$)#$svm->{source}$1# or + die "Failed to replace '$svm->{replace}' with ", + "'$svm->{source}' in $full_url\n"; $log_entry{metadata} = "$full_url\@$r $uuid"; $log_entry{svm_revision} = $r; $email ||= "$author\@$uuid" diff --git a/t/t9109-git-svn-svk-mirrorpaths.sh b/t/t9109-git-svn-svk-mirrorpaths.sh deleted file mode 100755 index 1e1b97b5fc..0000000000 --- a/t/t9109-git-svn-svk-mirrorpaths.sh +++ /dev/null @@ -1,106 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2006 Sam Vilian -# - -test_description='git-svn on SVK mirror paths' -. ./lib-git-svn.sh - -# ok, people who don't have SVK installed probably don't care about -# this test. - -# we set up the repository manually, because even if SVK is installed -# it is difficult to use it in a way that is idempotent. - -# we are not yet testing merge tickets.. - -uuid=b00bface-b1ff-c0ff-f0ff-b0bafe775e1e -url=https://really.slow.server.com/foobar - -test_expect_success 'initialize repo' " - git config svn-remote.svn.useSvmProps true && - - echo '#!/bin/sh' > $rawsvnrepo/hooks/pre-revprop-change && - echo 'exit 0' >> $rawsvnrepo/hooks/pre-revprop-change && - chmod +x $rawsvnrepo/hooks/pre-revprop-change && - - mkdir import && - cd import && - mkdir local && - echo hello > local/readme && - svn import -m 'random local work' . $svnrepo && - cd .. && - - svn co $svnrepo wc && - cd wc && - mkdir -p mirror/foobar && - svn add mirror && - svn ps svm:source $url mirror/foobar && - svn ps svm:uuid $uuid mirror/foobar && - svn ps svm:mirror / mirror/foobar && - svn commit -m 'setup mirror/foobar as mirror of upstream' && - svn ps -r 2 --revprop svm:headrev $uuid:0 $svnrepo && - - mkdir mirror/foobar/trunk - echo hello, world > mirror/foobar/trunk/readme && - svn add mirror/foobar/trunk && - svn commit -m 'first upstream revision' && - svn ps -r 3 --revprop svm:headrev $uuid:1 $svnrepo && - - svn up && - svn mkdir mirror/foobar/branches && - svn cp mirror/foobar/trunk mirror/foobar/branches/silly && - svn commit -m 'make branch for silliness' && - svn ps -r 4 --revprop svm:headrev $uuid:2 $svnrepo && - - svn up && - echo random untested feature >> mirror/foobar/trunk/readme && - poke mirror/foobar/trunk/readme && - svn commit -m 'add a c00l feature to trunk' && - svn ps -r 5 --revprop svm:headrev $uuid:3 $svnrepo && - - svn up && - echo bug fix >> mirror/foobar/branches/silly/readme && - poke mirror/foobar/branches/silly/readme && - svn commit -m 'fix a bug' && - svn ps -r 6 --revprop svm:headrev $uuid:4 $svnrepo && - - svn mkdir mirror/foobar/tags && - svn cp mirror/foobar/branches/silly mirror/foobar/tags/blah-1.0 && - svn commit -m 'make a release' && - svn ps -r 7 --revprop svm:headrev $uuid:5 $svnrepo && - - cd .. - " - -test_expect_success 'init an SVK mirror path' " - git-svn init -T trunk -t tags -b branches $svnrepo/mirror/foobar - " - -test_expect_success 'multi-fetch an SVK mirror path' "git-svn multi-fetch" - -test_expect_success 'got tag history OK' " - test \`git-log --pretty=oneline remotes/tags/blah-1.0 | wc -l\` -eq 3 - " - -test_expect_success 're-wrote git-svn-id URL, revision and UUID' " - git cat-file commit refs/remotes/trunk | \ - fgrep 'git-svn-id: $url/mirror/foobar/trunk@3 $uuid' && - git cat-file commit refs/remotes/tags/blah-1.0 | \ - fgrep 'git-svn-id: $url/mirror/foobar/tags/blah-1.0@5 $uuid' - git cat-file commit refs/remotes/silly | \ - fgrep 'git-svn-id: $url/mirror/foobar/branches/silly@4 $uuid' - " - -test_expect_success 're-wrote author e-mail domain UUID' " - test \`git log --pretty=fuller trunk | \ - grep '<.*@.*>' | fgrep '@$uuid>' | wc -l\` -eq 4 && - test \`git log --pretty=fuller remotes/silly | \ - grep '<.*@.*>' | fgrep '@$uuid>' | wc -l\` -eq 6 && - test \`git log --pretty=fuller remotes/tags/blah-1.0 | \ - grep '<.*@.*>' | fgrep '@$uuid>' | wc -l\` -eq 6 - " - -test_debug 'gitk --all &' - -test_done diff --git a/t/t9110-git-svn-use-svm-props.sh b/t/t9110-git-svn-use-svm-props.sh new file mode 100755 index 0000000000..9db0d8fd8d --- /dev/null +++ b/t/t9110-git-svn-use-svm-props.sh @@ -0,0 +1,51 @@ +#!/bin/sh +# +# Copyright (c) 2007 Eric Wong +# + +test_description='git-svn useSvmProps test' + +. ./lib-git-svn.sh + +test_expect_success 'load svm repo' " + svnadmin load -q $rawsvnrepo < ../t9110/svm.dump && + git-svn init -R arr -i bar $svnrepo/mirror/arr && + git-svn init -R argh -i dir $svnrepo/mirror/argh && + git-svn init -R argh -i e $svnrepo/mirror/argh/a/b/c/d/e && + git-config svn.useSvmProps true && + git-svn fetch --all + " + +uuid=161ce429-a9dd-4828-af4a-52023f968c89 + +bar_url=http://mayonaise/svnrepo/bar +test_expect_success 'verify metadata for /bar' " + git-cat-file commit refs/remotes/bar | \ + grep '^git-svn-id: $bar_url@12 $uuid$' && + git-cat-file commit refs/remotes/bar~1 | \ + grep '^git-svn-id: $bar_url@11 $uuid$' && + git-cat-file commit refs/remotes/bar~2 | \ + grep '^git-svn-id: $bar_url@10 $uuid$' && + git-cat-file commit refs/remotes/bar~3 | \ + grep '^git-svn-id: $bar_url@9 $uuid$' && + git-cat-file commit refs/remotes/bar~4 | \ + grep '^git-svn-id: $bar_url@6 $uuid$' && + git-cat-file commit refs/remotes/bar~5 | \ + grep '^git-svn-id: $bar_url@1 $uuid$' + " + +e_url=http://mayonaise/svnrepo/dir/a/b/c/d/e +test_expect_success 'verify metadata for /dir/a/b/c/d/e' " + git-cat-file commit refs/remotes/e | \ + grep '^git-svn-id: $e_url@1 $uuid$' + " + +dir_url=http://mayonaise/svnrepo/dir +test_expect_success 'verify metadata for /dir' " + git-cat-file commit refs/remotes/dir | \ + grep '^git-svn-id: $dir_url@2 $uuid$' && + git-cat-file commit refs/remotes/dir~1 | \ + grep '^git-svn-id: $dir_url@1 $uuid$' + " + +test_done diff --git a/t/t9110/svm.dump b/t/t9110/svm.dump new file mode 100644 index 0000000000..cc799c238d --- /dev/null +++ b/t/t9110/svm.dump @@ -0,0 +1,511 @@ +SVN-fs-dump-format-version: 2 + +UUID: de5973c6-545d-41da-aded-c265f9039e74 + +Revision-number: 0 +Prop-content-length: 56 +Content-length: 56 + +K 8 +svn:date +V 27 +2007-02-17T06:54:59.793104Z +PROPS-END + +Revision-number: 1 +Prop-content-length: 200 +Content-length: 200 + +K 7 +svn:log +V 40 +SVM: initializing mirror for /mirror/arr +K 10 +svn:author +V 3 +svm +K 11 +svm:headrev +V 39 +161ce429-a9dd-4828-af4a-52023f968c89:0 + +K 8 +svn:date +V 27 +2007-02-17T06:55:00.121647Z +PROPS-END + +Node-path: +Node-kind: dir +Node-action: change +Prop-content-length: 44 +Content-length: 44 + +K 10 +svm:mirror +V 12 +/mirror/arr + +PROPS-END + + +Node-path: mirror +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: mirror/arr +Node-kind: dir +Node-action: add +Prop-content-length: 116 +Content-length: 116 + +K 10 +svm:source +V 29 +http://mayonaise/svnrepo!/bar +K 8 +svm:uuid +V 36 +161ce429-a9dd-4828-af4a-52023f968c89 +PROPS-END + + +Revision-number: 2 +Prop-content-length: 182 +Content-length: 182 + +K 7 +svn:log +V 18 +import for git-svn +K 10 +svn:author +V 7 +svnsync +K 11 +svm:headrev +V 39 +161ce429-a9dd-4828-af4a-52023f968c89:1 + +K 8 +svn:date +V 27 +2007-02-17T05:10:52.108847Z +PROPS-END + +Node-path: mirror/arr +Node-kind: dir +Node-action: change +Prop-content-length: 116 +Content-length: 116 + +K 10 +svm:source +V 29 +http://mayonaise/svnrepo!/bar +K 8 +svm:uuid +V 36 +161ce429-a9dd-4828-af4a-52023f968c89 +PROPS-END + + +Node-path: mirror/arr/zzz +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 4 +Text-content-md5: 33b02bc15ce9557d2dd8484d58f95ac4 +Content-length: 14 + +PROPS-END +zzz + + +Revision-number: 3 +Prop-content-length: 230 +Content-length: 230 + +K 7 +svn:log +V 66 +new symlink is added to a file that was also just made executable + +K 10 +svn:author +V 7 +svnsync +K 11 +svm:headrev +V 39 +161ce429-a9dd-4828-af4a-52023f968c89:6 + +K 8 +svn:date +V 27 +2007-02-17T05:11:01.686891Z +PROPS-END + +Node-path: mirror/arr/zzz +Node-kind: file +Node-action: change +Prop-content-length: 36 +Text-content-length: 4 +Text-content-md5: 33b02bc15ce9557d2dd8484d58f95ac4 +Content-length: 40 + +K 14 +svn:executable +V 1 +* +PROPS-END +zzz + + +Revision-number: 4 +Prop-content-length: 192 +Content-length: 192 + +K 7 +svn:log +V 28 +/bar/d should be in the log + +K 10 +svn:author +V 7 +svnsync +K 11 +svm:headrev +V 39 +161ce429-a9dd-4828-af4a-52023f968c89:9 + +K 8 +svn:date +V 27 +2007-02-17T05:11:07.686552Z +PROPS-END + +Node-path: mirror/arr/d +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 4 +Text-content-md5: 0bee89b07a248e27c83fc3d5951213c1 +Content-length: 14 + +PROPS-END +abc + + +Revision-number: 5 +Prop-content-length: 185 +Content-length: 185 + +K 7 +svn:log +V 20 +add a new directory + +K 10 +svn:author +V 7 +svnsync +K 11 +svm:headrev +V 40 +161ce429-a9dd-4828-af4a-52023f968c89:10 + +K 8 +svn:date +V 27 +2007-02-17T05:11:08.405953Z +PROPS-END + +Node-path: mirror/arr/newdir +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: mirror/arr/newdir/dir +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 4 +Text-content-md5: 9cd599a3523898e6a12e13ec787da50a +Content-length: 14 + +PROPS-END +new + + +Revision-number: 6 +Prop-content-length: 196 +Content-length: 196 + +K 7 +svn:log +V 31 +modify a file in new directory + +K 10 +svn:author +V 7 +svnsync +K 11 +svm:headrev +V 40 +161ce429-a9dd-4828-af4a-52023f968c89:11 + +K 8 +svn:date +V 27 +2007-02-17T05:11:09.126645Z +PROPS-END + +Node-path: mirror/arr/newdir/dir +Node-kind: file +Node-action: change +Text-content-length: 8 +Text-content-md5: a950e20332358e523a5e9d571e47fa64 +Content-length: 8 + +new +foo + + +Revision-number: 7 +Prop-content-length: 179 +Content-length: 179 + +K 7 +svn:log +V 14 +update /bar/d + +K 10 +svn:author +V 7 +svnsync +K 11 +svm:headrev +V 40 +161ce429-a9dd-4828-af4a-52023f968c89:12 + +K 8 +svn:date +V 27 +2007-02-17T05:11:09.846221Z +PROPS-END + +Node-path: mirror/arr/d +Node-kind: file +Node-action: change +Text-content-length: 4 +Text-content-md5: 7abb78de7f2756ca8b511cbc879fd5e7 +Content-length: 4 + +cba + + +Revision-number: 8 +Prop-content-length: 201 +Content-length: 201 + +K 7 +svn:log +V 41 +SVM: initializing mirror for /mirror/argh +K 10 +svn:author +V 3 +svm +K 11 +svm:headrev +V 39 +161ce429-a9dd-4828-af4a-52023f968c89:0 + +K 8 +svn:date +V 27 +2007-02-17T06:56:03.703677Z +PROPS-END + +Node-path: +Node-kind: dir +Node-action: change +Prop-content-length: 57 +Content-length: 57 + +K 10 +svm:mirror +V 25 +/mirror/argh +/mirror/arr + +PROPS-END + + +Node-path: mirror/argh +Node-kind: dir +Node-action: add +Prop-content-length: 116 +Content-length: 116 + +K 10 +svm:source +V 29 +http://mayonaise/svnrepo!/dir +K 8 +svm:uuid +V 36 +161ce429-a9dd-4828-af4a-52023f968c89 +PROPS-END + + +Revision-number: 9 +Prop-content-length: 182 +Content-length: 182 + +K 7 +svn:log +V 18 +import for git-svn +K 10 +svn:author +V 7 +svnsync +K 11 +svm:headrev +V 39 +161ce429-a9dd-4828-af4a-52023f968c89:1 + +K 8 +svn:date +V 27 +2007-02-17T05:10:52.108847Z +PROPS-END + +Node-path: mirror/argh +Node-kind: dir +Node-action: change +Prop-content-length: 116 +Content-length: 116 + +K 10 +svm:source +V 29 +http://mayonaise/svnrepo!/dir +K 8 +svm:uuid +V 36 +161ce429-a9dd-4828-af4a-52023f968c89 +PROPS-END + + +Node-path: mirror/argh/a +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: mirror/argh/a/b +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: mirror/argh/a/b/c +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: mirror/argh/a/b/c/d +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: mirror/argh/a/b/c/d/e +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: mirror/argh/a/b/c/d/e/file +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 9 +Text-content-md5: 3fd46fe46fcdcf062c802ca60dc826d5 +Content-length: 19 + +PROPS-END +deep dir + + +Revision-number: 10 +Prop-content-length: 197 +Content-length: 197 + +K 7 +svn:log +V 33 +try a deep --rmdir with a commit + +K 10 +svn:author +V 7 +svnsync +K 11 +svm:headrev +V 39 +161ce429-a9dd-4828-af4a-52023f968c89:2 + +K 8 +svn:date +V 27 +2007-02-17T05:10:54.847015Z +PROPS-END + +Node-path: mirror/argh/file +Node-kind: file +Node-action: add +Node-copyfrom-rev: 9 +Node-copyfrom-path: mirror/argh/a/b/c/d/e/file +Text-content-length: 9 +Text-content-md5: 3fd46fe46fcdcf062c802ca60dc826d5 +Content-length: 9 + +deep dir + + +Node-path: mirror/argh/a +Node-action: delete + + -- cgit v1.3 From e2b36f6018062cfa44b6952ce73c0609db0af240 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 18 Feb 2007 01:30:35 -0800 Subject: git-svn: add test for useSvnsyncProps These tests are very similar as the ones I used for useSvmProps and expect the same results because both dumps were generated from the same original repo. Signed-off-by: Eric Wong --- t/t9111-git-svn-use-svnsync-props.sh | 51 ++++ t/t9111/svnsync.dump | 562 +++++++++++++++++++++++++++++++++++ 2 files changed, 613 insertions(+) create mode 100755 t/t9111-git-svn-use-svnsync-props.sh create mode 100644 t/t9111/svnsync.dump (limited to 't') diff --git a/t/t9111-git-svn-use-svnsync-props.sh b/t/t9111-git-svn-use-svnsync-props.sh new file mode 100755 index 0000000000..483d7f8159 --- /dev/null +++ b/t/t9111-git-svn-use-svnsync-props.sh @@ -0,0 +1,51 @@ +#!/bin/sh +# +# Copyright (c) 2007 Eric Wong +# + +test_description='git-svn useSvnsyncProps test' + +. ./lib-git-svn.sh + +test_expect_success 'load svnsync repo' " + svnadmin load -q $rawsvnrepo < ../t9111/svnsync.dump && + git-svn init -R arr -i bar $svnrepo/bar && + git-svn init -R argh -i dir $svnrepo/dir && + git-svn init -R argh -i e $svnrepo/dir/a/b/c/d/e && + git-config svn.useSvnsyncProps true && + git-svn fetch --all + " + +uuid=161ce429-a9dd-4828-af4a-52023f968c89 + +bar_url=http://mayonaise/svnrepo/bar +test_expect_success 'verify metadata for /bar' " + git-cat-file commit refs/remotes/bar | \ + grep '^git-svn-id: $bar_url@12 $uuid$' && + git-cat-file commit refs/remotes/bar~1 | \ + grep '^git-svn-id: $bar_url@11 $uuid$' && + git-cat-file commit refs/remotes/bar~2 | \ + grep '^git-svn-id: $bar_url@10 $uuid$' && + git-cat-file commit refs/remotes/bar~3 | \ + grep '^git-svn-id: $bar_url@9 $uuid$' && + git-cat-file commit refs/remotes/bar~4 | \ + grep '^git-svn-id: $bar_url@6 $uuid$' && + git-cat-file commit refs/remotes/bar~5 | \ + grep '^git-svn-id: $bar_url@1 $uuid$' + " + +e_url=http://mayonaise/svnrepo/dir/a/b/c/d/e +test_expect_success 'verify metadata for /dir/a/b/c/d/e' " + git-cat-file commit refs/remotes/e | \ + grep '^git-svn-id: $e_url@1 $uuid$' + " + +dir_url=http://mayonaise/svnrepo/dir +test_expect_success 'verify metadata for /dir' " + git-cat-file commit refs/remotes/dir | \ + grep '^git-svn-id: $dir_url@2 $uuid$' && + git-cat-file commit refs/remotes/dir~1 | \ + grep '^git-svn-id: $dir_url@1 $uuid$' + " + +test_done diff --git a/t/t9111/svnsync.dump b/t/t9111/svnsync.dump new file mode 100644 index 0000000000..a9a46eeb29 --- /dev/null +++ b/t/t9111/svnsync.dump @@ -0,0 +1,562 @@ +SVN-fs-dump-format-version: 2 + +UUID: b4bfe35e-f256-4096-874c-08c5639ecad7 + +Revision-number: 0 +Prop-content-length: 240 +Content-length: 240 + +K 18 +svn:sync-from-uuid +V 36 +161ce429-a9dd-4828-af4a-52023f968c89 +K 10 +svn:author +V 7 +svnsync +K 24 +svn:sync-last-merged-rev +V 2 +12 +K 8 +svn:date +V 27 +2007-02-17T05:10:52.017552Z +K 17 +svn:sync-from-url +V 24 +http://mayonaise/svnrepo +PROPS-END + +Revision-number: 1 +Prop-content-length: 120 +Content-length: 120 + +K 7 +svn:log +V 18 +import for git-svn +K 10 +svn:author +V 7 +svnsync +K 8 +svn:date +V 27 +2007-02-17T05:10:52.108847Z +PROPS-END + +Node-path: bar +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: bar/zzz +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 4 +Text-content-md5: 33b02bc15ce9557d2dd8484d58f95ac4 +Content-length: 14 + +PROPS-END +zzz + + +Node-path: dir +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: dir/a +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: dir/a/b +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: dir/a/b/c +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: dir/a/b/c/d +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: dir/a/b/c/d/e +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: dir/a/b/c/d/e/file +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 9 +Text-content-md5: 3fd46fe46fcdcf062c802ca60dc826d5 +Content-length: 19 + +PROPS-END +deep dir + + +Node-path: exec.sh +Node-kind: file +Node-action: add +Prop-content-length: 35 +Text-content-length: 10 +Text-content-md5: 3e2b31c72181b87149ff995e7202c0e3 +Content-length: 45 + +K 14 +svn:executable +V 0 + +PROPS-END +#!/bin/sh + + +Node-path: foo +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 4 +Text-content-md5: d3b07384d113edec49eaa6238ad5ff00 +Content-length: 14 + +PROPS-END +foo + + +Node-path: foo.link +Node-kind: file +Node-action: add +Prop-content-length: 33 +Text-content-length: 8 +Text-content-md5: 1043146e49ef02cab12eef865cb34ff3 +Content-length: 41 + +K 11 +svn:special +V 1 +* +PROPS-END +link foo + +Revision-number: 2 +Prop-content-length: 135 +Content-length: 135 + +K 7 +svn:log +V 33 +try a deep --rmdir with a commit + +K 10 +svn:author +V 7 +svnsync +K 8 +svn:date +V 27 +2007-02-17T05:10:54.847015Z +PROPS-END + +Node-path: dir/file +Node-kind: file +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: dir/a/b/c/d/e/file +Text-content-length: 9 +Text-content-md5: 3fd46fe46fcdcf062c802ca60dc826d5 +Content-length: 9 + +deep dir + + +Node-path: dir/a +Node-action: delete + + +Node-path: file +Node-kind: file +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: dir/a/b/c/d/e/file +Text-content-length: 9 +Text-content-md5: 3fd46fe46fcdcf062c802ca60dc826d5 +Content-length: 9 + +deep dir + + +Revision-number: 3 +Prop-content-length: 136 +Content-length: 136 + +K 7 +svn:log +V 34 +remove executable bit from a file + +K 10 +svn:author +V 7 +svnsync +K 8 +svn:date +V 27 +2007-02-17T05:10:58.232691Z +PROPS-END + +Node-path: exec.sh +Node-kind: file +Node-action: change +Prop-content-length: 10 +Text-content-length: 10 +Text-content-md5: 3e2b31c72181b87149ff995e7202c0e3 +Content-length: 20 + +PROPS-END +#!/bin/sh + + +Revision-number: 4 +Prop-content-length: 131 +Content-length: 131 + +K 7 +svn:log +V 29 +add executable bit back file + +K 10 +svn:author +V 7 +svnsync +K 8 +svn:date +V 27 +2007-02-17T05:10:59.666560Z +PROPS-END + +Node-path: exec.sh +Node-kind: file +Node-action: change +Prop-content-length: 36 +Text-content-length: 10 +Text-content-md5: 3e2b31c72181b87149ff995e7202c0e3 +Content-length: 46 + +K 14 +svn:executable +V 1 +* +PROPS-END +#!/bin/sh + + +Revision-number: 5 +Prop-content-length: 154 +Content-length: 154 + +K 7 +svn:log +V 52 +executable file becomes a symlink to bar/zzz (file) + +K 10 +svn:author +V 7 +svnsync +K 8 +svn:date +V 27 +2007-02-17T05:11:00.676495Z +PROPS-END + +Node-path: exec.sh +Node-kind: file +Node-action: change +Prop-content-length: 33 +Text-content-length: 12 +Text-content-md5: f138693371665cc117742508761d684d +Content-length: 45 + +K 11 +svn:special +V 1 +* +PROPS-END +link bar/zzz + +Revision-number: 6 +Prop-content-length: 168 +Content-length: 168 + +K 7 +svn:log +V 66 +new symlink is added to a file that was also just made executable + +K 10 +svn:author +V 7 +svnsync +K 8 +svn:date +V 27 +2007-02-17T05:11:01.686891Z +PROPS-END + +Node-path: bar/zzz +Node-kind: file +Node-action: change +Prop-content-length: 36 +Text-content-length: 4 +Text-content-md5: 33b02bc15ce9557d2dd8484d58f95ac4 +Content-length: 40 + +K 14 +svn:executable +V 1 +* +PROPS-END +zzz + + +Node-path: exec-2.sh +Node-kind: file +Node-action: add +Node-copyfrom-rev: 5 +Node-copyfrom-path: exec.sh +Text-content-length: 12 +Text-content-md5: f138693371665cc117742508761d684d +Content-length: 12 + +link bar/zzz + +Revision-number: 7 +Prop-content-length: 136 +Content-length: 136 + +K 7 +svn:log +V 34 +modify a symlink to become a file + +K 10 +svn:author +V 7 +svnsync +K 8 +svn:date +V 27 +2007-02-17T05:11:02.677035Z +PROPS-END + +Node-path: exec-2.sh +Node-kind: file +Node-action: change +Prop-content-length: 10 +Text-content-length: 9 +Text-content-md5: 8e92eff9e911886cede27d420f89c735 +Content-length: 19 + +PROPS-END +git help + + +Revision-number: 8 +Prop-content-length: 109 +Content-length: 109 + +K 7 +svn:log +V 8 +éï∏ + +K 10 +svn:author +V 7 +svnsync +K 8 +svn:date +V 27 +2007-02-17T05:11:03.676862Z +PROPS-END + +Node-path: exec-2.sh +Node-kind: file +Node-action: change +Text-content-length: 17 +Text-content-md5: 49881954063cf26ca48c212396a957ca +Content-length: 17 + +git help +# hello + + +Revision-number: 9 +Prop-content-length: 130 +Content-length: 130 + +K 7 +svn:log +V 28 +/bar/d should be in the log + +K 10 +svn:author +V 7 +svnsync +K 8 +svn:date +V 27 +2007-02-17T05:11:07.686552Z +PROPS-END + +Node-path: bar/d +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 4 +Text-content-md5: 0bee89b07a248e27c83fc3d5951213c1 +Content-length: 14 + +PROPS-END +abc + + +Revision-number: 10 +Prop-content-length: 122 +Content-length: 122 + +K 7 +svn:log +V 20 +add a new directory + +K 10 +svn:author +V 7 +svnsync +K 8 +svn:date +V 27 +2007-02-17T05:11:08.405953Z +PROPS-END + +Node-path: bar/newdir +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: bar/newdir/dir +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 4 +Text-content-md5: 9cd599a3523898e6a12e13ec787da50a +Content-length: 14 + +PROPS-END +new + + +Revision-number: 11 +Prop-content-length: 133 +Content-length: 133 + +K 7 +svn:log +V 31 +modify a file in new directory + +K 10 +svn:author +V 7 +svnsync +K 8 +svn:date +V 27 +2007-02-17T05:11:09.126645Z +PROPS-END + +Node-path: bar/newdir/dir +Node-kind: file +Node-action: change +Text-content-length: 8 +Text-content-md5: a950e20332358e523a5e9d571e47fa64 +Content-length: 8 + +new +foo + + +Revision-number: 12 +Prop-content-length: 116 +Content-length: 116 + +K 7 +svn:log +V 14 +update /bar/d + +K 10 +svn:author +V 7 +svnsync +K 8 +svn:date +V 27 +2007-02-17T05:11:09.846221Z +PROPS-END + +Node-path: bar/d +Node-kind: file +Node-action: change +Text-content-length: 4 +Text-content-md5: 7abb78de7f2756ca8b511cbc879fd5e7 +Content-length: 4 + +cba + + -- cgit v1.3 From 56cf9806a97f5fe9a91c38aa3eb8cec0c76480e6 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 24 Feb 2007 16:59:52 -0800 Subject: Update tests to use test-chmtime test-lib: Make sure test-chmtime has been built before starting. t4200-rerere: Removed non-portable date dependency and avoid touch Avoid "test -a" which isn't portable, either lib-git-svn: Use test-chmtime instead of Perl one-liner to poke Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano --- t/lib-git-svn.sh | 2 +- t/t4200-rerere.sh | 53 ++++++++++++++++++++--------------------------------- t/test-lib.sh | 6 ++++++ 3 files changed, 27 insertions(+), 34 deletions(-) (limited to 't') diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh index 27ad3b70f9..f6fe78cd27 100644 --- a/t/lib-git-svn.sh +++ b/t/lib-git-svn.sh @@ -46,5 +46,5 @@ rawsvnrepo="$svnrepo" svnrepo="file://$svnrepo" poke() { - perl -e '@x = stat($ARGV[0]); utime($x[8], $x[9] + 1, $ARGV[0])' "$1" + test-chmtime +1 "$1" } diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh index c571a1bd74..639d45fcec 100755 --- a/t/t4200-rerere.sh +++ b/t/t4200-rerere.sh @@ -112,39 +112,26 @@ rr2=.git/rr-cache/$sha2 mkdir $rr2 echo Hello > $rr2/preimage -case "$(date -d @11111111 +%s 2>/dev/null)" in -11111111) - # 'date' must be able to take arbitrary input with @11111111 notation. - # for this test to succeed. We should fix this part using more - # portable script someday. - - now=$(date +%s) - almost_15_days_ago=$(($now+60-15*86400)) - just_over_15_days_ago=$(($now-1-15*86400)) - almost_60_days_ago=$(($now+60-60*86400)) - just_over_60_days_ago=$(($now-1-60*86400)) - predate1="$(date -d "@$almost_60_days_ago" +%Y%m%d%H%M.%S)" - predate2="$(date -d "@$almost_15_days_ago" +%Y%m%d%H%M.%S)" - postdate1="$(date -d "@$just_over_60_days_ago" +%Y%m%d%H%M.%S)" - postdate2="$(date -d "@$just_over_15_days_ago" +%Y%m%d%H%M.%S)" - - touch -m -t "$predate1" $rr/preimage - touch -m -t "$predate2" $rr2/preimage - - test_expect_success 'garbage collection (part1)' 'git rerere gc' - - test_expect_success 'young records still live' \ - "test -f $rr/preimage -a -f $rr2/preimage" - - touch -m -t "$postdate1" $rr/preimage - touch -m -t "$postdate2" $rr2/preimage - - test_expect_success 'garbage collection (part2)' 'git rerere gc' - - test_expect_success 'old records rest in peace' \ - "test ! -f $rr/preimage -a ! -f $rr2/preimage" - ;; -esac +almost_15_days_ago=$((60-15*86400)) +just_over_15_days_ago=$((-1-15*86400)) +almost_60_days_ago=$((60-60*86400)) +just_over_60_days_ago=$((-1-60*86400)) + +test-chmtime =$almost_60_days_ago $rr/preimage +test-chmtime =$almost_15_days_ago $rr2/preimage + +test_expect_success 'garbage collection (part1)' 'git rerere gc' + +test_expect_success 'young records still live' \ + "test -f $rr/preimage && test -f $rr2/preimage" + +test-chmtime =$just_over_60_days_ago $rr/preimage +test-chmtime =$just_over_15_days_ago $rr2/preimage + +test_expect_success 'garbage collection (part2)' 'git rerere gc' + +test_expect_success 'old records rest in peace' \ + "test ! -f $rr/preimage && test ! -f $rr2/preimage" test_done diff --git a/t/test-lib.sh b/t/test-lib.sh index a403fe042b..c0754747fb 100755 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -264,6 +264,12 @@ test -d ../templates/blt || { error "You haven't built things yet, have you?" } +if ! test -x ../test-chmtime; then + echo >&2 'You need to build test-chmtime:' + echo >&2 'Run "make test-chmtime" in the source (toplevel) directory' + exit 1 +fi + # Test repository test=trash rm -fr "$test" -- cgit v1.3