{"thread":{"id":"3407","subject":"[PATCH 5/9] git-svn: change ; to && in addremove()","startedAt":"2006-02-20T18:57:28Z","lastAt":"2006-02-20T18:57:28Z","messageCount":4,"participants":["Eric Wong"],"isPatch":true,"patchVersion":1,"patchTotal":9},"messages":[{"id":"16461","messageId":"11404618481876-git-send-email-normalperson@yhbt.net","threadId":"3407","inReplyTo":"11404618483821-git-send-email-normalperson@yhbt.net","subject":"[PATCH 5/9] git-svn: change ; to && in addremove()","fromName":"Eric Wong","fromEmail":"normalperson@yhbt.net","sentAt":"2006-02-20T18:57:28Z","receivedAt":"2006-02-20T18:57:28Z","isPatch":true,"sender":{"key":"e@80x24.org","avatar":null},"body":"Signed-off-by: Eric Wong <normalperson@yhbt.net>\n\n---\n\n contrib/git-svn/git-svn |    4 ++--\n 1 files changed, 2 insertions(+), 2 deletions(-)\n\nff01f98a865018be26baf1008c669009e95a93bf\ndiff --git a/contrib/git-svn/git-svn b/contrib/git-svn/git-svn\nindex 4391bc3..25c248d 100755\n--- a/contrib/git-svn/git-svn\n+++ b/contrib/git-svn/git-svn\n@@ -580,10 +580,10 @@ sub sys { system(@_) == 0 or croak $? }\n \n sub git_addremove {\n \tsystem( \"git-diff-files --name-only -z \".\n-\t\t\t\t\" | git-update-index --remove -z --stdin; \".\n+\t\t\t\t\" | git-update-index --remove -z --stdin && \".\n \t\t\"git-ls-files -z --others \".\n \t\t\t\"'--exclude-from=$GIT_DIR/$GIT_SVN/info/exclude'\".\n-\t\t\t\t\" | git-update-index --add -z --stdin; \"\n+\t\t\t\t\" | git-update-index --add -z --stdin\"\n \t\t) == 0 or croak $?\n }\n \n-- \n1.2.0.gdee6\n"},{"id":"16462","messageId":"11404618483094-git-send-email-normalperson@yhbt.net","threadId":"3407","inReplyTo":"11404618481876-git-send-email-normalperson@yhbt.net","subject":"[PATCH 6/9] contrib/git-svn.txt: add a note about renamed/copied directory support","fromName":"Eric Wong","fromEmail":"normalperson@yhbt.net","sentAt":"2006-02-20T18:57:28Z","receivedAt":"2006-02-20T18:57:28Z","isPatch":true,"sender":{"key":"e@80x24.org","avatar":null},"body":"Signed-off-by: Eric Wong <normalperson@yhbt.net>\n\n---\n\n contrib/git-svn/git-svn.txt |    7 +++++++\n 1 files changed, 7 insertions(+), 0 deletions(-)\n\n72331d46b99b182406e06070e905123f76abbac8\ndiff --git a/contrib/git-svn/git-svn.txt b/contrib/git-svn/git-svn.txt\nindex 07a236f..cf098d7 100644\n--- a/contrib/git-svn/git-svn.txt\n+++ b/contrib/git-svn/git-svn.txt\n@@ -206,6 +206,13 @@ working trees with metadata files.\n svn:keywords can't be ignored in Subversion (at least I don't know of\n a way to ignore them).\n \n+Renamed and copied directories are not detected by git and hence not\n+tracked when committing to SVN.  I do not plan on adding support for\n+this as it's quite difficult and time-consuming to get working for all\n+the possible corner cases (git doesn't do it, either).  Renamed and\n+copied files are fully supported if they're similar enough for git to\n+detect them.\n+\n Author\n ------\n Written by Eric Wong <normalperson@yhbt.net>.\n-- \n1.2.0.gdee6\n"},{"id":"16463","messageId":"11404618483587-git-send-email-normalperson@yhbt.net","threadId":"3407","inReplyTo":"11404618483094-git-send-email-normalperson@yhbt.net","subject":"[PATCH 7/9] git-svn: fix several corner-case and rare bugs with 'commit'","fromName":"Eric Wong","fromEmail":"normalperson@yhbt.net","sentAt":"2006-02-20T18:57:28Z","receivedAt":"2006-02-20T18:57:28Z","isPatch":true,"sender":{"key":"e@80x24.org","avatar":null},"body":"None of these were really show-stoppers (or even triggered)\non most of the trees I've tracked.\n\n* Node change prevention for identically named nodes.  This is\n  a limitation of SVN, but we find the error and exit before\n  it's passed to SVN so we don't dirty our working tree when our\n  commit fails.  git-svn will exit with an error code 1 if any\n  of the following conditions are found:\n\n  1.  a directory is removed and a file of the same name of the\n      removed directory is created\n  1a. a file has its parent directory removed and the file is\n      takes the name of the removed parent directory::\n          baz/zzz    =>  baz\n  2.  a file is removed and a directory of the same name of the\n      removed file is created.\n  2a. a file is moved into a deeper directory that shares the\n      previous name of the file::\n          dir/$file  =>  dir/file/$file\n\n  Since SVN cannot handle these cases, the user will have to\n  manually split the commit into several parts.\n\n* --rmdir now handles nested/deep removals. If dir/a/b/c/d/e/file\n  is removed, and everything else is in the dir/ hierarchy is\n  otherwise empty, then dir/ will be deleted when file is deleted\n  from svn and --rmdir specified.\n\n* Always assert that we have written the tree we want to write\n  on commits.  This helped me find several bugs in the symlink\n  handling code (which as been fixed).\n\n* Several symlink handling fixes.  We now refuse to set\n  permissions on symlinks.  We also always unlink a file\n  if we're going to overwrite it.\n\n* Apply changes in a pre-determined order, so we always have\n  rename from locations handy before we delete them.\n\nSigned-off-by: Eric Wong <normalperson@yhbt.net>\n\n---\n\n contrib/git-svn/git-svn |  260 ++++++++++++++++++++++++++++++++++++-----------\n 1 files changed, 200 insertions(+), 60 deletions(-)\n\ncc9e5f27fee073de35fd6f50ec15ea59dda8b71b\ndiff --git a/contrib/git-svn/git-svn b/contrib/git-svn/git-svn\nindex 25c248d..3a59454 100755\n--- a/contrib/git-svn/git-svn\n+++ b/contrib/git-svn/git-svn\n@@ -238,7 +238,11 @@ sub commit {\n \tmy $svn_current_rev =  svn_info('.')->{'Last Changed Rev'};\n \tforeach my $c (@revs) {\n \t\tprint \"Committing $c\\n\";\n-\t\tsvn_checkout_tree($svn_current_rev, $c);\n+\t\tmy $mods = svn_checkout_tree($svn_current_rev, $c);\n+\t\tif (scalar @$mods == 0) {\n+\t\t\tprint \"Skipping, no changes detected\\n\";\n+\t\t\tnext;\n+\t\t}\n \t\t$svn_current_rev = svn_commit_tree($svn_current_rev, $c);\n \t}\n \tprint \"Done committing \",scalar @revs,\" revisions to SVN\\n\";\n@@ -267,9 +271,9 @@ sub setup_git_svn {\n }\n \n sub assert_svn_wc_clean {\n-\tmy ($svn_rev, $commit) = @_;\n+\tmy ($svn_rev, $treeish) = @_;\n \tcroak \"$svn_rev is not an integer!\\n\" unless ($svn_rev =~ /^\\d+$/);\n-\tcroak \"$commit is not a sha1!\\n\" unless ($commit =~ /^$sha1$/o);\n+\tcroak \"$treeish is not a sha1!\\n\" unless ($treeish =~ /^$sha1$/o);\n \tmy $svn_info = svn_info('.');\n \tif ($svn_rev != $svn_info->{'Last Changed Rev'}) {\n \t\tcroak \"Expected r$svn_rev, got r\",\n@@ -282,12 +286,42 @@ sub assert_svn_wc_clean {\n \t\tprint STDERR $_ foreach @status;\n \t\tcroak;\n \t}\n-\tmy ($tree_a) = grep(/^tree $sha1$/o,`git-cat-file commit $commit`);\n-\t$tree_a =~ s/^tree //;\n-\tchomp $tree_a;\n-\tchomp(my $tree_b = `GIT_INDEX_FILE=$GIT_SVN_INDEX git-write-tree`);\n-\tif ($tree_a ne $tree_b) {\n-\t\tcroak \"$svn_rev != $commit, $tree_a != $tree_b\\n\";\n+\tassert_tree($treeish);\n+}\n+\n+sub assert_tree {\n+\tmy ($treeish) = @_;\n+\tcroak \"Not a sha1: $treeish\\n\" unless $treeish =~ /^$sha1$/o;\n+\tchomp(my $type = `git-cat-file -t $treeish`);\n+\tmy $expected;\n+\twhile ($type eq 'tag') {\n+\t\tchomp(($treeish, $type) = `git-cat-file tag $treeish`);\n+\t}\n+\tif ($type eq 'commit') {\n+\t\t$expected = (grep /^tree /,`git-cat-file commit $treeish`)[0];\n+\t\t($expected) = ($expected =~ /^tree ($sha1)$/);\n+\t\tdie \"Unable to get tree from $treeish\\n\" unless $expected;\n+\t} elsif ($type eq 'tree') {\n+\t\t$expected = $treeish;\n+\t} else {\n+\t\tdie \"$treeish is a $type, expected tree, tag or commit\\n\";\n+\t}\n+\n+\tmy $old_index = $ENV{GIT_INDEX_FILE};\n+\tmy $tmpindex = $GIT_SVN_INDEX.'.assert-tmp';\n+\tif (-e $tmpindex) {\n+\t\tunlink $tmpindex or croak $!;\n+\t}\n+\t$ENV{GIT_INDEX_FILE} = $tmpindex;\n+\tgit_addremove();\n+\tchomp(my $tree = `git-write-tree`);\n+\tif ($old_index) {\n+\t\t$ENV{GIT_INDEX_FILE} = $old_index;\n+\t} else {\n+\t\tdelete $ENV{GIT_INDEX_FILE};\n+\t}\n+\tif ($tree ne $expected) {\n+\t\tcroak \"Tree mismatch, Got: $tree, Expected: $expected\\n\";\n \t}\n }\n \n@@ -298,7 +332,6 @@ sub parse_diff_tree {\n \tmy @mods;\n \twhile (<$diff_fh>) {\n \t\tchomp $_; # this gets rid of the trailing \"\\0\"\n-\t\tprint $_,\"\\n\";\n \t\tif ($state eq 'meta' && /^:(\\d{6})\\s(\\d{6})\\s\n \t\t\t\t\t$sha1\\s($sha1)\\s([MTCRAD])\\d*$/xo) {\n \t\t\tpush @mods, {\tmode_a => $1, mode_b => $2,\n@@ -309,36 +342,44 @@ sub parse_diff_tree {\n \t\t\t\t$state = 'file_b';\n \t\t\t}\n \t\t} elsif ($state eq 'file_a') {\n-\t\t\tmy $x = $mods[$#mods] or croak __LINE__,\": Empty array\\n\";\n+\t\t\tmy $x = $mods[$#mods] or croak \"Empty array\\n\";\n \t\t\tif ($x->{chg} !~ /^(?:C|R)$/) {\n-\t\t\t\tcroak __LINE__,\": Error parsing $_, $x->{chg}\\n\";\n+\t\t\t\tcroak \"Error parsing $_, $x->{chg}\\n\";\n \t\t\t}\n \t\t\t$x->{file_a} = $_;\n \t\t\t$state = 'file_b';\n \t\t} elsif ($state eq 'file_b') {\n-\t\t\tmy $x = $mods[$#mods] or croak __LINE__,\": Empty array\\n\";\n+\t\t\tmy $x = $mods[$#mods] or croak \"Empty array\\n\";\n \t\t\tif (exists $x->{file_a} && $x->{chg} !~ /^(?:C|R)$/) {\n-\t\t\t\tcroak __LINE__,\": Error parsing $_, $x->{chg}\\n\";\n+\t\t\t\tcroak \"Error parsing $_, $x->{chg}\\n\";\n \t\t\t}\n \t\t\tif (!exists $x->{file_a} && $x->{chg} =~ /^(?:C|R)$/) {\n-\t\t\t\tcroak __LINE__,\": Error parsing $_, $x->{chg}\\n\";\n+\t\t\t\tcroak \"Error parsing $_, $x->{chg}\\n\";\n \t\t\t}\n \t\t\t$x->{file_b} = $_;\n \t\t\t$state = 'meta';\n \t\t} else {\n-\t\t\tcroak __LINE__,\": Error parsing $_\\n\";\n+\t\t\tcroak \"Error parsing $_\\n\";\n \t\t}\n \t}\n \tclose $diff_fh or croak $!;\n+\n \treturn \\@mods;\n }\n \n sub svn_check_prop_executable {\n \tmy $m = shift;\n-\tif ($m->{mode_b} =~ /755$/ && $m->{mode_a} !~ /755$/) {\n-\t\tsys(qw(svn propset svn:executable 1), $m->{file_b});\n+\treturn if -l $m->{file_b};\n+\tif ($m->{mode_b} =~ /755$/) {\n+\t\tchmod((0755 &~ umask),$m->{file_b}) or croak $!;\n+\t\tif ($m->{mode_a} !~ /755$/) {\n+\t\t\tsys(qw(svn propset svn:executable 1), $m->{file_b});\n+\t\t}\n+\t\t-x $m->{file_b} or croak \"$m->{file_b} is not executable!\\n\";\n \t} elsif ($m->{mode_b} !~ /755$/ && $m->{mode_a} =~ /755$/) {\n \t\tsys(qw(svn propdel svn:executable), $m->{file_b});\n+\t\tchmod((0644 &~ umask),$m->{file_b}) or croak $!;\n+\t\t-x $m->{file_b} and croak \"$m->{file_b} is executable!\\n\";\n \t}\n }\n \n@@ -349,84 +390,166 @@ sub svn_ensure_parent_path {\n \tsys(qw(svn add -N), $dir_b) unless (-d \"$dir_b/.svn\");\n }\n \n+sub precommit_check {\n+\tmy $mods = shift;\n+\tmy (%rm_file, %rmdir_check, %added_check);\n+\n+\tmy %o = ( D => 0, R => 1, C => 2, A => 3, M => 3, T => 3 );\n+\tforeach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {\n+\t\tif ($m->{chg} eq 'R') {\n+\t\t\tif (-d $m->{file_b}) {\n+\t\t\t\terr_dir_to_file(\"$m->{file_a} => $m->{file_b}\");\n+\t\t\t}\n+\t\t\t# dir/$file => dir/file/$file\n+\t\t\tmy $dirname = dirname($m->{file_b});\n+\t\t\twhile ($dirname ne File::Spec->curdir) {\n+\t\t\t\tif ($dirname ne $m->{file_a}) {\n+\t\t\t\t\t$dirname = dirname($dirname);\n+\t\t\t\t\tnext;\n+\t\t\t\t}\n+\t\t\t\terr_file_to_dir(\"$m->{file_a} => $m->{file_b}\");\n+\t\t\t}\n+\t\t\t# baz/zzz => baz (baz is a file)\n+\t\t\t$dirname = dirname($m->{file_a});\n+\t\t\twhile ($dirname ne File::Spec->curdir) {\n+\t\t\t\tif ($dirname ne $m->{file_b}) {\n+\t\t\t\t\t$dirname = dirname($dirname);\n+\t\t\t\t\tnext;\n+\t\t\t\t}\n+\t\t\t\terr_dir_to_file(\"$m->{file_a} => $m->{file_b}\");\n+\t\t\t}\n+\t\t}\n+\t\tif ($m->{chg} =~ /^(D|R)$/) {\n+\t\t\tmy $t = $1 eq 'D' ? 'file_b' : 'file_a';\n+\t\t\t$rm_file{ $m->{$t} } = 1;\n+\t\t\tmy $dirname = dirname( $m->{$t} );\n+\t\t\tmy $basename = basename( $m->{$t} );\n+\t\t\t$rmdir_check{$dirname}->{$basename} = 1;\n+\t\t} elsif ($m->{chg} =~ /^(?:A|C)$/) {\n+\t\t\tif (-d $m->{file_b}) {\n+\t\t\t\terr_dir_to_file($m->{file_b});\n+\t\t\t}\n+\t\t\tmy $dirname = dirname( $m->{file_b} );\n+\t\t\tmy $basename = basename( $m->{file_b} );\n+\t\t\t$added_check{$dirname}->{$basename} = 1;\n+\t\t\twhile ($dirname ne File::Spec->curdir) {\n+\t\t\t\tif ($rm_file{$dirname}) {\n+\t\t\t\t\terr_file_to_dir($m->{file_b});\n+\t\t\t\t}\n+\t\t\t\t$dirname = dirname $dirname;\n+\t\t\t}\n+\t\t}\n+\t}\n+\treturn (\\%rmdir_check, \\%added_check);\n+\n+\tsub err_dir_to_file {\n+\t\tmy $file = shift;\n+\t\tprint STDERR \"Node change from directory to file \",\n+\t\t\t\t\"is not supported by Subversion: \",$file,\"\\n\";\n+\t\texit 1;\n+\t}\n+\tsub err_file_to_dir {\n+\t\tmy $file = shift;\n+\t\tprint STDERR \"Node change from file to directory \",\n+\t\t\t\t\"is not supported by Subversion: \",$file,\"\\n\";\n+\t\texit 1;\n+\t}\n+}\n+\n sub svn_checkout_tree {\n-\tmy ($svn_rev, $commit) = @_;\n+\tmy ($svn_rev, $treeish) = @_;\n \tmy $from = file_to_s(\"$REV_DIR/$svn_rev\");\n \tassert_svn_wc_clean($svn_rev,$from);\n-\tprint \"diff-tree '$from' '$commit'\\n\";\n+\tprint \"diff-tree '$from' '$treeish'\\n\";\n \tmy $pid = open my $diff_fh, '-|';\n \tdefined $pid or croak $!;\n \tif ($pid == 0) {\n \t\tmy @diff_tree = qw(git-diff-tree -z -r -C);\n \t\tpush @diff_tree, '--find-copies-harder' if $_find_copies_harder;\n \t\tpush @diff_tree, \"-l$_l\" if defined $_l;\n-\t\texec(@diff_tree, $from, $commit) or croak $!;\n+\t\texec(@diff_tree, $from, $treeish) or croak $!;\n \t}\n \tmy $mods = parse_diff_tree($diff_fh);\n \tunless (@$mods) {\n \t\t# git can do empty commits, SVN doesn't allow it...\n-\t\treturn $svn_rev;\n+\t\treturn $mods;\n \t}\n-\tmy %rm;\n-\tforeach my $m (@$mods) {\n+\tmy ($rm, $add) = precommit_check($mods);\n+\n+\tmy %o = ( D => 1, R => 0, C => -1, A => 3, M => 3, T => 3 );\n+\tforeach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {\n \t\tif ($m->{chg} eq 'C') {\n \t\t\tsvn_ensure_parent_path( $m->{file_b} );\n \t\t\tsys(qw(svn cp),\t\t$m->{file_a}, $m->{file_b});\n-\t\t\tblob_to_file(\t\t$m->{sha1_b}, $m->{file_b});\n+\t\t\tapply_mod_line_blob($m);\n \t\t\tsvn_check_prop_executable($m);\n \t\t} elsif ($m->{chg} eq 'D') {\n-\t\t\t$rm{dirname $m->{file_b}}->{basename $m->{file_b}} = 1;\n \t\t\tsys(qw(svn rm --force), $m->{file_b});\n \t\t} elsif ($m->{chg} eq 'R') {\n \t\t\tsvn_ensure_parent_path( $m->{file_b} );\n \t\t\tsys(qw(svn mv --force), $m->{file_a}, $m->{file_b});\n-\t\t\tblob_to_file(\t\t$m->{sha1_b}, $m->{file_b});\n+\t\t\tapply_mod_line_blob($m);\n \t\t\tsvn_check_prop_executable($m);\n-\t\t\t$rm{dirname $m->{file_a}}->{basename $m->{file_a}} = 1;\n \t\t} elsif ($m->{chg} eq 'M') {\n-\t\t\tif ($m->{mode_b} =~ /^120/ && $m->{mode_a} =~ /^120/) {\n-\t\t\t\tunlink $m->{file_b} or croak $!;\n-\t\t\t\tblob_to_symlink($m->{sha1_b}, $m->{file_b});\n-\t\t\t} else {\n-\t\t\t\tblob_to_file($m->{sha1_b}, $m->{file_b});\n-\t\t\t}\n+\t\t\tapply_mod_line_blob($m);\n \t\t\tsvn_check_prop_executable($m);\n \t\t} elsif ($m->{chg} eq 'T') {\n \t\t\tsys(qw(svn rm --force),$m->{file_b});\n-\t\t\tif ($m->{mode_b} =~ /^120/ && $m->{mode_a} =~ /^100/) {\n-\t\t\t\tblob_to_symlink($m->{sha1_b}, $m->{file_b});\n-\t\t\t} else {\n-\t\t\t\tblob_to_file($m->{sha1_b}, $m->{file_b});\n-\t\t\t}\n-\t\t\tsvn_check_prop_executable($m);\n+\t\t\tapply_mod_line_blob($m);\n \t\t\tsys(qw(svn add --force), $m->{file_b});\n+\t\t\tsvn_check_prop_executable($m);\n \t\t} elsif ($m->{chg} eq 'A') {\n \t\t\tsvn_ensure_parent_path( $m->{file_b} );\n-\t\t\tblob_to_file(\t\t$m->{sha1_b}, $m->{file_b});\n-\t\t\tif ($m->{mode_b} =~ /755$/) {\n-\t\t\t\tchmod 0755, $m->{file_b};\n-\t\t\t}\n+\t\t\tapply_mod_line_blob($m);\n \t\t\tsys(qw(svn add --force), $m->{file_b});\n+\t\t\tsvn_check_prop_executable($m);\n \t\t} else {\n \t\t\tcroak \"Invalid chg: $m->{chg}\\n\";\n \t\t}\n \t}\n-\tif ($_rmdir) {\n-\t\tmy $old_index = $ENV{GIT_INDEX_FILE};\n-\t\t$ENV{GIT_INDEX_FILE} = $GIT_SVN_INDEX;\n-\t\tforeach my $dir (keys %rm) {\n-\t\t\tmy $files = $rm{$dir};\n-\t\t\tmy @files;\n-\t\t\tforeach (safe_qx('svn','ls',$dir)) {\n-\t\t\t\tchomp;\n-\t\t\t\tpush @files, $_ unless $files->{$_};\n-\t\t\t}\n-\t\t\tsys(qw(svn rm),$dir) unless @files;\n-\t\t}\n-\t\tif ($old_index) {\n-\t\t\t$ENV{GIT_INDEX_FILE} = $old_index;\n-\t\t} else {\n-\t\t\tdelete $ENV{GIT_INDEX_FILE};\n+\n+\tassert_tree($treeish);\n+\tif ($_rmdir) { # remove empty directories\n+\t\thandle_rmdir($rm, $add);\n+\t}\n+\tassert_tree($treeish);\n+\treturn $mods;\n+}\n+\n+# svn ls doesn't work with respect to the current working tree, but what's\n+# in the repository.  There's not even an option for it... *sigh*\n+# (added files don't show up and removed files remain in the ls listing)\n+sub svn_ls_current {\n+\tmy ($dir, $rm, $add) = @_;\n+\tchomp(my @ls = safe_qx('svn','ls',$dir));\n+\tmy @ret = ();\n+\tforeach (@ls) {\n+\t\ts#/$##; # trailing slashes are evil\n+\t\tpush @ret, $_ unless $rm->{$dir}->{$_};\n+\t}\n+\tif (exists $add->{$dir}) {\n+\t\tpush @ret, keys %{$add->{$dir}};\n+\t}\n+\treturn \\@ret;\n+}\n+\n+sub handle_rmdir {\n+\tmy ($rm, $add) = @_;\n+\n+\tforeach my $dir (sort {length $b <=> length $a} keys %$rm) {\n+\t\tmy $ls = svn_ls_current($dir, $rm, $add);\n+\t\tnext if (scalar @$ls);\n+\t\tsys(qw(svn rm --force),$dir);\n+\n+\t\tmy $dn = dirname $dir;\n+\t\t$rm->{ $dn }->{ basename $dir } = 1;\n+\t\t$ls = svn_ls_current($dn, $rm, $add);\n+\t\twhile (scalar @$ls == 0 && $dn ne File::Spec->curdir) {\n+\t\t\tsys(qw(svn rm --force),$dn);\n+\t\t\t$dir = basename $dn;\n+\t\t\t$dn = dirname $dn;\n+\t\t\t$rm->{ $dn }->{ $dir } = 1;\n+\t\t\t$ls = svn_ls_current($dn, $rm, $add);\n \t\t}\n \t}\n }\n@@ -692,10 +815,23 @@ sub git_commit {\n \treturn $commit;\n }\n \n+sub apply_mod_line_blob {\n+\tmy $m = shift;\n+\tif ($m->{mode_b} =~ /^120/) {\n+\t\tblob_to_symlink($m->{sha1_b}, $m->{file_b});\n+\t} else {\n+\t\tblob_to_file($m->{sha1_b}, $m->{file_b});\n+\t}\n+}\n+\n sub blob_to_symlink {\n \tmy ($blob, $link) = @_;\n \tdefined $link or croak \"\\$link not defined!\\n\";\n \tcroak \"Not a sha1: $blob\\n\" unless $blob =~ /^$sha1$/o;\n+\tif (-l $link || -f _) {\n+\t\tunlink $link or croak $!;\n+\t}\n+\n \tmy $dest = `git-cat-file blob $blob`; # no newline, so no chomp\n \tsymlink $dest, $link or croak $!;\n }\n@@ -704,6 +840,10 @@ sub blob_to_file {\n \tmy ($blob, $file) = @_;\n \tdefined $file or croak \"\\$file not defined!\\n\";\n \tcroak \"Not a sha1: $blob\\n\" unless $blob =~ /^$sha1$/o;\n+\tif (-l $file || -f _) {\n+\t\tunlink $file or croak $!;\n+\t}\n+\n \topen my $blob_fh, '>', $file or croak \"$!: $file\\n\";\n \tmy $pid = fork;\n \tdefined $pid or croak $!;\n-- \n1.2.0.gdee6\n"},{"id":"16467","messageId":"11404618483821-git-send-email-normalperson@yhbt.net","threadId":"3407","inReplyTo":"1140461846433-git-send-email-normalperson@yhbt.net","subject":"[PATCH 4/9] git-svn: remove any need for the XML::Simple dependency","fromName":"Eric Wong","fromEmail":"normalperson@yhbt.net","sentAt":"2006-02-20T18:57:28Z","receivedAt":"2006-02-20T18:57:28Z","isPatch":true,"sender":{"key":"e@80x24.org","avatar":null},"body":"XML::Simple was originally required back when I made svn-arch-mirror\nbecause I needed to explictly track renames with Arch.  Then I carried\nit over to git-svn because I was afraid somebody could commit an svn\nlog message that could throw off a non-XML log parser.  Then I noticed\nthe <n> lines column in the header.  So, no more XML :)\n\nSigned-off-by: Eric Wong <normalperson@yhbt.net>\n\n---\n\n contrib/git-svn/git-svn |   84 ++++++++++++++++++++---------------------------\n 1 files changed, 35 insertions(+), 49 deletions(-)\n\n9b380ed2f8f1b18f95d12b86cb760f95e6e0cefe\ndiff --git a/contrib/git-svn/git-svn b/contrib/git-svn/git-svn\nindex 5f23d6b..4391bc3 100755\n--- a/contrib/git-svn/git-svn\n+++ b/contrib/git-svn/git-svn\n@@ -21,7 +21,7 @@ $ENV{LC_ALL} = 'C';\n \n # If SVN:: library support is added, please make the dependencies\n # optional and preserve the capability to use the command-line client.\n-# See what I do with XML::Simple to make the dependency optional.\n+# use eval { require SVN::... } to make it lazy load\n use Carp qw/croak/;\n use IO::File qw//;\n use File::Basename qw/dirname basename/;\n@@ -177,8 +177,7 @@ sub fetch {\n \tpush @log_args, \"-r$_revision\";\n \tpush @log_args, '--stop-on-copy' unless $_no_stop_copy;\n \n-\teval { require XML::Simple or croak $! };\n-\tmy $svn_log = $@ ? svn_log_raw(@log_args) : svn_log_xml(@log_args);\n+\tmy $svn_log = svn_log_raw(@log_args);\n \t@$svn_log = sort { $a->{revision} <=> $b->{revision} } @$svn_log;\n \n \tmy $base = shift @$svn_log or croak \"No base revision!\\n\";\n@@ -476,49 +475,6 @@ sub svn_commit_tree {\n \treturn fetch(\"$rev_committed=$commit\")->{revision};\n }\n \n-sub svn_log_xml {\n-\tmy (@log_args) = @_;\n-\tmy $log_fh = IO::File->new_tmpfile or croak $!;\n-\n-\tmy $pid = fork;\n-\tdefined $pid or croak $!;\n-\n-\tif ($pid == 0) {\n-\t\topen STDOUT, '>&', $log_fh or croak $!;\n-\t\texec (qw(svn log --xml), @log_args) or croak $!\n-\t}\n-\n-\twaitpid $pid, 0;\n-\tcroak $? if $?;\n-\n-\tseek $log_fh, 0, 0;\n-\tmy @svn_log;\n-\tmy $log = XML::Simple::XMLin( $log_fh,\n-\t\t\t\tForceArray => ['path','revision','logentry'],\n-\t\t\t\tKeepRoot => 0,\n-\t\t\t\tKeyAttr => {\tlogentry => '+revision',\n-\t\t\t\t\t\tpaths => '+path' },\n-\t\t\t)->{logentry};\n-\tforeach my $r (sort {$a <=> $b} keys %$log) {\n-\t\tmy $log_msg = $log->{$r};\n-\t\tmy ($Y,$m,$d,$H,$M,$S) = ($log_msg->{date} =~\n-\t\t\t\t\t/(\\d{4})\\-(\\d\\d)\\-(\\d\\d)T\n-\t\t\t\t\t (\\d\\d)\\:(\\d\\d)\\:(\\d\\d)\\.\\d+Z$/x)\n-\t\t\t\t\t or croak \"Failed to parse date: \",\n-\t\t\t\t\t\t $log->{$r}->{date};\n-\t\t$log_msg->{date} = \"+0000 $Y-$m-$d $H:$M:$S\";\n-\n-\t\t# XML::Simple can't handle <msg></msg> as a string:\n-\t\tif (ref $log_msg->{msg} eq 'HASH') {\n-\t\t\t$log_msg->{msg} = \"\\n\";\n-\t\t} else {\n-\t\t\t$log_msg->{msg} .= \"\\n\";\n-\t\t}\n-\t\tpush @svn_log, $log->{$r};\n-\t}\n-\treturn \\@svn_log;\n-}\n-\n sub svn_log_raw {\n \tmy (@log_args) = @_;\n \tmy $pid = open my $log_fh,'-|';\n@@ -529,21 +485,42 @@ sub svn_log_raw {\n \t}\n \n \tmy @svn_log;\n-\tmy $state;\n+\tmy $state = 'sep';\n \twhile (<$log_fh>) {\n \t\tchomp;\n \t\tif (/^\\-{72}$/) {\n+\t\t\tif ($state eq 'msg') {\n+\t\t\t\tif ($svn_log[$#svn_log]->{lines}) {\n+\t\t\t\t\t$svn_log[$#svn_log]->{msg} .= $_.\"\\n\";\n+\t\t\t\t\tunless(--$svn_log[$#svn_log]->{lines}) {\n+\t\t\t\t\t\t$state = 'sep';\n+\t\t\t\t\t}\n+\t\t\t\t} else {\n+\t\t\t\t\tcroak \"Log parse error at: $_\\n\",\n+\t\t\t\t\t\t$svn_log[$#svn_log]->{revision},\n+\t\t\t\t\t\t\"\\n\";\n+\t\t\t\t}\n+\t\t\t\tnext;\n+\t\t\t}\n+\t\t\tif ($state ne 'sep') {\n+\t\t\t\tcroak \"Log parse error at: $_\\n\",\n+\t\t\t\t\t\"state: $state\\n\",\n+\t\t\t\t\t$svn_log[$#svn_log]->{revision},\n+\t\t\t\t\t\"\\n\";\n+\t\t\t}\n \t\t\t$state = 'rev';\n \n \t\t\t# if we have an empty log message, put something there:\n \t\t\tif (@svn_log) {\n \t\t\t\t$svn_log[$#svn_log]->{msg} ||= \"\\n\";\n+\t\t\t\tdelete $svn_log[$#svn_log]->{lines};\n \t\t\t}\n \t\t\tnext;\n \t\t}\n \t\tif ($state eq 'rev' && s/^r(\\d+)\\s*\\|\\s*//) {\n \t\t\tmy $rev = $1;\n-\t\t\tmy ($author, $date) = split(/\\s*\\|\\s*/, $_, 2);\n+\t\t\tmy ($author, $date, $lines) = split(/\\s*\\|\\s*/, $_, 3);\n+\t\t\t($lines) = ($lines =~ /(\\d+)/);\n \t\t\tmy ($Y,$m,$d,$H,$M,$S,$tz) = ($date =~\n \t\t\t\t\t/(\\d{4})\\-(\\d\\d)\\-(\\d\\d)\\s\n \t\t\t\t\t (\\d\\d)\\:(\\d\\d)\\:(\\d\\d)\\s([\\-\\+]\\d+)/x)\n@@ -551,6 +528,7 @@ sub svn_log_raw {\n \t\t\tmy %log_msg = (\trevision => $rev,\n \t\t\t\t\tdate => \"$tz $Y-$m-$d $H:$M:$S\",\n \t\t\t\t\tauthor => $author,\n+\t\t\t\t\tlines => $lines,\n \t\t\t\t\tmsg => '' );\n \t\t\tpush @svn_log, \\%log_msg;\n \t\t\t$state = 'msg_start';\n@@ -560,7 +538,15 @@ sub svn_log_raw {\n \t\tif ($state eq 'msg_start' && /^$/) {\n \t\t\t$state = 'msg';\n \t\t} elsif ($state eq 'msg') {\n-\t\t\t$svn_log[$#svn_log]->{msg} .= $_.\"\\n\";\n+\t\t\tif ($svn_log[$#svn_log]->{lines}) {\n+\t\t\t\t$svn_log[$#svn_log]->{msg} .= $_.\"\\n\";\n+\t\t\t\tunless (--$svn_log[$#svn_log]->{lines}) {\n+\t\t\t\t\t$state = 'sep';\n+\t\t\t\t}\n+\t\t\t} else {\n+\t\t\t\tcroak \"Log parse error at: $_\\n\",\n+\t\t\t\t\t$svn_log[$#svn_log]->{revision},\"\\n\";\n+\t\t\t}\n \t\t}\n \t}\n \tclose $log_fh or croak $?;\n-- \n1.2.0.gdee6\n"}]}